Showing preview only (231K chars total). Download the full file or copy to clipboard to get everything.
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, '<string>', '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:<url id="cv1cref6o68qmpt26ol0" type="url" status="parsed" title="GitHub - echo094/decode-js: JS混淆代码的AST分析工具 AST analysis tool for obfuscated JS code" wc="2165">https://github.com/echo094/decode-js</url>
//Modify:<url id="cv1cref6o68qmpt26olg" type="url" status="parsed" title="GitHub - smallfawn/decode_action: 世界上本来不存在加密,加密的人多了,也便成就了解密" wc="741">https://github.com/smallfawn/decode_action</url>
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:<url id=\"cv1cref6o68qmpt26ol0\" type=\"url\" status=\"parsed\" title=\"GitHub - echo094/decode-js: JS混淆代码的AST分析工具 AST analysis tool for obfuscated JS code\" wc=\"2165\">https://github.com/echo094/decode-js</url>",
"//Modify:<url id=\"cv1cref6o68qmpt26olg\" type=\"url\" status=\"parsed\" title=\"GitHub - smallfawn/decode_action: 世界上本来不存在加密,加密的人多了,也便成就了解密\" wc=\"741\">https://github.com/smallfawn/decode_action</url>"
].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 ) {
*
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
SYMBOL INDEX (145 symbols across 32 files)
FILE: input.js
function _0x4d77 (line 1) | function _0x4d77(){const _0x2f79e5=['\u67e5\u8be2\u521d\u59cb\u79ef\u520...
function printNotice (line 1) | function printNotice(){const _0x2655bb=_0x2ddd;console[_0x2655bb(0x167)]...
function desensitizeMobile (line 1) | function desensitizeMobile(_0x3e5dea){const _0x36b811=_0x532065;if(!_0x3...
function checkRegLink (line 1) | function checkRegLink(){const _0x142061=_0x532065;try{const _0x208b14=/\...
function _0x2ddd (line 1) | function _0x2ddd(_0x2a5c8e,_0x1d1cb5){const _0x4d7723=_0x4d77();return _...
function commonHeaders (line 1) | function commonHeaders(_0x39001f){const _0x4205b5=_0x532065;return{'\x55...
function handleAccount (line 1) | async function handleAccount(_0x30df42,_0x4c6efc){const _0x286330=_0x532...
function main (line 1) | async function main(){const _0x10c2b1=_0x532065;checkRegLink();const _0x...
FILE: output.js
function printNotice (line 4) | function printNotice() {
constant REQUIRED_REG_LINK (line 12) | const REQUIRED_REG_LINK = "http://h5.yidingyuecheng.com/#/pages/register...
constant CK_DIR (line 13) | const CK_DIR = path.resolve(__dirname, "ydyc_ck");
constant ENV_NAME (line 14) | const ENV_NAME = "ydyc_zm";
function desensitizeMobile (line 15) | function desensitizeMobile(_0x3e5dea) {
function checkRegLink (line 21) | function checkRegLink() {
function commonHeaders (line 50) | function commonHeaders(_0x39001f) {
function handleAccount (line 63) | async function handleAccount(_0x30df42, _0x4c6efc) {
function main (line 173) | async function main() {
FILE: src/decode.py
function try_decompress (line 17) | def try_decompress(data):
function try_decode_base64 (line 48) | def try_decode_base64(data):
function extract_base64_encoded (line 59) | def extract_base64_encoded(data):
function Encoded_script_decode (line 71) | def Encoded_script_decode(data):
function decrypt_nested (line 76) | def decrypt_nested(data):
function process_data (line 112) | def process_data(data):
FILE: src/plugin/awsc.js
function RemoveVoid (line 12) | function RemoveVoid(path) {
function LintConditionalAssign (line 18) | function LintConditionalAssign(path) {
function LintConditionalIf (line 31) | function LintConditionalIf(ast) {
function LintLogicalIf (line 98) | function LintLogicalIf(path) {
function LintIfStatement (line 113) | function LintIfStatement(path) {
function LintIfTest (line 130) | function LintIfTest(path) {
function LintSwitchCase (line 145) | function LintSwitchCase(path) {
function LintReturn (line 154) | function LintReturn(path) {
function LintSequence (line 169) | function LintSequence(path) {
function LintBlock (line 186) | function LintBlock(path) {
FILE: src/plugin/eval.js
function unpack (line 8) | function unpack(code) {
function pack (line 37) | function pack(code) {
FILE: src/plugin/jjencode.js
function getCode (line 7) | function getCode(code) {
FILE: src/plugin/obfuscator.js
function virtualGlobalEval (line 27) | function virtualGlobalEval(jsStr) {
function decodeObject (line 41) | function decodeObject(ast) {
function stringArrayV0 (line 105) | function stringArrayV0(ast) {
function stringArrayV2 (line 209) | function stringArrayV2(ast) {
function stringArrayV3 (line 315) | function stringArrayV3(ast) {
function decodeGlobal (line 437) | function decodeGlobal(ast) {
function stringArrayLite (line 580) | function stringArrayLite(ast) {
function decodeCodeBlock (line 619) | function decodeCodeBlock(ast) {
function cleanSwitchCode (line 631) | function cleanSwitchCode(path) {
function cleanDeadCode (line 729) | function cleanDeadCode(ast) {
function standardIfStatement (line 736) | function standardIfStatement(path) {
function standardLoop (line 774) | function standardLoop(path) {
function purifyCode (line 781) | function purifyCode(ast) {
function checkPattern (line 846) | function checkPattern(code, pattern) {
method VariableDeclarator (line 859) | VariableDeclarator(path) {
method FunctionDeclaration (line 914) | FunctionDeclaration(path) {
method VariableDeclarator (line 987) | VariableDeclarator(path) {
function unlockEnv (line 1030) | function unlockEnv(ast) {
FILE: src/plugin/sojson.js
function virtualGlobalEval (line 20) | function virtualGlobalEval(jsStr) {
function decodeGlobal (line 24) | function decodeGlobal(ast) {
function cleanSwitchCode (line 95) | function cleanSwitchCode(path) {
function cleanDeadCode (line 173) | function cleanDeadCode(ast) {
function checkPattern (line 180) | function checkPattern(code, pattern) {
method VariableDeclarator (line 201) | VariableDeclarator(path) {
method FunctionDeclaration (line 250) | FunctionDeclaration(path) {
method VariableDeclarator (line 311) | VariableDeclarator(path) {
method StringLiteral (line 387) | StringLiteral(path) {
function unlockEnv (line 401) | function unlockEnv(ast) {
function purifyFunction (line 413) | function purifyFunction(path) {
function purifyCode (line 449) | function purifyCode(ast) {
FILE: src/plugin/sojsonv7.js
function virtualGlobalEval (line 21) | function virtualGlobalEval(jsStr) {
function evalOneTime (line 24) | function evalOneTime(str) {
function decodeGlobal (line 31) | function decodeGlobal(ast) {
function cleanSwitchCode1 (line 343) | function cleanSwitchCode1(path) {
function cleanSwitchCode2 (line 421) | function cleanSwitchCode2(path) {
function cleanDeadCode (line 498) | function cleanDeadCode(ast) {
function removeUniqueCall (line 506) | function removeUniqueCall(path) {
function unlockDebugger (line 537) | function unlockDebugger(path) {
function unlockConsole (line 580) | function unlockConsole(path) {
function unlockLint (line 608) | function unlockLint(path) {
function unlockDomainLock (line 619) | function unlockDomainLock(path) {
function unlockEnv (line 654) | function unlockEnv(ast) {
function purifyFunction (line 672) | function purifyFunction(path) {
function purifyCode (line 716) | function purifyCode(ast) {
FILE: src/utility/check-func.js
function checkPattern (line 1) | function checkPattern(code, pattern) {
FILE: src/utility/safe-func.js
function safeDeleteNode (line 3) | function safeDeleteNode(name, path) {
function safeGetLiteral (line 32) | function safeGetLiteral(path) {
function safeGetName (line 45) | function safeGetName(path) {
function safeReplace (line 55) | function safeReplace(path, value) {
FILE: src/visitor/calculate-constant-exp.js
function checkLiteral (line 5) | function checkLiteral(node) {
function calculateBinaryExpression (line 29) | function calculateBinaryExpression(path) {
function calculateUnaryExpression (line 60) | function calculateUnaryExpression(path) {
FILE: src/visitor/calculate-rstring.js
method StringLiteral (line 9) | StringLiteral(path) {
FILE: src/visitor/delete-illegal-return.js
method ReturnStatement (line 5) | ReturnStatement(path) {
FILE: src/visitor/jsconfuser/anti-tooling.js
function deAntiToolingCheckFunc (line 3) | function deAntiToolingCheckFunc(path) {
function deAntiToolingExtract (line 17) | function deAntiToolingExtract(path, func_name) {
method FunctionDeclaration (line 40) | FunctionDeclaration(path) {
FILE: src/visitor/jsconfuser/control-flow.js
function checkControlVar (line 6) | function checkControlVar(path) {
method ObjectExpression (line 82) | ObjectExpression(path) {
method ObjectExpression (line 172) | ObjectExpression(path) {
FILE: src/visitor/jsconfuser/duplicate-literal.js
function checkArrayName (line 11) | function checkArrayName(path) {
function parseArrayWarp (line 47) | function parseArrayWarp(vm, path) {
method ArrayExpression (line 87) | ArrayExpression(path) {
FILE: src/visitor/jsconfuser/global-concealing.js
function findGlobalVar (line 11) | function findGlobalVar(glo_name, glo_path) {
function getGlobalConcealingNames (line 55) | function getGlobalConcealingNames(glo_fn_path) {
method FunctionDeclaration (line 109) | FunctionDeclaration(path) {
FILE: src/visitor/jsconfuser/global.js
function findGlobalFn (line 38) | function findGlobalFn(path) {
FILE: src/visitor/jsconfuser/minify.js
function checkArrowWrap (line 4) | function checkArrowWrap(path) {
method Identifier (line 66) | Identifier(path) {
FILE: src/visitor/jsconfuser/opaque-predicates.js
function checkOpaqueObject (line 15) | function checkOpaqueObject(path) {
method ObjectExpression (line 119) | ObjectExpression(path) {
FILE: src/visitor/jsconfuser/stack.js
function checkFuncLen (line 17) | function checkFuncLen(path) {
function initStackCache (line 57) | function initStackCache(len) {
function processAssignLeft (line 67) | function processAssignLeft(vm, cache, path, prop_name, stk_name) {
function processAssignInvalid (line 137) | function processAssignInvalid(cache, path, prop_name) {
function processReplace (line 143) | function processReplace(cache, path, prop_name) {
function checkStackInvalid (line 158) | function checkStackInvalid(path, invalid) {
function checkChangeValid (line 187) | function checkChangeValid(invalid, used) {
function tryStackReplace (line 197) | function tryStackReplace(path, len, invalid, used) {
function getStackParamLen (line 241) | function getStackParamLen(path) {
function processStackParam (line 281) | function processStackParam(path, len) {
method Identifier (line 305) | Identifier(path) {
method RestElement (line 341) | RestElement(path) {
FILE: src/visitor/jsconfuser/string-compression.js
function findStringDecoder (line 12) | function findStringDecoder(path) {
function findStringGet (line 42) | function findStringGet(path) {
function findStringSplit (line 65) | function findStringSplit(path) {
function findStringFn (line 81) | function findStringFn(path, name) {
method Identifier (line 118) | Identifier(path) {
FILE: src/visitor/jsconfuser/string-concealing.js
function insertDepItemVar (line 14) | function insertDepItemVar(deps, name, path) {
function findGlobalFnRef (line 49) | function findGlobalFnRef(obj) {
function findBufferToString (line 96) | function findBufferToString(obj) {
function generatorStringConcealingDepCode (line 150) | function generatorStringConcealingDepCode(obj) {
function renameProperty (line 165) | function renameProperty(member) {
function processSingleGetter (line 243) | function processSingleGetter(obj, decoder_name, decoder_path) {
method FunctionDeclaration (line 340) | FunctionDeclaration(path) {
function tryStringConcealingPlace (line 366) | function tryStringConcealingPlace(path) {
method StringLiteral (line 396) | StringLiteral(path) {
method ArrayExpression (line 416) | ArrayExpression(path) {
method ObjectExpression (line 432) | ObjectExpression(path) {
FILE: src/visitor/lint-if-statement.js
function LintIfStatement (line 3) | function LintIfStatement(path) {
FILE: src/visitor/merge-object.js
function mergeObject (line 3) | function mergeObject(path) {
FILE: src/visitor/parse-control-flow-storage.js
function parseObject (line 5) | function parseObject(path) {
FILE: src/visitor/prune-if-branch.js
function pruneIfBranch (line 1) | function pruneIfBranch(path) {
FILE: src/visitor/split-assignment.js
method IfStatement (line 10) | IfStatement(path) {
method VariableDeclaration (line 35) | VariableDeclaration(path) {
FILE: src/visitor/split-member-object.js
function splitMemberObject (line 1) | function splitMemberObject(path) {
FILE: src/visitor/split-sequence.js
function doSplit (line 3) | function doSplit(insertPath, path) {
function splitSequence (line 13) | function splitSequence(path) {
FILE: src/visitor/split-variable-declaration.js
function splitVariableDeclaration (line 3) | function splitVariableDeclaration(path) {
Condensed preview — 44 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (235K chars).
[
{
"path": ".github/workflows/decode.yml",
"chars": 818,
"preview": "name: Decode JavaScript File\n\non:\n push:\n branches:\n - main\njobs:\n decode:\n runs-on: ubuntu-latest\n perm"
},
{
"path": "README.md",
"chars": 323,
"preview": "# QLScriptpublic\n\n\nfork仓库后 把待解密的脚本放入到input.js里面 等待60s左右即可在output.js看到解密脚本\n\npython脚本同理 放入到input.py里面 等待60s左右即可在output.py看"
},
{
"path": "input.js",
"chars": 16144,
"preview": "function _0x4d77(){const _0x2f79e5=['\\u67e5\\u8be2\\u521d\\u59cb\\u79ef\\u5206\\u51fa\\u9519\\uff1a','\\x70\\x61\\x74\\x68','\\x5b\\u2"
},
{
"path": "input.py",
"chars": 34923,
"preview": "# -*- coding: utf-8 -*-\n\n'''\nPowered By Beidu\nCreate at [2024-11-28 21:12]\nENV: ck变量:cj_ck, 卡密变量: CN_Card\nCRON: 11 1,3,5"
},
{
"path": "output.js",
"chars": 7796,
"preview": "//Wed Apr 01 2026 00:52:28 GMT+0000 (Coordinated Universal Time)\n//Base:<url id=\"cv1cref6o68qmpt26ol0\" type=\"url\" status"
},
{
"path": "package.json",
"chars": 671,
"preview": "{\n \"name\": \"decode-js\",\n \"type\": \"module\",\n \"scripts\": {\n \"decode\": \"node src/main.js\",\n \"deob\": \"node src/main"
},
{
"path": "src/decode.py",
"chars": 3391,
"preview": "import base64\nimport bz2\nimport zlib\nimport lzma\nimport gzip\nfrom datetime import datetime\n#from Crypto.Cipher import AE"
},
{
"path": "src/main.js",
"chars": 3391,
"preview": "import fs from 'fs';\nimport { fileURLToPath } from 'url';\nimport * as path from 'path';\nimport process from 'process';\n\n"
},
{
"path": "src/plugin/awsc.js",
"chars": 6331,
"preview": "/**\n * Reference:\n * * [某宝登录bx-ua参数逆向思路(fireyejs 225算法)](https://zhuanlan.zhihu.com/p/626187669)\n */\nimport { parse } fr"
},
{
"path": "src/plugin/common.js",
"chars": 850,
"preview": "import { parse } from '@babel/parser'\nimport _generate from '@babel/generator'\nconst generator = _generate.default\nimpor"
},
{
"path": "src/plugin/eval.js",
"chars": 1286,
"preview": "import { parse } from '@babel/parser'\nimport _generate from '@babel/generator'\nconst generator = _generate.default\nimpor"
},
{
"path": "src/plugin/jjencode.js",
"chars": 1613,
"preview": "/**\n * Check the format and decode if possible\n *\n * @param {string} code the encoded code\n * @returns null or string\n *"
},
{
"path": "src/plugin/jsconfuser.js",
"chars": 2247,
"preview": "import { parse } from '@babel/parser'\nimport _generate from '@babel/generator'\nconst generator = _generate.default\nimpor"
},
{
"path": "src/plugin/obfuscator.js",
"chars": 30545,
"preview": "/**\n * 整合自下面两个项目:\n * * cilame/v_jstools\n * * Cqxstevexw/decodeObfuscator\n */\nimport { parse } from '@babel/parser'\nimpor"
},
{
"path": "src/plugin/sojson.js",
"chars": 13919,
"preview": "/**\n * 在 babel_asttool.js 的基础上修改而来\n */\nimport { parse } from '@babel/parser'\nimport _generate from '@babel/generator'\nco"
},
{
"path": "src/plugin/sojsonv7.js",
"chars": 21618,
"preview": "/**\n * For jsjiami.com.v7\n */\nimport { parse } from '@babel/parser'\nimport _generate from '@babel/generator'\nconst gener"
},
{
"path": "src/utility/check-func.js",
"chars": 241,
"preview": "function checkPattern(code, pattern) {\n let i = 0\n let j = 0\n while (i < code.length && j < pattern.length) {\n if "
},
{
"path": "src/utility/safe-func.js",
"chars": 1477,
"preview": "import * as t from '@babel/types'\n\nfunction safeDeleteNode(name, path) {\n let binding\n if (path.isFunctionDeclaration("
},
{
"path": "src/visitor/calculate-constant-exp.js",
"chars": 2904,
"preview": "import _generate from '@babel/generator'\nconst generator = _generate.default\nimport * as t from '@babel/types'\n\nfunction"
},
{
"path": "src/visitor/calculate-rstring.js",
"chars": 736,
"preview": "import _generate from '@babel/generator'\nconst generator = _generate.default\nimport * as t from '@babel/types'\n\n/**\n * \""
},
{
"path": "src/visitor/delete-extra.js",
"chars": 246,
"preview": "/**\n * 0x10 -> 16, \"\\u0058\" -> \"X\"\n * not ASCII-safe (disable jsescOption:minimal to keep ASCII-safe)\n */\nexport default"
},
{
"path": "src/visitor/delete-illegal-return.js",
"chars": 164,
"preview": "/**\n * delete ReturnStatement in Program scope\n */\nexport default {\n ReturnStatement(path) {\n if (!path.getFunctionP"
},
{
"path": "src/visitor/delete-nested-blocks.js",
"chars": 784,
"preview": "const isIntersect = (path, bindings) => {\n path.scope.crawl()\n for (const key of Object.keys(bindings)) {\n if (path"
},
{
"path": "src/visitor/delete-unreachable-code.js",
"chars": 1297,
"preview": "import * as t from '@babel/types'\n\n/**\n * DFS the BlockStatement to find and return the location of the first\n * ReturnS"
},
{
"path": "src/visitor/delete-unused-var.js",
"chars": 824,
"preview": "import * as t from '@babel/types'\n\n/**\n * Delete unused variables with the following exceptions:\n *\n * - ForOfStatement\n"
},
{
"path": "src/visitor/jsconfuser/anti-tooling.js",
"chars": 1162,
"preview": "import * as t from '@babel/types'\n\nfunction deAntiToolingCheckFunc(path) {\n if (path.node.params.length) {\n return f"
},
{
"path": "src/visitor/jsconfuser/control-flow.js",
"chars": 4364,
"preview": "import safeFunc from '../../utility/safe-func.js'\nconst safeGetLiteral = safeFunc.safeGetLiteral\nconst safeGetName = saf"
},
{
"path": "src/visitor/jsconfuser/duplicate-literal.js",
"chars": 3218,
"preview": "import _generate from '@babel/generator'\nconst generator = _generate.default\nimport * as t from '@babel/types'\n\nimport i"
},
{
"path": "src/visitor/jsconfuser/global-concealing.js",
"chars": 3976,
"preview": "import _generate from '@babel/generator'\nconst generator = _generate.default\nimport * as t from '@babel/types'\n\nimport f"
},
{
"path": "src/visitor/jsconfuser/global.js",
"chars": 2079,
"preview": "import _generate from '@babel/generator'\nconst generator = _generate.default\nimport * as t from '@babel/types'\n\nimport s"
},
{
"path": "src/visitor/jsconfuser/minify.js",
"chars": 2324,
"preview": "import _generate from '@babel/generator'\nconst generator = _generate.default\n\nfunction checkArrowWrap(path) {\n if (path"
},
{
"path": "src/visitor/jsconfuser/opaque-predicates.js",
"chars": 4331,
"preview": "import _generate from '@babel/generator'\nconst generator = _generate.default\nimport * as t from '@babel/types'\n\nimport i"
},
{
"path": "src/visitor/jsconfuser/stack.js",
"chars": 9116,
"preview": "import { parse } from '@babel/parser'\nimport _generate from '@babel/generator'\nconst generator = _generate.default\nimpor"
},
{
"path": "src/visitor/jsconfuser/string-compression.js",
"chars": 4777,
"preview": "import _generate from '@babel/generator'\nconst generator = _generate.default\n\nimport ivm from 'isolated-vm'\nconst isolat"
},
{
"path": "src/visitor/jsconfuser/string-concealing.js",
"chars": 12529,
"preview": "import _generate from '@babel/generator'\nconst generator = _generate.default\nimport * as t from '@babel/types'\n\nimport i"
},
{
"path": "src/visitor/lint-if-statement.js",
"chars": 541,
"preview": "import * as t from '@babel/types'\n\nfunction LintIfStatement(path) {\n let { test, consequent, alternate } = path.node\n "
},
{
"path": "src/visitor/merge-object.js",
"chars": 5138,
"preview": "import * as t from '@babel/types'\n\nfunction mergeObject(path) {\n const { id, init } = path.node\n if (!t.isObjectExpres"
},
{
"path": "src/visitor/parse-control-flow-storage.js",
"chars": 5268,
"preview": "import _generate from '@babel/generator'\nconst generator = _generate.default\nimport * as t from '@babel/types'\n\nfunction"
},
{
"path": "src/visitor/prune-if-branch.js",
"chars": 1018,
"preview": "function pruneIfBranch(path) {\n function clear(path, toggle) {\n // 判定成立\n if (toggle) {\n path.replaceWith(pat"
},
{
"path": "src/visitor/split-assignment.js",
"chars": 1490,
"preview": "import * as t from '@babel/types'\n\n/**\n * Split the AssignmentExpressions. For example:\n *\n * - In the test of IfStateme"
},
{
"path": "src/visitor/split-member-object.js",
"chars": 774,
"preview": "function splitMemberObject(path) {\n const object = path.get('object')\n if (!object.isAssignmentExpression()) {\n ret"
},
{
"path": "src/visitor/split-sequence.js",
"chars": 1240,
"preview": "import * as t from '@babel/types'\n\nfunction doSplit(insertPath, path) {\n const expressions = path.node.expressions\n co"
},
{
"path": "src/visitor/split-variable-declaration.js",
"chars": 711,
"preview": "import * as t from '@babel/types'\n\nfunction splitVariableDeclaration(path) {\n // The scope of a for statement is its bo"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the smallfawn/decode_action GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 44 files (213.5 KB), approximately 79.7k tokens, and a symbol index with 145 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.