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