mirror of
https://github.com/smallfawn/decode_action.git
synced 2025-12-20 00:35:19 +08:00
1110 lines
31 KiB
JavaScript
1110 lines
31 KiB
JavaScript
/**
|
|
* 整合自下面两个项目:
|
|
* * cilame/v_jstools
|
|
* * Cqxstevexw/decodeObfuscator
|
|
*/
|
|
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))
|
|
}
|
|
|
|
/**
|
|
* Extract the literal value of an object, and remove an object if all
|
|
* references to the object are replaced.
|
|
*/
|
|
function decodeObject(ast) {
|
|
function collectObject(path) {
|
|
const id = path.node.id
|
|
const init = path.node.init
|
|
if (!t.isIdentifier(id) || !t.isObjectExpression(init)) {
|
|
return
|
|
}
|
|
const obj_name = id.name
|
|
const bind = path.scope.getBinding(obj_name)
|
|
let valid = true
|
|
let count = 0
|
|
let obj = {}
|
|
for (const item of init.properties) {
|
|
if (!t.isObjectProperty(item) || !t.isLiteral(item.value)) {
|
|
valid = false
|
|
break
|
|
}
|
|
if (!t.isIdentifier(item.key)) {
|
|
valid = false
|
|
break
|
|
}
|
|
++count
|
|
obj[item.key.name] = item.value
|
|
}
|
|
if (!valid || !count) {
|
|
return
|
|
}
|
|
let safe = true
|
|
for (let ref of bind.referencePaths) {
|
|
const parent = ref.parentPath
|
|
if (ref.key !== 'object' || !parent.isMemberExpression()) {
|
|
safe = false
|
|
continue
|
|
}
|
|
const key = parent.node.property
|
|
if (!t.isIdentifier(key) || parent.node.computed) {
|
|
safe = false
|
|
continue
|
|
}
|
|
if (Object.prototype.hasOwnProperty.call(obj, key.name)) {
|
|
parent.replaceWith(obj[key.name])
|
|
} else {
|
|
safe = false
|
|
}
|
|
}
|
|
bind.scope.crawl()
|
|
if (safe) {
|
|
path.remove()
|
|
console.log(`删除对象: ${obj_name}`)
|
|
}
|
|
}
|
|
traverse(ast, {
|
|
VariableDeclarator: collectObject,
|
|
})
|
|
return ast
|
|
}
|
|
|
|
/**
|
|
* Before version 2.19.0, the string-array is a single array.
|
|
* Hence, we have to find StringArrayRotateFunction instead.
|
|
*
|
|
* @param {t.File} ast The ast file
|
|
* @returns Object
|
|
*/
|
|
function stringArrayV2(ast) {
|
|
console.info('Try v2 mode...')
|
|
let obj = {
|
|
version: 2,
|
|
stringArrayName: null,
|
|
stringArrayCodes: [],
|
|
stringArrayCalls: [],
|
|
}
|
|
// Function to rotate string list ("func2")
|
|
function find_rotate_function(path) {
|
|
const callee = path.get('callee')
|
|
const args = path.node.arguments
|
|
if (
|
|
!callee.isFunctionExpression() ||
|
|
callee.node.params.length !== 2 ||
|
|
args.length == 0 ||
|
|
args.length > 2 ||
|
|
!t.isIdentifier(args[0])
|
|
) {
|
|
return
|
|
}
|
|
const arr = callee.node.params[0].name
|
|
const cmpV = callee.node.params[1].name
|
|
// >= 2.10.0
|
|
const fp1 = `(){try{if()break${arr}push(${arr}shift())}catch(){${arr}push(${arr}shift())}}`
|
|
// < 2.10.0
|
|
const fp2 = `=function(){while(--){${arr}push(${arr}shift)}}${cmpV}`
|
|
const code = '' + callee.get('body')
|
|
if (!checkPattern(code, fp1) && !checkPattern(code, fp2)) {
|
|
return
|
|
}
|
|
obj.stringArrayName = args[0].name
|
|
// The string array can be found by its binding
|
|
const bind = path.scope.getBinding(obj.stringArrayName)
|
|
const def = t.variableDeclaration('var', [bind.path.node])
|
|
obj.stringArrayCodes.push(generator(def, { minified: true }).code)
|
|
// The calls can be found by its references
|
|
for (let ref of bind.referencePaths) {
|
|
if (ref?.listKey === 'arguments') {
|
|
// This is the rotate function
|
|
continue
|
|
}
|
|
if (ref.findParent((path) => path.removed)) {
|
|
continue
|
|
}
|
|
// the key is 'object'
|
|
let up1 = ref.getFunctionParent()
|
|
if (up1.node.id) {
|
|
// 2.12.0 <= v < 2.15.4
|
|
// The `stringArrayCallsWrapperName` is included in the definition
|
|
obj.stringArrayCalls.push(up1.node.id.name)
|
|
obj.stringArrayCodes.push(generator(up1.node, { minified: true }).code)
|
|
up1.remove()
|
|
continue
|
|
}
|
|
if (up1.key === 'init') {
|
|
// v < 2.12.0
|
|
// The `stringArrayCallsWrapperName` is defined by VariableDeclarator
|
|
up1 = up1.parentPath
|
|
obj.stringArrayCalls.push(up1.node.id.name)
|
|
up1 = up1.parentPath
|
|
obj.stringArrayCodes.push(generator(up1.node, { minified: true }).code)
|
|
up1.remove()
|
|
continue
|
|
}
|
|
// 2.15.4 <= v < 2.19.0
|
|
// The function includes another function with the same name
|
|
up1 = up1.parentPath
|
|
const wrapper = up1.node.left.name
|
|
let up2 = up1.getFunctionParent()
|
|
if (!up2 || up2.node?.id?.name !== wrapper) {
|
|
console.warn('Unexpected reference!')
|
|
continue
|
|
}
|
|
obj.stringArrayCalls.push(wrapper)
|
|
obj.stringArrayCodes.push(generator(up2.node, { minified: true }).code)
|
|
up2.remove()
|
|
}
|
|
// Remove the string array
|
|
bind.path.remove()
|
|
// Add the rotate function
|
|
const node = t.expressionStatement(path.node)
|
|
obj.stringArrayCodes.push(generator(node, { minified: true }).code)
|
|
path.stop()
|
|
if (path.parentPath.isUnaryExpression()) {
|
|
path.parentPath.remove()
|
|
} else {
|
|
path.remove()
|
|
}
|
|
}
|
|
traverse(ast, { CallExpression: find_rotate_function })
|
|
if (obj.stringArrayCodes.length < 3 || !obj.stringArrayCalls.length) {
|
|
console.error('Essential code missing!')
|
|
obj.stringArrayName = null
|
|
}
|
|
return obj
|
|
}
|
|
|
|
/**
|
|
* Find the string-array codes by matching string-array function
|
|
* (valid version >= 2.19.0)
|
|
*
|
|
* @param {t.File} ast The ast file
|
|
* @returns Object
|
|
*/
|
|
function stringArrayV3(ast) {
|
|
console.info('Try v3 mode...')
|
|
let ob_func_str = []
|
|
let ob_dec_name = []
|
|
let ob_string_func_name = null
|
|
// Normally, the string array func ("func1") follows the template below:
|
|
// function aaa() {
|
|
// const bbb = [...]
|
|
// aaa = function () {
|
|
// return bbb;
|
|
// };
|
|
// return aaa();
|
|
// }
|
|
// In some cases (lint), the assignment is merged into the ReturnStatement
|
|
// After finding the possible func1, this method will check all the binding
|
|
// references and put the child encode function into list.
|
|
function find_string_array_function(path) {
|
|
if (path.getFunctionParent()) {
|
|
return
|
|
}
|
|
if (
|
|
!t.isIdentifier(path.node.id) ||
|
|
path.node.params.length ||
|
|
!t.isBlockStatement(path.node.body)
|
|
) {
|
|
return
|
|
}
|
|
const body = path.node.body.body
|
|
if (body.length < 2 || body.length > 3) {
|
|
return
|
|
}
|
|
const name_func = path.node.id.name
|
|
let string_var = -1
|
|
try {
|
|
if (
|
|
body[0].declarations.length != 1 ||
|
|
!(string_var = body[0].declarations[0].id.name) ||
|
|
!t.isArrayExpression(body[0].declarations[0].init)
|
|
) {
|
|
return
|
|
}
|
|
const nodes = [...body]
|
|
nodes.shift()
|
|
const code = generator(t.BlockStatement(nodes)).code
|
|
const fp = `${name_func}=function(){return${string_var}}${name_func}()`
|
|
if (!checkPattern(code, fp)) {
|
|
return
|
|
}
|
|
} catch {
|
|
return
|
|
}
|
|
const binding = path.scope.getBinding(name_func)
|
|
if (!binding.referencePaths) {
|
|
return
|
|
}
|
|
let paths = binding.referencePaths
|
|
let nodes = []
|
|
// The sorting function maybe missing in some config
|
|
function find2(refer_path) {
|
|
if (
|
|
refer_path.parentPath.isCallExpression() &&
|
|
refer_path.listKey === 'arguments' &&
|
|
refer_path.key === 0
|
|
) {
|
|
let rm_path = refer_path.parentPath
|
|
if (rm_path.parentPath.isExpressionStatement()) {
|
|
rm_path = rm_path.parentPath
|
|
}
|
|
nodes.push([rm_path.node, 'func2'])
|
|
rm_path.remove()
|
|
}
|
|
}
|
|
paths.map(find2)
|
|
function find3(refer_path) {
|
|
if (refer_path.findParent((path) => path.removed)) {
|
|
return
|
|
}
|
|
if (
|
|
refer_path.parentPath.isCallExpression() &&
|
|
refer_path.key === 'callee'
|
|
) {
|
|
let rm_path = refer_path.parentPath.getFunctionParent()
|
|
if (name_func == rm_path.node.id.name) {
|
|
return
|
|
}
|
|
nodes.push([rm_path.node, 'func3'])
|
|
rm_path.remove()
|
|
} else {
|
|
console.error('Unexpected reference')
|
|
}
|
|
}
|
|
paths.map(find3)
|
|
if (!name_func) {
|
|
return
|
|
}
|
|
ob_string_func_name = name_func
|
|
ob_func_str.push(generator(path.node, { minified: true }).code)
|
|
nodes.map(function (item) {
|
|
let node = item[0]
|
|
if (item[1] == 'func3') {
|
|
ob_dec_name.push(node.id.name)
|
|
}
|
|
if (t.isCallExpression(node)) {
|
|
node = t.expressionStatement(node)
|
|
}
|
|
ob_func_str.push(generator(node, { minified: true }).code)
|
|
})
|
|
path.stop()
|
|
path.remove()
|
|
}
|
|
traverse(ast, { FunctionDeclaration: find_string_array_function })
|
|
return {
|
|
version: 3,
|
|
stringArrayName: ob_string_func_name,
|
|
stringArrayCodes: ob_func_str,
|
|
stringArrayCalls: ob_dec_name,
|
|
}
|
|
}
|
|
|
|
function decodeGlobal(ast) {
|
|
let obj = stringArrayV3(ast)
|
|
if (!obj.stringArrayName) {
|
|
obj = stringArrayV2(ast)
|
|
if (!obj.stringArrayName) {
|
|
console.error('Cannot find string list!')
|
|
return false
|
|
}
|
|
}
|
|
console.log(`String List Name: ${obj.stringArrayName}`)
|
|
let ob_func_str = obj.stringArrayCodes
|
|
let ob_dec_name = obj.stringArrayCalls
|
|
try {
|
|
virtualGlobalEval(ob_func_str.join(';'))
|
|
} catch (e) {
|
|
// issue #31
|
|
if (e.name === 'ReferenceError') {
|
|
let lost = e.message.split(' ')[0]
|
|
traverse(ast, {
|
|
Program(path) {
|
|
ob_dec_name.push(lost)
|
|
let loc = path.scope.getBinding(lost).path
|
|
let obj = t.variableDeclaration(loc.parent.kind, [loc.node])
|
|
ob_func_str.unshift(generator(obj, { minified: true }).code)
|
|
loc.remove()
|
|
path.stop()
|
|
},
|
|
})
|
|
virtualGlobalEval(ob_func_str.join(';'))
|
|
}
|
|
}
|
|
|
|
// 循环删除混淆函数
|
|
let call_dict = {}
|
|
let exist_names = ob_dec_name
|
|
let collect_codes = []
|
|
let collect_names = []
|
|
function do_parse_value(path) {
|
|
let name = path.node.callee.name
|
|
if (path.node.callee && exist_names.indexOf(name) != -1) {
|
|
let old_call = path + ''
|
|
try {
|
|
// 运行成功则说明函数为直接调用并返回字符串
|
|
let new_str = virtualGlobalEval(old_call)
|
|
console.log(`map: ${old_call} -> ${new_str}`)
|
|
call_dict[old_call] = new_str
|
|
} catch (e) {
|
|
// 运行失败则说明函数为其它混淆函数的子函数
|
|
console.log(`sub: ${old_call}`)
|
|
}
|
|
}
|
|
}
|
|
function do_collect_remove(path) {
|
|
// 可以删除所有已收集混淆函数的定义
|
|
// 因为根函数已被删除 即使保留也无法运行
|
|
let node = path.node?.left
|
|
if (!node) {
|
|
node = path.node?.id
|
|
}
|
|
let name = node?.name
|
|
if (exist_names.indexOf(name) != -1) {
|
|
// console.log(`del: ${name}`)
|
|
if (path.parentPath.isCallExpression()) {
|
|
path.replaceWith(node)
|
|
} else {
|
|
path.remove()
|
|
}
|
|
}
|
|
}
|
|
function do_collect_func_dec(path) {
|
|
// function A (...) { return function B (...) }
|
|
do_collect_func(path, path)
|
|
}
|
|
function do_collect_func_var(path) {
|
|
// var A = function (...) { return function B (...) }
|
|
let func_path = path.get('init')
|
|
if (!func_path.isFunctionExpression()) {
|
|
return
|
|
}
|
|
do_collect_func(path, func_path)
|
|
}
|
|
function do_collect_func(root, path) {
|
|
if (
|
|
path.node.body.body.length == 1 &&
|
|
path.node.body.body[0].type == 'ReturnStatement' &&
|
|
path.node.body.body[0].argument?.type == 'CallExpression' &&
|
|
path.node.body.body[0].argument.callee.type == 'Identifier' &&
|
|
// path.node.params.length == 5 &&
|
|
root.node.id
|
|
) {
|
|
let call_func = path.node.body.body[0].argument.callee.name
|
|
if (exist_names.indexOf(call_func) == -1) {
|
|
return
|
|
}
|
|
let name = root.node.id.name
|
|
let t = generator(root.node, { minified: true }).code
|
|
if (collect_names.indexOf(name) == -1) {
|
|
collect_codes.push(t)
|
|
collect_names.push(name)
|
|
} else {
|
|
console.log(`err: redef ${name}`)
|
|
}
|
|
}
|
|
}
|
|
function do_collect_var(path) {
|
|
// var A = B
|
|
let left, right
|
|
if (t.isVariableDeclarator(path.node)) {
|
|
left = path.node.id
|
|
right = path.node.init
|
|
} else {
|
|
left = path.node.left
|
|
right = path.node.right
|
|
}
|
|
if (right?.type == 'Identifier' && exist_names.indexOf(right.name) != -1) {
|
|
let name = left.name
|
|
let t = 'var ' + generator(path.node, { minified: true }).code
|
|
if (collect_names.indexOf(name) == -1) {
|
|
collect_codes.push(t)
|
|
collect_names.push(name)
|
|
} else {
|
|
console.warning(`redef ${name}`)
|
|
}
|
|
}
|
|
}
|
|
while (exist_names.length) {
|
|
// 查找已收集混淆函数的调用并建立替换关系
|
|
traverse(ast, { CallExpression: do_parse_value })
|
|
// 删除被使用过的定义
|
|
traverse(ast, { FunctionDeclaration: do_collect_remove })
|
|
traverse(ast, { VariableDeclarator: do_collect_remove })
|
|
traverse(ast, { AssignmentExpression: do_collect_remove })
|
|
// 收集所有调用已收集混淆函数的混淆函数
|
|
collect_codes = []
|
|
collect_names = []
|
|
traverse(ast, { FunctionDeclaration: do_collect_func_dec })
|
|
traverse(ast, { VariableDeclarator: do_collect_func_var })
|
|
traverse(ast, { VariableDeclarator: do_collect_var })
|
|
traverse(ast, { AssignmentExpression: do_collect_var })
|
|
exist_names = collect_names
|
|
// 执行找到的函数
|
|
virtualGlobalEval(collect_codes.join(';'))
|
|
}
|
|
// 替换混淆函数
|
|
function do_replace(path) {
|
|
let old_call = path + ''
|
|
if (Object.prototype.hasOwnProperty.call(call_dict, old_call)) {
|
|
path.replaceWith(t.StringLiteral(call_dict[old_call]))
|
|
}
|
|
}
|
|
traverse(ast, { CallExpression: do_replace })
|
|
return true
|
|
}
|
|
|
|
function stringArrayLite(ast) {
|
|
const visitor = {
|
|
VariableDeclarator(path) {
|
|
const name = path.node.id.name
|
|
if (!path.get('init').isArrayExpression()) {
|
|
return
|
|
}
|
|
const elements = path.node.init.elements
|
|
for (const element of elements) {
|
|
if (!t.isLiteral(element)) {
|
|
return
|
|
}
|
|
}
|
|
const bind = path.scope.getBinding(name)
|
|
if (!bind.constant) {
|
|
return
|
|
}
|
|
for (const ref of bind.referencePaths) {
|
|
if (
|
|
!ref.parentPath.isMemberExpression() ||
|
|
ref.key !== 'object' ||
|
|
ref.parentPath.key == 'left' ||
|
|
!t.isNumericLiteral(ref.parent.property)
|
|
) {
|
|
return
|
|
}
|
|
}
|
|
console.log(`Extract string array: ${name}`)
|
|
for (const ref of bind.referencePaths) {
|
|
const i = ref.parent.property.value
|
|
ref.parentPath.replaceWith(elements[i])
|
|
}
|
|
bind.scope.crawl()
|
|
path.remove()
|
|
},
|
|
}
|
|
traverse(ast, visitor)
|
|
}
|
|
|
|
function calcBinary(path) {
|
|
let tps = ['StringLiteral', 'BooleanLiteral', 'NumericLiteral']
|
|
let nod = path.node
|
|
function judge(e) {
|
|
return (
|
|
tps.indexOf(e.type) != -1 ||
|
|
(e.type == 'UnaryExpression' && tps.indexOf(e.argument.type) != -1)
|
|
)
|
|
}
|
|
function make_rep(e) {
|
|
if (typeof e == 'number') {
|
|
return t.NumericLiteral(e)
|
|
}
|
|
if (typeof e == 'string') {
|
|
return t.StringLiteral(e)
|
|
}
|
|
if (typeof e == 'boolean') {
|
|
return t.BooleanLiteral(e)
|
|
}
|
|
throw Error('unknown type' + typeof e)
|
|
}
|
|
if (judge(nod.left) && judge(nod.right)) {
|
|
path.replaceWith(make_rep(eval(path + '')))
|
|
}
|
|
}
|
|
|
|
function decodeCodeBlock(ast) {
|
|
// 合并字面量
|
|
traverse(ast, { BinaryExpression: { exit: calcBinary } })
|
|
// 先合并分离的Object定义
|
|
const mergeObject = require('../visitor/merge-object')
|
|
traverse(ast, mergeObject)
|
|
// 在变量定义完成后判断是否为代码块加密内容
|
|
const parseControlFlowStorage = require('../visitor/parse-control-flow-storage')
|
|
traverse(ast, parseControlFlowStorage)
|
|
// 合并字面量(在解除区域混淆后会出现新的可合并分割)
|
|
traverse(ast, { BinaryExpression: { exit: calcBinary } })
|
|
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
|
|
let valid = false
|
|
if (t.isBooleanLiteral(node.test) && node.test.value) {
|
|
valid = true
|
|
}
|
|
if (t.isArrayExpression(node.test) && node.test.elements.length === 0) {
|
|
valid = true
|
|
}
|
|
if (!valid) {
|
|
return
|
|
}
|
|
if (!t.isBlockStatement(node.body)) {
|
|
return
|
|
}
|
|
const body = node.body.body
|
|
if (
|
|
!t.isSwitchStatement(body[0]) ||
|
|
!t.isMemberExpression(body[0].discriminant) ||
|
|
!t.isBreakStatement(body[1])
|
|
) {
|
|
return
|
|
}
|
|
// switch语句的两个变量
|
|
const swithStm = body[0]
|
|
const arrName = swithStm.discriminant.object.name
|
|
const argName = swithStm.discriminant.property.argument.name
|
|
// 在while上面的节点寻找这两个变量
|
|
let arr = []
|
|
let rm = []
|
|
path.getAllPrevSiblings().forEach((pre_path) => {
|
|
if (!pre_path.isVariableDeclaration()) {
|
|
return
|
|
}
|
|
for (let i = 0; i < pre_path.node.declarations.length; ++i) {
|
|
const declaration = pre_path.get(`declarations.${i}`)
|
|
let { id, init } = declaration.node
|
|
if (arrName == id.name) {
|
|
if (t.isStringLiteral(init?.callee?.object)) {
|
|
arr = init.callee.object.value.split('|')
|
|
rm.push(declaration)
|
|
}
|
|
}
|
|
if (argName == id.name) {
|
|
if (t.isLiteral(init)) {
|
|
rm.push(declaration)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
if (rm.length !== 2) {
|
|
return
|
|
}
|
|
rm.forEach((pre_path) => {
|
|
pre_path.remove()
|
|
})
|
|
console.log(`扁平化还原: ${arrName}[${argName}]`)
|
|
// 重建代码块
|
|
const caseList = swithStm.cases
|
|
let resultBody = []
|
|
arr.map((targetIdx) => {
|
|
// 从当前序号开始直到遇到continue
|
|
let valid = true
|
|
targetIdx = parseInt(targetIdx)
|
|
while (valid && targetIdx < caseList.length) {
|
|
const targetBody = caseList[targetIdx].consequent
|
|
const test = caseList[targetIdx].test
|
|
if (!t.isStringLiteral(test) || parseInt(test.value) !== targetIdx) {
|
|
console.log(`switch中出现乱序的序号: ${test.value}:${targetIdx}`)
|
|
}
|
|
for (let i = 0; i < targetBody.length; ++i) {
|
|
const s = targetBody[i]
|
|
if (t.isContinueStatement(s)) {
|
|
valid = false
|
|
break
|
|
}
|
|
if (t.isReturnStatement(s)) {
|
|
valid = false
|
|
resultBody.push(s)
|
|
break
|
|
}
|
|
if (t.isBreakStatement(s)) {
|
|
console.log(`switch中出现意外的break: ${arrName}[${argName}]`)
|
|
} else {
|
|
resultBody.push(s)
|
|
}
|
|
}
|
|
targetIdx++
|
|
}
|
|
})
|
|
// 替换整个while语句
|
|
path.replaceInline(resultBody)
|
|
}
|
|
|
|
function cleanDeadCode(ast) {
|
|
traverse(ast, { UnaryExpression: purifyBoolean })
|
|
traverse(ast, { IfStatement: cleanIFCode })
|
|
traverse(ast, { ConditionalExpression: cleanIFCode })
|
|
traverse(ast, { WhileStatement: { exit: cleanSwitchCode } })
|
|
return ast
|
|
}
|
|
|
|
const splitVariableDeclarator = {
|
|
VariableDeclarator(path) {
|
|
const init = path.get('init')
|
|
if (!init.isAssignmentExpression()) {
|
|
return
|
|
}
|
|
path.parentPath.insertBefore(init.node)
|
|
init.replaceWith(init.node.left)
|
|
path.parentPath.scope.crawl()
|
|
},
|
|
}
|
|
|
|
function standardIfStatement(path) {
|
|
const consequent = path.get('consequent')
|
|
const alternate = path.get('alternate')
|
|
const test = path.get('test')
|
|
const evaluateTest = test.evaluateTruthy()
|
|
|
|
if (!consequent.isBlockStatement()) {
|
|
consequent.replaceWith(t.BlockStatement([consequent.node]))
|
|
}
|
|
if (alternate.node !== null && !alternate.isBlockStatement()) {
|
|
alternate.replaceWith(t.BlockStatement([alternate.node]))
|
|
}
|
|
|
|
if (consequent.node.body.length == 0) {
|
|
if (alternate.node == null) {
|
|
path.replaceWith(test.node)
|
|
} else {
|
|
consequent.replaceWith(alternate.node)
|
|
alternate.remove()
|
|
path.node.alternate = null
|
|
test.replaceWith(t.unaryExpression('!', test.node, true))
|
|
}
|
|
}
|
|
|
|
if (alternate.isBlockStatement() && alternate.node.body.length == 0) {
|
|
alternate.remove()
|
|
path.node.alternate = null
|
|
}
|
|
|
|
if (evaluateTest === true) {
|
|
path.replaceWithMultiple(consequent.node.body)
|
|
} else if (evaluateTest === false) {
|
|
alternate.node === null
|
|
? path.remove()
|
|
: path.replaceWithMultiple(alternate.node.body)
|
|
}
|
|
}
|
|
|
|
function standardLoop(path) {
|
|
const node = path.node
|
|
if (!t.isBlockStatement(node.body)) {
|
|
node.body = t.BlockStatement([node.body])
|
|
}
|
|
}
|
|
|
|
function splitSequence(path) {
|
|
let { scope, parentPath, node } = path
|
|
let expressions = node.expressions
|
|
if (parentPath.isReturnStatement({ argument: node })) {
|
|
let lastExpression = expressions.pop()
|
|
for (let expression of expressions) {
|
|
parentPath.insertBefore(t.ExpressionStatement(expression))
|
|
}
|
|
|
|
path.replaceInline(lastExpression)
|
|
} else if (parentPath.isExpressionStatement({ expression: node })) {
|
|
let body = []
|
|
expressions.forEach((express) => {
|
|
body.push(t.ExpressionStatement(express))
|
|
})
|
|
path.replaceInline(body)
|
|
} else {
|
|
return
|
|
}
|
|
|
|
scope.crawl()
|
|
}
|
|
|
|
function purifyCode(ast) {
|
|
// 标准化if语句
|
|
traverse(ast, { IfStatement: standardIfStatement })
|
|
// 标准化for语句
|
|
traverse(ast, { ForStatement: standardLoop })
|
|
// 标准化while语句
|
|
traverse(ast, { WhileStatement: standardLoop })
|
|
// 删除空语句
|
|
traverse(ast, {
|
|
EmptyStatement: (path) => {
|
|
path.remove()
|
|
},
|
|
})
|
|
// 删除未使用的变量
|
|
traverse(ast, splitVariableDeclarator)
|
|
const deleteUnusedVar = require('../visitor/delete-unused-var')
|
|
traverse(ast, deleteUnusedVar)
|
|
// 替换索引器
|
|
function FormatMember(path) {
|
|
let curNode = path.node
|
|
if (!t.isStringLiteral(curNode.property)) {
|
|
return
|
|
}
|
|
if (curNode.computed === undefined || !curNode.computed === true) {
|
|
return
|
|
}
|
|
if (!/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(curNode.property.value)) {
|
|
return
|
|
}
|
|
curNode.property = t.identifier(curNode.property.value)
|
|
curNode.computed = false
|
|
}
|
|
traverse(ast, { MemberExpression: FormatMember })
|
|
|
|
// 替换类和对象的计算方法和计算属性
|
|
// ["method"](){} -> "method"(){}
|
|
function FormatComputed(path) {
|
|
let curNode = path.node
|
|
if (!t.isStringLiteral(curNode.key)) {
|
|
return
|
|
}
|
|
curNode.computed = false
|
|
}
|
|
// "method"(){} -> method(){}
|
|
function stringLiteralToIdentifier(path) {
|
|
let curNode = path.node
|
|
if (!t.isStringLiteral(curNode.key) || curNode.computed === true) {
|
|
return
|
|
}
|
|
if (!/^[a-zA-Z_$][0-9a-zA-Z_$]*$/.test(curNode.key.value)) {
|
|
return
|
|
}
|
|
curNode.key = t.identifier(curNode.key.value)
|
|
}
|
|
traverse(ast, {
|
|
'Method|Property': (path) => {
|
|
FormatComputed(path)
|
|
stringLiteralToIdentifier(path)
|
|
},
|
|
})
|
|
|
|
// 拆分语句
|
|
traverse(ast, { SequenceExpression: splitSequence })
|
|
return ast
|
|
}
|
|
|
|
function checkPattern(code, pattern) {
|
|
let i = 0
|
|
let j = 0
|
|
while (i < code.length && j < pattern.length) {
|
|
if (code[i] == pattern[j]) {
|
|
++j
|
|
}
|
|
++i
|
|
}
|
|
return j == pattern.length
|
|
}
|
|
|
|
const deleteSelfDefendingCode = {
|
|
VariableDeclarator(path) {
|
|
const { id, init } = path.node
|
|
const selfName = id.name
|
|
if (!t.isCallExpression(init)) {
|
|
return
|
|
}
|
|
if (!t.isIdentifier(init.callee)) {
|
|
return
|
|
}
|
|
const callName = init.callee.name
|
|
const args = init.arguments
|
|
if (
|
|
args.length != 2 ||
|
|
!t.isThisExpression(args[0]) ||
|
|
!t.isFunctionExpression(args[1])
|
|
) {
|
|
return
|
|
}
|
|
const block = generator(args[1]).code
|
|
const patterns = [
|
|
// @7920538
|
|
`return${selfName}.toString().search().toString().constructor(${selfName}).search()`,
|
|
// @7135b09
|
|
`const=function(){const=.constructor()return.test(${selfName})}return()`,
|
|
// #94
|
|
`var=function(){var=.constructor()return.test(${selfName})}return()`,
|
|
]
|
|
let valid = false
|
|
for (let pattern of patterns) {
|
|
valid |= checkPattern(block, pattern)
|
|
}
|
|
if (!valid) {
|
|
return
|
|
}
|
|
const refs = path.scope.bindings[selfName].referencePaths
|
|
for (let ref of refs) {
|
|
if (ref.key == 'callee') {
|
|
ref.parentPath.remove()
|
|
break
|
|
}
|
|
}
|
|
path.remove()
|
|
console.info(`Remove SelfDefendingFunc: ${selfName}`)
|
|
const scope = path.scope.getBinding(callName).scope
|
|
scope.crawl()
|
|
const bind = scope.bindings[callName]
|
|
if (bind.referenced) {
|
|
console.error(`Call func ${callName} unexpected ref!`)
|
|
}
|
|
bind.path.remove()
|
|
console.info(`Remove CallFunc: ${callName}`)
|
|
},
|
|
}
|
|
|
|
const deleteDebugProtectionCode = {
|
|
FunctionDeclaration(path) {
|
|
const { id, params, body } = path.node
|
|
if (
|
|
!t.isIdentifier(id) ||
|
|
params.length !== 1 ||
|
|
!t.isIdentifier(params[0]) ||
|
|
!t.isBlockStatement(body) ||
|
|
body.body.length !== 2 ||
|
|
!t.isFunctionDeclaration(body.body[0]) ||
|
|
!t.isTryStatement(body.body[1])
|
|
) {
|
|
return
|
|
}
|
|
const debugName = id.name
|
|
const ret = params[0].name
|
|
const subNode = body.body[0]
|
|
if (
|
|
!t.isIdentifier(subNode.id) ||
|
|
subNode.params.length !== 1 ||
|
|
!t.isIdentifier(subNode.params[0])
|
|
) {
|
|
return
|
|
}
|
|
const subName = subNode.id.name
|
|
const counter = subNode.params[0].name
|
|
const code = generator(body).code
|
|
const pattern = `function${subName}(${counter}){${counter}debugger${subName}(++${counter})}try{if(${ret})return${subName}${subName}(0)}catch(){}`
|
|
if (!checkPattern(code, pattern)) {
|
|
return
|
|
}
|
|
const scope1 = path.parentPath.scope
|
|
const refs = scope1.bindings[debugName].referencePaths
|
|
for (let ref of refs) {
|
|
if (ref.findParent((path) => path.removed)) {
|
|
continue
|
|
}
|
|
if (ref.key == 0) {
|
|
// DebugProtectionFunctionInterval @e8e92c6
|
|
const rm = ref.getFunctionParent().parentPath
|
|
rm.remove()
|
|
continue
|
|
}
|
|
// ref.key == 'callee'
|
|
const up1 = ref.getFunctionParent()
|
|
const callName = up1.parent.callee.name
|
|
if (callName === 'setInterval') {
|
|
// DebugProtectionFunctionInterval @51523c0
|
|
const rm = up1.parentPath
|
|
rm.remove()
|
|
continue
|
|
}
|
|
const up2 = up1.getFunctionParent()?.parentPath
|
|
if (up2) {
|
|
// DebugProtectionFunctionCall
|
|
const scope2 = up2.scope.getBinding(callName).scope
|
|
up2.remove()
|
|
scope1.crawl()
|
|
scope2.crawl()
|
|
const bind = scope2.bindings[callName]
|
|
bind.path.remove()
|
|
console.info(`Remove CallFunc: ${callName}`)
|
|
continue
|
|
}
|
|
// exceptions #95
|
|
const rm = ref.parentPath
|
|
rm.remove()
|
|
}
|
|
path.remove()
|
|
console.info(`Remove DebugProtectionFunc: ${debugName}`)
|
|
},
|
|
}
|
|
|
|
const deleteConsoleOutputCode = {
|
|
VariableDeclarator(path) {
|
|
const { id, init } = path.node
|
|
const selfName = id.name
|
|
if (!t.isCallExpression(init)) {
|
|
return
|
|
}
|
|
if (!t.isIdentifier(init.callee)) {
|
|
return
|
|
}
|
|
const callName = init.callee.name
|
|
const args = init.arguments
|
|
if (
|
|
args.length != 2 ||
|
|
!t.isThisExpression(args[0]) ||
|
|
!t.isFunctionExpression(args[1])
|
|
) {
|
|
return
|
|
}
|
|
const block = generator(args[1]).code
|
|
const pattern = `console=console=log,warn,info,error,for(){${callName}constructor.prototype.bind${callName}${callName}bind${callName}}`
|
|
if (!checkPattern(block, pattern)) {
|
|
return
|
|
}
|
|
const refs = path.scope.bindings[selfName].referencePaths
|
|
for (let ref of refs) {
|
|
if (ref.key == 'callee') {
|
|
ref.parentPath.remove()
|
|
break
|
|
}
|
|
}
|
|
path.remove()
|
|
console.info(`Remove ConsoleOutputFunc: ${selfName}`)
|
|
const scope = path.scope.getBinding(callName).scope
|
|
scope.crawl()
|
|
const bind = scope.bindings[callName]
|
|
if (bind.referenced) {
|
|
console.error(`Call func ${callName} unexpected ref!`)
|
|
}
|
|
bind.path.remove()
|
|
console.info(`Remove CallFunc: ${callName}`)
|
|
},
|
|
}
|
|
|
|
function unlockEnv(ast) {
|
|
//可能会误删一些代码,可屏蔽
|
|
traverse(ast, deleteSelfDefendingCode)
|
|
traverse(ast, deleteDebugProtectionCode)
|
|
traverse(ast, deleteConsoleOutputCode)
|
|
return ast
|
|
}
|
|
|
|
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
|
|
},
|
|
NumericLiteral: ({ node }) => {
|
|
delete node.extra
|
|
},
|
|
})
|
|
console.log('还原数值...')
|
|
if (!decodeObject(ast)) {
|
|
return null
|
|
}
|
|
console.log('处理全局加密...')
|
|
if (!decodeGlobal(ast)) {
|
|
return null
|
|
}
|
|
console.log('提高代码可读性...')
|
|
ast = purifyCode(ast)
|
|
console.log('处理代码块加密...')
|
|
stringArrayLite(ast)
|
|
ast = decodeCodeBlock(ast)
|
|
console.log('清理死代码...')
|
|
ast = cleanDeadCode(ast)
|
|
// 刷新代码
|
|
ast = parse(
|
|
generator(ast, {
|
|
comments: false,
|
|
jsescOption: { minimal: true },
|
|
}).code,
|
|
{ errorRecovery: true }
|
|
)
|
|
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
|
|
}
|