diff --git a/src/main.js b/src/main.js index e641d0cb5..4749c2ac9 100644 --- a/src/main.js +++ b/src/main.js @@ -5,7 +5,6 @@ const PluginSojson = require('./plugin/sojson.js') const PluginSojsonV7 = require('./plugin/sojsonv7.js') const PluginObfuscator = require('./plugin/obfuscator.js') const PluginAwsc = require('./plugin/awsc.js') -const PluginObfuscator2 = require('./plugin/obfuscator2.js') // 读取参数 let encodeFile = 'input.js' @@ -29,8 +28,7 @@ let pluginUsed = ''; // 循环尝试不同的插件,直到源代码与处理后的代码不一致 const plugins = [ - { name: 'obfuscator', plugin: PluginObfuscator2 }, - { name: "ob2", plugin: PluginObfuscator }, + { name: 'obfuscator', plugin: PluginObfuscator }, { name: 'sojsonv7', plugin: PluginSojsonV7 }, { name: 'sojson', plugin: PluginSojson }, { name: 'common', plugin: PluginCommon },// 最后一次使用通用插件 diff --git a/src/plugin/common.js b/src/plugin/common.js index d48bf7208..471657b3e 100644 --- a/src/plugin/common.js +++ b/src/plugin/common.js @@ -12,8 +12,8 @@ module.exports = function (code) { } const deleteExtra = require('../visitor/delete-extra') traverse(ast, deleteExtra) - const calculateBinary = require('../visitor/calculate-binary') - traverse(ast, calculateBinary) + const calculateConstantExp = require('../visitor/calculate-constant-exp') + traverse(ast, calculateConstantExp) const calculateRString = require('../visitor/calculate-rstring') traverse(ast, calculateRString) code = generator(ast).code diff --git a/src/plugin/obfuscator.js b/src/plugin/obfuscator.js index 1a73ad4a7..44131b66a 100644 --- a/src/plugin/obfuscator.js +++ b/src/plugin/obfuscator.js @@ -9,6 +9,7 @@ const traverse = require('@babel/traverse').default const t = require('@babel/types') const ivm = require('isolated-vm') const PluginEval = require('./eval.js') +const calculateConstantExp = require('../visitor/calculate-constant-exp') const isolate = new ivm.Isolate() const globalContext = isolate.createContextSync() @@ -16,6 +17,12 @@ function virtualGlobalEval(jsStr) { return globalContext.evalSync(String(jsStr)) } +const optGenMin = { + comments: false, + minified: true, + jsescOption: { minimal: true }, +} + /** * Extract the literal value of an object, and remove an object if all * references to the object are replaced. @@ -77,6 +84,110 @@ function decodeObject(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. * Hence, we have to find StringArrayRotateFunction instead. @@ -119,7 +230,7 @@ function stringArrayV2(ast) { // 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) + obj.stringArrayCodes.push(generator(def, optGenMin).code) // The calls can be found by its references for (let ref of bind.referencePaths) { if (ref?.listKey === 'arguments') { @@ -134,19 +245,19 @@ function stringArrayV2(ast) { 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() + obj.stringArrayCodes.push(generator(up1.node, optGenMin).code) + up1.node.body = t.blockStatement([]) + obj.stringArrayCalls.push({ name: up1.node.id.name, path: up1 }) continue } if (up1.key === 'init') { // v < 2.12.0 // The `stringArrayCallsWrapperName` is defined by VariableDeclarator up1 = up1.parentPath - obj.stringArrayCalls.push(up1.node.id.name) - up1 = up1.parentPath - obj.stringArrayCodes.push(generator(up1.node, { minified: true }).code) - up1.remove() + const node = t.variableDeclaration('var', [up1.node]) + obj.stringArrayCodes.push(generator(node, optGenMin).code) + up1.node.init = null + obj.stringArrayCalls.push({ name: up1.node.id.name, path: up1 }) continue } // 2.15.4 <= v < 2.19.0 @@ -158,15 +269,16 @@ function stringArrayV2(ast) { console.warn('Unexpected reference!') continue } - obj.stringArrayCalls.push(wrapper) - obj.stringArrayCodes.push(generator(up2.node, { minified: true }).code) - up2.remove() + obj.stringArrayCodes.push(generator(up2.node, optGenMin).code) + up1.remove() + up2.node.body = t.blockStatement([]) + obj.stringArrayCalls.push({ name: wrapper, path: up2 }) } // Remove the string array bind.path.remove() // Add the rotate function const node = t.expressionStatement(path.node) - obj.stringArrayCodes.push(generator(node, { minified: true }).code) + obj.stringArrayCodes.push(generator(node, optGenMin).code) path.stop() if (path.parentPath.isUnaryExpression()) { path.parentPath.remove() @@ -232,7 +344,7 @@ function stringArrayV3(ast) { } const nodes = [...body] 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}()` if (!checkPattern(code, fp)) { return @@ -274,8 +386,9 @@ function stringArrayV3(ast) { if (name_func == rm_path.node.id.name) { return } - nodes.push([rm_path.node, 'func3']) - rm_path.remove() + const code = generator(rm_path.node, optGenMin).code + rm_path.node.body = t.blockStatement([]) + nodes.push([code, 'func3', rm_path]) } else { console.error('Unexpected reference') } @@ -285,16 +398,18 @@ function stringArrayV3(ast) { return } 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) { - let node = item[0] 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)) { node = t.expressionStatement(node) } - ob_func_str.push(generator(node, { minified: true }).code) + ob_func_str.push(generator(node, optGenMin).code) }) path.stop() path.remove() @@ -312,14 +427,17 @@ function decodeGlobal(ast) { let obj = stringArrayV3(ast) if (!obj.stringArrayName) { obj = stringArrayV2(ast) - if (!obj.stringArrayName) { - console.error('Cannot find string list!') - return false - } + } + if (!obj.stringArrayName) { + obj = stringArrayV0(ast) + } + if (!obj.stringArrayName) { + console.error('Cannot find string list!') + return false } console.log(`String List Name: ${obj.stringArrayName}`) let ob_func_str = obj.stringArrayCodes - let ob_dec_name = obj.stringArrayCalls + let ob_dec_call = obj.stringArrayCalls try { virtualGlobalEval(ob_func_str.join(';')) } catch (e) { @@ -328,11 +446,11 @@ function decodeGlobal(ast) { 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() + ob_func_str.unshift(generator(obj, optGenMin).code) + loc.node.init = null + ob_dec_call.push({ name: lost, path: loc }) path.stop() }, }) @@ -340,125 +458,111 @@ function decodeGlobal(ast) { } } - // 循环删除混淆函数 - 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 getChild(father) { + if (father.key !== 'argument' || !father.parentPath.isReturnStatement()) { + console.error(`Unexpected chained call: ${father}`) + return null + } + const func = father.getFunctionParent() + let name = func.node.id?.name + let root + let code + if (name) { + // FunctionDeclaration + // function A (...) { return function B (...) } + root = func + code = generator(root.node, optGenMin).code + } else { + // FunctionExpression + // var A = function (...) { return function B (...) } + root = func.parentPath + code = generator(t.variableDeclaration('var', [root])).code + name = root.node.id.name } - } - 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() - } + return { + name: name, + path: root, + code: code, } } - 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 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, + }) } } - } - 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}`) - } + for (let ref of refs_next) { + dfs(stk, ref) } - } - 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])) + binding.scope.crawl() + console.log(`Exit sub ${stk.length}:${cur_val}`) + stk.pop() + if (!item.path.parentPath.isCallExpression()) { + item.path.remove() + binding.scope.crawl() + return } + // 只会出现在AssignmentExpression情况下 需要再次运行 + item.path.replaceWith(t.identifier(cur_val)) + item.path = binding.path + binding.scope.crawl() + dfs(stk, item) + } + for (let item of ob_dec_call) { + item.code = '' + dfs([], item) } - traverse(ast, { CallExpression: do_replace }) return true } @@ -501,35 +605,9 @@ function stringArrayLite(ast) { 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 } }) + traverse(ast, calculateConstantExp) // 先合并分离的Object定义 const mergeObject = require('../visitor/merge-object') traverse(ast, mergeObject) @@ -537,69 +615,10 @@ function decodeCodeBlock(ast) { const parseControlFlowStorage = require('../visitor/parse-control-flow-storage') traverse(ast, parseControlFlowStorage) // 合并字面量(在解除区域混淆后会出现新的可合并分割) - traverse(ast, { BinaryExpression: { exit: calcBinary } }) + traverse(ast, calculateConstantExp) 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语句 @@ -699,9 +718,9 @@ function cleanSwitchCode(path) { } function cleanDeadCode(ast) { - traverse(ast, { UnaryExpression: purifyBoolean }) - traverse(ast, { IfStatement: cleanIFCode }) - traverse(ast, { ConditionalExpression: cleanIFCode }) + traverse(ast, calculateConstantExp) + const pruneIfBranch = require('../visitor/prune-if-branch') + traverse(ast, pruneIfBranch) traverse(ast, { WhileStatement: { exit: cleanSwitchCode } }) 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) { // 标准化if语句 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 } @@ -1061,6 +1058,12 @@ module.exports = function (code) { // IllegalReturn const deleteIllegalReturn = require('../visitor/delete-illegal-return') 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, { StringLiteral: ({ node }) => { @@ -1086,22 +1089,13 @@ module.exports = function (code) { console.log('清理死代码...') ast = cleanDeadCode(ast) // 刷新代码 - ast = parse( - generator(ast, { - comments: false, - jsescOption: { minimal: true }, - }).code, - { errorRecovery: true } - ) + ast = parse(generator(ast, optGenMin).code, { errorRecovery: true }) console.log('提高代码可读性...') ast = purifyCode(ast) console.log('解除环境限制...') ast = unlockEnv(ast) console.log('净化完成') - code = generator(ast, { - comments: false, - jsescOption: { minimal: true }, - }).code + code = generator(ast, { jsescOption: { minimal: true } }).code if (global_eval) { code = PluginEval.pack(code) } diff --git a/src/plugin/obfuscator2.js b/src/plugin/obfuscator2.js deleted file mode 100644 index 3b89718cd..000000000 --- a/src/plugin/obfuscator2.js +++ /dev/null @@ -1,1405 +0,0 @@ -/** - * 整合自下面两个项目: - * * 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 vm = require('vm') -const { VM } = require('vm2') -const PluginEval = require('./eval.js') - -let globalContext = vm.createContext() -let vm2 = new VM({ - allowAsync: false, - sandbox: globalContext, -}) -function virtualGlobalEval(jsStr) { - return vm2.run(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(path) { - // function A (...) { return function B (...) } - 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 && - path.node.id - ) { - let call_func = path.node.body.body[0].argument.callee.name - if (exist_names.indexOf(call_func) == -1) { - return - } - let name = path.node.id.name - let t = generator(path.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 }) - 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 mergeObject(path) { - // 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; - // | - // | - // | - // v - // 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" - // }; - // - // Note: - // Constant objects in the original code can be splitted - // AssignmentExpression can be moved to ReturnStatement - const { id, init } = path.node - if (!t.isObjectExpression(init)) { - // 判断是否是定义对象 - return - } - let name = id.name - let scope = path.scope - let binding = scope.getBinding(name) - if (!binding || !binding.constant) { - // 确认该对象没有被多次定义 - 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 idx = path.parentPath.key - let cur = 0 - let valid = true - // Check references in sequence - while (cur < binding.references) { - const ref = binding.referencePaths[cur] - 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 - while (bk.parentPath.isExpression()) { - bk = bk.parentPath - } - if (bk.parentPath.isExpressionStatement()) { - bk = bk.parentPath - } - if (bk.parentPath !== container || bk.key - idx > 1) { - break - } - idx = bk.key - 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.isVariableDeclarator() || - ref.parentPath.isAssignmentExpression() - ) { - ref.replaceWith(left) - } else { - if (ref.parentPath.isSequenceExpression() && ref.container.length === 1) { - ref = ref.parentPath - } - ref.remove() - } - } - while (cur < binding.references) { - const ref = binding.referencePaths[cur++] - const up1 = ref.parentPath - if (!up1.isVariableDeclarator()) { - continue - } - let child = up1.node.id.name - if (!up1.scope.bindings[child]?.constant) { - continue - } - up1.scope.rename(child, name, up1.scope.block) - up1.remove() - } - scope.crawl() -} - -function unpackCall(path) { - // 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][...] - // }; - // 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"]; - // | - // | - // | - // v - // var aa = 123 == 456; - // var bb = bcd(11, 22); - // var cc = dcb(); - // var dd = "eee"; - // var ee = A[x][y][...]; - 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() - } -} - -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定义 - traverse(ast, { VariableDeclarator: { exit: mergeObject } }) - // 在变量定义完成后判断是否为代码块加密内容 - traverse(ast, { VariableDeclarator: { exit: unpackCall } }) - // 合并字面量(在解除区域混淆后会出现新的可合并分割) - 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) => { - 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()`, - ] - 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 - } - // DebugProtectionFunctionCall - const up2 = up1.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 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 -} diff --git a/src/plugin/sojson.js b/src/plugin/sojson.js index cf01508b2..9babebc49 100644 --- a/src/plugin/sojson.js +++ b/src/plugin/sojson.js @@ -7,6 +7,7 @@ const traverse = require('@babel/traverse').default const t = require('@babel/types') const ivm = require('isolated-vm') const PluginEval = require('./eval.js') +const calculateConstantExp = require('../visitor/calculate-constant-exp') const isolate = new ivm.Isolate() const globalContext = isolate.createContextSync() @@ -85,65 +86,6 @@ function decodeGlobal(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) { // 扁平控制: // 会使用一个恒为true的while语句包裹一个switch语句 @@ -223,9 +165,9 @@ function cleanSwitchCode(path) { } function cleanDeadCode(ast) { - traverse(ast, { UnaryExpression: purifyBoolean }) - traverse(ast, { IfStatement: cleanIFCode }) - traverse(ast, { ConditionalExpression: cleanIFCode }) + traverse(ast, calculateConstantExp) + const pruneIfBranch = require('../visitor/prune-if-branch') + traverse(ast, pruneIfBranch) traverse(ast, { WhileStatement: { exit: cleanSwitchCode } }) return ast } @@ -502,54 +444,8 @@ function purifyFunction(path) { 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 }) - } + // 计算常量表达式 + traverse(ast, calculateConstantExp) // 替换索引器 function FormatMember(path) { // _0x19882c['removeCookie']['toString']() @@ -573,26 +469,8 @@ function purifyCode(ast) { } 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 }) + const splitSequence = require('../visitor/split-sequence') + traverse(ast, splitSequence) // 删除空语句 traverse(ast, { EmptyStatement: (path) => { diff --git a/src/plugin/sojsonv7.js b/src/plugin/sojsonv7.js index 19d351bd4..6a1013e7b 100644 --- a/src/plugin/sojsonv7.js +++ b/src/plugin/sojsonv7.js @@ -7,12 +7,19 @@ const traverse = require('@babel/traverse').default const t = require('@babel/types') const ivm = require('isolated-vm') const PluginEval = require('./eval.js') +const calculateConstantExp = require('../visitor/calculate-constant-exp') const isolate = new ivm.Isolate() const globalContext = isolate.createContextSync() function virtualGlobalEval(jsStr) { return globalContext.evalSync(String(jsStr)) } +function evalOneTime(str) { + const vm = new ivm.Isolate() + const ret = vm.createContextSync().evalSync(String(str)) + vm.dispose() + return ret +} function decodeGlobal(ast) { // 清理空语句 @@ -313,65 +320,6 @@ function decodeGlobal(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) { // 扁平控制: // 会使用一个恒为true的while语句包裹一个switch语句 @@ -482,12 +430,13 @@ function cleanSwitchCode2(path) { } let test = '' + pre_path try { - arr = eval(test + `;${arrName}`) + arr = evalOneTime(test + `;${arrName}.join('|')`) + arr = arr.split('|') } catch { // } } - if (!arr) { + if (!Array.isArray(arr)) { return } console.log(`扁平化还原: ${arrName}[${argName}]`) @@ -527,9 +476,9 @@ function cleanSwitchCode2(path) { } function cleanDeadCode(ast) { - traverse(ast, { UnaryExpression: purifyBoolean }) - traverse(ast, { IfStatement: cleanIFCode }) - traverse(ast, { ConditionalExpression: cleanIFCode }) + traverse(ast, calculateConstantExp) + const pruneIfBranch = require('../visitor/prune-if-branch') + traverse(ast, pruneIfBranch) traverse(ast, { WhileStatement: { exit: cleanSwitchCode1 } }) traverse(ast, { ForStatement: { exit: cleanSwitchCode2 } }) return ast @@ -748,54 +697,8 @@ function purifyFunction(path) { 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 }) - } + // 计算常量表达式 + traverse(ast, calculateConstantExp) // 替换索引器 function FormatMember(path) { // _0x19882c['removeCookie']['toString']() @@ -819,28 +722,8 @@ function purifyCode(ast) { } 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 } }) + const splitSequence = require('../visitor/split-sequence') + traverse(ast, splitSequence) // 删除空语句 traverse(ast, { EmptyStatement: (path) => { diff --git a/src/visitor/calculate-binary.js b/src/visitor/calculate-binary.js deleted file mode 100644 index 3b87d183b..000000000 --- a/src/visitor/calculate-binary.js +++ /dev/null @@ -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 { - // - } - }, -} diff --git a/src/visitor/calculate-constant-exp.js b/src/visitor/calculate-constant-exp.js new file mode 100644 index 000000000..657df4f48 --- /dev/null +++ b/src/visitor/calculate-constant-exp.js @@ -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 }, +} diff --git a/src/visitor/delete-unused-var.js b/src/visitor/delete-unused-var.js index 87ae201b1..cca67b312 100644 --- a/src/visitor/delete-unused-var.js +++ b/src/visitor/delete-unused-var.js @@ -15,6 +15,9 @@ module.exports = { if (!binding || binding.referenced || !binding.constant) { return } + if (!t.isLiteral(node.init)) { + return + } const up1 = path.parentPath const up2 = up1?.parentPath if (t.isForOfStatement(up2)) { diff --git a/src/visitor/lint-if-statement.js b/src/visitor/lint-if-statement.js new file mode 100644 index 000000000..d76e89658 --- /dev/null +++ b/src/visitor/lint-if-statement.js @@ -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 }, +} diff --git a/src/visitor/merge-object.js b/src/visitor/merge-object.js index 7ef9fba10..8f8155e58 100644 --- a/src/visitor/merge-object.js +++ b/src/visitor/merge-object.js @@ -162,7 +162,7 @@ function mergeObject(path) { } else { path.remove() } - scope.crawl() + binding.scope.crawl() } /** diff --git a/src/visitor/prune-if-branch.js b/src/visitor/prune-if-branch.js new file mode 100644 index 000000000..c98f3d82f --- /dev/null +++ b/src/visitor/prune-if-branch.js @@ -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, +} diff --git a/src/visitor/split-member-object.js b/src/visitor/split-member-object.js new file mode 100644 index 000000000..0316b7439 --- /dev/null +++ b/src/visitor/split-member-object.js @@ -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, +} diff --git a/src/visitor/split-sequence.js b/src/visitor/split-sequence.js new file mode 100644 index 000000000..4294fca36 --- /dev/null +++ b/src/visitor/split-sequence.js @@ -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, +} diff --git a/src/visitor/split-variable-declaration.js b/src/visitor/split-variable-declaration.js new file mode 100644 index 000000000..6dee8ae40 --- /dev/null +++ b/src/visitor/split-variable-declaration.js @@ -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, +}