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