Repository: smallfawn/decode_action Branch: main Commit: 20668835fb10 Files: 44 Total size: 213.5 KB Directory structure: gitextract_x1jt3_r8/ ├── .github/ │ └── workflows/ │ └── decode.yml ├── README.md ├── input.js ├── input.py ├── output.js ├── output.py ├── package.json └── src/ ├── decode.py ├── main.js ├── plugin/ │ ├── awsc.js │ ├── common.js │ ├── eval.js │ ├── jjencode.js │ ├── jsconfuser.js │ ├── obfuscator.js │ ├── sojson.js │ └── sojsonv7.js ├── utility/ │ ├── check-func.js │ └── safe-func.js └── visitor/ ├── calculate-constant-exp.js ├── calculate-rstring.js ├── delete-extra.js ├── delete-illegal-return.js ├── delete-nested-blocks.js ├── delete-unreachable-code.js ├── delete-unused-var.js ├── jsconfuser/ │ ├── anti-tooling.js │ ├── control-flow.js │ ├── duplicate-literal.js │ ├── global-concealing.js │ ├── global.js │ ├── minify.js │ ├── opaque-predicates.js │ ├── stack.js │ ├── string-compression.js │ └── string-concealing.js ├── lint-if-statement.js ├── merge-object.js ├── parse-control-flow-storage.js ├── prune-if-branch.js ├── split-assignment.js ├── split-member-object.js ├── split-sequence.js └── split-variable-declaration.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/decode.yml ================================================ name: Decode JavaScript File on: push: branches: - main jobs: decode: runs-on: ubuntu-latest permissions: contents: write steps: - name: Checkout repository uses: actions/checkout@v2 - name: Execute Python script for decoding run: | python src/decode.py - name: Install dependencies and run decode run: | npm install npm run decode -- [-i input.js] [-o output.js] - name: Configure Git author run: | git config --local user.email "action@github.com" git config --local user.name "action" - name: Save decoded output to repository run: | git status ls git add output.js git add output.py git commit -m "Add decoded output file" git push ================================================ FILE: README.md ================================================ # QLScriptpublic fork仓库后 把待解密的脚本放入到input.js里面 等待60s左右即可在output.js看到解密脚本 python脚本同理 放入到input.py里面 等待60s左右即可在output.py看到解密脚本 目前支持zlib bz2 lzma 刚使用action的人手动点一下仓库上方action 初始化 脚本自适应检测加密方式 支持sojson [源jsjiami.v6] 支持sojsonv7 [源jsjiami.v7] 支持obfuscator [市面上通用加密] 支持awsc [阿里云混淆] 支持jjencode [源jjencode] 支持common [common] ================================================ FILE: input.js ================================================ function _0x4d77(){const _0x2f79e5=['\u67e5\u8be2\u521d\u59cb\u79ef\u5206\u51fa\u9519\uff1a','\x70\x61\x74\x68','\x5b\u274c\x20\x45\x52\x52\x4f\x52\x5d\x20\u6ce8\u518c\u94fe\u63a5\u88ab\u4fee\u6539\uff01','\x5b\u274c\x20\x45\x52\x52\x4f\x52\x5d\x20\u73af\u5883\u53d8\u91cf','\x34\x76\x4c\x52\x6b\x61\x4a','\x50\x4f\x53\x54','\uff08\x63\x6f\x64\x65\uff1a','\x6d\x73\x67','\u79ef\u5206\u53d8\u52a8\uff1a','\u7684\x43\x4b\u5df2\u4fdd\u5b58\u5230\u672c\u5730\x5d','\x65\x78\x69\x73\x74\x73\x53\x79\x6e\x63','\x72\x65\x73\x6f\x6c\x76\x65','\x34\x77\x50\x42\x6a\x69\x45','\x68\x74\x74\x70\x3a\x2f\x2f\x68\x35\x2e\x79\x69\x64\x69\x6e\x67\x79\x75\x65\x63\x68\x65\x6e\x67\x2e\x63\x6f\x6d\x2f\x61\x70\x69\x2f\x6d\x69\x73\x73\x69\x6f\x6e\x2f\x73\x69\x67\x6e','\x6e\x61\x6d\x65','\x5b\u274c\x20\x45\x52\x52\x4f\x52\x5d\x20\u5f53\u524d\u94fe\u63a5\uff1a','\x6a\x73\x6f\x6e','\x0a\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d','\x31\x34\x6c\x43\x4c\x42\x4c\x6d','\x5b\u26a0\ufe0f\x20\u8d26\u53f7\u683c\u5f0f\u9519\u8bef\uff1a','\x6d\x6b\x64\x69\x72\x53\x79\x6e\x63','\x74\x6f\x46\x69\x78\x65\x64','\x61\x70\x70\x6c\x69\x63\x61\x74\x69\x6f\x6e\x2f\x6a\x73\x6f\x6e','\x5b\u274c\x20\x45\x52\x52\x4f\x52\x5d\x20\u811a\u672c\u6821\u9a8c\u5931\u8d25\uff1a','\x72\x65\x61\x64\x46\x69\x6c\x65\x53\x79\x6e\x63','\u7b7e\u5230\u540e\u79ef\u5206\uff1a','\x5b\u274c\x20\u8d26\u53f7','\x31\x30\x57\x78\x74\x63\x6c\x65','\x3d\x3d\x3d\x3d\x3d\x20\u516c\u544a\u4fe1\u606f\x20\x3d\x3d\x3d\x3d\x3d','\u7684\x43\x4b\u6587\u4ef6\uff0c\u9a8c\u8bc1\u6709\u6548\u6027\x2e\x2e\x2e\x5d','\x65\x78\x69\x74','\u8131\u654f\u59d3\u540d\uff1a','\u7684\x43\x4b\u6587\u4ef6\uff0c\u6267\u884c\u767b\u5f55\x2e\x2e\x2e\x5d','\x73\x70\x6c\x69\x74','\x73\x74\x72\x69\x6e\x67\x69\x66\x79','\x75\x74\x66\x2d\x38','\x5b\u2139\ufe0f\x20\u672a\u627e\u5230\u8d26\u53f7','\x66\x69\x6c\x74\x65\x72','\x73\x75\x63\x63\x65\x73\x73','\x36\x34\x31\x38\x31\x38\x34\x5a\x4a\x5a\x50\x48\x43','\x64\x61\x74\x61','\x2e\x74\x78\x74','\u914d\u7f6e\u4e3a\u7a7a\uff0c\u8bf7\u68c0\u67e5','\x0a\ud83d\udcbb\x20\u9752\u9f99\u811a\u672c\uff1a\x68\x74\x74\x70\x73\x3a\x2f\x2f\x70\x61\x6e\x2e\x71\x75\x61\x72\x6b\x2e\x63\x6e\x2f\x73\x2f\x61\x34\x30\x64\x66\x33\x35\x38\x36\x38\x65\x33\x0a\ud83d\udcac\x20\u4f01\u9e45\u7fa4\u804a\uff1a\x68\x74\x74\x70\x73\x3a\x2f\x2f\x71\x6d\x2e\x71\x71\x2e\x63\x6f\x6d\x2f\x71\x2f\x75\x74\x37\x59\x4d\x6d\x6f\x4b\x59\x77\x0a\ud83d\udcf1\x20\u4f01\u9e45\u9891\u9053\uff1a\x68\x74\x74\x70\x73\x3a\x2f\x2f\x70\x64\x2e\x71\x71\x2e\x63\x6f\x6d\x2f\x73\x2f\x39\x79\x6d\x63\x71\x6b\x73\x31\x33\x0a\x20\x20\x20\x20','\u8bfb\u53d6\x2f\u6821\u9a8c\x43\x4b\u5931\u8d25\uff1a','\x6d\x61\x74\x63\x68','\x5b\u2705\x20\x53\x55\x43\x43\x45\x53\x53\x5d\x20\u6ce8\u518c\u94fe\u63a5\u6821\u9a8c\u901a\u8fc7','\uff0c\u8bf7\u6dfb\u52a0\u8d26\u53f7\u5bc6\u7801\uff08\u683c\u5f0f\uff1a\u624b\u673a\u53f7\x7c\u5bc6\u7801\x20\u56de\u8f66\u5206\u9694\uff09','\x34\x30\x30\x30\x35\x38\x35\x54\x6b\x6d\x56\x56\x57','\x65\x6e\x76','\x36\x38\x30\x36\x36\x39\x34\x67\x74\x72\x52\x64\x4c','\x31\x38\x31\x36\x32\x30\x33\x4f\x4b\x57\x4b\x5a\x63','\x68\x74\x74\x70\x3a\x2f\x2f\x68\x35\x2e\x79\x69\x64\x69\x6e\x67\x79\x75\x65\x63\x68\x65\x6e\x67\x2e\x63\x6f\x6d\x2f\x23\x2f\x70\x61\x67\x65\x73\x2f\x72\x65\x67\x69\x73\x74\x65\x72\x2f\x69\x6e\x64\x65\x78\x3f\x70\x72\x6f\x6d\x6f\x43\x6f\x64\x65\x3d\x50\x4f\x43\x31\x33\x30\x31\x35\x39','\u672a\u627e\u5230\u300c\x2f\x2a\x20\u6ce8\u518c\u94fe\u63a5\x3a\x20\x78\x78\x78\x20\x2a\x2f\u300d\u683c\u5f0f\u7684\u6ce8\u91ca','\x63\x6f\x64\x65','\u7b7e\u5230\u524d\u79ef\u5206\uff1a','\x31\x36\x35\x37\x32\x33\x38\x34\x47\x51\x68\x52\x55\x78','\x0a\x5b\u2705\x20\u6240\u6709\u8d26\u53f7\u5904\u7406\u5b8c\u6210\x5d','\x6c\x6f\x67','\x5b\u2139\ufe0f\x20\u5f00\u59cb\u5904\u7406\u8d26\u53f7\uff1a','\x68\x74\x74\x70\x3a\x2f\x2f\x68\x35\x2e\x79\x69\x64\x69\x6e\x67\x79\x75\x65\x63\x68\x65\x6e\x67\x2e\x63\x6f\x6d\x2f','\x68\x74\x74\x70\x3a\x2f\x2f\x68\x35\x2e\x79\x69\x64\x69\x6e\x67\x79\x75\x65\x63\x68\x65\x6e\x67\x2e\x63\x6f\x6d\x2f\x61\x70\x69\x2f\x75\x73\x65\x72\x2f\x69\x6e\x66\x6f','\x0a\x5b\u26a0\ufe0f\x20\u8d26\u53f7','\x5b\u274c\x20\x45\x52\x52\x4f\x52\x5d\x20\u5408\u6cd5\u94fe\u63a5\uff1a','\x6d\x65\x73\x73\x61\x67\x65','\x4d\x6f\x7a\x69\x6c\x6c\x61\x2f\x35\x2e\x30\x20\x28\x4c\x69\x6e\x75\x78\x3b\x20\x41\x6e\x64\x72\x6f\x69\x64\x20\x31\x30\x3b\x20\x4b\x29\x20\x41\x70\x70\x6c\x65\x57\x65\x62\x4b\x69\x74\x2f\x35\x33\x37\x2e\x33\x36\x20\x28\x4b\x48\x54\x4d\x4c\x2c\x20\x6c\x69\x6b\x65\x20\x47\x65\x63\x6b\x6f\x29\x20\x43\x68\x72\x6f\x6d\x65\x2f\x31\x33\x33\x2e\x30\x2e\x30\x2e\x30\x20\x4d\x6f\x62\x69\x6c\x65\x20\x53\x61\x66\x61\x72\x69\x2f\x35\x33\x37\x2e\x33\x36\x20\x45\x64\x67\x41\x2f\x31\x33\x33\x2e\x30\x2e\x30\x2e\x30','\x68\x74\x74\x70\x3a\x2f\x2f\x68\x35\x2e\x79\x69\x64\x69\x6e\x67\x79\x75\x65\x63\x68\x65\x6e\x67\x2e\x63\x6f\x6d\x2f\x61\x70\x69\x2f\x75\x73\x65\x72\x2f\x6c\x6f\x67\x69\x6e','\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d','\x6c\x65\x6e\x67\x74\x68','\u7684\x43\x4b\u8fc7\u671f\x2f\u65e0\u6548\uff0c\u51c6\u5907\u91cd\u65b0\u767b\u5f55\x2e\x2e\x2e\x5d','\x5b\u274c\x20\x45\x52\x52\x4f\x52\x5d\x20\u672a\u914d\u7f6e\u73af\u5883\u53d8\u91cf','\x79\x64\x79\x63\x5f\x7a\x6d','\u67e5\u8be2\u6700\u7ec8\u79ef\u5206\u51fa\u9519\uff1a','\x70\x6f\x69\x6e\x74','\x6d\x61\x70','\x34\x34\x30\x37\x39\x38\x59\x56\x6d\x61\x4b\x43','\u767b\u5f55\u51fa\u9519\uff1a','\x74\x72\x69\x6d','\u5f00\u59cb\u767b\u5f55\x2e\x2e\x2e\x5d','\x31\x34\x36\x38\x36\x30\x37\x38\x46\x6f\x4d\x69\x6f\x68','\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d\x3d','\x6d\x61\x72\x6b\x2e\x76\x69\x61\x2e\x67\x70','\x68\x74\x74\x70\x3a\x2f\x2f\x68\x35\x2e\x79\x69\x64\x69\x6e\x67\x79\x75\x65\x63\x68\x65\x6e\x67\x2e\x63\x6f\x6d','\x0a\x5b\u2705\x20\u8d26\u53f7','\uff0c\u91cd\u65b0\u767b\u5f55\x2e\x2e\x2e\x5d','\x5b\u2705\x20\u8d26\u53f7','\x67\x7a\x69\x70\x2c\x20\x64\x65\x66\x6c\x61\x74\x65'];_0x4d77=function(){return _0x2f79e5;};return _0x4d77();}const _0x532065=_0x2ddd;(function(_0x41e400,_0x4daf27){const _0x325cf9=_0x2ddd,_0xbde78c=_0x41e400();while(!![]){try{const _0x153720=-parseInt(_0x325cf9(0x190))/0x1*(parseInt(_0x325cf9(0x178))/0x2)+parseInt(_0x325cf9(0x160))/0x3*(-parseInt(_0x325cf9(0x188))/0x4)+-parseInt(_0x325cf9(0x15d))/0x5+parseInt(_0x325cf9(0x15f))/0x6+-parseInt(_0x325cf9(0x196))/0x7*(-parseInt(_0x325cf9(0x154))/0x8)+parseInt(_0x325cf9(0x165))/0x9+-parseInt(_0x325cf9(0x19f))/0xa*(parseInt(_0x325cf9(0x17c))/0xb);if(_0x153720===_0x4daf27)break;else _0xbde78c['push'](_0xbde78c['shift']());}catch(_0x8a31e6){_0xbde78c['push'](_0xbde78c['shift']());}}}(_0x4d77,0xe9ecf));function printNotice(){const _0x2655bb=_0x2ddd;console[_0x2655bb(0x167)](_0x2655bb(0x1a0)),console[_0x2655bb(0x167)](_0x2655bb(0x158)[_0x2655bb(0x17a)]()),console['\x6c\x6f\x67'](_0x2655bb(0x170));}printNotice();const fs=require('\x66\x73'),path=require(_0x532065(0x185)),REQUIRED_REG_LINK=_0x532065(0x161),CK_DIR=path[_0x532065(0x18f)](__dirname,'\x79\x64\x79\x63\x5f\x63\x6b'),ENV_NAME=_0x532065(0x174);function desensitizeMobile(_0x3e5dea){const _0x36b811=_0x532065;if(!_0x3e5dea||_0x3e5dea[_0x36b811(0x171)]!==0xb)return _0x3e5dea;return _0x3e5dea['\x72\x65\x70\x6c\x61\x63\x65'](/(\d{3})\d{4}(\d{4})/,'\x24\x31\x2a\x2a\x2a\x2a\x24\x32');}function checkRegLink(){const _0x142061=_0x532065;try{const _0x208b14=/\/\*\s*注册链接:\s*(.+?)\s*\*\//,_0x3fe438=__filename,_0x36b24f=fs['\x72\x65\x61\x64\x46\x69\x6c\x65\x53\x79\x6e\x63'](_0x3fe438,'\x75\x74\x66\x2d\x38'),_0x1f4952=_0x36b24f['\x73\x70\x6c\x69\x74'](/\r?\n/);let _0x48d03f='';for(let _0x4e4b88 of _0x1f4952){const _0x505f8e=_0x4e4b88[_0x142061(0x17a)]()[_0x142061(0x15a)](_0x208b14);if(_0x505f8e&&_0x505f8e[0x1]){_0x48d03f=_0x505f8e[0x1][_0x142061(0x17a)]();break;}}if(!_0x48d03f)throw new Error(_0x142061(0x162));if(_0x48d03f!==REQUIRED_REG_LINK){console['\x6c\x6f\x67'](_0x142061(0x186)),console[_0x142061(0x167)](_0x142061(0x16c)+REQUIRED_REG_LINK),console[_0x142061(0x167)](_0x142061(0x193)+_0x48d03f);throw new Error('\u6ce8\u518c\u94fe\u63a5\u6821\u9a8c\u5931\u8d25\x0a\u8bf7\u5230\u4f5c\u8005\u7f51\u76d8\u91cc\u4e0b\u8f7d\u6b63\u7248');}console[_0x142061(0x167)](_0x142061(0x15b));}catch(_0xca380e){console[_0x142061(0x167)](_0x142061(0x19b)+_0xca380e['\x6d\x65\x73\x73\x61\x67\x65']),process[_0x142061(0x1a2)](0x1);}}function _0x2ddd(_0x2a5c8e,_0x1d1cb5){const _0x4d7723=_0x4d77();return _0x2ddd=function(_0x2ddddc,_0x5bb5b1){_0x2ddddc=_0x2ddddc-0x151;let _0x24c9ee=_0x4d7723[_0x2ddddc];return _0x24c9ee;},_0x2ddd(_0x2a5c8e,_0x1d1cb5);}function commonHeaders(_0x39001f){const _0x4205b5=_0x532065;return{'\x55\x73\x65\x72\x2d\x41\x67\x65\x6e\x74':_0x4205b5(0x16e),'\x41\x63\x63\x65\x70\x74\x2d\x45\x6e\x63\x6f\x64\x69\x6e\x67':_0x4205b5(0x183),'\x43\x6f\x6e\x74\x65\x6e\x74\x2d\x54\x79\x70\x65':_0x4205b5(0x19a),'\x73\x6f\x75\x72\x63\x65':'\x68\x35','\x74\x6f\x6b\x65\x6e':_0x39001f,'\x4f\x72\x69\x67\x69\x6e':_0x4205b5(0x17f),'\x58\x2d\x52\x65\x71\x75\x65\x73\x74\x65\x64\x2d\x57\x69\x74\x68':_0x4205b5(0x17e),'\x52\x65\x66\x65\x72\x65\x72':_0x4205b5(0x169),'\x41\x63\x63\x65\x70\x74\x2d\x4c\x61\x6e\x67\x75\x61\x67\x65':'\x7a\x68\x2d\x43\x4e\x2c\x7a\x68\x3b\x71\x3d\x30\x2e\x39\x2c\x65\x6e\x2d\x55\x53\x3b\x71\x3d\x30\x2e\x38\x2c\x65\x6e\x3b\x71\x3d\x30\x2e\x37'};}async function handleAccount(_0x30df42,_0x4c6efc){const _0x286330=_0x532065,_0x233eed=desensitizeMobile(_0x30df42);console[_0x286330(0x167)](_0x286330(0x195)),console[_0x286330(0x167)](_0x286330(0x168)+_0x233eed+'\x5d'),console['\x6c\x6f\x67'](_0x286330(0x17d));!fs['\x65\x78\x69\x73\x74\x73\x53\x79\x6e\x63'](CK_DIR)&&fs[_0x286330(0x198)](CK_DIR,{'\x72\x65\x63\x75\x72\x73\x69\x76\x65':!![]});const _0xc31206=path[_0x286330(0x18f)](CK_DIR,_0x30df42+_0x286330(0x156));let _0xb1c840='';try{if(fs[_0x286330(0x18e)](_0xc31206)){_0xb1c840=fs[_0x286330(0x19c)](_0xc31206,_0x286330(0x1a7))[_0x286330(0x17a)](),console['\x6c\x6f\x67']('\x5b\u2139\ufe0f\x20\u8bfb\u53d6\u5230\u8d26\u53f7'+_0x233eed+_0x286330(0x1a1));const _0x39c434=await fetch(_0x286330(0x16a),{'\x6d\x65\x74\x68\x6f\x64':'\x50\x4f\x53\x54','\x68\x65\x61\x64\x65\x72\x73':commonHeaders(_0xb1c840),'\x62\x6f\x64\x79':JSON[_0x286330(0x1a6)]({})}),_0x3fb30d=await _0x39c434[_0x286330(0x194)]();_0x3fb30d[_0x286330(0x153)]&&_0x3fb30d[_0x286330(0x163)]===0x0?console[_0x286330(0x167)](_0x286330(0x182)+_0x233eed+'\u7684\x43\x4b\u6709\u6548\uff0c\u76f4\u63a5\u4f7f\u7528\x5d'):(console[_0x286330(0x167)]('\x5b\u26a0\ufe0f\x20\u8d26\u53f7'+_0x233eed+_0x286330(0x172)),_0xb1c840='');}else console[_0x286330(0x167)](_0x286330(0x151)+_0x233eed+_0x286330(0x1a4));}catch(_0x57444d){console[_0x286330(0x167)]('\x5b\u26a0\ufe0f\x20\u8d26\u53f7'+_0x233eed+_0x286330(0x159)+_0x57444d[_0x286330(0x16d)]+_0x286330(0x181)),_0xb1c840='';}if(!_0xb1c840)try{console[_0x286330(0x167)]('\x5b\u2139\ufe0f\x20\u8d26\u53f7'+_0x233eed+_0x286330(0x17b));const _0x2de728={'\x6d\x6f\x62\x69\x6c\x65':_0x30df42,'\x70\x61\x73\x73\x77\x6f\x72\x64':_0x4c6efc},_0x1b47e=await fetch(_0x286330(0x16f),{'\x6d\x65\x74\x68\x6f\x64':'\x50\x4f\x53\x54','\x68\x65\x61\x64\x65\x72\x73':commonHeaders(''),'\x62\x6f\x64\x79':JSON[_0x286330(0x1a6)](_0x2de728)}),_0x2cce7a=await _0x1b47e[_0x286330(0x194)]();if(_0x2cce7a['\x73\x75\x63\x63\x65\x73\x73']&&_0x2cce7a[_0x286330(0x163)]===0x0)_0xb1c840=_0x2cce7a[_0x286330(0x155)],console['\x6c\x6f\x67'](_0x286330(0x182)+_0x233eed+'\u767b\u5f55\u6210\u529f\x5d'),fs['\x77\x72\x69\x74\x65\x46\x69\x6c\x65\x53\x79\x6e\x63'](_0xc31206,_0xb1c840,_0x286330(0x1a7)),console[_0x286330(0x167)](_0x286330(0x182)+_0x233eed+_0x286330(0x18d));else throw new Error('\u767b\u5f55\u5931\u8d25\uff1a'+_0x2cce7a[_0x286330(0x18b)]+_0x286330(0x18a)+_0x2cce7a[_0x286330(0x163)]+'\uff09');}catch(_0x2332c6){console[_0x286330(0x167)]('\x5b\u274c\x20\u8d26\u53f7'+_0x233eed+_0x286330(0x179)+_0x2332c6[_0x286330(0x16d)]+'\x5d');return;}let _0x93744d=0x0,_0xd5d7f7='';try{const _0x2f6efd=await fetch(_0x286330(0x16a),{'\x6d\x65\x74\x68\x6f\x64':_0x286330(0x189),'\x68\x65\x61\x64\x65\x72\x73':commonHeaders(_0xb1c840),'\x62\x6f\x64\x79':JSON[_0x286330(0x1a6)]({})}),_0x57d838=await _0x2f6efd[_0x286330(0x194)]();if(_0x57d838['\x73\x75\x63\x63\x65\x73\x73']&&_0x57d838[_0x286330(0x163)]===0x0){const _0x5c3dff=_0x57d838[_0x286330(0x155)];_0xd5d7f7=_0x5c3dff[_0x286330(0x192)][_0x286330(0x171)]>0x1?_0x5c3dff[_0x286330(0x192)][0x0]+'\x2a'['\x72\x65\x70\x65\x61\x74'](_0x5c3dff[_0x286330(0x192)][_0x286330(0x171)]-0x1):_0x5c3dff[_0x286330(0x192)],_0x93744d=Number(_0x5c3dff[_0x286330(0x176)])[_0x286330(0x199)](0x2),console['\x6c\x6f\x67'](_0x286330(0x180)+_0x233eed+'\u521d\u59cb\u4fe1\u606f\x5d'),console['\x6c\x6f\x67'](_0x286330(0x1a3)+_0xd5d7f7),console[_0x286330(0x167)](_0x286330(0x164)+_0x93744d+'\x20\u5206');}else throw new Error('\u67e5\u8be2\u521d\u59cb\u4fe1\u606f\u5931\u8d25\uff1a'+_0x57d838[_0x286330(0x18b)]);}catch(_0x32f31b){console['\x6c\x6f\x67'](_0x286330(0x19e)+_0x233eed+_0x286330(0x184)+_0x32f31b[_0x286330(0x16d)]+'\x5d');return;}try{const _0x11f4e6=await fetch(_0x286330(0x191),{'\x6d\x65\x74\x68\x6f\x64':'\x50\x4f\x53\x54','\x68\x65\x61\x64\x65\x72\x73':commonHeaders(_0xb1c840),'\x62\x6f\x64\x79':JSON[_0x286330(0x1a6)]({})}),_0x172df5=await _0x11f4e6[_0x286330(0x194)]();_0x172df5[_0x286330(0x153)]||_0x172df5[_0x286330(0x163)]===0x0?console['\x6c\x6f\x67']('\x0a\x5b\u2705\x20\u8d26\u53f7'+_0x233eed+'\u7b7e\u5230\u7ed3\u679c\x5d\x20'+_0x172df5[_0x286330(0x18b)]):console[_0x286330(0x167)](_0x286330(0x16b)+_0x233eed+'\u7b7e\u5230\u63d0\u793a\x5d\x20'+_0x172df5[_0x286330(0x18b)]);}catch(_0xc6a3ce){console[_0x286330(0x167)](_0x286330(0x19e)+_0x233eed+'\u7b7e\u5230\u51fa\u9519\uff1a'+_0xc6a3ce['\x6d\x65\x73\x73\x61\x67\x65']+'\x5d');}try{const _0x2bd980=await fetch('\x68\x74\x74\x70\x3a\x2f\x2f\x68\x35\x2e\x79\x69\x64\x69\x6e\x67\x79\x75\x65\x63\x68\x65\x6e\x67\x2e\x63\x6f\x6d\x2f\x61\x70\x69\x2f\x75\x73\x65\x72\x2f\x69\x6e\x66\x6f',{'\x6d\x65\x74\x68\x6f\x64':'\x50\x4f\x53\x54','\x68\x65\x61\x64\x65\x72\x73':commonHeaders(_0xb1c840),'\x62\x6f\x64\x79':JSON[_0x286330(0x1a6)]({})}),_0x2d7910=await _0x2bd980['\x6a\x73\x6f\x6e']();if(_0x2d7910[_0x286330(0x153)]&&_0x2d7910[_0x286330(0x163)]===0x0){const _0x3611cb=Number(_0x2d7910[_0x286330(0x155)][_0x286330(0x176)])[_0x286330(0x199)](0x2),_0x19cf59=(_0x3611cb-_0x93744d)[_0x286330(0x199)](0x2);console[_0x286330(0x167)](_0x286330(0x180)+_0x233eed+'\u6700\u7ec8\u4fe1\u606f\u6c47\u603b\x5d'),console['\x6c\x6f\x67'](_0x286330(0x1a3)+_0xd5d7f7),console['\x6c\x6f\x67'](_0x286330(0x164)+_0x93744d+'\x20\u5206'),console[_0x286330(0x167)](_0x286330(0x19d)+_0x3611cb+'\x20\u5206'),console['\x6c\x6f\x67'](_0x286330(0x18c)+(_0x19cf59>0x0?'\x2b':'')+_0x19cf59+'\x20\u5206');}else throw new Error('\u67e5\u8be2\u6700\u7ec8\u4fe1\u606f\u5931\u8d25\uff1a'+_0x2d7910[_0x286330(0x18b)]);}catch(_0x10d25c){console[_0x286330(0x167)](_0x286330(0x19e)+_0x233eed+_0x286330(0x175)+_0x10d25c[_0x286330(0x16d)]+'\x5d');}}async function main(){const _0x10c2b1=_0x532065;checkRegLink();const _0x55c639=process[_0x10c2b1(0x15e)][ENV_NAME]||'';!_0x55c639&&(console[_0x10c2b1(0x167)](_0x10c2b1(0x173)+ENV_NAME+_0x10c2b1(0x15c)),process[_0x10c2b1(0x1a2)](0x1));const _0x254a32=_0x55c639[_0x10c2b1(0x1a5)](/\r?\n/)[_0x10c2b1(0x152)](_0x138461=>_0x138461[_0x10c2b1(0x17a)]());_0x254a32[_0x10c2b1(0x171)]===0x0&&(console[_0x10c2b1(0x167)](_0x10c2b1(0x187)+ENV_NAME+_0x10c2b1(0x157)),process[_0x10c2b1(0x1a2)](0x1));console[_0x10c2b1(0x167)]('\x5b\u2139\ufe0f\x20\u5171\u68c0\u6d4b\u5230\x20'+_0x254a32[_0x10c2b1(0x171)]+'\x20\u4e2a\u8d26\u53f7\uff0c\u5f00\u59cb\u6279\u91cf\u5904\u7406\x2e\x2e\x2e\x5d');for(const _0x5c13e9 of _0x254a32){const [_0x291b78,_0x5094c5]=_0x5c13e9[_0x10c2b1(0x1a5)]('\x7c')[_0x10c2b1(0x177)](_0x570265=>_0x570265[_0x10c2b1(0x17a)]());if(!_0x291b78||!_0x5094c5){console[_0x10c2b1(0x167)](_0x10c2b1(0x197)+_0x5c13e9+'\uff0c\u8bf7\u6309\u300c\u624b\u673a\u53f7\x7c\u5bc6\u7801\u300d\u683c\u5f0f\u914d\u7f6e\x5d');continue;}await handleAccount(_0x291b78,_0x5094c5);}console[_0x10c2b1(0x167)](_0x10c2b1(0x166));}main(); ================================================ FILE: input.py ================================================ # -*- coding: utf-8 -*- ''' Powered By Beidu Create at [2024-11-28 21:12] ENV: ck变量:cj_ck, 卡密变量: CN_Card CRON: 11 1,3,5,7,9,11,13,15,17,19,22 * * * ''' import base64, zlib, lzma, bz2, gzip exec((lambda Gkd: compile(Gkd, '', 'exec'))(zlib.decompress(lzma.decompress(bz2.decompress(gzip.decompress(base64.b64decode('H4sIADJsSGcC/wArQNS/QlpoOTFBWSZTWahC+igAMP7/////////////////////////////////////////////4D+/e9d07u73tqW77z3u+93bvG+83l9977W2++m7ur7r27b19s+951fdHvuvee8+97x996+vt5e9971487bPkPb1tfe+e973t9dfOvO7tne++Kr23vmrube975b57e9vV9pW+3fe7u895vc32n3nb7vvahvN3eee++333Nnd3174dq23053j177Y93329ffeu93OO9973q+729fdva597W11Tu33d7pd9nO7e6vfd7bx77vufI671877t58vny717u3vvPPt77xPL25m+699vvve++7dzm2fd9yuSe7d9fb77yFT2AaATANNRphoDQ1NpGBPQTBkm1TyMTEyMaTTaCYAJowExoJgmI0wRhMTBMTCNNGEwEzQEzEE2pmmINEwBNUOqp/sgE9AaCYAmAAAT0BGJpgmTJ6RgEzSZMTE0wTAJgATE0xMCMNGmjIGgYhoNBpo1GCbU1PTDRogp7QaankxNSiFU/2pgGppip/oJgMENCYTJgAJsqfqYJphMBMaTTAmCPRoBk0DJpoNDTQ0aEDAmTTBDRpoxGExomE0NPQmTJo0wjJkaVEqn/oAGkxo1PJTwTEwmaABMmTMo0ybJpowmQNEwmyaDTIAAU2aBMATE0m2gJgjTSn4E9TaEYmhgJgjCmyanlPFNoyaNNGqiFU/wAAEwBqYaYjAmjAmmho1PJpT2gI9NCZTxTyZGATKnsI0wTCTw0IYNIZJ4BNqPVPAieAKeTKenqnqemJonppNkZMTAExTylDqqf+mCaYBGhkyaaNBMAJ6CaYJiYTamjJiYJ6UeTTVP0YTAmmmAmNI00yZMACaaYDQE9TCepiYjAAIZMaU8ARpo1PUZk1MqIAEAigXQD+IDhBAPfgINCH38AyDpH500cXHcvR03wZHD0NLqdkEPnvd77TPePaD8l+856WxfQZ9LiRSWWrtkB1eZvMce7f7ifIpPeBSk4Ih2wjWQX3/2BvvboFfSBmGHyMGOLOtp2VFEIO46H6K7IaPOPoBDCBSzjA4I39nOFZwaR0nxaJ0xHcs3B6btUMMadg03n4/xv8VybWvmP99pNpzVMqF7ft4Xq36AOSrxGyGrSTukhU/vJ/FBK1/KH90i0Cf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'))))).decode())) ================================================ FILE: output.js ================================================ //Wed Apr 01 2026 00:52:28 GMT+0000 (Coordinated Universal Time) //Base:https://github.com/echo094/decode-js //Modify:https://github.com/smallfawn/decode_action function printNotice() { console.log("===== 公告信息 ====="); console.log("\n💻 青龙脚本:https://pan.quark.cn/s/a40df35868e3\n💬 企鹅群聊:https://qm.qq.com/q/ut7YMmoKYw\n📱 企鹅频道:https://pd.qq.com/s/9ymcqks13\n ".trim()); console.log("===================="); } printNotice(); const fs = require("fs"); const path = require("path"); const REQUIRED_REG_LINK = "http://h5.yidingyuecheng.com/#/pages/register/index?promoCode=POC130159"; const CK_DIR = path.resolve(__dirname, "ydyc_ck"); const ENV_NAME = "ydyc_zm"; function desensitizeMobile(_0x3e5dea) { if (!_0x3e5dea || _0x3e5dea.length !== 11) { return _0x3e5dea; } return _0x3e5dea.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2"); } function checkRegLink() { try { const _0x208b14 = /\/\*\s*注册链接:\s*(.+?)\s*\*\//; const _0x3fe438 = __filename; const _0x36b24f = fs.readFileSync(_0x3fe438, "utf-8"); const _0x1f4952 = _0x36b24f.split(/\r?\n/); let _0x48d03f = ""; for (let _0x4e4b88 of _0x1f4952) { const _0x505f8e = _0x4e4b88.trim().match(_0x208b14); if (_0x505f8e && _0x505f8e[1]) { _0x48d03f = _0x505f8e[1].trim(); break; } } if (!_0x48d03f) { throw new Error("未找到「/* 注册链接: xxx */」格式的注释"); } if (_0x48d03f !== REQUIRED_REG_LINK) { console.log("[❌ ERROR] 注册链接被修改!"); console.log("[❌ ERROR] 合法链接:" + REQUIRED_REG_LINK); console.log("[❌ ERROR] 当前链接:" + _0x48d03f); throw new Error("注册链接校验失败\n请到作者网盘里下载正版"); } console.log("[✅ SUCCESS] 注册链接校验通过"); } catch (_0xca380e) { console.log("[❌ ERROR] 脚本校验失败:" + _0xca380e.message); process.exit(1); } } function commonHeaders(_0x39001f) { return { "User-Agent": "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Mobile Safari/537.36 EdgA/133.0.0.0", "Accept-Encoding": "gzip, deflate", "Content-Type": "application/json", source: "h5", token: _0x39001f, Origin: "http://h5.yidingyuecheng.com", "X-Requested-With": "mark.via.gp", Referer: "http://h5.yidingyuecheng.com/", "Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7" }; } async function handleAccount(_0x30df42, _0x4c6efc) { const _0x233eed = desensitizeMobile(_0x30df42); console.log("\n===================================="); console.log("[ℹ️ 开始处理账号:" + _0x233eed + "]"); console.log("===================================="); !fs.existsSync(CK_DIR) && fs.mkdirSync(CK_DIR, { recursive: true }); const _0xc31206 = path.resolve(CK_DIR, _0x30df42 + ".txt"); let _0xb1c840 = ""; try { if (fs.existsSync(_0xc31206)) { _0xb1c840 = fs.readFileSync(_0xc31206, "utf-8").trim(); console.log("[ℹ️ 读取到账号" + _0x233eed + "的CK文件,验证有效性...]"); const _0x39c434 = await fetch("http://h5.yidingyuecheng.com/api/user/info", { method: "POST", headers: commonHeaders(_0xb1c840), body: JSON.stringify({}) }); const _0x3fb30d = await _0x39c434.json(); _0x3fb30d.success && _0x3fb30d.code === 0 ? console.log("[✅ 账号" + _0x233eed + "的CK有效,直接使用]") : (console.log("[⚠️ 账号" + _0x233eed + "的CK过期/无效,准备重新登录...]"), _0xb1c840 = ""); } else { console.log("[ℹ️ 未找到账号" + _0x233eed + "的CK文件,执行登录...]"); } } catch (_0x57444d) { console.log("[⚠️ 账号" + _0x233eed + "读取/校验CK失败:" + _0x57444d.message + ",重新登录...]"); _0xb1c840 = ""; } if (!_0xb1c840) { try { console.log("[ℹ️ 账号" + _0x233eed + "开始登录...]"); const _0x2de728 = { mobile: _0x30df42, password: _0x4c6efc }; const _0x1b47e = await fetch("http://h5.yidingyuecheng.com/api/user/login", { method: "POST", headers: commonHeaders(""), body: JSON.stringify(_0x2de728) }); const _0x2cce7a = await _0x1b47e.json(); if (_0x2cce7a.success && _0x2cce7a.code === 0) { _0xb1c840 = _0x2cce7a.data; console.log("[✅ 账号" + _0x233eed + "登录成功]"); fs.writeFileSync(_0xc31206, _0xb1c840, "utf-8"); console.log("[✅ 账号" + _0x233eed + "的CK已保存到本地]"); } else { throw new Error("登录失败:" + _0x2cce7a.msg + "(code:" + _0x2cce7a.code + ")"); } } catch (_0x2332c6) { console.log("[❌ 账号" + _0x233eed + "登录出错:" + _0x2332c6.message + "]"); return; } } let _0x93744d = 0; let _0xd5d7f7 = ""; try { const _0x2f6efd = await fetch("http://h5.yidingyuecheng.com/api/user/info", { method: "POST", headers: commonHeaders(_0xb1c840), body: JSON.stringify({}) }); const _0x57d838 = await _0x2f6efd.json(); if (_0x57d838.success && _0x57d838.code === 0) { const _0x5c3dff = _0x57d838.data; _0xd5d7f7 = _0x5c3dff.name.length > 1 ? _0x5c3dff.name[0] + "*".repeat(_0x5c3dff.name.length - 1) : _0x5c3dff.name; _0x93744d = Number(_0x5c3dff.point).toFixed(2); console.log("\n[✅ 账号" + _0x233eed + "初始信息]"); console.log("脱敏姓名:" + _0xd5d7f7); console.log("签到前积分:" + _0x93744d + " 分"); } else { throw new Error("查询初始信息失败:" + _0x57d838.msg); } } catch (_0x32f31b) { console.log("[❌ 账号" + _0x233eed + "查询初始积分出错:" + _0x32f31b.message + "]"); return; } try { const _0x11f4e6 = await fetch("http://h5.yidingyuecheng.com/api/mission/sign", { method: "POST", headers: commonHeaders(_0xb1c840), body: JSON.stringify({}) }); const _0x172df5 = await _0x11f4e6.json(); _0x172df5.success || _0x172df5.code === 0 ? console.log("\n[✅ 账号" + _0x233eed + "签到结果] " + _0x172df5.msg) : console.log("\n[⚠️ 账号" + _0x233eed + "签到提示] " + _0x172df5.msg); } catch (_0xc6a3ce) { console.log("[❌ 账号" + _0x233eed + "签到出错:" + _0xc6a3ce.message + "]"); } try { const _0x2bd980 = await fetch("http://h5.yidingyuecheng.com/api/user/info", { method: "POST", headers: commonHeaders(_0xb1c840), body: JSON.stringify({}) }); const _0x2d7910 = await _0x2bd980.json(); if (_0x2d7910.success && _0x2d7910.code === 0) { const _0x3611cb = Number(_0x2d7910.data.point).toFixed(2); const _0x19cf59 = (_0x3611cb - _0x93744d).toFixed(2); console.log("\n[✅ 账号" + _0x233eed + "最终信息汇总]"); console.log("脱敏姓名:" + _0xd5d7f7); console.log("签到前积分:" + _0x93744d + " 分"); console.log("签到后积分:" + _0x3611cb + " 分"); console.log("积分变动:" + (_0x19cf59 > 0 ? "+" : "") + _0x19cf59 + " 分"); } else { throw new Error("查询最终信息失败:" + _0x2d7910.msg); } } catch (_0x10d25c) { console.log("[❌ 账号" + _0x233eed + "查询最终积分出错:" + _0x10d25c.message + "]"); } } async function main() { checkRegLink(); const _0x55c639 = process.env[ENV_NAME] || ""; !_0x55c639 && (console.log("[❌ ERROR] 未配置环境变量" + ENV_NAME + ",请添加账号密码(格式:手机号|密码 回车分隔)"), process.exit(1)); const _0x254a32 = _0x55c639.split(/\r?\n/).filter(_0x138461 => _0x138461.trim()); _0x254a32.length === 0 && (console.log("[❌ ERROR] 环境变量" + ENV_NAME + "配置为空,请检查"), process.exit(1)); console.log("[ℹ️ 共检测到 " + _0x254a32.length + " 个账号,开始批量处理...]"); for (const _0x5c13e9 of _0x254a32) { const [_0x291b78, _0x5094c5] = _0x5c13e9.split("|").map(_0x570265 => _0x570265.trim()); if (!_0x291b78 || !_0x5094c5) { console.log("[⚠️ 账号格式错误:" + _0x5c13e9 + ",请按「手机号|密码」格式配置]"); continue; } await handleAccount(_0x291b78, _0x5094c5); } console.log("\n[✅ 所有账号处理完成]"); } main(); ================================================ FILE: package.json ================================================ { "name": "decode-js", "type": "module", "scripts": { "decode": "node src/main.js", "deob": "node src/main.js -t obfuscator", "deso": "node src/main.js -t sojson", "desov7": "node src/main.js -t sojsonv7", "lint": "eslint --ext .js --fix src" }, "dependencies": { "@babel/generator": "^7.17.10", "@babel/parser": "^7.17.10", "@babel/traverse": "^7.17.10", "@babel/types": "^7.17.10", "base64url": "^3.0.1", "crypto-js": "^4.2.0", "eslint": "^8.23.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-prettier": "^4.2.1", "isolated-vm": "^4.7.2", "prettier": "^2.7.1", "vm2": "^3.9.11" } } ================================================ FILE: src/decode.py ================================================ import base64 import bz2 import zlib import lzma import gzip from datetime import datetime #from Crypto.Cipher import AES #from cryptography.fernet import Fernet #from Crypto.Cipher import ChaCha20 # 获取当前日期和时间 now = datetime.now() # 将日期和时间格式化为字符串 formatted_date = now.strftime("%Y-%m-%d %H:%M:%S") def try_decompress(data): try: decompressed_data = gzip.decompress(data) return decompressed_data except Exception as e: pass # 尝试使用 bz2 解压缩 try: decompressed_data = bz2.decompress(data) # print("使用 bz2 解压缩成功") return decompressed_data except Exception as e: pass # 尝试使用 zlib 解压缩 try: decompressed_data = zlib.decompress(data) # print("使用 zlib 解压缩成功") return decompressed_data except Exception as e: pass # 尝试使用 lzma 解压缩 try: decompressed_data = lzma.decompress(data) # print("使用 lzma 解压缩成功") return decompressed_data except Exception as e: pass # 如果无法解压缩,则返回原始数据 return data def try_decode_base64(data): try: decoded_data = base64.b64decode(data) # print("使用 base64 解码成功") return decoded_data except Exception as e: pass # 如果无法解码,则返回原始数据 return data def extract_base64_encoded(data): # 查找 base64.b64decode( 的起始位置 start_idx = data.find("base64.b64decode(") if start_idx == -1: return None # 如果未找到目标字符串,返回 None # 查找 ' 的位置,从 base64.b64decode( 后面开始找 quote_idx = data.index("'", start_idx + len("base64.b64decode(")) # 提取 'XXXX' 中的 XXXX 部分 encoded_string = data[quote_idx + 1:data.index("'", quote_idx + 1)] return encoded_string def Encoded_script_decode(data): return def decrypt_nested(data): while True: new_data = try_decode_base64(data) # print("解密前的数据:", data) new_data = try_decompress(new_data) # print("解密后的数据:", new_data) if "exec(" in str(new_data): # 更新 decrypted_data 以便下一次循环使用 if "Encoded script" in str(new_data): new_data = "该加密未适配 敬请期待" print("该加密未适配 敬请期待") break elif "exec(" in str(new_data): data = extract_base64_encoded(str(new_data)) else: print("未知 加密 无法进一步解密") new_data = "未知 加密 无法进一步解密" break # 如果 new_data 中不再包含 "exec",跳出循环 # print(data) else: print("无法进一步解密,退出循环") break # 如果 new_data 中不再包含 "exec",跳出循环 return new_data # 返回最终解密后的数据 with open('./input.py', 'r', encoding='utf-8') as file: # 读取文件内容 content = file.read().strip() # 打印内容 encoded_data = extract_base64_encoded(content) # print(encoded_data) # 解密嵌套加密数据 final_decrypted_data = decrypt_nested(encoded_data) # 输出最终解密结果 # print("最终解密结果:") def process_data(data): if isinstance(data, str): # 如果是字符串,则编码为字节对象 byte_data = data.encode('utf-8') elif isinstance(data, bytes): # 如果已经是字节对象,则直接使用 byte_data = data else: # 如果不是字符串也不是字节对象,抛出异常或做其他处理 raise TypeError("Expected string or bytes-like object") return byte_data print(final_decrypted_data) with open("./output.py", 'wb') as f: f.write(process_data("#")+process_data(formatted_date)+process_data("\n")+process_data(final_decrypted_data)) ================================================ FILE: src/main.js ================================================ import fs from 'fs'; import { fileURLToPath } from 'url'; import * as path from 'path'; import process from 'process'; // Dynamically import ESM modules const commonModule = await import('./plugin/common.js'); const jjencodeModule = await import('./plugin/jjencode.js'); const sojsonModule = await import('./plugin/sojson.js'); const sojsonv7Module = await import('./plugin/sojsonv7.js'); const obfuscatorModule = await import('./plugin/obfuscator.js'); const awscModule = await import('./plugin/awsc.js'); const jsconfuserModule = await import('./plugin/jsconfuser.js'); // Provide default exports if necessary const PluginCommon = commonModule.default || commonModule; const PluginJjencode = jjencodeModule.default || jjencodeModule; const PluginSojson = sojsonModule.default || sojsonModule; const PluginSojsonV7 = sojsonv7Module.default || sojsonv7Module; const PluginObfuscator = obfuscatorModule.default || obfuscatorModule; const PluginAwsc = awscModule.default || awscModule; const PluginJsconfuser = jsconfuserModule.default || jsconfuserModule; // Read command-line arguments let encodeFile = 'input.js'; let decodeFile = 'output.js'; for (let i = 2; i < process.argv.length; i += 2) { if (process.argv[i] === '-i') { encodeFile = process.argv[i + 1]; } else if (process.argv[i] === '-o') { decodeFile = process.argv[i + 1]; } } console.log(`输入: ${encodeFile}`); console.log(`输出: ${decodeFile}`); // Read source code const sourceCode = fs.readFileSync(encodeFile, { encoding: 'utf-8' }); let processedCode = sourceCode; let pluginUsed = ''; let time; // Try plugins in sequence until the processed code differs from the original const plugins = [ { name: 'obfuscator', plugin: PluginObfuscator }, { name: 'sojsonv7', plugin: PluginSojsonV7 }, { name: 'sojson', plugin: PluginSojson }, { name: 'jsconfuser', plugin: PluginJsconfuser }, { name: 'awsc', plugin: PluginAwsc }, { name: 'jjencode', plugin: PluginJjencode }, { name: 'common', plugin: PluginCommon }, // Use common plugin last ]; for (const plugin of plugins) { // Check for specific string in sourceCode to break early if (sourceCode.indexOf('smEcV') !== -1) { break; } try { const code = plugin.plugin(sourceCode); if (code && code !== processedCode) { processedCode = code; pluginUsed = plugin.name; break; } } catch (error) { console.error(`插件 ${plugin.name} 处理时发生错误: ${error.message}`); } } // Check if processed code differs from source code if (processedCode !== sourceCode) { time = new Date(); const header = [ `//${time}`, "//Base:https://github.com/echo094/decode-js", "//Modify:https://github.com/smallfawn/decode_action" ].join('\n'); // Combine header and processed code const outputCode = header + '\n' + processedCode; // Write to file fs.writeFile(decodeFile, outputCode, (err) => { if (err) { throw err; } else { console.log(`使用插件 ${pluginUsed} 成功处理并写入文件 ${decodeFile}`); } }); } else { console.log(`所有插件处理后的代码与原代码一致,未写入文件。`); } ================================================ FILE: src/plugin/awsc.js ================================================ /** * Reference: * * [某宝登录bx-ua参数逆向思路(fireyejs 225算法)](https://zhuanlan.zhihu.com/p/626187669) */ import { parse } from '@babel/parser' import _generate from '@babel/generator' const generator = _generate.default import _traverse from '@babel/traverse' const traverse = _traverse.default import * as t from '@babel/types' function RemoveVoid(path) { if (path.node.operator === 'void') { path.replaceWith(path.node.argument) } } function LintConditionalAssign(path) { if (!t.isAssignmentExpression(path?.parent)) { return } let { test, consequent, alternate } = path.node let { operator, left } = path.parent consequent = t.assignmentExpression(operator, left, consequent) alternate = t.assignmentExpression(operator, left, alternate) path.parentPath.replaceWith( t.conditionalExpression(test, consequent, alternate) ) } function LintConditionalIf(ast) { function conditional(path) { let { test, consequent, alternate } = path.node // console.log(generator(test, { minified: true }).code) if (t.isSequenceExpression(path.parent)) { if (!sequence(path.parentPath)) { path.stop() } return } if (t.isLogicalExpression(path.parent)) { if (!logical(path.parentPath)) { path.stop() } return } if (!t.isExpressionStatement(path.parent)) { console.error(`Unexpected parent type: ${path.parent.type}`) path.stop() return } consequent = t.expressionStatement(consequent) alternate = t.expressionStatement(alternate) let statement = t.ifStatement(test, consequent, alternate) path.replaceWithMultiple(statement) } function sequence(path) { if (t.isLogicalExpression(path.parent)) { return logical(path.parentPath) } let body = [] for (const item of path.node.expressions) { body.push(t.expressionStatement(item)) } let node = t.blockStatement(body, []) let replace_path = path if (t.isExpressionStatement(path.parent)) { replace_path = path.parentPath } else if (!t.isBlockStatement(path.parent)) { console.error(`Unexpected parent type: ${path.parent.type}`) return false } replace_path.replaceWith(node) return true } function logical(path) { let { operator, left, right } = path.node if (operator !== '&&') { console.error(`Unexpected logical operator: ${operator}`) return false } if (!t.isExpressionStatement(path.parent)) { console.error(`Unexpected parent type: ${path.parent.type}`) return false } let node = t.ifStatement(left, t.expressionStatement(right)) path.parentPath.replaceWith(node) return true } traverse(ast, { ConditionalExpression: { enter: conditional }, }) } function LintLogicalIf(path) { let { operator, left, right } = path.node if (operator !== '&&') { // console.warn(`Unexpected logical operator: ${operator}`) return } if (!t.isExpressionStatement(path.parent)) { console.warn(`Unexpected parent type: ${path.parent.type}`) return } let node = t.ifStatement(left, t.expressionStatement(right)) path.parentPath.replaceWith(node) return } function LintIfStatement(path) { let { test, consequent, alternate } = path.node let changed = false if (!t.isBlockStatement(consequent)) { consequent = t.blockStatement([consequent]) changed = true } if (alternate && !t.isBlockStatement(alternate)) { alternate = t.blockStatement([alternate]) changed = true } if (!changed) { return } path.replaceWith(t.ifStatement(test, consequent, alternate)) } function LintIfTest(path) { let { test, consequent, alternate } = path.node if (!t.isSequenceExpression(test)) { return } if (!t.isBlockStatement(path.parent)) { return } let body = test.expressions let last = body.pop() let before = t.expressionStatement(t.sequenceExpression(body)) path.insertBefore(before) path.replaceWith(t.ifStatement(last, consequent, alternate)) } function LintSwitchCase(path) { let { test, consequent } = path.node if (consequent.length == 1 && t.isBlockStatement(consequent[0])) { return } let block = t.blockStatement(consequent) path.replaceWith(t.switchCase(test, [block])) } function LintReturn(path) { let { argument } = path.node if (!t.isSequenceExpression(argument)) { return } if (!t.isBlockStatement(path.parent)) { return } let body = argument.expressions let last = body.pop() let before = t.expressionStatement(t.sequenceExpression(body)) path.insertBefore(before) path.replaceWith(t.returnStatement(last)) } function LintSequence(path) { let body = [] for (const item of path.node.expressions) { body.push(t.expressionStatement(item)) } let node = t.blockStatement(body, []) let replace_path = path if (t.isExpressionStatement(path.parent)) { replace_path = path.parentPath } else if (!t.isBlockStatement(path.parent)) { console.warn(`Unexpected parent type: ${path.parent.type}`) return } replace_path.replaceWith(node) return } function LintBlock(path) { let { body } = path.node if (!body.length) { return } let changed = false let arr = [] for (const item of body) { if (!t.isBlockStatement(item)) { arr.push(item) continue } changed = true for (const sub of item.body) { arr.push(sub) } } if (!changed) { return } path.replaceWith(t.blockStatement(arr)) } export default function (code) { let ast = parse(code) // Lint traverse(ast, { UnaryExpression: RemoveVoid, }) traverse(ast, { ConditionalExpression: { exit: LintConditionalAssign }, }) LintConditionalIf(ast) traverse(ast, { LogicalExpression: { exit: LintLogicalIf }, }) traverse(ast, { IfStatement: { exit: LintIfStatement }, }) traverse(ast, { IfStatement: { enter: LintIfTest }, }) traverse(ast, { SwitchCase: { enter: LintSwitchCase }, }) traverse(ast, { ReturnStatement: { enter: LintReturn }, }) traverse(ast, { SequenceExpression: { exit: LintSequence }, }) traverse(ast, { BlockStatement: { exit: LintBlock }, }) code = generator(ast, { comments: false, jsescOption: { minimal: true }, }).code return code } ================================================ FILE: src/plugin/common.js ================================================ import { parse } from '@babel/parser' import _generate from '@babel/generator' const generator = _generate.default import _traverse from '@babel/traverse' const traverse = _traverse.default import deleteUnreachableCode from '../visitor/delete-unreachable-code.js' import deleteNestedBlocks from '../visitor/delete-nested-blocks.js' import calculateConstantExp from '../visitor/calculate-constant-exp.js' import calculateRString from '../visitor/calculate-rstring.js' export default function (code) { let ast try { ast = parse(code, { errorRecovery: true }) } catch (e) { console.error(`Cannot parse code: ${e.reasonCode}`) return null } traverse(ast, deleteUnreachableCode) traverse(ast, deleteNestedBlocks) traverse(ast, calculateConstantExp) traverse(ast, calculateRString) code = generator(ast).code return code } ================================================ FILE: src/plugin/eval.js ================================================ import { parse } from '@babel/parser' import _generate from '@babel/generator' const generator = _generate.default import _traverse from '@babel/traverse' const traverse = _traverse.default import * as t from '@babel/types' function unpack(code) { let ast = parse(code, { errorRecovery: true }) let lines = ast.program.body let data = null for (let line of lines) { if (t.isEmptyStatement(line)) { continue } if (data) { return null } if ( t.isCallExpression(line?.expression) && line.expression.callee?.name === 'eval' && line.expression.arguments.length === 1 && t.isCallExpression(line.expression.arguments[0]) ) { data = t.expressionStatement(line.expression.arguments[0]) continue } return null } if (!data) { return null } code = generator(data, { minified: true }).code return eval(code) } function pack(code) { let ast1 = parse('(function(){}())') let ast2 = parse(code) traverse(ast1, { FunctionExpression(path) { let body = t.blockStatement(ast2.program.body) path.replaceWith(t.functionExpression(null, [], body)) path.stop() }, }) code = generator(ast1, { minified: false }).code return code } export default { unpack, pack, } ================================================ FILE: src/plugin/jjencode.js ================================================ /** * Check the format and decode if possible * * @param {string} code the encoded code * @returns null or string */ function getCode(code) { // split the code by semicolon let blocks = [] for (let line of code.split(';')) { if (line.length && line !== '\n') { blocks.push(line) } } if (blocks.length !== 6) { console.error('The number of code blocks is incorrect!') return null } // try to get the global variable name const line1 = blocks[0].split('=') if (line1.length !== 2 || line1[1].indexOf('~[]') === -1) { console.error('Cannot find variable name!') return null } // extract the target code const target = blocks[5] const variable = line1[0] const left = `${variable}.$(${variable}.$(${variable}.$$+"\\""+` let i = 0 let s = 0 while (i < left.length && s < target.length) { if (left[i] === target[s]) { ++i } ++s } const right = '"\\"")())()' let j = right.length - 1 let e = target.length - 1 while (j >= 0 && e >= 0) { if (right[j] === target[e]) { --j } --e } if (s >= e) { console.error('Cannot find the target code!') return null } const selected = target.substring(s, e) blocks[5] = `${variable}.$(${variable}.$$+"\\""+${selected}+"\\"")()` const result = eval(blocks.join(';')) return result } /** * This encoding method originates from http://utf-8.jp/public/jjencode.html, * and it does not change the original code (encoder, not obfuscation). */ export default function (code) { code = getCode(code) if (!code) { return null } return code } ================================================ FILE: src/plugin/jsconfuser.js ================================================ import { parse } from '@babel/parser' import _generate from '@babel/generator' const generator = _generate.default import _traverse from '@babel/traverse' const traverse = _traverse.default import calculateConstantExp from '../visitor/calculate-constant-exp.js' import pruneIfBranch from '../visitor/prune-if-branch.js' import jcAntiTooling from '../visitor/jsconfuser/anti-tooling.js' import jcControlFlow from '../visitor/jsconfuser/control-flow.js' import jcDuplicateLiteral from '../visitor/jsconfuser/duplicate-literal.js' import jcGlobalConcealing from '../visitor/jsconfuser/global-concealing.js' import jcMinifyInit from '../visitor/jsconfuser/minify.js' import jcOpaquePredicates from '../visitor/jsconfuser/opaque-predicates.js' import jcStackInit from '../visitor/jsconfuser/stack.js' import jcStringCompression from '../visitor/jsconfuser/string-compression.js' import jcStringConceal from '../visitor/jsconfuser/string-concealing.js' export default function (code) { let ast try { ast = parse(code, { errorRecovery: true }) } catch (e) { console.error(`Cannot parse code: ${e.reasonCode}`) return null } // AntiTooling traverse(ast, jcAntiTooling) // Minify const jcMinify = jcMinifyInit() traverse(ast, jcMinify.deMinifyArrow) // DuplicateLiteralsRemoval traverse(ast, jcDuplicateLiteral) // Stack const jcStack = jcStackInit(jcMinify.arrowFunc) traverse(ast, jcStack.deStackFuncLen) traverse(ast, jcStack.deStackFuncOther) // StringCompression traverse(ast, jcStringCompression) // StringConcealing traverse(ast, jcStringConceal.deStringConcealing) traverse(ast, jcStringConceal.deStringConcealingPlace) // StringSplitting traverse(ast, calculateConstantExp) // Stack (run again) traverse(ast, jcStack.deStackFuncOther) // OpaquePredicates traverse(ast, jcOpaquePredicates) traverse(ast, calculateConstantExp) traverse(ast, pruneIfBranch) // GlobalConcealing traverse(ast, jcGlobalConcealing) // ControlFlowFlattening traverse(ast, jcControlFlow.deControlFlowFlatteningStateless) traverse(ast, calculateConstantExp) // ExpressionObfuscation code = generator(ast, { comments: false, jsescOption: { minimal: true }, }).code return code } ================================================ FILE: src/plugin/obfuscator.js ================================================ /** * 整合自下面两个项目: * * cilame/v_jstools * * Cqxstevexw/decodeObfuscator */ import { parse } from '@babel/parser' import _generate from '@babel/generator' const generator = _generate.default import _traverse from '@babel/traverse' const traverse = _traverse.default import * as t from '@babel/types' import ivm from 'isolated-vm' import PluginEval from './eval.js' import calculateConstantExp from '../visitor/calculate-constant-exp.js' import deleteIllegalReturn from '../visitor/delete-illegal-return.js' import deleteUnusedVar from '../visitor/delete-unused-var.js' import lintIfStatement from '../visitor/lint-if-statement.js' import mergeObject from '../visitor/merge-object.js' import parseControlFlowStorage from '../visitor/parse-control-flow-storage.js' import pruneIfBranch from '../visitor/prune-if-branch.js' import splitAssignment from '../visitor/split-assignment.js' import splitSequence from '../visitor/split-sequence.js' import splitVarDeclaration from '../visitor/split-variable-declaration.js' const isolate = new ivm.Isolate() const globalContext = isolate.createContextSync() function virtualGlobalEval(jsStr) { return globalContext.evalSync(String(jsStr)) } const optGenMin = { comments: false, minified: true, jsescOption: { minimal: true }, } /** * Extract the literal value of an object, and remove an object if all * references to the object are replaced. */ function decodeObject(ast) { function collectObject(path) { const id = path.node.id const init = path.node.init if (!t.isIdentifier(id) || !t.isObjectExpression(init)) { return } const obj_name = id.name const bind = path.scope.getBinding(obj_name) let valid = true let count = 0 let obj = {} for (const item of init.properties) { if (!t.isObjectProperty(item) || !t.isLiteral(item.value)) { valid = false break } if (!t.isIdentifier(item.key)) { valid = false break } ++count obj[item.key.name] = item.value } if (!valid || !count) { return } let safe = true for (let ref of bind.referencePaths) { const parent = ref.parentPath if (ref.key !== 'object' || !parent.isMemberExpression()) { safe = false continue } const key = parent.node.property if (!t.isIdentifier(key) || parent.node.computed) { safe = false continue } if (Object.prototype.hasOwnProperty.call(obj, key.name)) { parent.replaceWith(obj[key.name]) } else { safe = false } } bind.scope.crawl() if (safe) { path.remove() console.log(`删除对象: ${obj_name}`) } } traverse(ast, { VariableDeclarator: collectObject, }) return ast } /** * If the StringArrayRotateFunction does not exist, we can only verify a * string-array by checking the StringArrayCallsWrapper. * * @param {t.File} ast The ast file * @returns Object */ function stringArrayV0(ast) { console.info('Try v0 mode...') function check_wrapper(ref, array_name) { ref = ref.parentPath const index = ref.node.property?.name if (ref.key !== 'init') { return null } ref = ref.parentPath const value = ref.node.id?.name ref = ref.getFunctionParent() if (!index || ref.node.params?.[0]?.name !== index) { return null } const container = ref.node.body.body const ret_node = container[container.length - 1] if (!t.isReturnStatement(ret_node) || ret_node.argument?.name !== value) { return null } if (ref.key !== 'init') { return null } const rm_path = ref.parentPath if (array_name == rm_path.node.id.name) { return null } return rm_path } // check if all the binding references are wrapper function check_other_function(path, array_name) { const binding = path.scope.getBinding(array_name) if (!binding.referencePaths) { return } const ob_func_str = [] const ob_dec_call = [] for (const ref of binding.referencePaths) { if (ref.findParent((path) => path.removed)) { continue } if (ref.parentPath.isMemberExpression() && ref.key === 'object') { const rm_path = check_wrapper(ref, array_name) if (!rm_path) { console.error('Unexpected reference') return null } const code = generator(rm_path.node, optGenMin).code rm_path.get('body').replaceWith(t.blockStatement([])) ob_func_str.push(code) ob_dec_call.push({ name: rm_path.node.id.name, path: rm_path }) } else { console.error('Unexpected reference') return null } } if (!ob_func_str.length) { return null } ob_func_str.push(generator(path.node, optGenMin).code) path.remove() return { version: 0, stringArrayName: array_name, stringArrayCodes: ob_func_str, stringArrayCalls: ob_dec_call, } } let ret_obj = { version: 0, stringArrayName: null, stringArrayCodes: [], stringArrayCalls: [], } function check_string_array(path) { if (path.getFunctionParent()) { return } const init = path.get('init') if (!init.isArrayExpression()) { return } if (!init.node.elements.length) { return } const array_name = path.node.id.name const obj = check_other_function(path, array_name) if (obj) { ret_obj = obj path.stop() } } traverse(ast, { VariableDeclarator: check_string_array, }) return ret_obj } /** * Before version 2.19.0, the string-array is a single array. * Hence, we have to find StringArrayRotateFunction instead. * * @param {t.File} ast The ast file * @returns Object */ function stringArrayV2(ast) { console.info('Try v2 mode...') let obj = { version: 2, stringArrayName: null, stringArrayCodes: [], stringArrayCalls: [], } // Function to rotate string list ("func2") function find_rotate_function(path) { const callee = path.get('callee') const args = path.node.arguments if ( !callee.isFunctionExpression() || callee.node.params.length !== 2 || args.length == 0 || args.length > 2 || !t.isIdentifier(args[0]) ) { return } const arr = callee.node.params[0].name const cmpV = callee.node.params[1].name // >= 2.10.0 const fp1 = `(){try{if()break${arr}push(${arr}shift())}catch(){${arr}push(${arr}shift())}}` // < 2.10.0 const fp2 = `=function(){while(--){${arr}push(${arr}shift)}}${cmpV}` const code = '' + callee.get('body') if (!checkPattern(code, fp1) && !checkPattern(code, fp2)) { return } obj.stringArrayName = args[0].name // The string array can be found by its binding const bind = path.scope.getBinding(obj.stringArrayName) const def = t.variableDeclaration('var', [bind.path.node]) obj.stringArrayCodes.push(generator(def, optGenMin).code) // The calls can be found by its references for (let ref of bind.referencePaths) { if (ref?.listKey === 'arguments') { // This is the rotate function continue } if (ref.findParent((path) => path.removed)) { continue } // the key is 'object' let up1 = ref.getFunctionParent() if (up1.node.id) { // 2.12.0 <= v < 2.15.4 // The `stringArrayCallsWrapperName` is included in the definition obj.stringArrayCodes.push(generator(up1.node, optGenMin).code) up1.node.body = t.blockStatement([]) obj.stringArrayCalls.push({ name: up1.node.id.name, path: up1 }) continue } if (up1.key === 'init') { // v < 2.12.0 // The `stringArrayCallsWrapperName` is defined by VariableDeclarator up1 = up1.parentPath const node = t.variableDeclaration('var', [up1.node]) obj.stringArrayCodes.push(generator(node, optGenMin).code) up1.node.init = null obj.stringArrayCalls.push({ name: up1.node.id.name, path: up1 }) continue } // 2.15.4 <= v < 2.19.0 // The function includes another function with the same name up1 = up1.parentPath const wrapper = up1.node.left.name let up2 = up1.getFunctionParent() if (!up2 || up2.node?.id?.name !== wrapper) { console.warn('Unexpected reference!') continue } obj.stringArrayCodes.push(generator(up2.node, optGenMin).code) up1.remove() up2.node.body = t.blockStatement([]) obj.stringArrayCalls.push({ name: wrapper, path: up2 }) } // Remove the string array bind.path.remove() // Add the rotate function const node = t.expressionStatement(path.node) obj.stringArrayCodes.push(generator(node, optGenMin).code) path.stop() if (path.parentPath.isUnaryExpression()) { path.parentPath.remove() } else { path.remove() } } traverse(ast, { CallExpression: find_rotate_function }) if (obj.stringArrayCodes.length < 3 || !obj.stringArrayCalls.length) { console.error('Essential code missing!') obj.stringArrayName = null } return obj } /** * Find the string-array codes by matching string-array function * (valid version >= 2.19.0) * * @param {t.File} ast The ast file * @returns Object */ function stringArrayV3(ast) { console.info('Try v3 mode...') let ob_func_str = [] let ob_dec_name = [] let ob_string_func_name = null // Normally, the string array func ("func1") follows the template below: // function aaa() { // const bbb = [...] // aaa = function () { // return bbb; // }; // return aaa(); // } // In some cases (lint), the assignment is merged into the ReturnStatement // After finding the possible func1, this method will check all the binding // references and put the child encode function into list. function find_string_array_function(path) { if (path.getFunctionParent()) { return } if ( !t.isIdentifier(path.node.id) || path.node.params.length || !t.isBlockStatement(path.node.body) ) { return } const body = path.node.body.body if (body.length < 2 || body.length > 3) { return } const name_func = path.node.id.name let string_var = -1 try { if ( body[0].declarations.length != 1 || !(string_var = body[0].declarations[0].id.name) || !t.isArrayExpression(body[0].declarations[0].init) ) { return } const nodes = [...body] nodes.shift() const code = generator(t.BlockStatement(nodes), optGenMin).code const fp = `${name_func}=function(){return${string_var}}${name_func}()` if (!checkPattern(code, fp)) { return } } catch { return } const binding = path.scope.getBinding(name_func) if (!binding.referencePaths) { return } let paths = binding.referencePaths let nodes = [] // The sorting function maybe missing in some config function find2(refer_path) { if ( refer_path.parentPath.isCallExpression() && refer_path.listKey === 'arguments' && refer_path.key === 0 ) { let rm_path = refer_path.parentPath if (rm_path.parentPath.isExpressionStatement()) { rm_path = rm_path.parentPath } nodes.push([rm_path.node, 'func2']) rm_path.remove() } } paths.map(find2) function find3(refer_path) { if (refer_path.findParent((path) => path.removed)) { return } if ( refer_path.parentPath.isCallExpression() && refer_path.key === 'callee' ) { let rm_path = refer_path.parentPath.getFunctionParent() if (name_func == rm_path.node.id.name) { return } const code = generator(rm_path.node, optGenMin).code rm_path.node.body = t.blockStatement([]) nodes.push([code, 'func3', rm_path]) } else { console.error('Unexpected reference') } } paths.map(find3) if (!name_func) { return } ob_string_func_name = name_func ob_func_str.push(generator(path.node, optGenMin).code) nodes.map(function (item) { if (item[1] == 'func3') { ob_func_str.push(item[0]) ob_dec_name.push({ name: item[2].node.id.name, path: item[2] }) return } let node = item[0] if (t.isCallExpression(node)) { node = t.expressionStatement(node) } ob_func_str.push(generator(node, optGenMin).code) }) path.stop() path.remove() } traverse(ast, { FunctionDeclaration: find_string_array_function }) return { version: 3, stringArrayName: ob_string_func_name, stringArrayCodes: ob_func_str, stringArrayCalls: ob_dec_name, } } function decodeGlobal(ast) { let obj = stringArrayV3(ast) if (!obj.stringArrayName) { obj = stringArrayV2(ast) } if (!obj.stringArrayName) { obj = stringArrayV0(ast) } if (!obj.stringArrayName) { console.error('Cannot find string list!') return false } console.log(`String List Name: ${obj.stringArrayName}`) let ob_func_str = obj.stringArrayCodes let ob_dec_call = obj.stringArrayCalls try { virtualGlobalEval(ob_func_str.join(';')) } catch (e) { // issue #31 if (e.name === 'ReferenceError') { let lost = e.message.split(' ')[0] traverse(ast, { Program(path) { let loc = path.scope.getBinding(lost).path let obj = t.variableDeclaration(loc.parent.kind, [loc.node]) ob_func_str.unshift(generator(obj, optGenMin).code) loc.node.init = null ob_dec_call.push({ name: lost, path: loc }) path.stop() }, }) virtualGlobalEval(ob_func_str.join(';')) } } // 递归删除混淆函数 function getChild(father) { if (father.key !== 'argument' || !father.parentPath.isReturnStatement()) { console.error(`Unexpected chained call: ${father}`) return null } const func = father.getFunctionParent() let name = func.node.id?.name let root let code if (name) { // FunctionDeclaration // function A (...) { return function B (...) } root = func code = generator(root.node, optGenMin).code } else { // FunctionExpression // var A = function (...) { return function B (...) } root = func.parentPath code = generator(t.variableDeclaration('var', [root])).code name = root.node.id.name } return { name: name, path: root, code: code, } } function dfs(stk, item) { stk.push(item) const cur_val = item.name console.log(`Enter sub ${stk.length}:${cur_val}`) let pfx = '' for (let parent of stk) { pfx += parent.code + ';' } virtualGlobalEval(pfx) let scope = item.path.scope if (item.path.isFunctionDeclaration()) { scope = item.path.parentPath.scope } const binding = scope.getBinding(cur_val) binding.scope.crawl() const refs = binding.scope.bindings[cur_val].referencePaths const refs_next = [] // 有4种链式调用情况: // - VariableDeclarator和FunctionDeclaration为原版 // - AssignmentExpression 出现于 #50 // - FunctionExpression 出现于 #94 for (let ref of refs) { const parent = ref.parentPath if (ref.key === 'callee') { // CallExpression let old_call = parent + '' try { // 运行成功则说明函数为直接调用并返回字符串 let new_str = virtualGlobalEval(old_call) console.log(`map: ${old_call} -> ${new_str}`) parent.replaceWith(t.StringLiteral(new_str)) } catch (e) { // 运行失败则说明函数为其它混淆函数的子函数 console.log(`sub: ${old_call}`) const ret = getChild(parent) if (ret) { refs_next.push(ret) } } } else if (ref.key === 'init') { // VariableDeclarator refs_next.push({ name: ref.parent.id.name, path: ref.parentPath, code: 'var ' + ref.parentPath, }) } else if (ref.key === 'right') { // AssignmentExpression // 这种情况尚不完善 可能会产生额外替换 refs_next.push({ name: ref.parent.left.name, path: ref.parentPath, code: 'var ' + ref.parentPath, }) } } for (let ref of refs_next) { dfs(stk, ref) } binding.scope.crawl() console.log(`Exit sub ${stk.length}:${cur_val}`) stk.pop() if (!item.path.parentPath.isCallExpression()) { item.path.remove() binding.scope.crawl() return } // 只会出现在AssignmentExpression情况下 需要再次运行 item.path.replaceWith(t.identifier(cur_val)) item.path = binding.path binding.scope.crawl() dfs(stk, item) } for (let item of ob_dec_call) { item.code = '' dfs([], item) } return true } function stringArrayLite(ast) { const visitor = { VariableDeclarator(path) { const name = path.node.id.name if (!path.get('init').isArrayExpression()) { return } const elements = path.node.init.elements for (const element of elements) { if (!t.isLiteral(element)) { return } } const bind = path.scope.getBinding(name) if (!bind.constant) { return } for (const ref of bind.referencePaths) { if ( !ref.parentPath.isMemberExpression() || ref.key !== 'object' || ref.parentPath.key == 'left' || !t.isNumericLiteral(ref.parent.property) ) { return } } console.log(`Extract string array: ${name}`) for (const ref of bind.referencePaths) { const i = ref.parent.property.value ref.parentPath.replaceWith(elements[i]) } bind.scope.crawl() path.remove() }, } traverse(ast, visitor) } function decodeCodeBlock(ast) { // 合并字面量 traverse(ast, calculateConstantExp) // 先合并分离的Object定义 traverse(ast, mergeObject) // 在变量定义完成后判断是否为代码块加密内容 traverse(ast, parseControlFlowStorage) // 合并字面量(在解除区域混淆后会出现新的可合并分割) traverse(ast, calculateConstantExp) return ast } function cleanSwitchCode(path) { // 扁平控制: // 会使用一个恒为true的while语句包裹一个switch语句 // switch语句的执行顺序又while语句上方的字符串决定 // 首先碰断是否符合这种情况 const node = path.node let valid = false if (t.isBooleanLiteral(node.test) && node.test.value) { valid = true } if (t.isArrayExpression(node.test) && node.test.elements.length === 0) { valid = true } if (!valid) { return } if (!t.isBlockStatement(node.body)) { return } const body = node.body.body if ( !t.isSwitchStatement(body[0]) || !t.isMemberExpression(body[0].discriminant) || !t.isBreakStatement(body[1]) ) { return } // switch语句的两个变量 const swithStm = body[0] const arrName = swithStm.discriminant.object.name const argName = swithStm.discriminant.property.argument.name // 在while上面的节点寻找这两个变量 let arr = [] let rm = [] path.getAllPrevSiblings().forEach((pre_path) => { if (!pre_path.isVariableDeclaration()) { return } for (let i = 0; i < pre_path.node.declarations.length; ++i) { const declaration = pre_path.get(`declarations.${i}`) let { id, init } = declaration.node if (arrName == id.name) { if (t.isStringLiteral(init?.callee?.object)) { arr = init.callee.object.value.split('|') rm.push(declaration) } } if (argName == id.name) { if (t.isLiteral(init)) { rm.push(declaration) } } } }) if (rm.length !== 2) { return } rm.forEach((pre_path) => { pre_path.remove() }) console.log(`扁平化还原: ${arrName}[${argName}]`) // 重建代码块 const caseList = swithStm.cases let resultBody = [] arr.map((targetIdx) => { // 从当前序号开始直到遇到continue let valid = true targetIdx = parseInt(targetIdx) while (valid && targetIdx < caseList.length) { const targetBody = caseList[targetIdx].consequent const test = caseList[targetIdx].test if (!t.isStringLiteral(test) || parseInt(test.value) !== targetIdx) { console.log(`switch中出现乱序的序号: ${test.value}:${targetIdx}`) } for (let i = 0; i < targetBody.length; ++i) { const s = targetBody[i] if (t.isContinueStatement(s)) { valid = false break } if (t.isReturnStatement(s)) { valid = false resultBody.push(s) break } if (t.isBreakStatement(s)) { console.log(`switch中出现意外的break: ${arrName}[${argName}]`) } else { resultBody.push(s) } } targetIdx++ } }) // 替换整个while语句 path.replaceInline(resultBody) } function cleanDeadCode(ast) { traverse(ast, calculateConstantExp) traverse(ast, pruneIfBranch) traverse(ast, { WhileStatement: { exit: cleanSwitchCode } }) return ast } function standardIfStatement(path) { const consequent = path.get('consequent') const alternate = path.get('alternate') const test = path.get('test') const evaluateTest = test.evaluateTruthy() if (!consequent.isBlockStatement()) { consequent.replaceWith(t.BlockStatement([consequent.node])) } if (alternate.node !== null && !alternate.isBlockStatement()) { alternate.replaceWith(t.BlockStatement([alternate.node])) } if (consequent.node.body.length == 0) { if (alternate.node == null) { path.replaceWith(test.node) } else { consequent.replaceWith(alternate.node) alternate.remove() path.node.alternate = null test.replaceWith(t.unaryExpression('!', test.node, true)) } } if (alternate.isBlockStatement() && alternate.node.body.length == 0) { alternate.remove() path.node.alternate = null } if (evaluateTest === true) { path.replaceWithMultiple(consequent.node.body) } else if (evaluateTest === false) { alternate.node === null ? path.remove() : path.replaceWithMultiple(alternate.node.body) } } function standardLoop(path) { const node = path.node if (!t.isBlockStatement(node.body)) { node.body = t.BlockStatement([node.body]) } } function purifyCode(ast) { // 标准化if语句 traverse(ast, { IfStatement: standardIfStatement }) // 标准化for语句 traverse(ast, { ForStatement: standardLoop }) // 标准化while语句 traverse(ast, { WhileStatement: standardLoop }) // 删除空语句 traverse(ast, { EmptyStatement: (path) => { path.remove() }, }) traverse(ast, splitAssignment) // 删除未使用的变量 traverse(ast, deleteUnusedVar) // 替换索引器 function FormatMember(path) { let curNode = path.node if (!t.isStringLiteral(curNode.property)) { return } if (curNode.computed === undefined || !curNode.computed === true) { return } if (!/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(curNode.property.value)) { return } curNode.property = t.identifier(curNode.property.value) curNode.computed = false } traverse(ast, { MemberExpression: FormatMember }) // 替换类和对象的计算方法和计算属性 // ["method"](){} -> "method"(){} function FormatComputed(path) { let curNode = path.node if (!t.isStringLiteral(curNode.key)) { return } curNode.computed = false } // "method"(){} -> method(){} function stringLiteralToIdentifier(path) { let curNode = path.node if (!t.isStringLiteral(curNode.key) || curNode.computed === true) { return } if (!/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(curNode.key.value)) { return } curNode.key = t.identifier(curNode.key.value) } traverse(ast, { 'Method|Property': (path) => { FormatComputed(path) stringLiteralToIdentifier(path) }, }) // 拆分语句 traverse(ast, splitSequence) return ast } function checkPattern(code, pattern) { let i = 0 let j = 0 while (i < code.length && j < pattern.length) { if (code[i] == pattern[j]) { ++j } ++i } return j == pattern.length } const deleteSelfDefendingCode = { VariableDeclarator(path) { const { id, init } = path.node const selfName = id.name if (!t.isCallExpression(init)) { return } if (!t.isIdentifier(init.callee)) { return } const callName = init.callee.name const args = init.arguments if ( args.length != 2 || !t.isThisExpression(args[0]) || !t.isFunctionExpression(args[1]) ) { return } const block = generator(args[1]).code const patterns = [ // @7920538 `return${selfName}.toString().search().toString().constructor(${selfName}).search()`, // @7135b09 `const=function(){const=.constructor()return.test(${selfName})}return()`, // #94 `var=function(){var=.constructor()return.test(${selfName})}return()`, ] let valid = false for (let pattern of patterns) { valid |= checkPattern(block, pattern) } if (!valid) { return } const refs = path.scope.bindings[selfName].referencePaths for (let ref of refs) { if (ref.key == 'callee') { ref.parentPath.remove() break } } path.remove() console.info(`Remove SelfDefendingFunc: ${selfName}`) const scope = path.scope.getBinding(callName).scope scope.crawl() const bind = scope.bindings[callName] if (bind.referenced) { console.error(`Call func ${callName} unexpected ref!`) } bind.path.remove() console.info(`Remove CallFunc: ${callName}`) }, } const deleteDebugProtectionCode = { FunctionDeclaration(path) { const { id, params, body } = path.node if ( !t.isIdentifier(id) || params.length !== 1 || !t.isIdentifier(params[0]) || !t.isBlockStatement(body) || body.body.length !== 2 || !t.isFunctionDeclaration(body.body[0]) || !t.isTryStatement(body.body[1]) ) { return } const debugName = id.name const ret = params[0].name const subNode = body.body[0] if ( !t.isIdentifier(subNode.id) || subNode.params.length !== 1 || !t.isIdentifier(subNode.params[0]) ) { return } const subName = subNode.id.name const counter = subNode.params[0].name const code = generator(body).code const pattern = `function${subName}(${counter}){${counter}debugger${subName}(++${counter})}try{if(${ret})return${subName}${subName}(0)}catch(){}` if (!checkPattern(code, pattern)) { return } const scope1 = path.parentPath.scope const refs = scope1.bindings[debugName].referencePaths for (let ref of refs) { if (ref.findParent((path) => path.removed)) { continue } if (ref.key == 0) { // DebugProtectionFunctionInterval @e8e92c6 const rm = ref.getFunctionParent().parentPath rm.remove() continue } // ref.key == 'callee' const up1 = ref.getFunctionParent() const callName = up1.parent.callee.name if (callName === 'setInterval') { // DebugProtectionFunctionInterval @51523c0 const rm = up1.parentPath rm.remove() continue } const up2 = up1.getFunctionParent()?.parentPath if (up2) { // DebugProtectionFunctionCall const scope2 = up2.scope.getBinding(callName).scope up2.remove() scope1.crawl() scope2.crawl() const bind = scope2.bindings[callName] bind.path.remove() console.info(`Remove CallFunc: ${callName}`) continue } // exceptions #95 const rm = ref.parentPath rm.remove() } path.remove() console.info(`Remove DebugProtectionFunc: ${debugName}`) }, } const deleteConsoleOutputCode = { VariableDeclarator(path) { const { id, init } = path.node const selfName = id.name if (!t.isCallExpression(init)) { return } if (!t.isIdentifier(init.callee)) { return } const callName = init.callee.name const args = init.arguments if ( args.length != 2 || !t.isThisExpression(args[0]) || !t.isFunctionExpression(args[1]) ) { return } const block = generator(args[1]).code const pattern = `console=console=log,warn,info,error,for(){${callName}constructor.prototype.bind${callName}${callName}bind${callName}}` if (!checkPattern(block, pattern)) { return } const refs = path.scope.bindings[selfName].referencePaths for (let ref of refs) { if (ref.key == 'callee') { ref.parentPath.remove() break } } path.remove() console.info(`Remove ConsoleOutputFunc: ${selfName}`) const scope = path.scope.getBinding(callName).scope scope.crawl() const bind = scope.bindings[callName] if (bind.referenced) { console.error(`Call func ${callName} unexpected ref!`) } bind.path.remove() console.info(`Remove CallFunc: ${callName}`) }, } function unlockEnv(ast) { //可能会误删一些代码,可屏蔽 traverse(ast, deleteSelfDefendingCode) traverse(ast, deleteDebugProtectionCode) traverse(ast, deleteConsoleOutputCode) return ast } export default function (code) { let ret = PluginEval.unpack(code) let global_eval = false if (ret) { global_eval = true code = ret } let ast try { ast = parse(code, { errorRecovery: true }) } catch (e) { console.error(`Cannot parse code: ${e.reasonCode}`) return null } // IllegalReturn traverse(ast, deleteIllegalReturn) // Lint before split statements traverse(ast, lintIfStatement) // Split declarations to avoid bugs traverse(ast, splitVarDeclaration) // 清理二进制显示内容 traverse(ast, { StringLiteral: ({ node }) => { delete node.extra }, NumericLiteral: ({ node }) => { delete node.extra }, }) console.log('还原数值...') if (!decodeObject(ast)) { return null } console.log('处理全局加密...') if (!decodeGlobal(ast)) { return null } console.log('提高代码可读性...') ast = purifyCode(ast) console.log('处理代码块加密...') stringArrayLite(ast) ast = decodeCodeBlock(ast) console.log('清理死代码...') ast = cleanDeadCode(ast) // 刷新代码 ast = parse(generator(ast, optGenMin).code, { errorRecovery: true }) console.log('提高代码可读性...') ast = purifyCode(ast) console.log('解除环境限制...') ast = unlockEnv(ast) console.log('净化完成') code = generator(ast, { jsescOption: { minimal: true } }).code if (global_eval) { code = PluginEval.pack(code) } return code } ================================================ FILE: src/plugin/sojson.js ================================================ /** * 在 babel_asttool.js 的基础上修改而来 */ import { parse } from '@babel/parser' import _generate from '@babel/generator' const generator = _generate.default import _traverse from '@babel/traverse' const traverse = _traverse.default import * as t from '@babel/types' import ivm from 'isolated-vm' import PluginEval from './eval.js' import calculateConstantExp from '../visitor/calculate-constant-exp.js' import deleteUnusedVar from '../visitor/delete-unused-var.js' import parseControlFlowStorage from '../visitor/parse-control-flow-storage.js' import pruneIfBranch from '../visitor/prune-if-branch.js' import splitSequence from '../visitor/split-sequence.js' const isolate = new ivm.Isolate() const globalContext = isolate.createContextSync() function virtualGlobalEval(jsStr) { return globalContext.evalSync(String(jsStr)) } function decodeGlobal(ast) { // 清理空语句 let i = 0 while (i < ast.program.body.length) { if (t.isEmptyStatement(ast.program.body[i])) { ast.program.body.splice(i, 1) } else { ++i } } // 前3句非空语句分别为签名信息、预处理函数、解密函数。 if (i < 3) { console.log('Error: code too short') return false } // 分离解密语句与内容语句 let decrypt_code = ast.program.body.slice(0, 3) if (!t.isVariableDeclaration(decrypt_code[0])) { console.log('Error: line 1 is not variable declaration') return false } let decrypt_fun = decrypt_code[2] if (t.isExpressionStatement(decrypt_fun)) { decrypt_fun = decrypt_code[1] } let decrypt_val if (t.isVariableDeclaration(decrypt_fun)) { decrypt_val = decrypt_fun.declarations[0].id.name } else if (t.isFunctionDeclaration(decrypt_fun)) { decrypt_val = decrypt_fun.id.name } else { console.log('Error: cannot find decrypt variable') return false } console.log(`主加密变量: ${decrypt_val}`) let content_code = ast.program.body.slice(3) // 运行解密语句 ast.program.body = decrypt_code let { code } = generator(ast, { compact: true, }) virtualGlobalEval(code) // 遍历内容语句 function funToStr(path) { let node = path.node if (!t.isIdentifier(node.callee, { name: decrypt_val })) { return } let tmp = path.toString() let value = virtualGlobalEval(tmp) // console.log(`还原前:${tmp} 还原后:${value}`) path.replaceWith(t.valueToNode(value)) } function memToStr(path) { let node = path.node if (!t.isIdentifier(node.object, { name: decrypt_val })) { return } let tmp = path.toString() let value = virtualGlobalEval(tmp) // console.log(`还原前:${tmp} 还原后:${value}`) path.replaceWith(t.valueToNode(value)) } ast.program.body = content_code traverse(ast, { CallExpression: funToStr, MemberExpression: memToStr, }) return ast } function cleanSwitchCode(path) { // 扁平控制: // 会使用一个恒为true的while语句包裹一个switch语句 // switch语句的执行顺序又while语句上方的字符串决定 // 首先碰断是否符合这种情况 const node = path.node if (!(t.isBooleanLiteral(node.test) || t.isUnaryExpression(node.test))) { return } if (!(node.test.prefix || node.test.value)) { return } if (!t.isBlockStatement(node.body)) { return } const body = node.body.body if ( !t.isSwitchStatement(body[0]) || !t.isMemberExpression(body[0].discriminant) || !t.isBreakStatement(body[1]) ) { return } // switch语句的两个变量 const swithStm = body[0] const arrName = swithStm.discriminant.object.name const argName = swithStm.discriminant.property.argument.name console.log(`扁平化还原: ${arrName}[${argName}]`) // 在while上面的节点寻找这两个变量 let arr = [] path.getAllPrevSiblings().forEach((pre_path) => { const { declarations } = pre_path.node let { id, init } = declarations[0] if (arrName == id.name) { arr = init.callee.object.value.split('|') pre_path.remove() } if (argName == id.name) { pre_path.remove() } }) // 重建代码块 const caseList = swithStm.cases let resultBody = [] arr.map((targetIdx) => { // 从当前序号开始直到遇到continue let valid = true targetIdx = parseInt(targetIdx) while (valid && targetIdx < caseList.length) { const targetBody = caseList[targetIdx].consequent const test = caseList[targetIdx].test if (!t.isStringLiteral(test) || parseInt(test.value) !== targetIdx) { console.log(`switch中出现乱序的序号: ${test.value}:${targetIdx}`) } for (let i = 0; i < targetBody.length; ++i) { const s = targetBody[i] if (t.isContinueStatement(s)) { valid = false break } if (t.isReturnStatement(s)) { valid = false resultBody.push(s) break } if (t.isBreakStatement(s)) { console.log(`switch中出现意外的break: ${arrName}[${argName}]`) } else { resultBody.push(s) } } targetIdx++ } }) // 替换整个while语句 path.replaceInline(resultBody) } function cleanDeadCode(ast) { traverse(ast, calculateConstantExp) traverse(ast, pruneIfBranch) traverse(ast, { WhileStatement: { exit: cleanSwitchCode } }) return ast } function checkPattern(code, pattern) { let i = 0 let j = 0 while (i < code.length && j < pattern.length) { if (code[i] == pattern[j]) { ++j } ++i } return j == pattern.length } /** * Two RegExp tests will be conducted here: * * If '\n' exists (code formatted) * * If '\u' or '\x' does not exist (literal formatted) * * An infinite call stack will appear if either of the test fails. * (by replacing the 'e' with '\u0435') */ const deleteSelfDefendingCode = { VariableDeclarator(path) { const { id, init } = path.node const selfName = id.name if (!t.isCallExpression(init)) { return } if (!t.isIdentifier(init.callee)) { return } const callName = init.callee.name const args = init.arguments if ( args.length != 2 || !t.isThisExpression(args[0]) || !t.isFunctionExpression(args[1]) ) { return } const block = generator(args[1]).code const pattern = `RegExp()return.test(.toString())RegExp()return.test(.toString())\u0435\u0435` if (!checkPattern(block, pattern)) { return } const refs = path.scope.bindings[selfName].referencePaths for (let ref of refs) { if (ref.key == 'callee') { ref.parentPath.remove() break } } path.remove() console.info(`Remove SelfDefendingFunc: ${selfName}`) const scope = path.scope.getBinding(callName).scope scope.crawl() const bind = scope.bindings[callName] if (bind.referenced) { console.error(`Call func ${callName} unexpected ref!`) } bind.path.remove() console.info(`Remove CallFunc: ${callName}`) }, } /** * A "debugger" will be inserted by: * * v5: directly. * * v6: calling Function constructor twice. */ const deleteDebugProtectionCode = { FunctionDeclaration(path) { const { id, params, body } = path.node if ( !t.isIdentifier(id) || params.length !== 1 || !t.isIdentifier(params[0]) || !t.isBlockStatement(body) || body.body.length !== 2 || !t.isFunctionDeclaration(body.body[0]) || !t.isTryStatement(body.body[1]) ) { return } const debugName = id.name const ret = params[0].name const subNode = body.body[0] if ( !t.isIdentifier(subNode.id) || subNode.params.length !== 1 || !t.isIdentifier(subNode.params[0]) ) { return } const subName = subNode.id.name const counter = subNode.params[0].name const code = generator(body).code const pattern = `function${subName}(${counter}){${counter}debug${subName}(++${counter})}try{if(${ret})return${subName}${subName}(0)}catch(){}` if (!checkPattern(code, pattern)) { return } const scope1 = path.parentPath.scope const refs = scope1.bindings[debugName].referencePaths for (let ref of refs) { if (ref.findParent((path) => path.removed)) { continue } let parent = ref.getFunctionParent() if (parent.key == 0) { // DebugProtectionFunctionInterval // window.setInterval(Function(), ...) const rm = parent.parentPath rm.remove() continue } // DebugProtectionFunctionCall const callName = parent.parent.callee.name const up2 = parent.getFunctionParent().parentPath const scope2 = up2.scope.getBinding(callName).scope up2.remove() scope1.crawl() scope2.crawl() const bind = scope2.bindings[callName] bind.path.remove() console.info(`Remove CallFunc: ${callName}`) } path.remove() console.info(`Remove DebugProtectionFunc: ${debugName}`) }, } const deleteConsoleOutputCode = { VariableDeclarator(path) { const { id, init } = path.node const selfName = id.name if (!t.isCallExpression(init)) { return } if (!t.isIdentifier(init.callee)) { return } const callName = init.callee.name const args = init.arguments if ( args.length != 2 || !t.isThisExpression(args[0]) || !t.isFunctionExpression(args[1]) ) { return } const body = args[1].body.body if (body.length !== 3) { return } if ( !t.isVariableDeclaration(body[0]) || !t.isVariableDeclaration(body[1]) || !t.isIfStatement(body[2]) ) { return } const feature = [ [], ['window', 'process', 'require', 'global'], [ 'console', 'log', 'warn', 'debug', 'info', 'error', 'exception', 'trace', ], ] let valid = true for (let i = 1; i < 3; ++i) { const { code } = generator(body[i]) feature[i].map((key) => { if (code.indexOf(key) == -1) { valid = false } }) } if (!valid) { return } const refs = path.scope.bindings[selfName].referencePaths for (let ref of refs) { if (ref.key == 'callee') { ref.parentPath.remove() break } } path.remove() console.info(`Remove ConsoleOutputFunc: ${selfName}`) const scope = path.scope.getBinding(callName).scope scope.crawl() const bind = scope.bindings[callName] if (bind.referenced) { console.error(`Call func ${callName} unexpected ref!`) } bind.path.remove() console.info(`Remove CallFunc: ${callName}`) }, } const deleteVersionCheck = { StringLiteral(path) { const msg = '删除版本号,js会定期弹窗,还请支持我们的工作' if (path.node.value !== msg) { return } let fnPath = path.getFunctionParent().parentPath if (!fnPath.isCallExpression()) { return } fnPath.remove() console.log('Remove VersionCheck') }, } function unlockEnv(ast) { // 查找并删除`自卫模式`函数 traverse(ast, deleteSelfDefendingCode) // 查找并删除`禁止控制台调试`函数 traverse(ast, deleteDebugProtectionCode) // 清空`禁止控制台输出`函数 traverse(ast, deleteConsoleOutputCode) // 删除版本号检测 traverse(ast, deleteVersionCheck) return ast } function purifyFunction(path) { const node = path.node if (!t.isIdentifier(node.left) || !t.isFunctionExpression(node.right)) { return } const name = node.left.name if (node.right.body.body.length !== 1) { return } let retStmt = node.right.body.body[0] if (!t.isReturnStatement(retStmt)) { return } if (!t.isBinaryExpression(retStmt.argument, { operator: '+' })) { return } try { const fnPath = path.getFunctionParent() || path.scope.path fnPath.traverse({ CallExpression: function (_path) { const _node = _path.node.callee if (!t.isIdentifier(_node, { name: name })) { return } let args = _path.node.arguments _path.replaceWith(t.binaryExpression('+', args[0], args[1])) }, }) path.remove() console.log(`拼接类函数: ${name}`) } catch { let code = generator(path.node, { minified: true }).code console.warn('Purify function failed: ' + code) } } function purifyCode(ast) { // 净化拼接字符串的函数 traverse(ast, { AssignmentExpression: purifyFunction }) // 计算常量表达式 traverse(ast, calculateConstantExp) // 替换索引器 function FormatMember(path) { // _0x19882c['removeCookie']['toString']() // | // | // | // v // _0x19882c.removeCookie.toString() let curNode = path.node if (!t.isStringLiteral(curNode.property)) { return } if (curNode.computed === undefined || !curNode.computed === true) { return } if (!/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(curNode.property.value)) { return } curNode.property = t.identifier(curNode.property.value) curNode.computed = false } traverse(ast, { MemberExpression: FormatMember }) // 分割表达式 traverse(ast, splitSequence) // 删除空语句 traverse(ast, { EmptyStatement: (path) => { path.remove() }, }) // 删除未使用的变量 traverse(ast, deleteUnusedVar) return ast } export default function (code) { let ret = PluginEval.unpack(code) let global_eval = false if (ret) { global_eval = true code = ret } let ast = parse(code) // 清理二进制显示内容 traverse(ast, { StringLiteral: ({ node }) => { delete node.extra }, }) traverse(ast, { NumericLiteral: ({ node }) => { delete node.extra }, }) console.log('处理全局加密...') ast = decodeGlobal(ast) if (!ast) { return null } console.log('处理代码块加密...') traverse(ast, parseControlFlowStorage) console.log('清理死代码...') ast = cleanDeadCode(ast) // 刷新代码 ast = parse( generator(ast, { comments: false, jsescOption: { minimal: true }, }).code ) console.log('提高代码可读性...') ast = purifyCode(ast) console.log('解除环境限制...') ast = unlockEnv(ast) console.log('净化完成') code = generator(ast, { comments: false, jsescOption: { minimal: true }, }).code if (global_eval) { code = PluginEval.pack(code) } return code } ================================================ FILE: src/plugin/sojsonv7.js ================================================ /** * For jsjiami.com.v7 */ import { parse } from '@babel/parser' import _generate from '@babel/generator' const generator = _generate.default import _traverse from '@babel/traverse' const traverse = _traverse.default import * as t from '@babel/types' import ivm from 'isolated-vm' import PluginEval from './eval.js' import calculateConstantExp from '../visitor/calculate-constant-exp.js' import deleteIllegalReturn from '../visitor/delete-illegal-return.js' import deleteUnusedVar from '../visitor/delete-unused-var.js' import parseControlFlowStorage from '../visitor/parse-control-flow-storage.js' import pruneIfBranch from '../visitor/prune-if-branch.js' import splitSequence from '../visitor/split-sequence.js' const isolate = new ivm.Isolate() const globalContext = isolate.createContextSync() function virtualGlobalEval(jsStr) { return globalContext.evalSync(String(jsStr)) } function evalOneTime(str) { const vm = new ivm.Isolate() const ret = vm.createContextSync().evalSync(String(str)) vm.dispose() return ret } function decodeGlobal(ast) { // 清理空语句 let i = 0 while (i < ast.program.body.length) { if (t.isEmptyStatement(ast.program.body[i])) { ast.program.body.splice(i, 1) } else { ++i } } // line 1: version and string table // line x: preprocessing function of string table // line y: main encode function containing the var of string table if (i < 3) { console.log('Error: code too short') return false } // split the first line traverse(ast, { Program(path) { path.stop() const l1 = path.get('body.0') if (!l1.isVariableDeclaration()) { return } const defs = l1.node.declarations const kind = l1.node.kind for (let i = defs.length - 1; i; --i) { l1.insertAfter(t.VariableDeclaration(kind, [defs[i]])) l1.get(`declarations.${i}`).remove() } l1.scope.crawl() }, }) // find the main encode function // [version, string-array, call, ...] let decrypt_code = [] for (let i = 0; i < 3; ++i) { decrypt_code.push(t.EmptyStatement()) } const first_line = ast.program.body[0] let var_version if (t.isVariableDeclaration(first_line)) { if (first_line.declarations.length) { var_version = first_line.declarations[0].id.name } } else if (t.isCallExpression(first_line?.expression)) { let call_func = first_line.expression.callee?.name let i = ast.program.body.length let find = false while (--i) { let part = ast.program.body[i] if (!t.isFunctionDeclaration(part) || part?.id?.name !== call_func) { continue } if (find) { // remove duplicate definition ast.program.body[i] = t.emptyStatement() continue } find = true let obj = part.body.body[0]?.expression?.left if (!obj || !t.isMemberExpression(obj) || obj.object?.name !== 'global') { break } var_version = obj.property?.value decrypt_code.push(part) ast.program.body[i] = t.emptyStatement() continue } } if (!var_version) { console.error('Line 1 is not version variable!') return false } console.info(`Version var: ${var_version}`) decrypt_code[0] = first_line ast.program.body.shift() // iterate and classify all refs of var_version const refs = { string_var: null, string_path: null, def: [], } traverse(ast, { Identifier: (path) => { const name = path.node.name if (name !== var_version) { return } const up1 = path.parentPath if (up1.isVariableDeclarator()) { refs.def.push(path) } else if (up1.isArrayExpression()) { let node_table = path.getFunctionParent() while (node_table.getFunctionParent()) { node_table = node_table.getFunctionParent() } let var_string_table = null if (node_table.node.id) { var_string_table = node_table.node.id.name } else { while (!node_table.isVariableDeclarator()) { node_table = node_table.parentPath } var_string_table = node_table.node.id.name } let valid = true up1.traverse({ MemberExpression(path) { valid = false path.stop() }, }) if (valid) { refs.string_var = var_string_table refs.string_path = node_table } else { console.info(`Drop string table: ${var_string_table}`) } } else if (up1.isAssignmentExpression() && path.key === 'left') { // We don't need to execute this reference // Instead, we can delete it directly const up2 = up1.parentPath up2.replaceWith(up2.node.left) } else { console.warn(`Unexpected ref var_version: ${up1}`) } }, }) // check if contains string table let var_string_table = refs.string_var if (!var_string_table) { console.error('Cannot find string table') return false } // check if contains rotate function and decrypt variable let decrypt_val let decrypt_path let binds = refs.string_path.scope.getBinding(var_string_table) function parse_main_call(path) { decrypt_path = path const node = path.node const copy = t.functionDeclaration(node.id, node.params, node.body) node.body = t.blockStatement([]) return copy } // remove path of string table if (refs.string_path.isVariableDeclarator()) { decrypt_code[1] = t.variableDeclaration('var', [refs.string_path.node]) } else { decrypt_code[1] = refs.string_path.node } refs.string_path.remove() // iterate refs let cache = undefined for (let bind of binds.referencePaths) { if (bind.findParent((path) => path.removed)) { continue } const parent = bind.parentPath if (parent.isCallExpression() && bind.listKey === 'arguments') { // This is the rotate function. // If it's in a sequence expression, it can be handled together. // Or, we should handle it after this iteration. cache = parent continue } if (parent.isSequenceExpression()) { // rotate function decrypt_code.push(t.expressionStatement(parent.node)) const up2 = parent.parentPath if (up2.isIfStatement()) { // In the new version, rotate function will be enclosed by an // empty IfStatement up2.remove() } else { parent.remove() } continue } if (parent.isVariableDeclarator()) { // main decrypt val let top = parent.getFunctionParent() while (top.getFunctionParent()) { top = top.getFunctionParent() } decrypt_code[2] = parse_main_call(top) decrypt_val = top.node.id.name continue } if (parent.isCallExpression() && !parent.node.arguments.length) { // main decrypt val if (!t.isVariableDeclarator(parent.parentPath.node)) { continue } let top = parent.getFunctionParent() while (top.getFunctionParent()) { top = top.getFunctionParent() } decrypt_code[2] = parse_main_call(top) decrypt_val = top.node.id.name continue } if (parent.isExpressionStatement()) { parent.remove() continue } console.warn(`Unexpected ref var_string_table: ${parent}`) } // If rotateFunction is detected but not handled, we should handle it now. if (decrypt_code.length === 3 && cache) { if (cache.parentPath.isExpressionStatement()) { decrypt_code.push(cache.parent) cache = cache.parentPath } else { decrypt_code.push(t.expressionStatement(cache.node)) } cache.remove() } decrypt_path.parentPath.scope.crawl() if (!decrypt_val) { console.error('Cannot find decrypt variable') return } console.log(`Main call wrapper name: ${decrypt_val}`) // 运行解密语句 let content_code = ast.program.body ast.program.body = decrypt_code let { code } = generator(ast, { compact: true, }) virtualGlobalEval(code) // 遍历内容语句 ast.program.body = content_code function funToStr(path) { let tmp = path.toString() let value = virtualGlobalEval(tmp) // console.log(`还原前:${tmp} 还原后:${value}`) path.replaceWith(t.valueToNode(value)) } function memToStr(path) { let tmp = path.toString() let value = virtualGlobalEval(tmp) // console.log(`还原前:${tmp} 还原后:${value}`) path.replaceWith(t.valueToNode(value)) } function dfs(stk, item) { stk.push(item) const cur_val = item.name console.log(`Enter sub ${stk.length}:${cur_val}`) let pfx = '' for (let parent of stk) { pfx += parent.code + ';' } virtualGlobalEval(pfx) let scope = item.path.scope if (item.path.isFunctionDeclaration()) { scope = item.path.parentPath.scope } // var is function scoped and let is block scoped // Hence, var may not be in the current scope, e.g., in a for-loop const binding = scope.getBinding(cur_val) scope = binding.scope const refs = binding.referencePaths const refs_next = [] for (let ref of refs) { const parent = ref.parentPath if (ref.key === 'init') { // VariableDeclarator refs_next.push({ name: parent.node.id.name, path: parent, code: 'var ' + parent, }) } else if (ref.key === 'right') { // AssignmentExpression refs_next.push({ name: parent.node.left.name, path: parent, code: 'var ' + parent, }) } else if (ref.key === 'object') { // MemberExpression memToStr(parent) } else if (ref.key === 'callee') { // CallExpression funToStr(parent) } else { console.error('Unexpected reference') } } for (let ref of refs_next) { dfs(stk, ref) } scope.crawl() item.path.remove() scope.crawl() console.log(`Exit sub ${stk.length}:${cur_val}`) stk.pop() } const root = { name: decrypt_val, path: decrypt_path, code: '', } dfs([], root) return ast } function cleanSwitchCode1(path) { // 扁平控制: // 会使用一个恒为true的while语句包裹一个switch语句 // switch语句的执行顺序又while语句上方的字符串决定 // 首先碰断是否符合这种情况 const node = path.node if (!(t.isBooleanLiteral(node.test) || t.isUnaryExpression(node.test))) { return } if (!(node.test.prefix || node.test.value)) { return } if (!t.isBlockStatement(node.body)) { return } const body = node.body.body if ( !t.isSwitchStatement(body[0]) || !t.isMemberExpression(body[0].discriminant) || !t.isBreakStatement(body[1]) ) { return } // switch语句的两个变量 const swithStm = body[0] const arrName = swithStm.discriminant.object.name const argName = swithStm.discriminant.property.argument.name console.log(`扁平化还原: ${arrName}[${argName}]`) // 在while上面的节点寻找这两个变量 let arr = [] path.getAllPrevSiblings().forEach((pre_path) => { const { declarations } = pre_path.node let { id, init } = declarations[0] if (arrName == id.name) { arr = init.callee.object.value.split('|') pre_path.remove() } if (argName == id.name) { pre_path.remove() } }) // 重建代码块 const caseList = swithStm.cases let resultBody = [] arr.map((targetIdx) => { // 从当前序号开始直到遇到continue let valid = true targetIdx = parseInt(targetIdx) while (valid && targetIdx < caseList.length) { const targetBody = caseList[targetIdx].consequent const test = caseList[targetIdx].test if (!t.isStringLiteral(test) || parseInt(test.value) !== targetIdx) { console.log(`switch中出现乱序的序号: ${test.value}:${targetIdx}`) } for (let i = 0; i < targetBody.length; ++i) { const s = targetBody[i] if (t.isContinueStatement(s)) { valid = false break } if (t.isReturnStatement(s)) { valid = false resultBody.push(s) break } if (t.isBreakStatement(s)) { console.log(`switch中出现意外的break: ${arrName}[${argName}]`) } else { resultBody.push(s) } } targetIdx++ } }) // 替换整个while语句 path.replaceInline(resultBody) } function cleanSwitchCode2(path) { // 扁平控制: // 会使用一个空的for语句包裹一个switch语句 // switch语句的执行顺序由for语句上方的字符串决定 // 首先判断是否符合这种情况 const node = path.node if (node.init || node.test || node.update) { return } if (!t.isBlockStatement(node.body)) { return } const body = node.body.body if ( !t.isSwitchStatement(body[0]) || !t.isMemberExpression(body[0].discriminant) || !t.isBreakStatement(body[1]) ) { return } // switch语句的两个变量 const swithStm = body[0] const arrName = swithStm.discriminant.object.name const argName = swithStm.discriminant.property.argument.name // 在while上面的节点寻找这两个变量 let arr = null for (let pre_path of path.getAllPrevSiblings()) { if (!pre_path.isVariableDeclaration()) { continue } let test = '' + pre_path try { arr = evalOneTime(test + `;${arrName}.join('|')`) arr = arr.split('|') } catch { // } } if (!Array.isArray(arr)) { return } console.log(`扁平化还原: ${arrName}[${argName}]`) // 重建代码块 const caseMap = {} for (let item of swithStm.cases) { caseMap[item.test.value] = item.consequent } let resultBody = [] arr.map((targetIdx) => { // 从当前序号开始直到遇到continue let valid = true while (valid && targetIdx < arr.length) { const targetBody = caseMap[targetIdx] for (let i = 0; i < targetBody.length; ++i) { const s = targetBody[i] if (t.isContinueStatement(s)) { valid = false break } if (t.isReturnStatement(s)) { valid = false resultBody.push(s) break } if (t.isBreakStatement(s)) { console.log(`switch中出现意外的break: ${arrName}[${argName}]`) } else { resultBody.push(s) } } targetIdx++ } }) // 替换整个while语句 path.replaceInline(resultBody) } function cleanDeadCode(ast) { traverse(ast, calculateConstantExp) traverse(ast, pruneIfBranch) traverse(ast, { WhileStatement: { exit: cleanSwitchCode1 } }) traverse(ast, { ForStatement: { exit: cleanSwitchCode2 } }) return ast } function removeUniqueCall(path) { let up1 = path.parentPath let decorator = up1.node.callee.name console.info(`Remove decorator: ${decorator}`) let bind1 = up1.scope.getBinding(decorator) bind1.path.remove() if (up1.key === 'callee') { up1.parentPath.remove() } else if (up1.key === 'init') { let up2 = up1.parentPath let call = up2.node.id.name console.info(`Remove call: ${call}`) let bind2 = up2.scope.getBinding(call) up2.remove() for (let ref of bind2.referencePaths) { if (ref.findParent((path) => path.removed)) { continue } if (ref.key === 'callee') { let rm = ref.parentPath if (rm.key === 'expression') { rm = rm.parentPath } rm.remove() } else { console.warn(`Unexpected ref key: ${ref.key}`) } } } } function unlockDebugger(path) { const decl_path = path.getFunctionParent()?.getFunctionParent() if (!decl_path) { return } // Check if contains inf-loop let valid = false path.getFunctionParent().traverse({ WhileStatement(path) { if (t.isBooleanLiteral(path.node.test) && path.node.test) { valid = true } }, }) if (!valid) { return } const name = decl_path.node.id.name const bind = decl_path.scope.getBinding(name) console.info(`Debug test and inf-loop: ${name}`) for (let ref of bind.referencePaths) { if (ref.findParent((path) => path.removed)) { continue } if (ref.listKey === 'arguments') { // setInterval let rm = ref.getFunctionParent().parentPath if (rm.key === 'expression') { rm = rm.parentPath } rm.remove() } else if (ref.key === 'callee') { // lint test for this method let rm = ref.getFunctionParent() removeUniqueCall(rm) } else { console.warn(`Unexpected ref key: ${ref.key}`) } } decl_path.remove() path.stop() } function unlockConsole(path) { if (!t.isArrayExpression(path.node.init)) { return } let pattern = 'log|warn|debug|info|error|exception|table|trace' let count = 0 for (let ele of path.node.init.elements) { if (~pattern.indexOf(ele.value)) { ++count continue } return } if (count < 5) { return } let left1 = path.getSibling(0) const code = generator(left1.node, { minified: true }).code pattern = ['window', 'process', 'require', 'global'] pattern.map((key) => { if (code.indexOf(key) == -1) { return } }) let rm = path.getFunctionParent() removeUniqueCall(rm) } function unlockLint(path) { if (path.findParent((up) => up.removed)) { return } if (path.node.value !== '(((.+)+)+)+$') { return } let rm = path.getFunctionParent() removeUniqueCall(rm) } function unlockDomainLock(path) { const array_list = [ '[7,116,5,101,3,117,0,100]', '[5,110,0,100]', '[7,110,0,108]', '[7,101,0,104]', ] const checkArray = (node) => { const trim = node.split(' ').join('') for (let i = 0; i < 4; ++i) { if (array_list[i] == trim) { return i + 1 } } return 0 } if (path.findParent((up) => up.removed)) { return } let mask = 1 << checkArray('' + path) if (mask == 1) { return } let rm = path.getFunctionParent() rm.traverse({ ArrayExpression: function (item) { mask = mask | (1 << checkArray('' + item)) }, }) if (mask & 0b11110) { console.info('Find domain lock') removeUniqueCall(rm) } } function unlockEnv(ast) { // 删除`禁止控制台调试`函数 traverse(ast, { DebuggerStatement: unlockDebugger }) // 删除`禁止控制台输出`函数 traverse(ast, { VariableDeclarator: unlockConsole }) // 删除`禁止换行`函数 traverse(ast, { StringLiteral: unlockLint }) // 删除`安全域名`函数 traverse(ast, { ArrayExpression: unlockDomainLock }) } /** * If a function acts as follows: * A = function (p1, p2) { return p1 + p2 } * * Convert its call to a binary expression: * A(a, b) => a + b */ function purifyFunction(path) { const left = path.get('left') const right = path.get('right') if (!left.isIdentifier() || !right.isFunctionExpression()) { return } const name = left.node.name const params = right.node.params if (params.length !== 2) { return } const name1 = params[0].name const name2 = params[1].name if (right.node.body.body.length !== 1) { return } let retStmt = right.node.body.body[0] if (!t.isReturnStatement(retStmt)) { return } if (!t.isBinaryExpression(retStmt.argument, { operator: '+' })) { return } if ( retStmt.argument.left?.name !== name1 || retStmt.argument.right?.name !== name2 ) { return } const fnPath = path.getFunctionParent() || path.scope.path fnPath.traverse({ CallExpression: function (_path) { const _node = _path.node.callee if (!t.isIdentifier(_node, { name: name })) { return } let args = _path.node.arguments _path.replaceWith(t.binaryExpression('+', args[0], args[1])) }, }) path.remove() console.log(`拼接类函数: ${name}`) } function purifyCode(ast) { // 净化拼接字符串的函数 traverse(ast, { AssignmentExpression: purifyFunction }) // 计算常量表达式 traverse(ast, calculateConstantExp) // 替换索引器 function FormatMember(path) { // _0x19882c['removeCookie']['toString']() // | // | // | // v // _0x19882c.removeCookie.toString() let curNode = path.node if (!t.isStringLiteral(curNode.property)) { return } if (curNode.computed === undefined || !curNode.computed === true) { return } if (!/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(curNode.property.value)) { return } curNode.property = t.identifier(curNode.property.value) curNode.computed = false } traverse(ast, { MemberExpression: FormatMember }) // 分割表达式 traverse(ast, splitSequence) // 删除空语句 traverse(ast, { EmptyStatement: (path) => { path.remove() }, }) // 删除未使用的变量 traverse(ast, deleteUnusedVar) } export default function (code) { let ret = PluginEval.unpack(code) let global_eval = false if (ret) { global_eval = true code = ret } let ast try { ast = parse(code, { errorRecovery: true }) } catch (e) { console.error(`Cannot parse code: ${e.reasonCode}`) return null } // IllegalReturn traverse(ast, deleteIllegalReturn) // 清理二进制显示内容 traverse(ast, { StringLiteral: ({ node }) => { delete node.extra }, }) traverse(ast, { NumericLiteral: ({ node }) => { delete node.extra }, }) console.log('处理全局加密...') ast = decodeGlobal(ast) if (!ast) { return null } console.log('处理代码块加密...') traverse(ast, parseControlFlowStorage) console.log('清理死代码...') ast = cleanDeadCode(ast) // 刷新代码 ast = parse( generator(ast, { comments: false, jsescOption: { minimal: true }, }).code ) console.log('提高代码可读性...') purifyCode(ast) ast = parse(generator(ast, { comments: false }).code) console.log('解除环境限制...') unlockEnv(ast) console.log('净化完成') code = generator(ast, { comments: false, jsescOption: { minimal: true }, }).code if (global_eval) { code = PluginEval.pack(code) } return code } ================================================ FILE: src/utility/check-func.js ================================================ function checkPattern(code, pattern) { let i = 0 let j = 0 while (i < code.length && j < pattern.length) { if (code[i] == pattern[j]) { ++j } ++i } return j == pattern.length } export default { checkPattern, } ================================================ FILE: src/utility/safe-func.js ================================================ import * as t from '@babel/types' function safeDeleteNode(name, path) { let binding if (path.isFunctionDeclaration()) { binding = path.parentPath.scope.getBinding(name) } else { binding = path.scope.getBinding(name) } if (!binding) { return false } binding.scope.crawl() binding = binding.scope.getBinding(name) if (binding.references) { return false } for (const item of binding.constantViolations) { item.remove() } const decl = binding.path if (decl.removed) { return true } if (!decl.isVariableDeclarator() && !decl.isFunctionDeclaration()) { return true } binding.path.remove() return true } function safeGetLiteral(path) { if (path.isUnaryExpression()) { if (path.node.operator === '-' && path.get('argument').isNumericLiteral()) { return -1 * path.get('argument').node.value } return null } if (path.isLiteral()) { return path.node.value } return null } function safeGetName(path) { if (path.isIdentifier()) { return path.node.name } if (path.isLiteral()) { return path.node.value } return null } function safeReplace(path, value) { if (typeof value === 'string') { path.replaceWith(t.stringLiteral(value)) return } if (typeof value === 'number') { path.replaceWith(t.numericLiteral(value)) return } path.replaceWithSourceString(value) } export default { safeDeleteNode, safeGetLiteral, safeGetName, safeReplace, } ================================================ FILE: src/visitor/calculate-constant-exp.js ================================================ import _generate from '@babel/generator' const generator = _generate.default import * as t from '@babel/types' function checkLiteral(node) { if (t.isNumericLiteral(node)) { return 'positive' } if (t.isLiteral(node)) { return 'literal' } if (!t.isUnaryExpression(node)) { return false } if (!t.isNumericLiteral(node.argument)) { return false } if (node.operator === '-') { return 'negative' } return false } /** * Calculate BinaryExpression if left and right are both literals. * Otherwise, the expression can't be simplified. * Note that negative numbers are UnaryExpressions. */ function calculateBinaryExpression(path) { const { left, right } = path.node if (!checkLiteral(left) || !checkLiteral(right)) { return } const code = generator(path.node).code try { const ret = eval(code) // The strings cannot use replaceWithSourceString // For example, string "ab" will be parsed as identifier ab if (typeof ret === 'string') { path.replaceWith(t.stringLiteral(ret)) } else { path.replaceWithSourceString(ret) } } catch { // } } /** * Calculate UnaryExpression: * - the operator is `!` and the argument is ArrayExpression or Literal. * - the operator is `-` and the argument is a negative number * - the operator is `+`, or `~`, and the argument is a number * - the operator is 'void' and the argument is Literal. * - the operator is 'typeof' and the argument is Literal. * * Otherwise, the expression can't be simplified. * For example, `typeof window` can be calculated but it's not constant. */ function calculateUnaryExpression(path) { const node0 = path.node const node1 = node0.argument const isLiteral = checkLiteral(node1) if (node0.operator === '!') { if (t.isArrayExpression(node1)) { if (node1.elements.length === 0) { path.replaceWith(t.booleanLiteral(false)) } } if (isLiteral) { const code = generator(node0).code path.replaceWith(t.booleanLiteral(eval(code))) } return } if (node0.operator === '-') { if (isLiteral === 'negative') { const code = generator(node0).code path.replaceWithSourceString(eval(code)) } return } if (node0.operator === '+' || node0.operator === '~') { if (isLiteral === 'negative' || isLiteral === 'positive') { const code = generator(node0).code path.replaceWithSourceString(eval(code)) } return } if (node0.operator === 'void') { if (isLiteral) { path.replaceWith(t.identifier('undefined')) } return } if (node0.operator === 'typeof') { if (isLiteral) { const code = generator(node0).code path.replaceWith(t.stringLiteral(eval(code))) } return } } export default { BinaryExpression: { exit: calculateBinaryExpression }, UnaryExpression: { exit: calculateUnaryExpression }, } ================================================ FILE: src/visitor/calculate-rstring.js ================================================ import _generate from '@babel/generator' const generator = _generate.default import * as t from '@babel/types' /** * "sh".split("").reverse().join("") -> "hs" */ export default { StringLiteral(path) { if (path.key !== 'object') { return } let root = path let count = 6 while (root.parentPath && count) { if ( root.parentPath.isMemberExpression() || root.parentPath.isCallExpression() ) { root = root.parentPath --count } else { break } } if (count) { return } const code = generator(root.node).code try { const ret = eval(code) root.replaceWith(t.stringLiteral(ret)) } catch { // } }, } ================================================ FILE: src/visitor/delete-extra.js ================================================ /** * 0x10 -> 16, "\u0058" -> "X" * not ASCII-safe (disable jsescOption:minimal to keep ASCII-safe) */ export default { StringLiteral: ({ node }) => { delete node.extra }, NumericLiteral: ({ node }) => { delete node.extra }, } ================================================ FILE: src/visitor/delete-illegal-return.js ================================================ /** * delete ReturnStatement in Program scope */ export default { ReturnStatement(path) { if (!path.getFunctionParent()) { path.remove() } }, } ================================================ FILE: src/visitor/delete-nested-blocks.js ================================================ const isIntersect = (path, bindings) => { path.scope.crawl() for (const key of Object.keys(bindings)) { if (path.scope.hasBinding(key)) { return true } } return false } /** * Avoid nested blocks. * * This is slightly different from the @putout/plugin-remove-nested-blocks : * https://github.com/coderaiser/putout/issues/224#issuecomment-2614051528 */ export default { BlockStatement: (path) => { const { parentPath } = path if (!parentPath.isBlockStatement() && !parentPath.isProgram()) { return } let valid = path.container.length === 1 if (!isIntersect(parentPath, path.scope.bindings)) { valid = true } if (!valid) { return } path.replaceWithMultiple(path.node.body) path.scope.crawl() }, } ================================================ FILE: src/visitor/delete-unreachable-code.js ================================================ import * as t from '@babel/types' /** * DFS the BlockStatement to find and return the location of the first * ReturnStatement, which is not inside a Conditional Block. */ const checkReturnLocation = (body) => { for (let i = 0; i < body.length; ++i) { if (t.isReturnStatement(body[i])) { return i } if (t.isBlockStatement(body[i])) { const ret = checkReturnLocation(body[i].body) if (~ret) { return i } } } return -1 } /** * Remove the codes after the first ReturnStatement, which is not inside a * Conditional Block. The FunctionDeclaration will be preserved. * * This is slightly different from the @putout/plugin-remove-unreachable-code : * https://github.com/coderaiser/putout/issues/224#issuecomment-2614051528 */ export default { BlockStatement: (path) => { const body = path.node.body const loc = checkReturnLocation(body) if (loc == -1) { return } for (let i = body.length - 1; i > loc; --i) { if (t.isFunctionDeclaration(body[i])) { continue } body.splice(i, 1) } if (loc === 0 && t.isBlockStatement(body[0])) { const inner = body.shift() while (inner.body.length) { body.unshift(inner.body.pop()) } } path.scope.crawl() }, } ================================================ FILE: src/visitor/delete-unused-var.js ================================================ import * as t from '@babel/types' /** * Delete unused variables with the following exceptions: * * - ForOfStatement * - ForInStatement * */ export default { VariableDeclarator: (path) => { const { node, scope } = path const name = node.id.name const binding = scope.getBinding(name) if (!binding || binding.referenced || !binding.constant) { return } if (node.init && !t.isLiteral(node.init)) { return } const up1 = path.parentPath const up2 = up1?.parentPath if (t.isForOfStatement(up2)) { return } if (t.isForInStatement(up2)) { return } console.log(`Unused variable: ${name}`) if (up1.node.declarations.length === 1) { up1.remove() up1.scope.crawl() } else { path.remove() scope.crawl() } }, } ================================================ FILE: src/visitor/jsconfuser/anti-tooling.js ================================================ import * as t from '@babel/types' function deAntiToolingCheckFunc(path) { if (path.node.params.length) { return false } const body = path.node.body if (!t.isBlockStatement(body)) { return false } if (body.body.length) { return false } return true } function deAntiToolingExtract(path, func_name) { let binding = path.scope.getBinding(func_name) for (let ref of binding.referencePaths) { if (!ref.parentPath.isCallExpression() || !ref.key === 'callee') { continue } const call = ref.parentPath if (!call.listKey === 'body') { continue } for (let node of call.node.arguments) { call.insertBefore(node) } call.remove() } binding.scope.crawl() binding = path.scope.getBinding(func_name) if (binding.references === 0) { path.remove() } } const deAntiTooling = { FunctionDeclaration(path) { const func_name = path.node.id?.name if (!func_name) { return } if (!deAntiToolingCheckFunc(path)) { return } console.log(`AntiTooling Func Name: ${func_name}`) deAntiToolingExtract(path, func_name) }, } export default deAntiTooling ================================================ FILE: src/visitor/jsconfuser/control-flow.js ================================================ import safeFunc from '../../utility/safe-func.js' const safeGetLiteral = safeFunc.safeGetLiteral const safeGetName = safeFunc.safeGetName const safeReplace = safeFunc.safeReplace function checkControlVar(path) { const parent = path.parentPath if (path.key !== 'right' || !parent.isAssignmentExpression()) { return false } const var_path = parent.get('left') const var_name = var_path.node?.name if (!var_name) { return false } let root_path = parent.parentPath if (root_path.isExpressionStatement) { root_path = root_path.parentPath } const binding = parent.scope.getBinding(var_name) for (const ref of binding.referencePaths) { if (ref === var_path) { continue } let cur = ref let valid = false while (cur && cur !== root_path) { if (cur.isSwitchCase() || cur === path) { valid = true break } cur = cur.parentPath } if (!valid) { return false } if (ref.key === 'object') { const prop = ref.parentPath.get('property') if (!prop.isLiteral() && !prop.isIdentifier()) { return false } continue } if (ref.key === 'right') { const left = ref.parentPath.get('left') if (!left.isMemberExpression()) { return false } const obj = safeGetName(left.get('object')) if (obj !== var_name) { return false } continue } } return true } /** * Process the constant properties in the controlVar * * Template: * ```javascript * controlVar = { * // strings * key_string: 'StringLiteral', * // numbers * key_number: 'NumericLiteral', * } * ``` * * Some kinds of deadCode may in inserted to the fake chunks: * * ```javascript * controlVar = false * controlVar = undefined * controlVar[randomControlKey] = undefined * delete controlVar[randomControlKey] * ``` */ const deControlFlowFlatteningStateless = { ObjectExpression(path) { if (!checkControlVar(path)) { return } const parent = path.parentPath const var_name = parent.get('left').node?.name console.log(`[ControlFlowFlattening] parse stateless in obj: ${var_name}`) const props = {} const prop_num = path.node.properties.length for (let i = 0; i < prop_num; ++i) { const prop = path.get(`properties.${i}`) const key = safeGetName(prop.get('key')) const value = safeGetLiteral(prop.get('value')) if (!key || !value) { continue } props[key] = value } const binding = parent.scope.getBinding(var_name) for (const ref of binding.referencePaths) { if (ref.key !== 'object') { continue } const prop = safeGetName(ref.parentPath.get('property')) if (!prop) { continue } if (!Object.prototype.hasOwnProperty.call(props, prop)) { continue } const upper = ref.parentPath if (upper.key === 'left' && upper.parentPath.isAssignmentExpression()) { // this is in the fake chunk ref.parentPath.parentPath.remove() continue } safeReplace(ref.parentPath, props[prop]) } binding.scope.crawl() }, } /** * * Template: * ```javascript * flaggedLabels = { * currentLabel: { flagKey: 'xxx', flagValue : 'true or false' } * } * labelToStates[chunk[i].label] = stateValues: [] => caseStates[i] * initStateValues = labelToStates[startLabel] * endState * chunks = [ * { * body: [ * { * type: "GotoStatement", * label: "END_LABEL", * } * ], * } * { * label: "END_LABEL", * body: [], * } * ] * while (stateVars) { * switch (stateVars) { * // fake assignment expression * case fake_assignment: { * stateVar = 'rand' * // 'GotoStatement label' * } * // clone chunks * case fake_clone: { * // contain a real chunk * } * // fake jumps * case real_1: { * if (false) { * // 'GotoStatement label' * } * // follow with real statements * } * } * } * The key may exist in its parent's map * ``` */ const deControlFlowFlatteningState = { ObjectExpression(path) { if (!checkControlVar(path)) { return } }, } export default { deControlFlowFlatteningStateless, deControlFlowFlatteningState, } ================================================ FILE: src/visitor/jsconfuser/duplicate-literal.js ================================================ import _generate from '@babel/generator' const generator = _generate.default import * as t from '@babel/types' import ivm from 'isolated-vm' const isolate = new ivm.Isolate() import safeFunc from '../../utility/safe-func.js' const safeReplace = safeFunc.safeReplace function checkArrayName(path) { if (path.key !== 'argument') { return null } const ret_path = path.parentPath if (!ret_path.isReturnStatement() || ret_path.key !== 0) { return null } const array_fn_path = ret_path.getFunctionParent() const array_fn_name = array_fn_path.node.id?.name if (!array_fn_name) { return null } const binding = array_fn_path.parentPath.scope.bindings[array_fn_name] if (binding.references !== 1) { return null } let ref = binding.referencePaths[0] while (ref && !ref.isAssignmentExpression()) { ref = ref.parentPath } if (!ref) { return null } const array_name = ref.node.left?.name if (!array_name) { return null } return { func_name: array_fn_name, func_path: array_fn_path, array_name: array_name, array_path: ref, } } function parseArrayWarp(vm, path) { let func = path.getFunctionParent(path) let name = null let binding = null if (func.isArrowFunctionExpression()) { func = func.parentPath name = func.node.id.name binding = func.scope.getBinding(name) } else { name = func.node.id.name binding = func.parentPath.scope.getBinding(name) } console.log(`Process array warp function: ${name}`) vm.evalSync(generator(func.node).code) for (const ref of binding.referencePaths) { const call = ref.parentPath if (ref.key !== 'callee') { console.warn(`Unexpected ref of array warp function: ${call}`) continue } const value = vm.evalSync(generator(call.node).code) safeReplace(call, value) } binding.scope.crawl() binding = binding.scope.getBinding(name) if (!binding.references) { func.remove() } } /** * Template: * ```javascript * var arrayName = getArrayFn() * function getArrayFn (){ * return [...arrayExpression] * } * ``` */ const deDuplicateLiteral = { ArrayExpression(path) { let obj = checkArrayName(path) if (!obj) { return } console.log(`Find arrayName: ${obj.array_name}`) let decl_node = t.variableDeclarator( obj.array_path.node.left, obj.array_path.node.right ) decl_node = t.variableDeclaration('var', [decl_node]) const code = [generator(obj.func_path.node).code, generator(decl_node).code] let binding = obj.array_path.scope.getBinding(obj.array_name) for (const ref of binding.referencePaths) { const vm = isolate.createContextSync() vm.evalSync(code[0]) vm.evalSync(code[1]) parseArrayWarp(vm, ref) } binding.scope.crawl() binding = binding.scope.bindings[obj.array_name] if (!binding.references) { obj.array_path.remove() binding.path.remove() } binding = obj.func_path.parentPath.scope.getBinding(obj.func_name) binding.scope.crawl() binding = binding.scope.getBinding(obj.func_name) if (!binding.references) { obj.func_path.remove() } }, } export default deDuplicateLiteral ================================================ FILE: src/visitor/jsconfuser/global-concealing.js ================================================ import _generate from '@babel/generator' const generator = _generate.default import * as t from '@babel/types' import findGlobalFn from './global.js' import safeFunc from '../../utility/safe-func.js' const safeDeleteNode = safeFunc.safeDeleteNode import checkFunc from '../../utility/check-func.js' const checkPattern = checkFunc.checkPattern function findGlobalVar(glo_name, glo_path) { let tmp_path = glo_path.parentPath.getFunctionParent() if ( !tmp_path || !tmp_path.parentPath.isMemberExpression() || !tmp_path.parentPath.parentPath.isCallExpression() ) { return null } const tmp_body = tmp_path.node.body.body tmp_path = tmp_path.parentPath.parentPath const ret_node = tmp_body[tmp_body.length - 1] if ( !t.isReturnStatement(ret_node) || !t.isAssignmentExpression(ret_node.argument) ) { return null } const code = generator(ret_node.argument.right).code const template = `${glo_name}call(this)` if (!checkPattern(code, template)) { return null } const glo_var = ret_node.argument.left.name const binding = glo_path.scope.getBinding(glo_var) for (const ref of binding.referencePaths) { if ( !ref.parentPath.isMemberExpression() || !ref.parentPath.parentPath.isReturnStatement() ) { continue } const func_path = ref.getFunctionParent() const func_name = func_path.node.id.name return { glo_var: glo_var, tmp_path: tmp_path, glo_fn_name: func_name, glo_fn_path: func_path, } } return null } function getGlobalConcealingNames(glo_fn_path) { const obj = {} glo_fn_path.traverse({ SwitchCase(path) { const code = generator(path.node.test).code const key = parseInt(code) if (Number.isNaN(key)) { console.error(`[GlobalConcealing] concealed key: ${code}`) obj['invalid'] = true return } let consequent = path.node.consequent[0] if (t.isReturnStatement(consequent)) { obj[key] = consequent.argument.property.value } else { if (t.isExpressionStatement(consequent)) { consequent = consequent.expression } obj[key] = consequent.right.left.value } }, }) return obj } /** * Hide the global vars found by module GlobalAnalysis * * Template: * ```javascript * // Add to head: * var globalVar, tempVar = function () { * getGlobalVariableFnName = createGetGlobalTemplate() * return globalVar = getGlobalVariableFnName.call(this) * }["call"]() * // Add to foot: * function globalFn (indexParamName) { * var returnName * switch (indexParamName) { * case state_x: { * return globalVar[name] * } * case state_y: { * returnName = name || globalVar[name] * break * } * } * return globalVar[returnName] * } * // References: * // name -> globalFn(state) * ``` */ const deGlobalConcealing = { FunctionDeclaration(path) { const glo_obj = findGlobalFn(path) if (!glo_obj) { return null } const obj = findGlobalVar(glo_obj.glo_fn_name, glo_obj.glo_fn_path) if (!obj) { return null } console.log(`[GlobalConcealing] globalVar: ${obj.glo_var}`) const glo_vars = getGlobalConcealingNames(obj.glo_fn_path) console.log(`[GlobalConcealing] globalFn: ${obj.glo_fn_name}`) let binding = obj.glo_fn_path.parentPath.scope.getBinding(obj.glo_fn_name) let remain = false for (const ref of binding.referencePaths) { const repl_path = ref.parentPath if (ref.key !== 'callee' || !repl_path.isCallExpression()) { continue } const key = parseInt(generator(repl_path.node.arguments[0]).code) if (glo_vars[key]) { repl_path.replaceWith(t.identifier(glo_vars[key])) } else { remain = true } } if (!remain && safeDeleteNode(obj.glo_fn_name, obj.glo_fn_path)) { obj.tmp_path.remove() } }, } export default deGlobalConcealing ================================================ FILE: src/visitor/jsconfuser/global.js ================================================ import _generate from '@babel/generator' const generator = _generate.default import * as t from '@babel/types' import safeFunc from '../../utility/safe-func.js' const safeGetName = safeFunc.safeGetName import checkFunc from '../../utility/check-func.js' const checkPattern = checkFunc.checkPattern /** * GlobalTemplate 1 (currently not support): * ```javascript * function {getGlobalFnName}(){ * var localVar = false; * eval(${transform.jsConfuserVar("localVar")} + " = true") * if (!localVar) { * {countermeasures} * } * const root = eval("this"); * return root; * } * ``` * GlobalTemplate 2: * ```javascript * function {getGlobalFnName}(array = [a, b, c, d]){ * var bestMatch * var itemsToSearch = [] * try { * bestMatch = Object * itemsToSearch["push"](("")["__proto__"]["constructor"]["name"]) * } catch(e) { * } * // ... * return bestMatch || this; * } * ``` */ function findGlobalFn(path) { const glo_fn_name = path.node.id?.name if (!glo_fn_name) { return null } let node = path.node.params?.[0] if ( !node || !t.isAssignmentPattern(node) || !t.isArrayExpression(node.right) || node.right.elements.length !== 4 ) { return null } const array_name = node.left.name const code = generator(path.node.body).code const template = 'try{=Objectpush(__proto__constructorname)}catch{}' + `:for(;<${array_name}length;)try{=${array_name}[]()` + 'for()if(typeof)continue}catch{}return||this' if (!checkPattern(code, template)) { return } const deps = [] const array = path.get('params.0.right') for (let i = 0; i < 4; ++i) { const ele_name = safeGetName(array.get(`elements.${i}`)) const binding = path.scope.getBinding(ele_name) deps.push({ name: ele_name, path: binding.path, pos: binding.path.node.start, }) } deps.push({ name: glo_fn_name, path: path, pos: path.node.start, }) return { glo_fn_name: glo_fn_name, glo_fn_path: path, deps: deps, } } export default findGlobalFn ================================================ FILE: src/visitor/jsconfuser/minify.js ================================================ import _generate from '@babel/generator' const generator = _generate.default function checkArrowWrap(path) { if (path.node?.name !== 'arguments') { return null } if (!path.parentPath.isSpreadElement()) { return null } const call = path.parentPath.parentPath if (path.parentPath.listKey !== 'arguments' || !call.isCallExpression()) { return null } if (call.key !== 'argument' || !call.parentPath.isReturnStatement()) { return null } const func_name = call.node.callee?.name if (!func_name) { return null } let wrap = call.getFunctionParent() if (wrap.key !== 'init') { return null } wrap = wrap.parentPath const wrap_name = wrap.node.id?.name wrap = wrap.parentPath if ( wrap.listKey !== 'body' || wrap.key !== 0 || wrap.container.length !== 2 ) { return null } const str = generator(wrap.container[1]).code if (str.indexOf(wrap_name) === -1) { return null } wrap = wrap.getFunctionParent() const arrow_name = wrap.node?.id?.name if (!arrow_name || wrap.node.params?.[0]?.name !== func_name) { return null } return { name: arrow_name, path: wrap, } } /** * Template: * ```javascript * function arrowFunctionName (arrowFn, functionLength = 0){ * var functionObject = function(){ return arrowFn(...arguments) }; * return Object.defineProperty(functionObject, "length", { * "value": functionLength, * "configurable": true * }); * } * ``` */ export default function () { let arrowFunc = null const deMinifyArrow = { Identifier(path) { let obj = checkArrowWrap(path) if (!obj) { return } arrowFunc = obj.name console.log(`Find arrowFunctionName: ${obj.name}`) let binding = obj.path.parentPath.scope.bindings[obj.name] for (const ref of binding.referencePaths) { if (ref.key !== 'callee') { console.warn(`Unexpected ref of arrowFunctionName: ${obj.name}`) continue } const repl_path = ref.parentPath repl_path.replaceWith(repl_path.node.arguments[0]) } binding.scope.crawl() binding = obj.path.parentPath.scope.bindings[obj.name] if (!binding.references) { obj.path.remove() } }, } return { arrowFunc, deMinifyArrow, } } ================================================ FILE: src/visitor/jsconfuser/opaque-predicates.js ================================================ import _generate from '@babel/generator' const generator = _generate.default import * as t from '@babel/types' import ivm from 'isolated-vm' const isolate = new ivm.Isolate() import safeFunc from '../../utility/safe-func.js' const safeDeleteNode = safeFunc.safeDeleteNode const safeGetName = safeFunc.safeGetName const safeReplace = safeFunc.safeReplace import checkFunc from '../../utility/check-func.js' const checkPattern = checkFunc.checkPattern function checkOpaqueObject(path) { const parent = path.parentPath if (!parent.isAssignmentExpression()) { return null } const tmp_name = safeGetName(parent.get('left')) const func_path = parent.getFunctionParent() if ( !func_path || func_path.key !== 'callee' || !func_path.parentPath.isCallExpression() ) { return null } const func_body = func_path.node.body?.body if (!func_body || func_body.length < 2) { return null } const last_node = func_body[func_body.length - 1] if ( !t.isReturnStatement(last_node) || last_node.argument?.name !== tmp_name ) { return null } const root_path = func_path.parentPath.parentPath if (!root_path.isAssignmentExpression()) { return null } const pred_name = safeGetName(root_path.get('left')) const obj = { pred_name: pred_name, pred_path: root_path, props: {}, } for (const prop of path.node.properties) { const key = prop.key.name const value = prop.value if (t.isNumericLiteral(value)) { obj.props[key] = { type: 'number', } continue } if (t.isStringLiteral(value)) { obj.props[key] = { type: 'string', } continue } if (t.isArrayExpression(value)) { if (value.elements.length === 0) { obj.props[key] = { type: 'array_dep', } } continue } if (t.isArrowFunctionExpression(value) || t.isFunctionExpression(value)) { const param = value.params?.[0]?.left?.name if (!param) { continue } const code = generator(value).code const template = `(${param}=){if(${pred_name}[0])${pred_name}push()` + `return${pred_name}${param}}` if (checkPattern(code, template)) { obj.props[key] = { type: 'array', } } continue } } return obj } /** * Template: * ```javascript * // This is defined in the global space * var predicateName = (function () { * var tempName = { * prop_array_1: [], * prop_array: function (paramName = 'length') { * if (!predicateName[prop_array_1][0]) { * predicateName[prop_array_1][0].push(rand1) * } * return predicateName[prop_array_1][paramName] * }, * prop_number: rand2, * prop_string: rand_str, * } * return tempName * })() * // Below will appear multiple times * predicateName[prop_array]() ? test : fake * predicateName[prop_number] > rand3 ? test : fake * predicateName[prop_string].charAt(index) == real_char ? test : fake * predicateName[prop_string].charCodeAt(index) == real_char ? test : fake * ``` */ const deOpaquePredicates = { ObjectExpression(path) { const obj = checkOpaqueObject(path) if (!obj) { return } console.log(`[OpaquePredicates] predicateName : ${obj.pred_name}`) const vm = isolate.createContextSync() const code = generator(obj.pred_path.node).code vm.evalSync('var ' + code) obj.pred_path.get('right').replaceWith(t.numericLiteral(0)) let binding = obj.pred_path.scope.getBinding(obj.pred_name) binding.scope.crawl() binding = binding.scope.getBinding(obj.pred_name) for (const ref of binding.referencePaths) { if (ref.key !== 'object') { continue } const member = ref.parentPath const prop = member.get('property') if (!prop || !Object.prototype.hasOwnProperty.call(obj.props, prop)) { continue } let expr = member while ( expr.parentPath.isCallExpression() || expr.parentPath.isMemberExpression() ) { expr = expr.parentPath } const test = generator(expr.node).code const res = vm.evalSync(test) safeReplace(expr, res) } safeDeleteNode(obj.pred_name, obj.pred_path) }, } export default deOpaquePredicates ================================================ FILE: src/visitor/jsconfuser/stack.js ================================================ import { parse } from '@babel/parser' import _generate from '@babel/generator' const generator = _generate.default import * as t from '@babel/types' import ivm from 'isolated-vm' const isolate = new ivm.Isolate() import calculateConstantExp from '../calculate-constant-exp.js' import safeFunc from '../../utility/safe-func.js' const safeGetName = safeFunc.safeGetName const safeReplace = safeFunc.safeReplace let arrowFunc = null function checkFuncLen(path) { if (path.node?.name !== 'configurable' || path.key !== 'key') { return null } const prop = path.parentPath if (!prop.isObjectProperty() || prop.key !== 1) { return null } const obj = prop.parentPath if (obj.node.properties.length !== 2) { return null } if (obj.node.properties[0]?.key?.name !== 'value') { return null } if (obj.listKey !== 'arguments') { return null } const arg_num = obj.container.length if (obj.key !== arg_num - 1) { return null } const func_name = obj.container[arg_num - 3]?.name const warp = obj.getFunctionParent() if (warp.node.params?.[0]?.name !== func_name) { return null } const func_len_name = warp.node?.id?.name if (!func_len_name || func_len_name === arrowFunc) { return null } return { name: func_len_name, path: warp, } } /** * type: param, value, ref, invalid */ function initStackCache(len) { const cache = {} for (let i = 0; i < len; ++i) { cache[i] = { type: 'param', } } return cache } function processAssignLeft(vm, cache, path, prop_name, stk_name) { const father = path.parentPath const right = father.get('right') if (right.isBinaryExpression()) { cache[prop_name] = { type: 'invalid', } return } if (right.isLiteral()) { vm.evalSync(generator(father.node).code) cache[prop_name] = { type: 'value', value: right.node, } return } if (right.isArrayExpression()) { const elements = right.node.elements if (elements.length === 1 && elements[0]?.value === 'charCodeAt') { cache[prop_name] = { type: 'value', value: right.node, } return } } if (right.isUnaryExpression() && right.node.operator === '-') { vm.evalSync(generator(father.node).code) cache[prop_name] = { type: 'value', value: right.node, } return } if (right.isMemberExpression() && right.node.object?.name === stk_name) { const right_prop = right.get('property') if (right_prop.isBinaryExpression()) { return } let ref = safeGetName(right_prop) if (!Object.prototype.hasOwnProperty.call(cache, ref)) { cache[prop_name] = { type: 'invalid', } return } while (cache[ref].type === 'ref') { ref = cache[ref].value } if (cache[ref].type === 'value') { right.replaceWith(cache[ref].value) vm.evalSync(generator(father.node).code) cache[prop_name] = { type: 'value', value: cache[ref].value, } } else { cache[prop_name] = { type: 'ref', value: ref, } } return } cache[prop_name] = { type: 'invalid', } } function processAssignInvalid(cache, path, prop_name) { cache[prop_name] = { type: 'invalid', } } function processReplace(cache, path, prop_name) { const value = cache[prop_name].value const type = cache[prop_name].type if (type === 'ref') { path.node.computed = true safeReplace(path.get('property'), value) return true } if (type === 'value') { path.replaceWith(value) return true } return false } function checkStackInvalid(path, invalid) { const stk_name = path.node.params[0].argument.name const body_path = path.get('body') body_path.traverse({ MemberExpression: { exit(path) { if (path.node.object.name !== stk_name) { return } const father = path.parentPath const prop = path.get('property') const prop_name = safeGetName(prop) if (father.isUpdateExpression()) { invalid[prop_name] = 1 return } if (body_path.scope == father.scope) { return } if (!father.isAssignmentExpression() || path.key !== 'left') { return } invalid[prop_name] = 1 }, }, }) return invalid } function checkChangeValid(invalid, used) { let valid = true Object.keys(used).forEach(function (key) { if (Object.prototype.hasOwnProperty.call(invalid, key)) { valid = false } }) return valid } function tryStackReplace(path, len, invalid, used) { const stk_name = path.node.params[0].argument.name const body_path = path.get('body') const cache = initStackCache(len) const vm = isolate.createContextSync() vm.evalSync(`var ${stk_name} = []`) let changed = false body_path.traverse({ MemberExpression: { exit(path) { if (path.node.object.name !== stk_name) { return } const prop = path.get('property') if (prop.isBinaryExpression()) { return } const prop_name = safeGetName(prop) if (!prop_name) { return } if (Object.prototype.hasOwnProperty.call(invalid, prop_name)) { processAssignInvalid(cache, path, prop_name) return } const exist = Object.prototype.hasOwnProperty.call(cache, prop_name) if (exist && cache[prop_name].type === 'param') { return } const father = path.parentPath if (father.isAssignmentExpression() && path.key === 'left') { processAssignLeft(vm, cache, path, prop_name, stk_name) } else if (exist) { used[prop_name] = 1 changed |= processReplace(cache, path, prop_name) } }, }, }) const binding = body_path.scope.getBinding(stk_name) binding.scope.crawl() return changed } function getStackParamLen(path) { const stk_name = path.node.params?.[0]?.argument?.name if (!stk_name) { return 'unknown' } const body_path = path.get('body') let len = 'unknown' body_path.traverse({ MemberExpression: { exit(path) { if (path.node.object.name !== stk_name) { return } const prop = path.get('property') if (prop.isBinaryExpression()) { return } const prop_name = safeGetName(prop) if (!prop_name || prop_name !== 'length') { return } const father = path.parentPath if (!father.isAssignmentExpression() || path.key !== 'left') { return } const right = father.get('right') if (right.isBinaryExpression()) { return } if (!right.isLiteral()) { return } len = right.node.value path.stop() }, }, }) return len } function processStackParam(path, len) { if (path.isArrowFunctionExpression()) { console.log(`[Stack] Process arrowFunctionExpression, len: ${len}`) } else if (path.isFunctionExpression()) { console.log(`[Stack] Process functionExpression, len: ${len}`) } else { console.log(`[Stack] Process Function ${path.node.id.name}, len: ${len}`) } const orig_code = generator(path.node).code let changed = true const invalid = {} let used = {} while (changed) { checkStackInvalid(path, invalid) if (!checkChangeValid(invalid, used)) { path.replaceWith(parse(orig_code).program.body[0]) used = {} } changed = tryStackReplace(path, len, invalid, used) path.traverse(calculateConstantExp) } } const deStackFuncLen = { Identifier(path) { let obj = checkFuncLen(path) if (!obj) { return } console.log(`[Stack] Find functionLengthName: ${obj.name}`) let binding = obj.path.parentPath.scope.bindings[obj.name] for (const ref of binding.referencePaths) { if (ref.key !== 'callee') { console.warn( `[Stack] Unexpected ref of functionLengthName: ${obj.name}` ) continue } const repl_path = ref.parentPath const arg = repl_path.node.arguments[0] const len = repl_path.node.arguments[1].value if (t.isIdentifier(arg)) { const func_name = arg.name const func_decl = repl_path.scope.getBinding(func_name).path processStackParam(func_decl, len) repl_path.remove() } else { repl_path.replaceWith(arg) processStackParam(repl_path, len) } } binding.scope.crawl() binding = obj.path.parentPath.scope.bindings[obj.name] if (!binding.references) { obj.path.remove() } }, } const deStackFuncOther = { RestElement(path) { if (path.listKey !== 'params') { return } const func = path.getFunctionParent() const len = getStackParamLen(func) if (len === 'unknown') { return } processStackParam(func, len) }, } export default function (func) { arrowFunc = func return { deStackFuncLen, deStackFuncOther, } } ================================================ FILE: src/visitor/jsconfuser/string-compression.js ================================================ import _generate from '@babel/generator' const generator = _generate.default import ivm from 'isolated-vm' const isolate = new ivm.Isolate() import safeFunc from '../../utility/safe-func.js' const safeReplace = safeFunc.safeReplace import checkFunc from '../../utility/check-func.js' const checkPattern = checkFunc.checkPattern function findStringDecoder(path) { if (path.node?.name !== 'charCodeAt' || path.key !== 'property') { return null } let loop = path while (loop && !loop.isForStatement()) { loop = loop.parentPath } const i = loop?.node?.update?.argument?.name if (!i) { return null } const func = loop.getFunctionParent() const param = func.node.params?.[0]?.name if (!param) { return null } const code = generator(func.node).code const template = `function(${param}){var=${param}.split()for(${i}=1;${i}<.length;${i}++)` + `[${i}].charCodeAt(0)[${i}].push().charAt(0)return.join().split()}` if (!checkPattern(code, template)) { return null } return { name: func.node.id.name, path: func, } } function findStringGet(path) { const decoder_name = path.node.id.name let binding = path.parentPath.scope.getBinding(decoder_name) if (!binding || binding.references !== 1) { return null } const ref = binding.referencePaths[0] if (ref.key !== 1 || ref.listKey !== 'arguments') { return null } const get_ref_path = ref.parentPath.get('arguments.0') const get_name = get_ref_path.node?.name if (!get_name) { return null } binding = get_ref_path.scope.getBinding(get_name) return { name: get_name, path: binding.path, ref: get_ref_path, } } function findStringSplit(path) { while (path && !path.isAssignmentExpression()) { path = path.parentPath } const split_name = path?.node?.left?.name if (!split_name) { return null } const binding = path.scope.getBinding(split_name) return { name: split_name, path: path, def: binding.path, } } function findStringFn(path, name) { const binding = path.scope.getBinding(name) const ref = binding.referencePaths?.[0] if (!ref) { return null } const fn_path = ref.getFunctionParent(name) const fn_name = fn_path.node.id.name return { name: fn_name, path: fn_path, } } /** * Template: * ```javascript * var split = (function (getStringParamName, decoderParamName) { * return decoderParamName(getStringParamName()) * })(getStringName, decoder) * function getStringName () { * var str = splits[0] * var objectToTest = {} * if ('testingFor' in objectToTest) { * str += splits[1] * } * return str * } * function decoder (b) { * // DecodeTemplate * } * function fnName (index) { * return split[index] * } * ``` */ const deStringCompression = { Identifier(path) { const decoder_obj = findStringDecoder(path) if (!decoder_obj) { return } const get_obj = findStringGet(decoder_obj.path) if (!get_obj) { return } const split_obj = findStringSplit(get_obj.ref) if (!get_obj) { return } const fn_obj = findStringFn(split_obj.path, split_obj.name) if (!get_obj) { return } console.log(`Find stringCompression Fn: ${fn_obj.name}`) const vm = isolate.createContextSync() vm.evalSync(generator(decoder_obj.path.node).code) vm.evalSync(generator(get_obj.path.node).code) vm.evalSync('var ' + generator(split_obj.path.node).code) vm.evalSync(generator(fn_obj.path.node).code) let binding = fn_obj.path.parentPath.scope.getBinding(fn_obj.name) for (const ref of binding.referencePaths) { if (ref.key !== 'callee') { console.warn( `Unexpected ref of stringCompression Fn: ${ref.parentPath}` ) continue } const repl_path = ref.parentPath try { const value = vm.evalSync(generator(repl_path.node).code) safeReplace(repl_path, value) } catch (e) { console.warn( `Unexpected ref of stringCompression Fn: ${ref.parentPath}` ) } } binding.scope.crawl() binding = binding.scope.bindings[fn_obj.name] if (!binding.references) { fn_obj.path.remove() } binding.scope.crawl() binding = split_obj.path.scope.getBinding(split_obj.name) if (!binding.references) { split_obj.path.remove() split_obj.def.remove() } binding.scope.crawl() binding = get_obj.path.scope.getBinding(get_obj.name) if (!binding.references) { get_obj.path.remove() } binding.scope.crawl() binding = decoder_obj.path.scope.getBinding(decoder_obj.name) if (!binding.references) { decoder_obj.path.remove() } }, } export default deStringCompression ================================================ FILE: src/visitor/jsconfuser/string-concealing.js ================================================ import _generate from '@babel/generator' const generator = _generate.default import * as t from '@babel/types' import ivm from 'isolated-vm' const isolate = new ivm.Isolate() import findGlobalFn from './global.js' import safeFunc from '../../utility/safe-func.js' const safeDeleteNode = safeFunc.safeDeleteNode const safeGetName = safeFunc.safeGetName const safeReplace = safeFunc.safeReplace function insertDepItemVar(deps, name, path) { const binding = path.scope.getBinding(name) if (binding.path === path) { deps.push({ name: name, path: binding.path, node: t.variableDeclaration('var', [binding.path.node]), pos: binding.path.node.start, }) return } deps.push({ name: name, path: path, pos: path.node.start, }) deps.push({ name: name, path: binding.path, node: t.variableDeclaration('var', [binding.path.node]), pos: binding.path.node.start, }) } /** * Template: * ```javascript * var __globalObject = {getGlobalFnName}() || {}; * var __TextDecoder = __globalObject["TextDecoder"]; * var __Uint8Array = __globalObject["Uint8Array"]; * var __Buffer = __globalObject["Buffer"]; * var __String = __globalObject["String"] || String; * var __Array = __globalObject["Array"] || Array; * ``` */ function findGlobalFnRef(obj) { const path = obj.glo_fn_path const glo_fn_name = obj.glo_fn_name let binding = path.parentPath.scope.getBinding(glo_fn_name) let glo_fn_ref = binding.referencePaths[0] while (!glo_fn_ref.isAssignmentExpression()) { glo_fn_ref = glo_fn_ref.parentPath } const glo_obj_name = glo_fn_ref.node.left.name obj.glo_obj_name = glo_obj_name obj.glo_obj_path = glo_fn_ref obj.glo_obj_ref = {} insertDepItemVar(obj.deps, glo_obj_name, glo_fn_ref) binding = glo_fn_ref.scope.getBinding(glo_obj_name) for (const ref of binding.referencePaths) { const prop = safeGetName(ref.parentPath.get('property')) if (!prop) { continue } let root = ref while (!root.isAssignmentExpression()) { root = root.parentPath } const ref_name = safeGetName(root.get('left')) obj.glo_obj_ref[prop] = ref_name insertDepItemVar(obj.deps, ref_name, root) } return } /** * Template: * ```javascript * var utf8ArrayToStr = (function () { * // ... * })(); * function bufferToStringName () { * if(typeof __TextDecoder !== "undefined" && __TextDecoder) { * return new __TextDecoder()["decode"](new __Uint8Array(buffer)); * } else if(typeof __Buffer !== "undefined" && __Buffer) { * return __Buffer["from"](buffer)["toString"]("utf-8"); * } else { * return utf8ArrayToStr(buffer); * } * } * ``` */ function findBufferToString(obj) { const path = obj.glo_obj_path const ref_array = obj.glo_obj_ref['Array'] let binding = path.scope.getBinding(ref_array) for (const ref of binding.referencePaths) { if (ref.key !== 'callee') { continue } let a2s_path = ref.getFunctionParent() while (!a2s_path.isAssignmentExpression()) { a2s_path = a2s_path.parentPath } obj.a2s_name = safeGetName(a2s_path.get('left')) obj.a2s_path = a2s_path insertDepItemVar(obj.deps, obj.a2s_name, obj.a2s_path) break } if (!obj.a2s_name) { return false } binding = obj.a2s_path.scope.getBinding(obj.a2s_name) const b2s_path = binding.referencePaths[0].getFunctionParent() obj.b2s_name = safeGetName(b2s_path.get('id')) obj.b2s_path = b2s_path obj.deps.push({ name: obj.b2s_name, path: b2s_path, pos: b2s_path.node.start, }) binding = b2s_path.parentPath.scope.getBinding(obj.b2s_name) const child = [] for (const ref of binding.referencePaths) { const decode_fn = ref.getFunctionParent() let valid = false decode_fn.traverse({ StringLiteral(path) { if (path.node.value.length === 91) { valid = true path.stop() } }, }) if (!valid) { return false } child.push({ name: decode_fn.node.id.name, decoder: decode_fn, }) } obj.child = child return true } function generatorStringConcealingDepCode(obj) { obj.deps.sort((a, b) => { return a.pos - b.pos }) const dep_node = t.program([]) for (const item of obj.deps) { if (item.node) { dep_node.body.push(item.node) } else { dep_node.body.push(item.path.node) } } obj.dep_code = generator(dep_node).code } function renameProperty(member) { const obj_name = safeGetName(member.get('object')) const prop_name = safeGetName(member.get('property')) const new_name = member.scope.generateUidIdentifier(`_tmp_local_`) const binding = member.scope.getBinding(obj_name) let first = true for (const ref of binding.referencePaths) { const item = ref.parentPath const prop = safeGetName(item.get('property')) if (prop !== prop_name) { continue } if (first) { let body = item while (body.listKey !== 'body') { body = body.parentPath } body.container.unshift( t.variableDeclaration('var', [t.variableDeclarator(new_name)]) ) body.scope.crawl() first = false } item.replaceWith(new_name) } member.scope.crawl() } /** * Template: * ```javascript * var cacheName = [], arrayName = [] * // Below will appear multiple times * var getterFnName = (x, y, z, a, b)=>{ * if ( x !== y ) { * return b[x] || (b[x] = a(arrayName[x])) * } * // Add fake ifStatements * if(typeof a === "undefined") { * a = decodeFn * } * if(typeof b === "undefined") { * b = cacheName * } * } * // Base91 Algo * function decodeFn (str){ * var table = {__strTable__}; * var raw = "" + (str || ""); * var len = raw.length; * var ret = []; * var b = 0; * var n = 0; * var v = -1; * for (var i = 0; i < len; i++) { * var p = table.indexOf(raw[i]); * if (p === -1) continue; * if (v < 0) { * v = p; * } else { * v += p * 91; * b |= v << n; * n += (v & 8191) > 88 ? 13 : 14; * do { * ret.push(b & 0xff); * b >>= 8; * n -= 8; * } while (n > 7); * v = -1; * } * } * if (v > -1) { * ret.push((b | (v << n)) & 0xff); * } * return bufferToStringName(ret); * } * ``` */ function processSingleGetter(obj, decoder_name, decoder_path) { const decoder_code = generator(decoder_path.node).code let binding = decoder_path.parentPath.scope.getBinding(decoder_name) let getter_path = binding.referencePaths[0].getFunctionParent() while ( !getter_path.isAssignmentExpression() && !getter_path.isVariableDeclarator() ) { getter_path = getter_path.parentPath } let getter_name if (getter_path.isAssignmentExpression()) { if (getter_path.get('left').isMemberExpression()) { renameProperty(getter_path.get('left')) } getter_name = safeGetName(getter_path.get('left')) } else { getter_name = safeGetName(getter_path.get('id')) } console.log( `[StringConcealing] getter: ${getter_name} decoder: ${decoder_name}` ) const getter_code = 'var ' + generator(getter_path.node).code binding = getter_path.scope.getBinding(getter_name) if (getter_path.isAssignmentExpression()) { getter_path.get('right').replaceWith(t.numericLiteral(0)) } else { getter_path.get('init').replaceWith(t.numericLiteral(0)) } binding.scope.crawl() binding = getter_path.scope.getBinding(getter_name) let complete = false while (!complete) { complete = true const vm = isolate.createContextSync() vm.evalSync(obj.dep_code) try { for (const ref of binding.referencePaths) { if (ref.findParent((path) => path.removed)) { continue } let repl_path = ref.parentPath if (repl_path.isCallExpression()) { const args = repl_path.node.arguments if (args.length !== 1 || !t.isLiteral(args[0])) { console.warn(`[StringConcealing] Unexpected call: ${repl_path}`) continue } } else if (repl_path.isMemberExpression()) { repl_path = repl_path.parentPath } else { console.warn(`[StringConcealing] Unexpected ref: ${repl_path}`) continue } const eval_code = generator(repl_path.node).code // The name of getter can be the same as other dep functions const value = vm.evalSync( `(function (){${decoder_code}\n${getter_code}\nreturn ${eval_code}})()` ) safeReplace(repl_path, value) } } catch (e) { if (e.name !== 'ReferenceError') { console.warn(`[StringConcealing] Unexpected exception: ${e.message}`) return } complete = false const lost = e.message.split(' ')[0] const binding = getter_path.scope.getBinding(lost) if (!binding) { console.warn(`[StringConcealing] Missing cache or array: ${lost}`) return } let count = binding.constantViolations.length if (count) { console.warn(`[StringConcealing] Invalid violations ${lost} : ${count}`) return } count = binding.path.node.init.elements.length if (count) { console.log(`[StringConcealing] Add array : ${lost}`) obj.array_name = lost obj.array_path = binding.path } else { console.log(`[StringConcealing] Add cache : ${lost}`) obj.cache_name = lost obj.cache_path = binding.path } insertDepItemVar(obj.deps, lost, binding.path) generatorStringConcealingDepCode(obj) } } safeDeleteNode(getter_name, getter_path) safeDeleteNode(decoder_name, decoder_path) } const deStringConcealing = { FunctionDeclaration(path) { const obj = findGlobalFn(path) if (!obj) { return null } if (obj.glo_fn_path.parentPath.getFunctionParent()) { return null } findGlobalFnRef(obj) if (!findBufferToString(obj)) { return } generatorStringConcealingDepCode(obj) for (const item of obj.child) { processSingleGetter(obj, item.name, item.decoder) } safeDeleteNode(obj.array_name, obj.array_path) safeDeleteNode(obj.cache_name, obj.cache_path) // a2s and b2s are pairs if (safeDeleteNode(obj.b2s_name, obj.b2s_path)) { obj.a2s_path.remove() obj.a2s_path.scope.crawl() } }, } function tryStringConcealingPlace(path) { const parent = path.parentPath if (!parent.isAssignmentExpression()) { return } const name = safeGetName(parent.get('left')) let binding = parent.scope.getBinding(name) if (binding?.constantViolations?.length !== 1) { return } for (const ref of binding.referencePaths) { if (ref.key !== 'object' || ref.parentPath.key === 'callee') { return } } const code = generator(parent.node).code const vm = isolate.createContextSync() vm.evalSync('var ' + code) for (const ref of binding.referencePaths) { if (ref.key !== 'object') { continue } const test = generator(ref.parent).code const res = vm.evalSync(test) safeReplace(ref.parentPath, res) } safeDeleteNode(name, parent) } const deStringConcealingPlace = { StringLiteral(path) { if (path.key !== 'right' || !path.parentPath.isAssignmentExpression()) { return } const name = safeGetName(path.parentPath.get('left')) if (!name) { return } const binding = path.scope.getBinding(name) if (binding.constantViolations.length !== 1) { return } for (const ref of binding.referencePaths) { if (ref.node.start < path.node.start) { continue } ref.replaceWith(path.node) } safeDeleteNode(name, path.parentPath) }, ArrayExpression(path) { let valid = true if (path.node.elements.length === 0) { return } for (const ele of path.node.elements) { if (!t.isStringLiteral(ele)) { valid = false break } } if (!valid) { return } tryStringConcealingPlace(path) }, ObjectExpression(path) { let valid = true if (path.node.properties.length === 0) { return } for (const ele of path.node.properties) { if (!t.isStringLiteral(ele.value)) { valid = false break } } if (!valid) { return } tryStringConcealingPlace(path) }, } export default { deStringConcealing, deStringConcealingPlace, } ================================================ FILE: src/visitor/lint-if-statement.js ================================================ import * as t from '@babel/types' function LintIfStatement(path) { let { test, consequent, alternate } = path.node let changed = false if (!t.isBlockStatement(consequent)) { consequent = t.blockStatement([consequent]) changed = true } if (alternate && !t.isBlockStatement(alternate)) { alternate = t.blockStatement([alternate]) changed = true } if (!changed) { return } path.replaceWith(t.ifStatement(test, consequent, alternate)) } export default { IfStatement: { exit: LintIfStatement, }, } ================================================ FILE: src/visitor/merge-object.js ================================================ import * as t from '@babel/types' function mergeObject(path) { const { id, init } = path.node if (!t.isObjectExpression(init)) { // 判断是否是定义对象 return } let name = id.name let scope = path.scope let binding = scope.getBinding(name) const start = path.node.end let end = -1 let violation = null if (!binding.constant) { // Find the first constantViolation after this declaration for (let item of binding.constantViolations) { if (item.node.start <= start) { continue } if (item.isVariableDeclarator()) { end = item.node.start violation = item break } if (item.isAssignmentExpression()) { end = item.node.start violation = item break } return } } // 添加已有的key let keys = {} let properties = init.properties for (let prop of properties) { let key = null if (t.isStringLiteral(prop.key)) { key = prop.key.value } if (t.isIdentifier(prop.key)) { key = prop.key.name } if (key) { keys[key] = true } } // 遍历作用域检测是否含有局部混淆特征并合并成员 let merges = [] const container = path.parentPath.parentPath let cur = 0 let valid = true // Check references in sequence while (cur < binding.references) { const ref = binding.referencePaths[cur] // Ignore the references before this declaration if (ref.node.start <= start) { ++cur continue } // Ignore the references after the first constantViolation if (end >= 0 && ref.node.end >= end) { break } if (ref.key !== 'object' || !ref.parentPath.isMemberExpression()) { break } const me = ref.parentPath if (me.key !== 'left' || !me.parentPath.isAssignmentExpression()) { break } const ae = me.parentPath let bk = ae let stop = false while (bk.parentPath !== container) { if ( bk.parentPath.isSequenceExpression() || bk.parentPath.isVariableDeclarator() || bk.parentPath.isVariableDeclaration() || bk.parentPath.isExpressionStatement() ) { bk = bk.parentPath continue } stop = true break } if (stop) { break } const property = me.node.property let key = null if (t.isStringLiteral(property)) { key = property.value } if (t.isIdentifier(property)) { key = property.name } if (!key) { valid = false break } // 不允许出现重定义 if (Object.prototype.hasOwnProperty.call(keys, key)) { valid = false break } // 添加到列表 properties.push(t.ObjectProperty(t.valueToNode(key), ae.node.right)) keys[key] = true merges.push(ae) ++cur } if (!merges.length || !valid) { return } // Remove code console.log(`尝试性合并: ${name}`) for (let ref of merges) { const left = ref.node.left if (ref.parentPath.isSequenceExpression() && ref.container.length === 1) { ref = ref.parentPath } if ( ref.parentPath.isVariableDeclarator() || ref.parentPath.isAssignmentExpression() ) { ref.replaceWith(left) } else { ref.remove() } } // Check the remaining references const ref1 = binding.referencePaths[cur++] if (!ref1) { scope.crawl() return } const ref2 = binding.referencePaths[cur] // Don't replace the declarator if there exists more than one reference if (ref2 && ref2.node.end < end) { scope.crawl() return } // Check if the only reference is an assignment let key = ref1.key let up1 = ref1.parentPath if (up1.isSequenceExpression() && ref1.container.length === 1) { key = up1.key up1 = up1.parentPath } if (!up1.isVariableDeclarator() || key !== 'init') { scope.crawl() return } // Move the definition to its reference up1.node.init = path.node.init // Delete the original definition if (violation?.isAssignmentExpression()) { path.node.init = undefined } else { path.remove() } binding.scope.crawl() } /** * Collect the properties of one object and move it back to the declaration. * * One example made by ObjectExpressionKeysTransformer: * * ```javascript * var _0xb28de8 = {}; * _0xb28de8["abcd"] = function(_0x22293f, _0x5a165e) { * return _0x22293f == _0x5a165e; * }; * _0xb28de8.dbca = function(_0xfbac1e, _0x23462f, _0x556555) { * return _0xfbac1e(_0x23462f, _0x556555); * }; * _0xb28de8.aaa = function(_0x57e640) { * return _0x57e640(); * }; * _0xb28de8["bbb"] = "eee"; * var _0x15e145 = _0xb28de8; * ``` * * The result: * * ```javascript * var _0x15e145 = { * "abcd": function (_0x22293f, _0x5a165e) { * return _0x22293f == _0x5a165e; * }, * "dbca": function (_0xfbac1e, _0x23462f, _0x556555) { * return _0xfbac1e(_0x23462f, _0x556555); * }, * "aaa": function (_0x57e640) { * return _0x57e640(); * }, * "bbb": "eee" * }; * ``` * * Note: * - Constant objects in the original code can be splitted * - AssignmentExpression can be moved to ReturnStatement */ export default { VariableDeclarator: mergeObject, } ================================================ FILE: src/visitor/parse-control-flow-storage.js ================================================ import _generate from '@babel/generator' const generator = _generate.default import * as t from '@babel/types' function parseObject(path) { let node = path.node // 变量必须定义为Object类型才可能是代码块加密内容 if (!t.isObjectExpression(node.init)) { return } let objPropertiesList = node.init.properties if (objPropertiesList.length == 0) { return } // 遍历Object 判断每个元素是否符合格式 let objName = node.id.name let objKeys = {} // 有时会有重复的定义 let replCount = 0 objPropertiesList.map(function (prop) { if (!t.isObjectProperty(prop)) { return } let key if (t.isIdentifier(prop.key)) { key = prop.key.name } else { key = prop.key.value } if (t.isFunctionExpression(prop.value)) { // 符合要求的函数必须有且仅有一条return语句 if (prop.value.body.body.length !== 1) { return } let retStmt = prop.value.body.body[0] if (!t.isReturnStatement(retStmt)) { return } // 检测是否是3种格式之一 let repfunc = null if (t.isBinaryExpression(retStmt.argument)) { // 二元运算类型 repfunc = function (_path, args) { _path.replaceWith( t.binaryExpression(retStmt.argument.operator, args[0], args[1]) ) } } else if (t.isLogicalExpression(retStmt.argument)) { // 逻辑判断类型 repfunc = function (_path, args) { _path.replaceWith( t.logicalExpression(retStmt.argument.operator, args[0], args[1]) ) } } else if (t.isCallExpression(retStmt.argument)) { // 函数调用类型 调用的函数必须是传入的第一个参数 if (!t.isIdentifier(retStmt.argument.callee)) { return } if (retStmt.argument.callee.name !== prop.value.params[0]?.name) { return } repfunc = function (_path, args) { _path.replaceWith(t.callExpression(args[0], args.slice(1))) } } if (repfunc) { objKeys[key] = repfunc ++replCount } } else if (t.isStringLiteral(prop.value)) { let retStmt = prop.value.value objKeys[key] = function (_path) { _path.replaceWith(t.stringLiteral(retStmt)) } ++replCount } else if (t.isMemberExpression(prop.value)) { let retStmt = prop.value objKeys[key] = function (_path) { _path.replaceWith(retStmt) } ++replCount } }) // 如果Object内的元素不全符合要求 很有可能是普通的字符串类型 不需要替换 if (!replCount) { return } if (objPropertiesList.length !== replCount) { console.log( `不完整替换: ${objName} ${replCount}/${objPropertiesList.length}` ) return } // 遍历作用域进行替换 分为函数调用和字符串调用 console.log(`处理代码块: ${objName}`) let objUsed = {} function getReplaceFunc(_node) { // 这里开始所有的调用应该都在列表中 let key = null if (t.isStringLiteral(_node.property)) { key = _node.property.value } else if (t.isIdentifier(_node.property)) { key = _node.property.name } else { // Maybe the code was obfuscated more than once const code = generator(_node.property, { minified: true }).code console.log(`意外的调用: ${objName}[${code}]`) return null } if (!Object.prototype.hasOwnProperty.call(objKeys, key)) { // 这里应该是在死代码中 因为key不存在 return null } objUsed[key] = true return objKeys[key] } let bind = path.scope.getBinding(objName)?.referencePaths let usedCount = 0 // Replace reversely to handle nested cases correctly for (let i = bind.length - 1; i >= 0; --i) { let ref = bind[i] let up1 = ref.parentPath if (up1.isMemberExpression() && ref.key === 'object') { if (up1.key === 'left' && t.isAssignmentExpression(up1.parent)) { continue } let func = getReplaceFunc(up1.node) if (!func) { continue } ++usedCount let up2 = up1.parentPath if (up1.key === 'callee') { func(up2, up2.node.arguments) } else { func(up1) } } } path.scope.crawl() // 如果没有全部使用 就先不删除 if (usedCount !== bind.length) { console.log(`不完整使用: ${objName} ${usedCount}/${bind.length}`) } else { path.remove() } } /** * Parse control flow object * * Several kinds of expressions are collected, transformed, and merged into * the controlFlowStorage object by method FunctionControlFlowTransformer: * * - BinaryExpression * - CallExpression * - LogicalExpression * - Literal * * ```javascript * var _0xb28de8 = { * "abcd": function(_0x22293f, _0x5a165e) { * return _0x22293f == _0x5a165e; * }, * "dbca": function(_0xfbac1e, _0x23462f, _0x556555) { * return _0xfbac1e(_0x23462f, _0x556555); * }, * "aaa": function(_0x57e640) { * return _0x57e640(); * }, * "bbb": "eee", * "ccc": A[x][y][...] * }; * ``` * * This visitor can parse such objects and undo the transformation. * * ```javascript * // From * var aa = _0xb28de8["abcd"](123, 456); * var bb = _0xb28de8["dbca"](bcd, 11, 22); * var cc = _0xb28de8["aaa"](dcb); * var dd = _0xb28de8["bbb"]; * var ee = _0xb28de8["ccc"]; * // To * var aa = 123 == 456; * var bb = bcd(11, 22); * var cc = dcb(); * var dd = "eee"; * var ee = A[x][y][...]; * ``` */ export default { VariableDeclarator: { exit: parseObject, }, } ================================================ FILE: src/visitor/prune-if-branch.js ================================================ function pruneIfBranch(path) { function clear(path, toggle) { // 判定成立 if (toggle) { path.replaceWith(path.node.consequent) return } // 判定不成立 if (!path.node.alternate) { path.remove() return } path.replaceWith(path.node.alternate) } // 判断判定是否恒定 const test = path.node.test const types = ['StringLiteral', 'NumericLiteral', 'BooleanLiteral'] if (test.type === 'BinaryExpression') { if ( types.indexOf(test.left.type) !== -1 && types.indexOf(test.right.type) !== -1 ) { const left = JSON.stringify(test.left.value) const right = JSON.stringify(test.right.value) clear(path, eval(left + test.operator + right)) } } else if (types.indexOf(test.type) !== -1) { clear(path, eval(JSON.stringify(test.value))) } } /** * Prune the branch if the test is constant * * The code must be reloaded to update the references */ export default { IfStatement: pruneIfBranch, ConditionalExpression: pruneIfBranch, } ================================================ FILE: src/visitor/split-assignment.js ================================================ import * as t from '@babel/types' /** * Split the AssignmentExpressions. For example: * * - In the test of IfStatement * - In the VariableDeclaration */ export default { IfStatement(path) { if (!path.parentPath.isBlockStatement() && !path.parentPath.isProgram()) { return } let test = path.get('test') if (test.isAssignmentExpression()) { path.insertBefore(t.expressionStatement(test.node)) test.replaceWith(test.node.left) path.scope.crawl() return } if (test.isMemberExpression()) { let object = test.get('object') if (object.isAssignmentExpression()) { path.insertBefore(t.expressionStatement(object.node)) object.replaceWith(object.node.left) } let property = test.get('property') if (property.isAssignmentExpression()) { path.insertBefore(t.expressionStatement(property.node)) property.replaceWith(property.node.left) } path.scope.crawl() } }, VariableDeclaration(path) { if (!path.parentPath.isBlockStatement() && !path.parentPath.isProgram()) { return } for (let i = 0; i < path.node.declarations.length; ++i) { const declaration = path.get(`declarations.${i}`) const init = declaration.node.init if (!t.isAssignmentExpression(init)) { continue } path.insertBefore(t.ExpressionStatement(init)) declaration.get('init').replaceWith(init.left) } path.scope.crawl() }, } ================================================ FILE: src/visitor/split-member-object.js ================================================ function splitMemberObject(path) { const object = path.get('object') if (!object.isAssignmentExpression()) { return } let insertPath = path while (!insertPath?.listKey) { if (insertPath.parentPath.isAssignmentExpression()) { insertPath = insertPath.parentPath continue } if (insertPath.parentPath.isExpressionStatement()) { insertPath = insertPath.parentPath continue } return } insertPath.insertBefore(object.node) object.replaceWith(object.node.left) insertPath.scope.crawl() } /** * Split assignment operation in member object * * From: * ```javascript * (a = {})['b'] = c; * ``` * To: * ```javascript * a = {} * a['b'] = c; * ``` */ export default { MemberExpression: splitMemberObject, } ================================================ FILE: src/visitor/split-sequence.js ================================================ import * as t from '@babel/types' function doSplit(insertPath, path) { const expressions = path.node.expressions const lastExpression = expressions.pop() while (expressions.length) { insertPath.insertBefore(t.expressionStatement(expressions.shift())) } path.replaceWith(lastExpression) insertPath.scope.crawl() } function splitSequence(path) { let { parentPath } = path if (parentPath.isVariableDeclarator()) { // Skip if it's not the first VariableDeclarator if (parentPath.key !== 0) { return } let insertPath = parentPath.parentPath // Skip if the container of the VariableDeclaration is not an array if (!insertPath.listKey) { return } doSplit(insertPath, path) return } if (parentPath.isReturnStatement()) { if (!parentPath.listKey) { return } doSplit(parentPath, path) return } if (parentPath.isExpressionStatement()) { if (!parentPath.listKey) { return } doSplit(parentPath, path) return } } /** * The sequenceExpressions inside certain statements are splitted if possible: * * - VariableDeclarator * - ReturnStatement * - ExpressionStatement */ export default { SequenceExpression: splitSequence, } ================================================ FILE: src/visitor/split-variable-declaration.js ================================================ import * as t from '@babel/types' function splitVariableDeclaration(path) { // The scope of a for statement is its body if (path.parentPath.isFor()) { return } // The container must be an array if (!path.listKey) { return } const kind = path.node.kind const list = path.node.declarations if (list.length == 1) { return } for (let item of list) { path.insertBefore(t.variableDeclaration(kind, [item])) } path.remove() path.scope.crawl() } /** * Split the VariableDeclaration if it has more than one VariableDeclarator * * This operation will only be performed when its container is an array */ export default { VariableDeclaration: splitVariableDeclaration, }