This commit is contained in:
smallfawn
2024-09-29 21:32:08 +08:00
parent e5eef4071c
commit 19f765ff54
15 changed files with 581 additions and 1963 deletions

View File

@@ -5,7 +5,6 @@ const PluginSojson = require('./plugin/sojson.js')
const PluginSojsonV7 = require('./plugin/sojsonv7.js') const PluginSojsonV7 = require('./plugin/sojsonv7.js')
const PluginObfuscator = require('./plugin/obfuscator.js') const PluginObfuscator = require('./plugin/obfuscator.js')
const PluginAwsc = require('./plugin/awsc.js') const PluginAwsc = require('./plugin/awsc.js')
const PluginObfuscator2 = require('./plugin/obfuscator2.js')
// 读取参数 // 读取参数
let encodeFile = 'input.js' let encodeFile = 'input.js'
@@ -29,8 +28,7 @@ let pluginUsed = '';
// 循环尝试不同的插件,直到源代码与处理后的代码不一致 // 循环尝试不同的插件,直到源代码与处理后的代码不一致
const plugins = [ const plugins = [
{ name: 'obfuscator', plugin: PluginObfuscator2 }, { name: 'obfuscator', plugin: PluginObfuscator },
{ name: "ob2", plugin: PluginObfuscator },
{ name: 'sojsonv7', plugin: PluginSojsonV7 }, { name: 'sojsonv7', plugin: PluginSojsonV7 },
{ name: 'sojson', plugin: PluginSojson }, { name: 'sojson', plugin: PluginSojson },
{ name: 'common', plugin: PluginCommon },// 最后一次使用通用插件 { name: 'common', plugin: PluginCommon },// 最后一次使用通用插件

View File

@@ -12,8 +12,8 @@ module.exports = function (code) {
} }
const deleteExtra = require('../visitor/delete-extra') const deleteExtra = require('../visitor/delete-extra')
traverse(ast, deleteExtra) traverse(ast, deleteExtra)
const calculateBinary = require('../visitor/calculate-binary') const calculateConstantExp = require('../visitor/calculate-constant-exp')
traverse(ast, calculateBinary) traverse(ast, calculateConstantExp)
const calculateRString = require('../visitor/calculate-rstring') const calculateRString = require('../visitor/calculate-rstring')
traverse(ast, calculateRString) traverse(ast, calculateRString)
code = generator(ast).code code = generator(ast).code

View File

@@ -9,6 +9,7 @@ const traverse = require('@babel/traverse').default
const t = require('@babel/types') const t = require('@babel/types')
const ivm = require('isolated-vm') const ivm = require('isolated-vm')
const PluginEval = require('./eval.js') const PluginEval = require('./eval.js')
const calculateConstantExp = require('../visitor/calculate-constant-exp')
const isolate = new ivm.Isolate() const isolate = new ivm.Isolate()
const globalContext = isolate.createContextSync() const globalContext = isolate.createContextSync()
@@ -16,6 +17,12 @@ function virtualGlobalEval(jsStr) {
return globalContext.evalSync(String(jsStr)) return globalContext.evalSync(String(jsStr))
} }
const optGenMin = {
comments: false,
minified: true,
jsescOption: { minimal: true },
}
/** /**
* Extract the literal value of an object, and remove an object if all * Extract the literal value of an object, and remove an object if all
* references to the object are replaced. * references to the object are replaced.
@@ -77,6 +84,110 @@ function decodeObject(ast) {
return ast return ast
} }
/**
* If the StringArrayRotateFunction does not exist, we can only verify a
* string-array by checking the StringArrayCallsWrapper.
*
* @param {t.File} ast The ast file
* @returns Object
*/
function stringArrayV0(ast) {
console.info('Try v0 mode...')
function check_wrapper(ref, array_name) {
ref = ref.parentPath
const index = ref.node.property?.name
if (ref.key !== 'init') {
return null
}
ref = ref.parentPath
const value = ref.node.id?.name
ref = ref.getFunctionParent()
if (!index || ref.node.params?.[0]?.name !== index) {
return null
}
const container = ref.node.body.body
const ret_node = container[container.length - 1]
if (!t.isReturnStatement(ret_node) || ret_node.argument?.name !== value) {
return null
}
if (ref.key !== 'init') {
return null
}
const rm_path = ref.parentPath
if (array_name == rm_path.node.id.name) {
return null
}
return rm_path
}
// check if all the binding references are wrapper
function check_other_function(path, array_name) {
const binding = path.scope.getBinding(array_name)
if (!binding.referencePaths) {
return
}
const ob_func_str = []
const ob_dec_call = []
for (const ref of binding.referencePaths) {
if (ref.findParent((path) => path.removed)) {
continue
}
if (ref.parentPath.isMemberExpression() && ref.key === 'object') {
const rm_path = check_wrapper(ref, array_name)
if (!rm_path) {
console.error('Unexpected reference')
return null
}
const code = generator(rm_path.node, optGenMin).code
rm_path.get('body').replaceWith(t.blockStatement([]))
ob_func_str.push(code)
ob_dec_call.push({ name: rm_path.node.id.name, path: rm_path })
} else {
console.error('Unexpected reference')
return null
}
}
if (!ob_func_str.length) {
return null
}
ob_func_str.push(generator(path.node, optGenMin).code)
path.remove()
return {
version: 0,
stringArrayName: array_name,
stringArrayCodes: ob_func_str,
stringArrayCalls: ob_dec_call,
}
}
let ret_obj = {
version: 0,
stringArrayName: null,
stringArrayCodes: [],
stringArrayCalls: [],
}
function check_string_array(path) {
if (path.getFunctionParent()) {
return
}
const init = path.get('init')
if (!init.isArrayExpression()) {
return
}
if (!init.node.elements.length) {
return
}
const array_name = path.node.id.name
const obj = check_other_function(path, array_name)
if (obj) {
ret_obj = obj
path.stop()
}
}
traverse(ast, {
VariableDeclarator: check_string_array,
})
return ret_obj
}
/** /**
* Before version 2.19.0, the string-array is a single array. * Before version 2.19.0, the string-array is a single array.
* Hence, we have to find StringArrayRotateFunction instead. * Hence, we have to find StringArrayRotateFunction instead.
@@ -119,7 +230,7 @@ function stringArrayV2(ast) {
// The string array can be found by its binding // The string array can be found by its binding
const bind = path.scope.getBinding(obj.stringArrayName) const bind = path.scope.getBinding(obj.stringArrayName)
const def = t.variableDeclaration('var', [bind.path.node]) const def = t.variableDeclaration('var', [bind.path.node])
obj.stringArrayCodes.push(generator(def, { minified: true }).code) obj.stringArrayCodes.push(generator(def, optGenMin).code)
// The calls can be found by its references // The calls can be found by its references
for (let ref of bind.referencePaths) { for (let ref of bind.referencePaths) {
if (ref?.listKey === 'arguments') { if (ref?.listKey === 'arguments') {
@@ -134,19 +245,19 @@ function stringArrayV2(ast) {
if (up1.node.id) { if (up1.node.id) {
// 2.12.0 <= v < 2.15.4 // 2.12.0 <= v < 2.15.4
// The `stringArrayCallsWrapperName` is included in the definition // The `stringArrayCallsWrapperName` is included in the definition
obj.stringArrayCalls.push(up1.node.id.name) obj.stringArrayCodes.push(generator(up1.node, optGenMin).code)
obj.stringArrayCodes.push(generator(up1.node, { minified: true }).code) up1.node.body = t.blockStatement([])
up1.remove() obj.stringArrayCalls.push({ name: up1.node.id.name, path: up1 })
continue continue
} }
if (up1.key === 'init') { if (up1.key === 'init') {
// v < 2.12.0 // v < 2.12.0
// The `stringArrayCallsWrapperName` is defined by VariableDeclarator // The `stringArrayCallsWrapperName` is defined by VariableDeclarator
up1 = up1.parentPath up1 = up1.parentPath
obj.stringArrayCalls.push(up1.node.id.name) const node = t.variableDeclaration('var', [up1.node])
up1 = up1.parentPath obj.stringArrayCodes.push(generator(node, optGenMin).code)
obj.stringArrayCodes.push(generator(up1.node, { minified: true }).code) up1.node.init = null
up1.remove() obj.stringArrayCalls.push({ name: up1.node.id.name, path: up1 })
continue continue
} }
// 2.15.4 <= v < 2.19.0 // 2.15.4 <= v < 2.19.0
@@ -158,15 +269,16 @@ function stringArrayV2(ast) {
console.warn('Unexpected reference!') console.warn('Unexpected reference!')
continue continue
} }
obj.stringArrayCalls.push(wrapper) obj.stringArrayCodes.push(generator(up2.node, optGenMin).code)
obj.stringArrayCodes.push(generator(up2.node, { minified: true }).code) up1.remove()
up2.remove() up2.node.body = t.blockStatement([])
obj.stringArrayCalls.push({ name: wrapper, path: up2 })
} }
// Remove the string array // Remove the string array
bind.path.remove() bind.path.remove()
// Add the rotate function // Add the rotate function
const node = t.expressionStatement(path.node) const node = t.expressionStatement(path.node)
obj.stringArrayCodes.push(generator(node, { minified: true }).code) obj.stringArrayCodes.push(generator(node, optGenMin).code)
path.stop() path.stop()
if (path.parentPath.isUnaryExpression()) { if (path.parentPath.isUnaryExpression()) {
path.parentPath.remove() path.parentPath.remove()
@@ -232,7 +344,7 @@ function stringArrayV3(ast) {
} }
const nodes = [...body] const nodes = [...body]
nodes.shift() nodes.shift()
const code = generator(t.BlockStatement(nodes)).code const code = generator(t.BlockStatement(nodes), optGenMin).code
const fp = `${name_func}=function(){return${string_var}}${name_func}()` const fp = `${name_func}=function(){return${string_var}}${name_func}()`
if (!checkPattern(code, fp)) { if (!checkPattern(code, fp)) {
return return
@@ -274,8 +386,9 @@ function stringArrayV3(ast) {
if (name_func == rm_path.node.id.name) { if (name_func == rm_path.node.id.name) {
return return
} }
nodes.push([rm_path.node, 'func3']) const code = generator(rm_path.node, optGenMin).code
rm_path.remove() rm_path.node.body = t.blockStatement([])
nodes.push([code, 'func3', rm_path])
} else { } else {
console.error('Unexpected reference') console.error('Unexpected reference')
} }
@@ -285,16 +398,18 @@ function stringArrayV3(ast) {
return return
} }
ob_string_func_name = name_func ob_string_func_name = name_func
ob_func_str.push(generator(path.node, { minified: true }).code) ob_func_str.push(generator(path.node, optGenMin).code)
nodes.map(function (item) { nodes.map(function (item) {
let node = item[0]
if (item[1] == 'func3') { if (item[1] == 'func3') {
ob_dec_name.push(node.id.name) ob_func_str.push(item[0])
ob_dec_name.push({ name: item[2].node.id.name, path: item[2] })
return
} }
let node = item[0]
if (t.isCallExpression(node)) { if (t.isCallExpression(node)) {
node = t.expressionStatement(node) node = t.expressionStatement(node)
} }
ob_func_str.push(generator(node, { minified: true }).code) ob_func_str.push(generator(node, optGenMin).code)
}) })
path.stop() path.stop()
path.remove() path.remove()
@@ -312,14 +427,17 @@ function decodeGlobal(ast) {
let obj = stringArrayV3(ast) let obj = stringArrayV3(ast)
if (!obj.stringArrayName) { if (!obj.stringArrayName) {
obj = stringArrayV2(ast) obj = stringArrayV2(ast)
if (!obj.stringArrayName) { }
console.error('Cannot find string list!') if (!obj.stringArrayName) {
return false obj = stringArrayV0(ast)
} }
if (!obj.stringArrayName) {
console.error('Cannot find string list!')
return false
} }
console.log(`String List Name: ${obj.stringArrayName}`) console.log(`String List Name: ${obj.stringArrayName}`)
let ob_func_str = obj.stringArrayCodes let ob_func_str = obj.stringArrayCodes
let ob_dec_name = obj.stringArrayCalls let ob_dec_call = obj.stringArrayCalls
try { try {
virtualGlobalEval(ob_func_str.join(';')) virtualGlobalEval(ob_func_str.join(';'))
} catch (e) { } catch (e) {
@@ -328,11 +446,11 @@ function decodeGlobal(ast) {
let lost = e.message.split(' ')[0] let lost = e.message.split(' ')[0]
traverse(ast, { traverse(ast, {
Program(path) { Program(path) {
ob_dec_name.push(lost)
let loc = path.scope.getBinding(lost).path let loc = path.scope.getBinding(lost).path
let obj = t.variableDeclaration(loc.parent.kind, [loc.node]) let obj = t.variableDeclaration(loc.parent.kind, [loc.node])
ob_func_str.unshift(generator(obj, { minified: true }).code) ob_func_str.unshift(generator(obj, optGenMin).code)
loc.remove() loc.node.init = null
ob_dec_call.push({ name: lost, path: loc })
path.stop() path.stop()
}, },
}) })
@@ -340,125 +458,111 @@ function decodeGlobal(ast) {
} }
} }
// 循环删除混淆函数 // 递归删除混淆函数
let call_dict = {} function getChild(father) {
let exist_names = ob_dec_name if (father.key !== 'argument' || !father.parentPath.isReturnStatement()) {
let collect_codes = [] console.error(`Unexpected chained call: ${father}`)
let collect_names = [] return null
function do_parse_value(path) { }
let name = path.node.callee.name const func = father.getFunctionParent()
if (path.node.callee && exist_names.indexOf(name) != -1) { let name = func.node.id?.name
let old_call = path + '' let root
try { let code
// 运行成功则说明函数为直接调用并返回字符串 if (name) {
let new_str = virtualGlobalEval(old_call) // FunctionDeclaration
console.log(`map: ${old_call} -> ${new_str}`) // function A (...) { return function B (...) }
call_dict[old_call] = new_str root = func
} catch (e) { code = generator(root.node, optGenMin).code
// 运行失败则说明函数为其它混淆函数的子函数 } else {
console.log(`sub: ${old_call}`) // FunctionExpression
// var A = function (...) { return function B (...) }
root = func.parentPath
code = generator(t.variableDeclaration('var', [root])).code
name = root.node.id.name
}
return {
name: name,
path: root,
code: code,
}
}
function dfs(stk, item) {
stk.push(item)
const cur_val = item.name
console.log(`Enter sub ${stk.length}:${cur_val}`)
let pfx = ''
for (let parent of stk) {
pfx += parent.code + ';'
}
virtualGlobalEval(pfx)
let scope = item.path.scope
if (item.path.isFunctionDeclaration()) {
scope = item.path.parentPath.scope
}
const binding = scope.getBinding(cur_val)
binding.scope.crawl()
const refs = binding.scope.bindings[cur_val].referencePaths
const refs_next = []
// 有4种链式调用情况
// - VariableDeclarator和FunctionDeclaration为原版
// - AssignmentExpression 出现于 #50
// - FunctionExpression 出现于 #94
for (let ref of refs) {
const parent = ref.parentPath
if (ref.key === 'callee') {
// CallExpression
let old_call = parent + ''
try {
// 运行成功则说明函数为直接调用并返回字符串
let new_str = virtualGlobalEval(old_call)
console.log(`map: ${old_call} -> ${new_str}`)
parent.replaceWith(t.StringLiteral(new_str))
} catch (e) {
// 运行失败则说明函数为其它混淆函数的子函数
console.log(`sub: ${old_call}`)
const ret = getChild(parent)
if (ret) {
refs_next.push(ret)
}
}
} else if (ref.key === 'init') {
// VariableDeclarator
refs_next.push({
name: ref.parent.id.name,
path: ref.parentPath,
code: 'var ' + ref.parentPath,
})
} else if (ref.key === 'right') {
// AssignmentExpression
// 这种情况尚不完善 可能会产生额外替换
refs_next.push({
name: ref.parent.left.name,
path: ref.parentPath,
code: 'var ' + ref.parentPath,
})
} }
} }
} for (let ref of refs_next) {
function do_collect_remove(path) { dfs(stk, ref)
// 可以删除所有已收集混淆函数的定义
// 因为根函数已被删除 即使保留也无法运行
let node = path.node?.left
if (!node) {
node = path.node?.id
} }
let name = node?.name binding.scope.crawl()
if (exist_names.indexOf(name) != -1) { console.log(`Exit sub ${stk.length}:${cur_val}`)
// console.log(`del: ${name}`) stk.pop()
if (path.parentPath.isCallExpression()) { if (!item.path.parentPath.isCallExpression()) {
path.replaceWith(node) item.path.remove()
} else { binding.scope.crawl()
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 return
} }
do_collect_func(path, func_path) // 只会出现在AssignmentExpression情况下 需要再次运行
item.path.replaceWith(t.identifier(cur_val))
item.path = binding.path
binding.scope.crawl()
dfs(stk, item)
} }
function do_collect_func(root, path) { for (let item of ob_dec_call) {
if ( item.code = ''
path.node.body.body.length == 1 && dfs([], item)
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 return true
} }
@@ -501,35 +605,9 @@ function stringArrayLite(ast) {
traverse(ast, visitor) 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) { function decodeCodeBlock(ast) {
// 合并字面量 // 合并字面量
traverse(ast, { BinaryExpression: { exit: calcBinary } }) traverse(ast, calculateConstantExp)
// 先合并分离的Object定义 // 先合并分离的Object定义
const mergeObject = require('../visitor/merge-object') const mergeObject = require('../visitor/merge-object')
traverse(ast, mergeObject) traverse(ast, mergeObject)
@@ -537,69 +615,10 @@ function decodeCodeBlock(ast) {
const parseControlFlowStorage = require('../visitor/parse-control-flow-storage') const parseControlFlowStorage = require('../visitor/parse-control-flow-storage')
traverse(ast, parseControlFlowStorage) traverse(ast, parseControlFlowStorage)
// 合并字面量(在解除区域混淆后会出现新的可合并分割) // 合并字面量(在解除区域混淆后会出现新的可合并分割)
traverse(ast, { BinaryExpression: { exit: calcBinary } }) traverse(ast, calculateConstantExp)
return ast 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) { function cleanSwitchCode(path) {
// 扁平控制: // 扁平控制:
// 会使用一个恒为true的while语句包裹一个switch语句 // 会使用一个恒为true的while语句包裹一个switch语句
@@ -699,9 +718,9 @@ function cleanSwitchCode(path) {
} }
function cleanDeadCode(ast) { function cleanDeadCode(ast) {
traverse(ast, { UnaryExpression: purifyBoolean }) traverse(ast, calculateConstantExp)
traverse(ast, { IfStatement: cleanIFCode }) const pruneIfBranch = require('../visitor/prune-if-branch')
traverse(ast, { ConditionalExpression: cleanIFCode }) traverse(ast, pruneIfBranch)
traverse(ast, { WhileStatement: { exit: cleanSwitchCode } }) traverse(ast, { WhileStatement: { exit: cleanSwitchCode } })
return ast return ast
} }
@@ -763,29 +782,6 @@ function standardLoop(path) {
} }
} }
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) { function purifyCode(ast) {
// 标准化if语句 // 标准化if语句
traverse(ast, { IfStatement: standardIfStatement }) traverse(ast, { IfStatement: standardIfStatement })
@@ -848,7 +844,8 @@ function purifyCode(ast) {
}) })
// 拆分语句 // 拆分语句
traverse(ast, { SequenceExpression: splitSequence }) const splitSequence = require('../visitor/split-sequence')
traverse(ast, splitSequence)
return ast return ast
} }
@@ -1061,6 +1058,12 @@ module.exports = function (code) {
// IllegalReturn // IllegalReturn
const deleteIllegalReturn = require('../visitor/delete-illegal-return') const deleteIllegalReturn = require('../visitor/delete-illegal-return')
traverse(ast, deleteIllegalReturn) traverse(ast, deleteIllegalReturn)
// Lint before split statements
const lintIfStatement = require('../visitor/lint-if-statement')
traverse(ast, lintIfStatement)
// Split declarations to avoid bugs
const splitVarDeclaration = require('../visitor/split-variable-declaration')
traverse(ast, splitVarDeclaration)
// 清理二进制显示内容 // 清理二进制显示内容
traverse(ast, { traverse(ast, {
StringLiteral: ({ node }) => { StringLiteral: ({ node }) => {
@@ -1086,22 +1089,13 @@ module.exports = function (code) {
console.log('清理死代码...') console.log('清理死代码...')
ast = cleanDeadCode(ast) ast = cleanDeadCode(ast)
// 刷新代码 // 刷新代码
ast = parse( ast = parse(generator(ast, optGenMin).code, { errorRecovery: true })
generator(ast, {
comments: false,
jsescOption: { minimal: true },
}).code,
{ errorRecovery: true }
)
console.log('提高代码可读性...') console.log('提高代码可读性...')
ast = purifyCode(ast) ast = purifyCode(ast)
console.log('解除环境限制...') console.log('解除环境限制...')
ast = unlockEnv(ast) ast = unlockEnv(ast)
console.log('净化完成') console.log('净化完成')
code = generator(ast, { code = generator(ast, { jsescOption: { minimal: true } }).code
comments: false,
jsescOption: { minimal: true },
}).code
if (global_eval) { if (global_eval) {
code = PluginEval.pack(code) code = PluginEval.pack(code)
} }

File diff suppressed because it is too large Load Diff

View File

@@ -7,6 +7,7 @@ const traverse = require('@babel/traverse').default
const t = require('@babel/types') const t = require('@babel/types')
const ivm = require('isolated-vm') const ivm = require('isolated-vm')
const PluginEval = require('./eval.js') const PluginEval = require('./eval.js')
const calculateConstantExp = require('../visitor/calculate-constant-exp')
const isolate = new ivm.Isolate() const isolate = new ivm.Isolate()
const globalContext = isolate.createContextSync() const globalContext = isolate.createContextSync()
@@ -85,65 +86,6 @@ function decodeGlobal(ast) {
return ast 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) { function cleanSwitchCode(path) {
// 扁平控制: // 扁平控制:
// 会使用一个恒为true的while语句包裹一个switch语句 // 会使用一个恒为true的while语句包裹一个switch语句
@@ -223,9 +165,9 @@ function cleanSwitchCode(path) {
} }
function cleanDeadCode(ast) { function cleanDeadCode(ast) {
traverse(ast, { UnaryExpression: purifyBoolean }) traverse(ast, calculateConstantExp)
traverse(ast, { IfStatement: cleanIFCode }) const pruneIfBranch = require('../visitor/prune-if-branch')
traverse(ast, { ConditionalExpression: cleanIFCode }) traverse(ast, pruneIfBranch)
traverse(ast, { WhileStatement: { exit: cleanSwitchCode } }) traverse(ast, { WhileStatement: { exit: cleanSwitchCode } })
return ast return ast
} }
@@ -502,54 +444,8 @@ function purifyFunction(path) {
function purifyCode(ast) { function purifyCode(ast) {
// 净化拼接字符串的函数 // 净化拼接字符串的函数
traverse(ast, { AssignmentExpression: purifyFunction }) traverse(ast, { AssignmentExpression: purifyFunction })
// 净化变量定义中的常量数值 // 计算常量表达式
function purifyDecl(path) { traverse(ast, calculateConstantExp)
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) { function FormatMember(path) {
// _0x19882c['removeCookie']['toString']() // _0x19882c['removeCookie']['toString']()
@@ -573,26 +469,8 @@ function purifyCode(ast) {
} }
traverse(ast, { MemberExpression: FormatMember }) traverse(ast, { MemberExpression: FormatMember })
// 分割表达式 // 分割表达式
function removeComma(path) { const splitSequence = require('../visitor/split-sequence')
// a = 1, b = ddd(), c = null; traverse(ast, splitSequence)
// |
// |
// |
// 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, { traverse(ast, {
EmptyStatement: (path) => { EmptyStatement: (path) => {

View File

@@ -7,12 +7,19 @@ const traverse = require('@babel/traverse').default
const t = require('@babel/types') const t = require('@babel/types')
const ivm = require('isolated-vm') const ivm = require('isolated-vm')
const PluginEval = require('./eval.js') const PluginEval = require('./eval.js')
const calculateConstantExp = require('../visitor/calculate-constant-exp')
const isolate = new ivm.Isolate() const isolate = new ivm.Isolate()
const globalContext = isolate.createContextSync() const globalContext = isolate.createContextSync()
function virtualGlobalEval(jsStr) { function virtualGlobalEval(jsStr) {
return globalContext.evalSync(String(jsStr)) return globalContext.evalSync(String(jsStr))
} }
function evalOneTime(str) {
const vm = new ivm.Isolate()
const ret = vm.createContextSync().evalSync(String(str))
vm.dispose()
return ret
}
function decodeGlobal(ast) { function decodeGlobal(ast) {
// 清理空语句 // 清理空语句
@@ -313,65 +320,6 @@ function decodeGlobal(ast) {
return ast 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) { function cleanSwitchCode1(path) {
// 扁平控制: // 扁平控制:
// 会使用一个恒为true的while语句包裹一个switch语句 // 会使用一个恒为true的while语句包裹一个switch语句
@@ -482,12 +430,13 @@ function cleanSwitchCode2(path) {
} }
let test = '' + pre_path let test = '' + pre_path
try { try {
arr = eval(test + `;${arrName}`) arr = evalOneTime(test + `;${arrName}.join('|')`)
arr = arr.split('|')
} catch { } catch {
// //
} }
} }
if (!arr) { if (!Array.isArray(arr)) {
return return
} }
console.log(`扁平化还原: ${arrName}[${argName}]`) console.log(`扁平化还原: ${arrName}[${argName}]`)
@@ -527,9 +476,9 @@ function cleanSwitchCode2(path) {
} }
function cleanDeadCode(ast) { function cleanDeadCode(ast) {
traverse(ast, { UnaryExpression: purifyBoolean }) traverse(ast, calculateConstantExp)
traverse(ast, { IfStatement: cleanIFCode }) const pruneIfBranch = require('../visitor/prune-if-branch')
traverse(ast, { ConditionalExpression: cleanIFCode }) traverse(ast, pruneIfBranch)
traverse(ast, { WhileStatement: { exit: cleanSwitchCode1 } }) traverse(ast, { WhileStatement: { exit: cleanSwitchCode1 } })
traverse(ast, { ForStatement: { exit: cleanSwitchCode2 } }) traverse(ast, { ForStatement: { exit: cleanSwitchCode2 } })
return ast return ast
@@ -748,54 +697,8 @@ function purifyFunction(path) {
function purifyCode(ast) { function purifyCode(ast) {
// 净化拼接字符串的函数 // 净化拼接字符串的函数
traverse(ast, { AssignmentExpression: purifyFunction }) traverse(ast, { AssignmentExpression: purifyFunction })
// 净化变量定义中的常量数值 // 计算常量表达式
function purifyDecl(path) { traverse(ast, calculateConstantExp)
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) { function FormatMember(path) {
// _0x19882c['removeCookie']['toString']() // _0x19882c['removeCookie']['toString']()
@@ -819,28 +722,8 @@ function purifyCode(ast) {
} }
traverse(ast, { MemberExpression: FormatMember }) traverse(ast, { MemberExpression: FormatMember })
// 分割表达式 // 分割表达式
function removeComma(path) { const splitSequence = require('../visitor/split-sequence')
// a = 1, b = ddd(), c = null; traverse(ast, splitSequence)
// |
// |
// |
// 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, { traverse(ast, {
EmptyStatement: (path) => { EmptyStatement: (path) => {

View File

@@ -1,24 +0,0 @@
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 {
//
}
},
}

View File

@@ -0,0 +1,107 @@
const generator = require('@babel/generator').default
const t = require('@babel/types')
function checkLiteral(node) {
if (t.isNumericLiteral(node)) {
return 'positive'
}
if (t.isLiteral(node)) {
return 'literal'
}
if (!t.isUnaryExpression(node)) {
return false
}
if (!t.isNumericLiteral(node.argument)) {
return false
}
if (node.operator === '-') {
return 'negative'
}
return false
}
/**
* Calculate BinaryExpression if left and right are both literals.
* Otherwise, the expression can't be simplified.
* Note that negative numbers are UnaryExpressions.
*/
function calculateBinaryExpression(path) {
const { left, right } = path.node
if (!checkLiteral(left) || !checkLiteral(right)) {
return
}
const code = generator(path.node).code
try {
const ret = eval(code)
// The strings cannot use replaceWithSourceString
// For example, string "ab" will be parsed as identifier ab
if (typeof ret === 'string') {
path.replaceWith(t.stringLiteral(ret))
} else {
path.replaceWithSourceString(ret)
}
} catch {
//
}
}
/**
* Calculate UnaryExpression:
* - the operator is `!` and the argument is ArrayExpression or Literal.
* - the operator is `-` and the argument is a negative number
* - the operator is `+`, or `~`, and the argument is a number
* - the operator is 'void' and the argument is Literal.
* - the operator is 'typeof' and the argument is Literal.
*
* Otherwise, the expression can't be simplified.
* For example, `typeof window` can be calculated but it's not constant.
*/
function calculateUnaryExpression(path) {
const node0 = path.node
const node1 = node0.argument
const isLiteral = checkLiteral(node1)
if (node0.operator === '!') {
if (t.isArrayExpression(node1)) {
if (node1.elements.length === 0) {
path.replaceWith(t.booleanLiteral(false))
}
}
if (isLiteral) {
const code = generator(node0).code
path.replaceWith(t.booleanLiteral(eval(code)))
}
return
}
if (node0.operator === '-') {
if (isLiteral === 'negative') {
const code = generator(node0).code
path.replaceWithSourceString(eval(code))
}
return
}
if (node0.operator === '+' || node0.operator === '~') {
if (isLiteral === 'negative' || isLiteral === 'positive') {
const code = generator(node0).code
path.replaceWithSourceString(eval(code))
}
return
}
if (node0.operator === 'void') {
if (isLiteral) {
path.replaceWith(t.identifier('undefined'))
}
return
}
if (node0.operator === 'typeof') {
if (isLiteral) {
const code = generator(node0).code
path.replaceWith(t.stringLiteral(eval(code)))
}
return
}
}
module.exports = {
BinaryExpression: { exit: calculateBinaryExpression },
UnaryExpression: { exit: calculateUnaryExpression },
}

