一聚教程网:一个值得你收藏的教程网站

热门教程

怎样通过静态分析 AST 手动实现一个简单的 JavaScript 关键词混淆工具

时间:2026-07-27 09:56:54 编辑:袖梨 来源:一聚教程网

直接替换变量名会出错,因正则无法区分作用域和语义,而AST能精准识别Identifier节点并仅混淆局部变量和参数,跳过属性名、内置方法、全局变量等。

为什么直接替换变量名会出错

手动遍历字符串做正则替换,var a = 1; console.log(a); 中的 a 会被替换成新名字,但 console.log 里的 log 也可能被误伤——因为正则不区分作用域和语义。AST 能精准识别 Identifier 节点,并判断它是否为可混淆的局部变量或函数参数,跳过属性名、内置方法、全局变量等。

@babel/parser 解析并遍历 AST 的关键步骤

不用写词法分析器,Babel 提供了稳定、兼容性好的解析能力。重点不是“怎么生成 AST”,而是“怎么安全标记和替换”:

  • 只处理 VariableDeclarator(如 let x = 1 中的 x)和 FunctionDeclaration/ArrowFunctionExpression 的参数节点
  • 跳过 MemberExpression 中的 property(如 obj.namename),否则会破坏对象访问
  • scope.bindings 判断标识符是否为局部绑定(Babel 的 scope 对象在遍历时自动可用)
  • 避免混淆 thisargumentssuper 等上下文关键字,它们不是普通 Identifier

示例节选(Babel 插件写法):

export default function({ types: t }) {  return {    visitor: {      Identifier(path) {        const { node, scope } = path;        if (node.name === 'this' || node.name === 'arguments') return;        if (!scope.hasBinding(node.name)) return; // 不是当前作用域声明的,不碰        if (scope.getBinding(node.name).kind !== 'let' &&             scope.getBinding(node.name).kind !== 'const' &&             scope.getBinding(node.name).kind !== 'var') return;        // 此处生成新名字,如 'a' → '_0x1a2b'        path.replaceWith(t.identifier('_0x1a2b'));      }    }  };}

scope.hasBinding()scope.getBinding() 的实际差异

前者只是快速判断名字是否存在绑定,后者返回完整绑定对象,含 kindvar/let/const/param)、referencePaths(所有引用位置)、path(声明节点)。混淆时必须用 getBinding(),否则无法区分 let xobj.x ——两者都通过 hasBinding('x') 返回 false,但后者根本不在作用域中。

立即学习“Java免费学习笔记(深入)”;

  • scope.hasBinding('x')function f(x) { let x = 2; } 中对第一个 x(参数)返回 true,对第二个也返回 true,但它们属于不同绑定
  • 真正要替换的是绑定对象本身,所以必须用 scope.getBinding('x') 获取其声明路径,再决定是否重命名
  • 注意:参数绑定的 kind'param',不是 'let',漏判会导致函数参数没被混淆

混淆后代码执行失败的三个高频原因

不是 AST 没走通,而是语义被破坏:

  • class A { method() {} } 中的 method 当作可混淆标识符——其实它是 MethodDefinitionkey,不是 Identifier 节点;需额外处理 ObjectMethodClassMethodkey 属性,且仅当 key.type === 'Identifier' 且非计算属性时才考虑
  • 混淆了 import { foo } from './x' 中的 foo,导致模块导入失效——ESM 的导入绑定是只读的,不能重命名;应跳过所有 ImportSpecifierExportSpecifier 中的 localexported
  • 用固定映射表(如 {x: '_a', y: '_b'})混淆,但没做作用域隔离,导致嵌套函数内同名变量被统一替换,破坏闭包逻辑;必须为每个 Scope 实例维护独立的映射缓存

最易被忽略的是 this 绑定上下文:箭头函数里写的 this 不是标识符,但若混淆了外层函数名,可能让调试时的调用栈完全不可读——混淆工具不该动函数名,除非明确支持 functionName 选项并默认关闭。

热门栏目