如何通过静态分析 AST 手动编写一个简单的 JavaScript 代码关键词混淆工具

老敏小哥_3999

老敏小哥_3999

2026-04-13

308人浏览

原创

ast是javascript代码静态分析的基础,需先解析为树结构;混淆核心是安全重命名绑定标识符(如变量、函数名),保留关键字、内置对象及外部引用;须构建作用域链区分声明与引用,再用计数器生成唯一短名替换。

如何通过静态分析 ast 手动编写一个简单的 javascript 代码关键词混淆工具

理解 AST 与混淆目标

静态分析 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 根节点
  • 遍历 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; }

其中 foo、x、y 是**绑定标识符**(binding identifier),可混淆;而 x 在函数体内的两次出现是**引用标识符**(reference),需与声明匹配后统一替换。

手动实现简易作用域分析(无需完整 ES 规范):

Java Maven Secondary Analysis
Java Maven Secondary Analysis

分析ZIP压缩包或GitLab仓库中的Java Maven项目,确定二次开发范围、类数量、模块分布及生产相关指标。

下载
  • 深度优先遍历 AST,维护一个作用域栈(数组),每进入一个作用域(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)应独立命名,避免跨作用域污染
  • 替换时直接修改 AST 节点的 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 等特殊标识符
  • ES6 模块导入绑定(import { a as b } from './x')中,b 是本地绑定,可混淆;但 a 是导出名,不可混淆(除非你控制模块导出端)
  • 动态属性访问(obj[expr])中的 expr 不受混淆影响,无需特殊处理
  • 正则字面量、模板字符串、注释中的文本不参与 AST 分析,天然不受影响

不复杂但容易忽略。

Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

2023.06.20

4626

5

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

2023.07.04

1149

3

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

2023.07.04

4544

6

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

2023.09.01

920

4

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

2023.09.04

1816

5

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

2023.09.04

3305

4

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

2023.09.12

4293

6

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

2023.09.20

2800

5

java访问控制修饰符介绍
java访问控制修饰符介绍

java访问控制修饰符有四种,分别是public、protected、private、默认访问修饰符。详细介绍:1、public,public是最宽松的访问控制修饰符,被修饰的类、方法和变量可以被任何其他类访问,当一个类、方法或变量被声明为public时,它们可以在任何地方被访问,无论是同一个包中的类还是不同包中的类;2、protected修饰符等等。

2023.09.20

888

7

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
WebStorm 官方调试文档
WebStorm 官方调试文档

共0课时 | 0人学习

React 教程
React 教程

共58课时 | 12.1万人学习

TypeScript 教程
TypeScript 教程

共19课时 | 6.6万人学习