View File

@@ -15,6 +15,9 @@ module.exports = {
if (!binding || binding.referenced || !binding.constant) { if (!binding || binding.referenced || !binding.constant) {
return return
} }
if (!t.isLiteral(node.init)) {
return
}
const up1 = path.parentPath const up1 = path.parentPath
const up2 = up1?.parentPath const up2 = up1?.parentPath
if (t.isForOfStatement(up2)) { if (t.isForOfStatement(up2)) {

View File

@@ -0,0 +1,22 @@
const t = require('@babel/types')
function LintIfStatement(path) {
let { test, consequent, alternate } = path.node
let changed = false
if (!t.isBlockStatement(consequent)) {
consequent = t.blockStatement([consequent])
changed = true
}
if (alternate && !t.isBlockStatement(alternate)) {
alternate = t.blockStatement([alternate])
changed = true
}
if (!changed) {
return
}
path.replaceWith(t.ifStatement(test, consequent, alternate))
}
module.exports = {
IfStatement: { exit: LintIfStatement },
}

View File

@@ -162,7 +162,7 @@ function mergeObject(path) {
} else { } else {
path.remove() path.remove()
} }
scope.crawl() binding.scope.crawl()
} }
/** /**

View File

@@ -0,0 +1,40 @@
function pruneIfBranch(path) {
function clear(path, toggle) {
// 判定成立
if (toggle) {
path.replaceWith(path.node.consequent)
return
}
// 判定不成立
if (!path.node.alternate) {
path.remove()
return
}
path.replaceWith(path.node.alternate)
}
// 判断判定是否恒定
const test = path.node.test
const types = ['StringLiteral', 'NumericLiteral', 'BooleanLiteral']
if (test.type === 'BinaryExpression') {
if (
types.indexOf(test.left.type) !== -1 &&
types.indexOf(test.right.type) !== -1
) {
const left = JSON.stringify(test.left.value)
const right = JSON.stringify(test.right.value)
clear(path, eval(left + test.operator + right))
}
} else if (types.indexOf(test.type) !== -1) {
clear(path, eval(JSON.stringify(test.value)))
}
}
/**
* Prune the branch if the test is constant
*
* The code must be reloaded to update the references
*/
module.exports = {
IfStatement: pruneIfBranch,
ConditionalExpression: pruneIfBranch,
}

