mirror of
https://github.com/smallfawn/decode_action.git
synced 2025-12-19 16:25:10 +08:00
TEST
This commit is contained in:
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,2 +0,0 @@
|
|||||||
# Auto detect text files and perform LF normalization
|
|
||||||
* text=auto
|
|
||||||
32
.github/workflows/decode.yml
vendored
Normal file
32
.github/workflows/decode.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
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: List files in root directory
|
||||||
|
run: ls -l
|
||||||
|
- name: Install dependencies and run decode
|
||||||
|
run: |
|
||||||
|
npm install
|
||||||
|
npm run decode -- -t obfuscator [-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
|
||||||
|
git add output.js
|
||||||
|
git status
|
||||||
|
git commit -m "Add decoded output file"
|
||||||
|
git push
|
||||||
|
|
||||||
1
input.js
Normal file
1
input.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
(function(_0x444058,_0x570471){function _0x339db3(_0x1d0c2f,_0x3b1f5a,_0x4874cc,_0x2145a8,_0x27afc4){return _0x5263(_0x27afc4-0x195,_0x3b1f5a);}function _0x56d139(_0x27325d,_0x183565,_0x4fee98,_0x4ebe7d,_0x1e9086){return _0x5263(_0x1e9086-0x368,_0x4fee98);}var _0x200cf6=_0x444058();function _0x5eaa44(_0x13fd33,_0x266fba,_0xdf817,_0x2f810d,_0x287e1a){return _0x5263(_0x13fd33-0x1d8,_0xdf817);}function _0x46004f(_0x1a7d2d,_0x4b6fc0,_0x40aa99,_0x48f6da,_0x1a9c65){return _0x5263(_0x4b6fc0-0x338,_0x1a9c65);}function _0x4bf04b(_0x5b4f4e,_0xd2f654,_0x5f381a,_0x4e29c8,_0x9db713){return _0x5263(_0x9db713-0xd7,_0x5f381a);}while(!![]){try{var _0x179890=-parseInt(_0x339db3(0x2d5,0x2d1,0x2cc,0x2d5,0x2d4))/(0x136c*-0x1+0x548*0x1+0x4b7*0x3)*(-parseInt(_0x56d139(0x4a3,0x49e,0x4aa,0x4a1,0x4a4))/(-0x11*-0x85+-0x1358+-0x1*-0xa85))+-parseInt(_0x339db3(0x2d8,0x2d0,0x2d4,0x2d5,0x2d5))/(0x2272+0x12f7+-0x3566)*(parseInt(_0x56d139(0x4a7,0x4ad,0x4aa,0x4a8,0x4aa))/(-0x1*0x1ebb+0x85*-0x21+-0x994*-0x5))+parseInt(_0x5eaa44(0x322,0x323,0x326,0x31c,0x31b))/(0x4c*0x67+-0x6*-0xc0+0x5*-0x703)+parseInt(_0x4bf04b(0x21f,0x218,0x218,0x224,0x21f))/(-0x1d4+-0x6ae*0x4+0x9f*0x2e)+-parseInt(_0x4bf04b(0x21a,0x224,0x220,0x218,0x21e))/(0x16d6+-0x1f0*-0x1+-0x18bf)+parseInt(_0x46004f(0x472,0x479,0x477,0x480,0x477))/(-0xfe5+0x3a8+-0x1*-0xc45)*(-parseInt(_0x56d139(0x4b0,0x4b3,0x4a5,0x4b3,0x4ac))/(-0x1*-0x21dc+-0x145+-0x56d*0x6))+parseInt(_0x4bf04b(0x21a,0x20d,0x213,0x20f,0x214))/(0x23e8+-0x2379+-0x65)*(-parseInt(_0x46004f(0x479,0x47e,0x481,0x477,0x480))/(0xc88*0x1+-0x224e+0x15d1));if(_0x179890===_0x570471)break;else _0x200cf6['push'](_0x200cf6['shift']());}catch(_0x2cf0ac){_0x200cf6['push'](_0x200cf6['shift']());}}}(_0x4f47,-0x466*0x494+0x999da*0x1+0x1511b5));function _0x4f47(){var _0x3b4ba0=['PbtJQ','1UboPxT','3hjknQS','24dkllqJ','889964CuSUOU','log','992808dMSJWQ','Hello','5687PqdMXP','4884243rTzFPz','5881650hdzsay','\x20Worl','4674680kJDtDQ','2632414JrYWaV','24950NsVBxV'];_0x4f47=function(){return _0x3b4ba0;};return _0x4f47();}function hi(){function _0x3d370d(_0x2101a2,_0x21af60,_0x26c689,_0x44e706,_0x4f9c6c){return _0x5263(_0x21af60- -0x3ba,_0x4f9c6c);}function _0x3df3dc(_0x5b0f06,_0x1ad90a,_0x1fab14,_0x3cecd9,_0x1e9d24){return _0x5263(_0x1e9d24- -0xd3,_0x1fab14);}function _0x5e4abf(_0x363beb,_0x36d681,_0x15bd4d,_0x2e3669,_0x24fb12){return _0x5263(_0x2e3669-0x19,_0x15bd4d);}var _0x3c3202={};function _0x144232(_0x432acb,_0x3a5371,_0x499003,_0x5bf0fe,_0x591a73){return _0x5263(_0x5bf0fe- -0x4d,_0x432acb);}_0x3c3202[_0x3df3dc(0x6e,0x70,0x64,0x6f,0x6b)]=_0xe3fb9f(-0x1f9,-0x1ff,-0x200,-0x1fc,-0x202)+_0xe3fb9f(-0x1f8,-0x1fb,-0x200,-0x1ff,-0x1ff)+'d!';function _0xe3fb9f(_0x4ef0bf,_0x5d2307,_0x5b6659,_0x526774,_0x4dc04e){return _0x5263(_0x5d2307- -0x344,_0x526774);}var _0xb13bfe=_0x3c3202;console[_0xe3fb9f(-0x207,-0x201,-0x207,-0x201,-0x208)](_0xb13bfe[_0x5e4abf(0x15b,0x150,0x153,0x157,0x15c)]);}function _0x5263(_0x49a732,_0x2f0df8){var _0x58f2ba=_0x4f47();return _0x5263=function(_0x498a53,_0x357ad3){_0x498a53=_0x498a53-(0x152*-0xa+-0x1f1b+-0x59*-0x83);var _0x511ecd=_0x58f2ba[_0x498a53];return _0x511ecd;},_0x5263(_0x49a732,_0x2f0df8);}hi();
|
||||||
3283
package-lock.json
generated
Normal file
3283
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
package.json
Normal file
21
package.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "decode-js",
|
||||||
|
"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",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
50
src/main.js
Normal file
50
src/main.js
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
const fs = require('fs')
|
||||||
|
const PluginCommon = require('./plugin/common.js')
|
||||||
|
const PluginJjencode = require('./plugin/jjencode.js')
|
||||||
|
const PluginSojson = require('./plugin/sojson.js')
|
||||||
|
const PluginSojsonV7 = require('./plugin/sojsonv7.js')
|
||||||
|
const PluginObfuscator = require('./plugin/obfuscator.js')
|
||||||
|
const PluginAwsc = require('./plugin/awsc.js')
|
||||||
|
|
||||||
|
// 读取参数
|
||||||
|
let type = 'common'
|
||||||
|
let encodeFile = 'input.js'
|
||||||
|
let decodeFile = 'output.js'
|
||||||
|
for (let i = 2; i < process.argv.length; i += 2) {
|
||||||
|
if (process.argv[i] === '-t') {
|
||||||
|
type = process.argv[i + 1]
|
||||||
|
}
|
||||||
|
if (process.argv[i] === '-i') {
|
||||||
|
encodeFile = process.argv[i + 1]
|
||||||
|
}
|
||||||
|
if (process.argv[i] === '-o') {
|
||||||
|
decodeFile = process.argv[i + 1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(`类型: ${type}`)
|
||||||
|
console.log(`输入: ${encodeFile}`)
|
||||||
|
console.log(`输出: ${decodeFile}`)
|
||||||
|
|
||||||
|
// 读取源代码
|
||||||
|
const sourceCode = fs.readFileSync(encodeFile, { encoding: 'utf-8' })
|
||||||
|
|
||||||
|
// 净化源代码
|
||||||
|
let code
|
||||||
|
if (type === 'sojson') {
|
||||||
|
code = PluginSojson(sourceCode)
|
||||||
|
} else if (type === 'sojsonv7') {
|
||||||
|
code = PluginSojsonV7(sourceCode)
|
||||||
|
} else if (type === 'obfuscator') {
|
||||||
|
code = PluginObfuscator(sourceCode)
|
||||||
|
} else if (type === 'awsc') {
|
||||||
|
code = PluginAwsc(sourceCode)
|
||||||
|
} else if (type === 'jjencode') {
|
||||||
|
code = PluginJjencode(sourceCode)
|
||||||
|
} else {
|
||||||
|
code = PluginCommon(sourceCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输出代码
|
||||||
|
if (code) {
|
||||||
|
fs.writeFile(decodeFile, code, () => {})
|
||||||
|
}
|
||||||
244
src/plugin/awsc.js
Normal file
244
src/plugin/awsc.js
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
/**
|
||||||
|
* Reference:
|
||||||
|
* * [某宝登录bx-ua参数逆向思路(fireyejs 225算法)](https://zhuanlan.zhihu.com/p/626187669)
|
||||||
|
*/
|
||||||
|
const { parse } = require('@babel/parser')
|
||||||
|
const generator = require('@babel/generator').default
|
||||||
|
const traverse = require('@babel/traverse').default
|
||||||
|
const t = require('@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))
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = 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
|
||||||
|
}
|
||||||
21
src/plugin/common.js
Normal file
21
src/plugin/common.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
const { parse } = require('@babel/parser')
|
||||||
|
const generator = require('@babel/generator').default
|
||||||
|
const traverse = require('@babel/traverse').default
|
||||||
|
|
||||||
|
module.exports = function (code) {
|
||||||
|
let ast
|
||||||
|
try {
|
||||||
|
ast = parse(code, { errorRecovery: true })
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Cannot parse code: ${e.reasonCode}`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
const deleteExtra = require('../visitor/delete-extra')
|
||||||
|
traverse(ast, deleteExtra)
|
||||||
|
const calculateBinary = require('../visitor/calculate-binary')
|
||||||
|
traverse(ast, calculateBinary)
|
||||||
|
const calculateRString = require('../visitor/calculate-rstring')
|
||||||
|
traverse(ast, calculateRString)
|
||||||
|
code = generator(ast).code
|
||||||
|
return code
|
||||||
|
}
|
||||||
52
src/plugin/eval.js
Normal file
52
src/plugin/eval.js
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
const { parse } = require('@babel/parser')
|
||||||
|
const generator = require('@babel/generator').default
|
||||||
|
const traverse = require('@babel/traverse').default
|
||||||
|
const t = require('@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
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
unpack,
|
||||||
|
pack,
|
||||||
|
}
|
||||||
66
src/plugin/jjencode.js
Normal file
66
src/plugin/jjencode.js
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* 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).
|
||||||
|
*/
|
||||||
|
module.exports = function (code) {
|
||||||
|
code = getCode(code)
|
||||||
|
if (!code) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return code
|
||||||
|
}
|
||||||
1109
src/plugin/obfuscator.js
Normal file
1109
src/plugin/obfuscator.js
Normal file
File diff suppressed because it is too large
Load Diff
657
src/plugin/sojson.js
Normal file
657
src/plugin/sojson.js
Normal file
@@ -0,0 +1,657 @@
|
|||||||
|
/**
|
||||||
|
* 在 babel_asttool.js 的基础上修改而来
|
||||||
|
*/
|
||||||
|
const { parse } = require('@babel/parser')
|
||||||
|
const generator = require('@babel/generator').default
|
||||||
|
const traverse = require('@babel/traverse').default
|
||||||
|
const t = require('@babel/types')
|
||||||
|
const ivm = require('isolated-vm')
|
||||||
|
const PluginEval = require('./eval.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 purifyBoolean(path) {
|
||||||
|
// 简化 ![] 和 !![]
|
||||||
|
const node0 = path.node
|
||||||
|
if (node0.operator !== '!') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const node1 = node0.argument
|
||||||
|
if (t.isArrayExpression(node1) && node1.elements.length === 0) {
|
||||||
|
path.replaceWith(t.booleanLiteral(false))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!t.isUnaryExpression(node1) || node1.operator !== '!') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const node2 = node1.argument
|
||||||
|
if (t.isArrayExpression(node2) && node2.elements.length === 0) {
|
||||||
|
path.replaceWith(t.booleanLiteral(true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanIFCode(path) {
|
||||||
|
function clear(path, toggle) {
|
||||||
|
// 判定成立
|
||||||
|
if (toggle) {
|
||||||
|
if (path.node.consequent.type == 'BlockStatement') {
|
||||||
|
path.replaceWithMultiple(path.node.consequent.body)
|
||||||
|
} else {
|
||||||
|
path.replaceWith(path.node.consequent)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 判定不成立
|
||||||
|
if (!path.node.alternate) {
|
||||||
|
path.remove()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (path.node.alternate.type == 'BlockStatement') {
|
||||||
|
path.replaceWithMultiple(path.node.alternate.body)
|
||||||
|
} else {
|
||||||
|
path.replaceWith(path.node.alternate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 判断判定是否恒定
|
||||||
|
const test = path.node.test
|
||||||
|
const types = ['StringLiteral', 'NumericLiteral', 'BooleanLiteral']
|
||||||
|
if (test.type === 'BinaryExpression') {
|
||||||
|
if (
|
||||||
|
types.indexOf(test.left.type) !== -1 &&
|
||||||
|
types.indexOf(test.right.type) !== -1
|
||||||
|
) {
|
||||||
|
const left = JSON.stringify(test.left.value)
|
||||||
|
const right = JSON.stringify(test.right.value)
|
||||||
|
clear(path, eval(left + test.operator + right))
|
||||||
|
}
|
||||||
|
} else if (types.indexOf(test.type) !== -1) {
|
||||||
|
clear(path, eval(JSON.stringify(test.value)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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, { UnaryExpression: purifyBoolean })
|
||||||
|
traverse(ast, { IfStatement: cleanIFCode })
|
||||||
|
traverse(ast, { ConditionalExpression: cleanIFCode })
|
||||||
|
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 })
|
||||||
|
// 净化变量定义中的常量数值
|
||||||
|
function purifyDecl(path) {
|
||||||
|
if (t.isNumericLiteral(path.node.init)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const name = path.node.id.name
|
||||||
|
const { code } = generator(
|
||||||
|
{
|
||||||
|
type: 'Program',
|
||||||
|
body: [path.node.init],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
compact: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const valid = /^[-+*/%!<>&|~^ 0-9;]+$/.test(code)
|
||||||
|
if (!valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (/^[-][0-9]*$/.test(code)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const value = eval(code)
|
||||||
|
const node = t.valueToNode(value)
|
||||||
|
path.replaceWith(t.variableDeclarator(path.node.id, node))
|
||||||
|
console.log(`替换 ${name}: ${code} -> ${value}`)
|
||||||
|
}
|
||||||
|
traverse(ast, { VariableDeclarator: purifyDecl })
|
||||||
|
// 合并字符串
|
||||||
|
let end = false
|
||||||
|
function combineString(path) {
|
||||||
|
const op = path.node.operator
|
||||||
|
if (op !== '+') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const left = path.node.left
|
||||||
|
const right = path.node.right
|
||||||
|
if (!t.isStringLiteral(left) || !t.isStringLiteral(right)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
end = false
|
||||||
|
path.replaceWith(t.StringLiteral(eval(path + '')))
|
||||||
|
console.log(`合并字符串: ${path.node.value}`)
|
||||||
|
}
|
||||||
|
while (!end) {
|
||||||
|
end = true
|
||||||
|
traverse(ast, { BinaryExpression: combineString })
|
||||||
|
}
|
||||||
|
// 替换索引器
|
||||||
|
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 })
|
||||||
|
// 分割表达式
|
||||||
|
function removeComma(path) {
|
||||||
|
// a = 1, b = ddd(), c = null;
|
||||||
|
// |
|
||||||
|
// |
|
||||||
|
// |
|
||||||
|
// v
|
||||||
|
// a = 1;
|
||||||
|
// b = ddd();
|
||||||
|
// c = null;
|
||||||
|
let { expression } = path.node
|
||||||
|
if (!t.isSequenceExpression(expression)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let body = []
|
||||||
|
expression.expressions.forEach((express) => {
|
||||||
|
body.push(t.expressionStatement(express))
|
||||||
|
})
|
||||||
|
path.replaceInline(body)
|
||||||
|
}
|
||||||
|
traverse(ast, { ExpressionStatement: removeComma })
|
||||||
|
// 删除空语句
|
||||||
|
traverse(ast, {
|
||||||
|
EmptyStatement: (path) => {
|
||||||
|
path.remove()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
// 删除未使用的变量
|
||||||
|
const deleteUnusedVar = require('../visitor/delete-unused-var')
|
||||||
|
traverse(ast, deleteUnusedVar)
|
||||||
|
return ast
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = 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('处理代码块加密...')
|
||||||
|
const parseControlFlowStorage = require('../visitor/parse-control-flow-storage')
|
||||||
|
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
|
||||||
|
}
|
||||||
914
src/plugin/sojsonv7.js
Normal file
914
src/plugin/sojsonv7.js
Normal file
@@ -0,0 +1,914 @@
|
|||||||
|
/**
|
||||||
|
* For jsjiami.com.v7
|
||||||
|
*/
|
||||||
|
const { parse } = require('@babel/parser')
|
||||||
|
const generator = require('@babel/generator').default
|
||||||
|
const traverse = require('@babel/traverse').default
|
||||||
|
const t = require('@babel/types')
|
||||||
|
const ivm = require('isolated-vm')
|
||||||
|
const PluginEval = require('./eval.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
const refs = scope.bindings[cur_val].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 === 'object') {
|
||||||
|
// MemberExpression
|
||||||
|
memToStr(parent)
|
||||||
|
} else if (ref.key === 'callee') {
|
||||||
|
// CallExpression
|
||||||
|
funToStr(parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 purifyBoolean(path) {
|
||||||
|
// 简化 ![] 和 !![]
|
||||||
|
const node0 = path.node
|
||||||
|
if (node0.operator !== '!') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const node1 = node0.argument
|
||||||
|
if (t.isArrayExpression(node1) && node1.elements.length === 0) {
|
||||||
|
path.replaceWith(t.booleanLiteral(false))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!t.isUnaryExpression(node1) || node1.operator !== '!') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const node2 = node1.argument
|
||||||
|
if (t.isArrayExpression(node2) && node2.elements.length === 0) {
|
||||||
|
path.replaceWith(t.booleanLiteral(true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanIFCode(path) {
|
||||||
|
function clear(path, toggle) {
|
||||||
|
// 判定成立
|
||||||
|
if (toggle) {
|
||||||
|
if (path.node.consequent.type == 'BlockStatement') {
|
||||||
|
path.replaceWithMultiple(path.node.consequent.body)
|
||||||
|
} else {
|
||||||
|
path.replaceWith(path.node.consequent)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 判定不成立
|
||||||
|
if (!path.node.alternate) {
|
||||||
|
path.remove()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (path.node.alternate.type == 'BlockStatement') {
|
||||||
|
path.replaceWithMultiple(path.node.alternate.body)
|
||||||
|
} else {
|
||||||
|
path.replaceWith(path.node.alternate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 判断判定是否恒定
|
||||||
|
const test = path.node.test
|
||||||
|
const types = ['StringLiteral', 'NumericLiteral', 'BooleanLiteral']
|
||||||
|
if (test.type === 'BinaryExpression') {
|
||||||
|
if (
|
||||||
|
types.indexOf(test.left.type) !== -1 &&
|
||||||
|
types.indexOf(test.right.type) !== -1
|
||||||
|
) {
|
||||||
|
const left = JSON.stringify(test.left.value)
|
||||||
|
const right = JSON.stringify(test.right.value)
|
||||||
|
clear(path, eval(left + test.operator + right))
|
||||||
|
}
|
||||||
|
} else if (types.indexOf(test.type) !== -1) {
|
||||||
|
clear(path, eval(JSON.stringify(test.value)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = eval(test + `;${arrName}`)
|
||||||
|
} catch {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!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, { UnaryExpression: purifyBoolean })
|
||||||
|
traverse(ast, { IfStatement: cleanIFCode })
|
||||||
|
traverse(ast, { ConditionalExpression: cleanIFCode })
|
||||||
|
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 })
|
||||||
|
// 净化变量定义中的常量数值
|
||||||
|
function purifyDecl(path) {
|
||||||
|
if (t.isNumericLiteral(path.node.init)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const name = path.node.id.name
|
||||||
|
const { code } = generator(
|
||||||
|
{
|
||||||
|
type: 'Program',
|
||||||
|
body: [path.node.init],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
compact: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const valid = /^[-+*/%!<>&|~^ 0-9;]+$/.test(code)
|
||||||
|
if (!valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (/^[-][0-9]*$/.test(code)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const value = eval(code)
|
||||||
|
const node = t.valueToNode(value)
|
||||||
|
path.replaceWith(t.variableDeclarator(path.node.id, node))
|
||||||
|
console.log(`替换 ${name}: ${code} -> ${value}`)
|
||||||
|
}
|
||||||
|
traverse(ast, { VariableDeclarator: purifyDecl })
|
||||||
|
// 合并字符串
|
||||||
|
let end = false
|
||||||
|
function combineString(path) {
|
||||||
|
const op = path.node.operator
|
||||||
|
if (op !== '+') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const left = path.node.left
|
||||||
|
const right = path.node.right
|
||||||
|
if (!t.isStringLiteral(left) || !t.isStringLiteral(right)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
end = false
|
||||||
|
path.replaceWith(t.StringLiteral(eval(path + '')))
|
||||||
|
console.log(`合并字符串: ${path.node.value}`)
|
||||||
|
}
|
||||||
|
while (!end) {
|
||||||
|
end = true
|
||||||
|
traverse(ast, { BinaryExpression: combineString })
|
||||||
|
}
|
||||||
|
// 替换索引器
|
||||||
|
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 })
|
||||||
|
// 分割表达式
|
||||||
|
function removeComma(path) {
|
||||||
|
// a = 1, b = ddd(), c = null;
|
||||||
|
// |
|
||||||
|
// |
|
||||||
|
// |
|
||||||
|
// v
|
||||||
|
// a = 1;
|
||||||
|
// b = ddd();
|
||||||
|
// c = null;
|
||||||
|
if (!t.isExpressionStatement(path.parent)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let replace_path = path.parentPath
|
||||||
|
if (replace_path.listKey !== 'body') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for (const item of path.node.expressions) {
|
||||||
|
replace_path.insertBefore(t.expressionStatement(item))
|
||||||
|
}
|
||||||
|
replace_path.remove()
|
||||||
|
}
|
||||||
|
traverse(ast, { SequenceExpression: { exit: removeComma } })
|
||||||
|
// 删除空语句
|
||||||
|
traverse(ast, {
|
||||||
|
EmptyStatement: (path) => {
|
||||||
|
path.remove()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
// 删除未使用的变量
|
||||||
|
const deleteUnusedVar = require('../visitor/delete-unused-var')
|
||||||
|
traverse(ast, deleteUnusedVar)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = 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
|
||||||
|
const deleteIllegalReturn = require('../visitor/delete-illegal-return')
|
||||||
|
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('处理代码块加密...')
|
||||||
|
const parseControlFlowStorage = require('../visitor/parse-control-flow-storage')
|
||||||
|
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
|
||||||
|
}
|
||||||
24
src/visitor/calculate-binary.js
Normal file
24
src/visitor/calculate-binary.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
const generator = require('@babel/generator').default
|
||||||
|
const t = require('@babel/types')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate BinaryExpression if left and right are both literals.
|
||||||
|
* Otherwise, the expression can't be simplified.
|
||||||
|
*
|
||||||
|
* For example, `typeof window` can be calculated but it's not constant.
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
BinaryExpression(path) {
|
||||||
|
const { left, right } = path.node
|
||||||
|
if (!t.isLiteral(left) || !t.isLiteral(right)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const code = generator(path.node).code
|
||||||
|
try {
|
||||||
|
const ret = eval(code)
|
||||||
|
path.replaceWithSourceString(ret)
|
||||||
|
} catch {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
36
src/visitor/calculate-rstring.js
Normal file
36
src/visitor/calculate-rstring.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
const generator = require('@babel/generator').default
|
||||||
|
const t = require('@babel/types')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "sh".split("").reverse().join("") -> "hs"
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
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 {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
12
src/visitor/delete-extra.js
Normal file
12
src/visitor/delete-extra.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* 0x10 -> 16, "\u0058" -> "X"
|
||||||
|
* not ASCII-safe (disable jsescOption:minimal to keep ASCII-safe)
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
StringLiteral: ({ node }) => {
|
||||||
|
delete node.extra
|
||||||
|
},
|
||||||
|
NumericLiteral: ({ node }) => {
|
||||||
|
delete node.extra
|
||||||
|
},
|
||||||
|
}
|
||||||
10
src/visitor/delete-illegal-return.js
Normal file
10
src/visitor/delete-illegal-return.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/**
|
||||||
|
* delete ReturnStatement in Program scope
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
ReturnStatement(path) {
|
||||||
|
if (!path.getFunctionParent()) {
|
||||||
|
path.remove()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
35
src/visitor/delete-unused-var.js
Normal file
35
src/visitor/delete-unused-var.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
const t = require('@babel/types')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete unused variables with the following exceptions:
|
||||||
|
*
|
||||||
|
* - ForOfStatement
|
||||||
|
* - ForInStatement
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
VariableDeclarator: (path) => {
|
||||||
|
const { node, scope } = path
|
||||||
|
const name = node.id.name
|
||||||
|
const binding = scope.getBinding(name)
|
||||||
|
if (!binding || binding.referenced || !binding.constant) {
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
211
src/visitor/merge-object.js
Normal file
211
src/visitor/merge-object.js
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
const t = require('@babel/types')
|
||||||
|
|
||||||
|
function mergeObject(path) {
|
||||||
|
const { id, init } = path.node
|
||||||
|
if (!t.isObjectExpression(init)) {
|
||||||
|
// 判断是否是定义对象
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let name = id.name
|
||||||
|
let scope = path.scope
|
||||||
|
let binding = scope.getBinding(name)
|
||||||
|
const start = path.node.end
|
||||||
|
let end = -1
|
||||||
|
let violation = null
|
||||||
|
if (!binding.constant) {
|
||||||
|
// Find the first constantViolation after this declaration
|
||||||
|
for (let item of binding.constantViolations) {
|
||||||
|
if (item.node.start <= start) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if (item.isVariableDeclarator()) {
|
||||||
|
end = item.node.start
|
||||||
|
violation = item
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (item.isAssignmentExpression()) {
|
||||||
|
end = item.node.start
|
||||||
|
violation = item
|
||||||
|
break
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 添加已有的key
|
||||||
|
let keys = {}
|
||||||
|
let properties = init.properties
|
||||||
|
for (let prop of properties) {
|
||||||
|
let key = null
|
||||||
|
if (t.isStringLiteral(prop.key)) {
|
||||||
|
key = prop.key.value
|
||||||
|
}
|
||||||
|
if (t.isIdentifier(prop.key)) {
|
||||||
|
key = prop.key.name
|
||||||
|
}
|
||||||
|
if (key) {
|
||||||
|
keys[key] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 遍历作用域检测是否含有局部混淆特征并合并成员
|
||||||
|
let merges = []
|
||||||
|
const container = path.parentPath.parentPath
|
||||||
|
let cur = 0
|
||||||
|
let valid = true
|
||||||
|
// Check references in sequence
|
||||||
|
while (cur < binding.references) {
|
||||||
|
const ref = binding.referencePaths[cur]
|
||||||
|
// Ignore the references before this declaration
|
||||||
|
if (ref.node.start <= start) {
|
||||||
|
++cur
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Ignore the references after the first constantViolation
|
||||||
|
if (end >= 0 && ref.node.end >= end) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (ref.key !== 'object' || !ref.parentPath.isMemberExpression()) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
const me = ref.parentPath
|
||||||
|
if (me.key !== 'left' || !me.parentPath.isAssignmentExpression()) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
const ae = me.parentPath
|
||||||
|
let bk = ae
|
||||||
|
let stop = false
|
||||||
|
while (bk.parentPath !== container) {
|
||||||
|
if (
|
||||||
|
bk.parentPath.isSequenceExpression() ||
|
||||||
|
bk.parentPath.isVariableDeclarator() ||
|
||||||
|
bk.parentPath.isVariableDeclaration() ||
|
||||||
|
bk.parentPath.isExpressionStatement()
|
||||||
|
) {
|
||||||
|
bk = bk.parentPath
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
stop = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (stop) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
const property = me.node.property
|
||||||
|
let key = null
|
||||||
|
if (t.isStringLiteral(property)) {
|
||||||
|
key = property.value
|
||||||
|
}
|
||||||
|
if (t.isIdentifier(property)) {
|
||||||
|
key = property.name
|
||||||
|
}
|
||||||
|
if (!key) {
|
||||||
|
valid = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// 不允许出现重定义
|
||||||
|
if (Object.prototype.hasOwnProperty.call(keys, key)) {
|
||||||
|
valid = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// 添加到列表
|
||||||
|
properties.push(t.ObjectProperty(t.valueToNode(key), ae.node.right))
|
||||||
|
keys[key] = true
|
||||||
|
merges.push(ae)
|
||||||
|
++cur
|
||||||
|
}
|
||||||
|
if (!merges.length || !valid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Remove code
|
||||||
|
console.log(`尝试性合并: ${name}`)
|
||||||
|
for (let ref of merges) {
|
||||||
|
const left = ref.node.left
|
||||||
|
if (ref.parentPath.isSequenceExpression() && ref.container.length === 1) {
|
||||||
|
ref = ref.parentPath
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
ref.parentPath.isVariableDeclarator() ||
|
||||||
|
ref.parentPath.isAssignmentExpression()
|
||||||
|
) {
|
||||||
|
ref.replaceWith(left)
|
||||||
|
} else {
|
||||||
|
ref.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check the remaining references
|
||||||
|
const ref1 = binding.referencePaths[cur++]
|
||||||
|
if (!ref1) {
|
||||||
|
scope.crawl()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const ref2 = binding.referencePaths[cur]
|
||||||
|
// Don't replace the declarator if there exists more than one reference
|
||||||
|
if (ref2 && ref2.node.end < end) {
|
||||||
|
scope.crawl()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Check if the only reference is an assignment
|
||||||
|
let key = ref1.key
|
||||||
|
let up1 = ref1.parentPath
|
||||||
|
if (up1.isSequenceExpression() && ref1.container.length === 1) {
|
||||||
|
key = up1.key
|
||||||
|
up1 = up1.parentPath
|
||||||
|
}
|
||||||
|
if (!up1.isVariableDeclarator() || key !== 'init') {
|
||||||
|
scope.crawl()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Move the definition to its reference
|
||||||
|
up1.node.init = path.node.init
|
||||||
|
// Delete the original definition
|
||||||
|
if (violation?.isAssignmentExpression()) {
|
||||||
|
path.node.init = undefined
|
||||||
|
} else {
|
||||||
|
path.remove()
|
||||||
|
}
|
||||||
|
scope.crawl()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect the properties of one object and move it back to the declaration.
|
||||||
|
*
|
||||||
|
* One example made by ObjectExpressionKeysTransformer:
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* var _0xb28de8 = {};
|
||||||
|
* _0xb28de8["abcd"] = function(_0x22293f, _0x5a165e) {
|
||||||
|
* return _0x22293f == _0x5a165e;
|
||||||
|
* };
|
||||||
|
* _0xb28de8.dbca = function(_0xfbac1e, _0x23462f, _0x556555) {
|
||||||
|
* return _0xfbac1e(_0x23462f, _0x556555);
|
||||||
|
* };
|
||||||
|
* _0xb28de8.aaa = function(_0x57e640) {
|
||||||
|
* return _0x57e640();
|
||||||
|
* };
|
||||||
|
* _0xb28de8["bbb"] = "eee";
|
||||||
|
* var _0x15e145 = _0xb28de8;
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* The result:
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* var _0x15e145 = {
|
||||||
|
* "abcd": function (_0x22293f, _0x5a165e) {
|
||||||
|
* return _0x22293f == _0x5a165e;
|
||||||
|
* },
|
||||||
|
* "dbca": function (_0xfbac1e, _0x23462f, _0x556555) {
|
||||||
|
* return _0xfbac1e(_0x23462f, _0x556555);
|
||||||
|
* },
|
||||||
|
* "aaa": function (_0x57e640) {
|
||||||
|
* return _0x57e640();
|
||||||
|
* },
|
||||||
|
* "bbb": "eee"
|
||||||
|
* };
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Note:
|
||||||
|
* - Constant objects in the original code can be splitted
|
||||||
|
* - AssignmentExpression can be moved to ReturnStatement
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
VariableDeclarator: mergeObject,
|
||||||
|
}
|
||||||
196
src/visitor/parse-control-flow-storage.js
Normal file
196
src/visitor/parse-control-flow-storage.js
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
const generator = require('@babel/generator').default
|
||||||
|
const t = require('@babel/types')
|
||||||
|
|
||||||
|
function parseObject(path) {
|
||||||
|
let node = path.node
|
||||||
|
// 变量必须定义为Object类型才可能是代码块加密内容
|
||||||
|
if (!t.isObjectExpression(node.init)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let objPropertiesList = node.init.properties
|
||||||
|
if (objPropertiesList.length == 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 遍历Object 判断每个元素是否符合格式
|
||||||
|
let objName = node.id.name
|
||||||
|
let objKeys = {}
|
||||||
|
// 有时会有重复的定义
|
||||||
|
let replCount = 0
|
||||||
|
objPropertiesList.map(function (prop) {
|
||||||
|
if (!t.isObjectProperty(prop)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let key
|
||||||
|
if (t.isIdentifier(prop.key)) {
|
||||||
|
key = prop.key.name
|
||||||
|
} else {
|
||||||
|
key = prop.key.value
|
||||||
|
}
|
||||||
|
if (t.isFunctionExpression(prop.value)) {
|
||||||
|
// 符合要求的函数必须有且仅有一条return语句
|
||||||
|
if (prop.value.body.body.length !== 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let retStmt = prop.value.body.body[0]
|
||||||
|
if (!t.isReturnStatement(retStmt)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 检测是否是3种格式之一
|
||||||
|
let repfunc = null
|
||||||
|
if (t.isBinaryExpression(retStmt.argument)) {
|
||||||
|
// 二元运算类型
|
||||||
|
repfunc = function (_path, args) {
|
||||||
|
_path.replaceWith(
|
||||||
|
t.binaryExpression(retStmt.argument.operator, args[0], args[1])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (t.isLogicalExpression(retStmt.argument)) {
|
||||||
|
// 逻辑判断类型
|
||||||
|
repfunc = function (_path, args) {
|
||||||
|
_path.replaceWith(
|
||||||
|
t.logicalExpression(retStmt.argument.operator, args[0], args[1])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (t.isCallExpression(retStmt.argument)) {
|
||||||
|
// 函数调用类型 调用的函数必须是传入的第一个参数
|
||||||
|
if (!t.isIdentifier(retStmt.argument.callee)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (retStmt.argument.callee.name !== prop.value.params[0]?.name) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
repfunc = function (_path, args) {
|
||||||
|
_path.replaceWith(t.callExpression(args[0], args.slice(1)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (repfunc) {
|
||||||
|
objKeys[key] = repfunc
|
||||||
|
++replCount
|
||||||
|
}
|
||||||
|
} else if (t.isStringLiteral(prop.value)) {
|
||||||
|
let retStmt = prop.value.value
|
||||||
|
objKeys[key] = function (_path) {
|
||||||
|
_path.replaceWith(t.stringLiteral(retStmt))
|
||||||
|
}
|
||||||
|
++replCount
|
||||||
|
} else if (t.isMemberExpression(prop.value)) {
|
||||||
|
let retStmt = prop.value
|
||||||
|
objKeys[key] = function (_path) {
|
||||||
|
_path.replaceWith(retStmt)
|
||||||
|
}
|
||||||
|
++replCount
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// 如果Object内的元素不全符合要求 很有可能是普通的字符串类型 不需要替换
|
||||||
|
if (!replCount) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (objPropertiesList.length !== replCount) {
|
||||||
|
console.log(
|
||||||
|
`不完整替换: ${objName} ${replCount}/${objPropertiesList.length}`
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// 遍历作用域进行替换 分为函数调用和字符串调用
|
||||||
|
console.log(`处理代码块: ${objName}`)
|
||||||
|
let objUsed = {}
|
||||||
|
function getReplaceFunc(_node) {
|
||||||
|
// 这里开始所有的调用应该都在列表中
|
||||||
|
let key = null
|
||||||
|
if (t.isStringLiteral(_node.property)) {
|
||||||
|
key = _node.property.value
|
||||||
|
} else if (t.isIdentifier(_node.property)) {
|
||||||
|
key = _node.property.name
|
||||||
|
} else {
|
||||||
|
// Maybe the code was obfuscated more than once
|
||||||
|
const code = generator(_node.property, { minified: true }).code
|
||||||
|
console.log(`意外的调用: ${objName}[${code}]`)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (!Object.prototype.hasOwnProperty.call(objKeys, key)) {
|
||||||
|
// 这里应该是在死代码中 因为key不存在
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
objUsed[key] = true
|
||||||
|
return objKeys[key]
|
||||||
|
}
|
||||||
|
let bind = path.scope.getBinding(objName)?.referencePaths
|
||||||
|
let usedCount = 0
|
||||||
|
// Replace reversely to handle nested cases correctly
|
||||||
|
for (let i = bind.length - 1; i >= 0; --i) {
|
||||||
|
let ref = bind[i]
|
||||||
|
let up1 = ref.parentPath
|
||||||
|
if (up1.isMemberExpression() && ref.key === 'object') {
|
||||||
|
if (up1.key === 'left' && t.isAssignmentExpression(up1.parent)) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let func = getReplaceFunc(up1.node)
|
||||||
|
if (!func) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
++usedCount
|
||||||
|
let up2 = up1.parentPath
|
||||||
|
if (up1.key === 'callee') {
|
||||||
|
func(up2, up2.node.arguments)
|
||||||
|
} else {
|
||||||
|
func(up1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果没有全部使用 就先不删除
|
||||||
|
if (usedCount !== bind.length) {
|
||||||
|
console.log(`不完整使用: ${objName} ${usedCount}/${bind.length}`)
|
||||||
|
} else {
|
||||||
|
path.remove()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse control flow object
|
||||||
|
*
|
||||||
|
* Several kinds of expressions are collected, transformed, and merged into
|
||||||
|
* the controlFlowStorage object by method FunctionControlFlowTransformer:
|
||||||
|
*
|
||||||
|
* - BinaryExpression
|
||||||
|
* - CallExpression
|
||||||
|
* - LogicalExpression
|
||||||
|
* - Literal
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* var _0xb28de8 = {
|
||||||
|
* "abcd": function(_0x22293f, _0x5a165e) {
|
||||||
|
* return _0x22293f == _0x5a165e;
|
||||||
|
* },
|
||||||
|
* "dbca": function(_0xfbac1e, _0x23462f, _0x556555) {
|
||||||
|
* return _0xfbac1e(_0x23462f, _0x556555);
|
||||||
|
* },
|
||||||
|
* "aaa": function(_0x57e640) {
|
||||||
|
* return _0x57e640();
|
||||||
|
* },
|
||||||
|
* "bbb": "eee",
|
||||||
|
* "ccc": A[x][y][...]
|
||||||
|
* };
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* This visitor can parse such objects and undo the transformation.
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* // From
|
||||||
|
* var aa = _0xb28de8["abcd"](123, 456);
|
||||||
|
* var bb = _0xb28de8["dbca"](bcd, 11, 22);
|
||||||
|
* var cc = _0xb28de8["aaa"](dcb);
|
||||||
|
* var dd = _0xb28de8["bbb"];
|
||||||
|
* var ee = _0xb28de8["ccc"];
|
||||||
|
* // To
|
||||||
|
* var aa = 123 == 456;
|
||||||
|
* var bb = bcd(11, 22);
|
||||||
|
* var cc = dcb();
|
||||||
|
* var dd = "eee";
|
||||||
|
* var ee = A[x][y][...];
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
VariableDeclarator: {
|
||||||
|
exit: parseObject,
|
||||||
|
},
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user