AST是JavaScript代码静态分析的基础,需先解析为树结构;混淆核心是安全重命名绑定标识符(如变量、函数名),保留关键字、内置对象及外部引用;须构建作用域链区分声明与引用,再用计数器生成唯一短名替换。
静态分析 JavaScript 代码的第一步是将其转换为抽象语法树(AST)。AST 是源码的结构化表示,每个节点对应一种语法结构(如 Identifier、VariableDeclaration、FunctionExpression 等)。关键词混淆的核心目标是:在不改变程序行为的前提下,将可被安全重命名的标识符(如变量名、函数名、参数名)替换成无意义的短名称(如 _a、$0),同时保留关键字(if、return、class 等)、内置对象(Array、JSON)和外部引用(如全局变量 console、模块导入名)不变。
推荐使用 Acorn(轻量、标准兼容)或 @babel/parser(生态完善、支持新语法)。以 Acorn 为例:
acorn.parse(code, { ecmaVersion: 2022, sourceType: 'module' }) 得到 AST 根节点Identifier 的节点,但需过滤掉以下情况: Keyword 或 NullLiteral 等非标识符上下文(实际中 Identifier 节点本身不会代表关键字,但需检查其 name 是否为保留字)node.name 属于 JS 保留字(可用 is-reserved-word 库判断)obj.prop 中的 prop),且未被声明为局部变量——这类属于“字面量属性名”,不能混淆(除非启用更激进的属性名压缩,此处不考虑)MemberExpression 的 property 位置且 computed === false,且该属性名未在作用域中声明过仅靠 AST 结构不足以判断一个 Identifier 是否可重命名——必须区分“引用”和“声明”。例如:
function foo(x) { let y = x + 1; return y; }
立即学习“Java免费学习笔记(深入)”;
其中 foo、x、y 是**绑定标识符**(binding identifier),可混淆;而 x 在函数体内的两次出现是**引用标识符**(reference),需与声明匹配后统一替换。
手动实现简易作用域分析(无需完整 ES 规范):
FunctionDeclaration、FunctionExpression、ArrowFunctionExpression、BlockStatement 配合 let/const)就 push 新作用域对象VariableDeclarator.id、FunctionDeclaration.id、ArrowFunctionExpression.params、CatchClause.param)时,在当前作用域中记录该 name → { kind: 'var'|'let'|'const'|'function', node: ... }
Identifier 节点时,从内层向外层查找作用域,若命中则标记为“可混淆引用”,并关联到其声明节点Identifier(如直接写 console.log 中的 console)视为全局引用,跳过混淆混淆名策略要避免冲突、保持确定性(相同输入始终输出相同结果),推荐用计数器 + 字符集:
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$_'
nextName():从 1 开始编号,转为 chars 进制字符串(如 1→'a',27→'aa',53→'ba')i)应独立命名,避免跨作用域污染name 属性(Acorn AST 可变),最后用 escodegen 或 recast 生成代码示例片段逻辑:
// 声明节点处理伪代码<br>if (node.type === 'Identifier' && isBinding(node)) {<br> if (!bindingMap.has(node)) {<br> bindingMap.set(node, nextName());<br> }<br>}
// 引用节点处理伪代码<br>if (node.type === 'Identifier' && isReference(node)) {<br> const bindingNode = findBinding(node);<br> if (bindingNode && bindingMap.has(bindingNode)) {<br> node.name = bindingMap.get(bindingNode);<br> }<br>}
混淆后必须确保功能等价。几个关键检查点:
eval 或 Node.js vm 模块做快速 smoke test)this、arguments、super 等特殊标识符import { a as b } from './x')中,b 是本地绑定,可混淆;但 a 是导出名,不可混淆(除非你控制模块导出端)obj[expr])中的 expr 不受混淆影响,无需特殊处理不复杂但容易忽略。