保姆级教程:手把手教你用AST还原Twitter混淆代码(附完整脚本)

张开发
2026/4/12 12:26:57 15 分钟阅读

分享文章

保姆级教程:手把手教你用AST还原Twitter混淆代码(附完整脚本)
从零掌握ASTJavaScript混淆代码还原实战指南当你第一次面对Twitter这类平台经过混淆处理的JavaScript代码时那些层层嵌套的函数调用和难以理解的变量名往往让人望而生畏。作为前端开发者或安全研究人员理解如何逆向分析这类代码不仅是一项实用技能更是深入理解JavaScript运行机制的绝佳途径。本文将带你从AST基础开始逐步构建一个完整的混淆还原工具链。1. 认识JavaScript代码混淆与AST现代Web应用普遍采用代码混淆技术来保护知识产权并增加逆向难度。Twitter等大型平台使用的混淆方案通常具备以下特征多层函数嵌套核心逻辑被拆分成多个相互调用的函数动态字符串生成关键字符串通过运行时计算而非直接存储变量名替换有意义的标识符被替换为短随机字符串控制流平坦化添加大量无意义的分支和跳转// 典型混淆代码示例 function a(n,t,W,r,u){ return tu(0,u- -917-nt- -r,0,W) }抽象语法树(AST)是理解这类代码的关键。AST将源代码转换为树状数据结构每个节点代表代码中的特定结构。主流JavaScript解析器如Babel使用的AST规范包含超过100种节点类型但混淆还原主要涉及以下几种节点类型描述示例CallExpression函数调用func(arg1,arg2)MemberExpression成员访问obj.propertyBinaryExpression二元运算a bUnaryExpression一元运算!flagIdentifier标识符variableNameLiteral字面量string2. 搭建AST分析环境工欲善其事必先利其器。我们需要配置以下工具链Babel核心包npm install babel/core babel/parser babel/traverse babel/generator babel/types基础解析脚本const parser require(babel/parser); const traverse require(babel/traverse).default; const generator require(babel/generator).default; const t require(babel/types); const code 你的混淆代码; const ast parser.parse(code); traverse(ast, { // 各种访问器将在这里添加 }); const output generator(ast); console.log(output.code);实用调试技巧使用console.log(path.node)查看当前节点结构通过path.toString()快速获取代码片段利用AST Explorer在线工具实时观察解析结果注意处理生产环境代码时建议先在本地创建测试用例避免直接修改原始文件。3. 逐步还原混淆代码3.1 识别并提取解密函数混淆代码通常包含一个核心的解密函数其特征包括被大量其他函数调用参数中包含数值运算返回值为基础类型字符串/数字函数体包含特定模式如fromCharCode调用// 典型解密函数结构 function decrypt(a, b, c) { return String.fromCharCode((a b) ^ c); }编写AST访问器定位这类函数traverse(ast, { FunctionDeclaration(path) { const body path.get(body); const returns body.get(body).filter(p p.isReturnStatement()); if (returns.length 1) { const retExpr returns[0].get(argument); if (retExpr.isCallExpression() retExpr.get(callee).matchesPattern(String.fromCharCode)) { console.log(发现解密函数:, path.node.id.name); } } } });3.2 处理变量声明和字面量替换混淆代码中常见的变量模式包括常量变量var a 5;引用内置对象var b String;成员表达式var c String.fromCharCode;对应的AST处理策略traverse(ast, { VariableDeclarator(path) { const {id, init} path.node; if (t.isLiteral(init)) { // 处理常量替换 const binding path.scope.getBinding(id.name); binding.referencePaths.forEach(ref { ref.replaceWith(init); }); path.remove(); } else if (t.isIdentifier(init)) { // 处理全局对象引用 if (init.name String) { const binding path.scope.getBinding(id.name); binding.referencePaths.forEach(ref { ref.replaceWith(t.identifier(String)); }); } } } });3.3 解析链式函数调用Twitter风格的混淆常采用多层函数调用每层都可能修改参数。我们需要构建调用链的完整路径追踪参数在各层间的传递变化最终合并到核心解密函数function resolveCallChain(path) { let current path; const chain []; while (t.isCallExpression(current)) { const callee current.get(callee); chain.unshift(current.node); if (callee.isIdentifier()) { const binding path.scope.getBinding(callee.node.name); if (binding binding.path.isFunctionDeclaration()) { const returnStmt binding.path.get(body.body) .find(p p.isReturnStatement()); if (returnStmt) { current returnStmt.get(argument); continue; } } } break; } return chain; }3.4 动态计算最终值获得简化后的表达式后可通过以下方式计算结果安全评估function safeEval(expr) { const fn new Function(return ${expr}); return fn.call(null); }AST节点替换const result safeEval(1 2 * 3); path.replaceWith(t.valueToNode(result));处理特殊值if (typeof result string) { const decoded decodeURIComponent(escape(result)); path.replaceWith(t.stringLiteral(decoded)); }4. 构建完整处理流程将上述步骤整合为自动化脚本const { parse } require(babel/parser); const { default: traverse } require(babel/traverse); const { default: generate } require(babel/generator); const t require(babel/types); function deobfuscate(code) { // 1. 解析AST const ast parse(code); // 2. 第一轮遍历处理变量和简单替换 traverse(ast, variableVisitor); // 3. 第二轮遍历解析函数调用链 traverse(ast, callVisitor); // 4. 第三轮遍历计算并替换最终值 traverse(ast, evaluationVisitor); // 5. 生成干净代码 return generate(ast).code; } // 各访问器实现...实际应用中还需要考虑错误处理添加try-catch块处理意外结构性能优化对大型文件采用增量处理结果验证确保变换后的代码功能不变5. 实战案例与调试技巧以Twitter的x-client-transaction-id生成代码为例典型问题包括链式调用参数偏移// 原始调用 o(0, kY2[, 753, 790, 610) // 实际需要跟踪每个参数在各层的变化 function o(level, str, a, b, c) { return tu(level, c - -917 -a b - -level, 0, str); }动态数组访问// 需要解析的数组访问模式 var arr [/* 动态生成 */]; return arr[a b * c];调试技巧使用path.stop()在找到目标时终止遍历通过path.buildCodeFrameError()生成有意义的错误对复杂表达式使用path.evaluate()尝试静态求值// 表达式求值示例 const { confident, value } path.evaluate(); if (confident) { path.replaceWith(t.valueToNode(value)); }经过完整处理后原本难以理解的混淆代码将变为可读性良好的实现。例如一个Twitter的请求参数生成函数可能最终被还原为function generateTransactionId() { const timestamp Date.now(); const nonce Math.random().toString(36).slice(2, 8); return x-client-${timestamp}-${nonce}; }掌握AST技术不仅能帮助理解混淆代码还能提升对JavaScript语言本身的认识。我在实际项目中发现最有效的学习方式是从小规模混淆开始逐步增加复杂度同时建立自己的工具函数库来复用常见处理模式。

更多文章