View File

@@ -0,0 +1,38 @@
function splitMemberObject(path) {
const object = path.get('object')
if (!object.isAssignmentExpression()) {
return
}
let insertPath = path
while (!insertPath?.listKey) {
if (insertPath.parentPath.isAssignmentExpression()) {
insertPath = insertPath.parentPath
continue
}
if (insertPath.parentPath.isExpressionStatement()) {
insertPath = insertPath.parentPath
continue
}
return
}
insertPath.insertBefore(object.node)
object.replaceWith(object.node.left)
insertPath.scope.crawl()
}
/**
* Split assignment operation in member object
*
* From:
* ```javascript
* (a = {})['b'] = c;
* ```
* To:
* ```javascript
* a = {}
* a['b'] = c;
* ```
*/
module.exports = {
MemberExpression: splitMemberObject,
}

View File

@@ -0,0 +1,53 @@
const t = require('@babel/types')
function doSplit(insertPath, path) {
const expressions = path.node.expressions
const lastExpression = expressions.pop()
while (expressions.length) {
insertPath.insertBefore(t.expressionStatement(expressions.shift()))
}
path.replaceWith(lastExpression)
insertPath.scope.crawl()
}
function splitSequence(path) {
let { parentPath } = path
if (parentPath.isVariableDeclarator()) {
// Skip if it's not the first VariableDeclarator
if (parentPath.key !== 0) {
return
}
let insertPath = parentPath.parentPath
// Skip if the container of the VariableDeclaration is not an array
if (!insertPath.listKey) {
return
}
doSplit(insertPath, path)
return
}
if (parentPath.isReturnStatement()) {
if (!parentPath.listKey) {
return
}
doSplit(parentPath, path)
return
}
if (parentPath.isExpressionStatement()) {
if (!parentPath.listKey) {
return
}
doSplit(parentPath, path)
return
}
}
/**
* The sequenceExpressions inside certain statements are splitted if possible:
*
* - VariableDeclarator
* - ReturnStatement
* - ExpressionStatement
*/
module.exports = {
SequenceExpression: splitSequence,
}

View File

@@ -0,0 +1,31 @@
const t = require('@babel/types')
function splitVariableDeclaration(path) {
// The scope of a for statement is its body
if (path.parentPath.isFor()) {
return
}
// The container must be an array
if (!path.listKey) {
return
}
const kind = path.node.kind
const list = path.node.declarations
if (list.length == 1) {
return
}
for (let item of list) {
path.insertBefore(t.variableDeclaration(kind, [item]))
}
path.remove()
path.scope.crawl()
}
/**
* Split the VariableDeclaration if it has more than one VariableDeclarator
*
* This operation will only be performed when its container is an array
*/
module.exports = {
VariableDeclaration: splitVariableDeclaration,
}