JS变量名混淆原理(AST混淆)
什么是 AST(抽象语法树)?
简单来说,JS 引擎(如 V8)在执行代码前,第一步就是把代码变成 AST。
核心流程
我们的混淆过程其实就是以下三步曲:
-
Parse(解析): 将源代码(String)转换为 AST(JSON 树)。
-
Transform(转换): 遍历 AST,对树节点进行增删改查(混淆的核心)。
-
Generate(生成): 将修改后的 AST 重新转换回代码(String)。
我们可以尝试自己写代码混淆代码
nodejs在线混淆
需要的工具Babel 全家桶
npm install @babel/parser @babel/traverse @babel/types @babel/generator
// 引入Node.js内置的文件系统模块,用于读写文件(AST反混淆中:读入混淆的代码文件、写出还原后的代码文件)
const fs=require("fs");
// 核心作用:将JS代码字符串解析成AST抽象语法树(AST处理第一步)
const parser=require("@babel/parser");
// .default是固定导出用法;核心作用:遍历AST节点+修改AST结构(AST反混淆核心步骤,比如替换混淆节点、删除死代码)
const traverse=require("@babel/traverse").default
// 核心作用:将修改后的AST重新生成可读的JS代码字符串(AST处理最后一步)
const generator=require("@babel/generator").default
// 核心作用:判断AST节点类型/创建新AST节点/修改节点属性(反混淆中高频用,比如判断是否是三元表达式、创建字面量节点替换混淆节点)
const types=require("@babel/types");
实战---手写混淆代码
1.导入代码
const code=fs.readFileSync('input.js','utf-8');
1.1示例代码
let a=1;
let b=2;
let c=a+b;
let d=a+c;
console.log(a+b)
console.log(c)
2.转化为AST语法树
{sourceType:"unambiguous"}是指定「代码类型」的核心配置,unambiguous是自动判断代码类型
(script(脚本式)、module(模块式)),
const ast=parser.parse(code,{sourceType:"unambiguous"})
3.遍历 AST,处理每个节点
traverse(ast,visitor)
4.编写混淆逻辑(混淆逻辑后面讲)
// 变量名混淆
let map={}
const visitor={
Identifier(path){
let name=path.node.name
if (['console','log'].includes(name)){
return;
}
if (map[name]){path.node.name=map[name]; return;}
map[name]='Ox'+parseInt(Math.random()*10000000)
path.node.name=map[name]
}
}
4.1混淆逻辑
--原理是通过遍历 AST 的Identifier节点,直接修改节点的name属性,最终生成混淆后的代码--
1.白名单过滤,跳过console/log(其他的也是这样的逻辑,跳过原生的函数,如果想要混淆原生函数变量名可以先在代码里面hook然后混淆)
if (['console','log'].includes(name)){return;}
2.映射表校验,保证同名同新名(如果没有这个那么会导致混淆前的变量名是一样的,混淆后的变量名不一样,比如let a和c=a+b,混淆后这两个a不一样)
if (map[name]){path.node.name=map[name]; return;}
3.生成随机新名称,完成节点重命名(混淆逻辑关键)
map[name]='0x'+parseInt(Math.random()*10000000)
path.node.name=map[name]
5.导出混淆后的代码
const { code: output } = generator(ast, {
compact: false,
jsescOption: { minimal: true }
});
fs.writeFileSync("output.js", output);
5.1展示混淆后的代码
是不是非常熟悉,可以根据这样的基础拓展很多混淆方式,不仅仅是变量名,还能混淆运算符
let Ox5245213 = 1;
let Ox4903504 = 2;
let Ox1936835 = Ox5245213 + Ox4903504;
let Ox8947139 = Ox5245213 + Ox1936835;
console.log(Ox5245213 + Ox4903504);
console.log(Ox1936835);
总结
// 引入Node.js内置的文件系统模块,用于读写文件(AST反混淆中:读入混淆的代码文件、写出还原后的代码文件)
const fs=require("fs");
// 引入Babel的解析器模块(正确包名@babel/parser),核心作用:将JS代码字符串解析成AST抽象语法树(AST处理第一步)
const parser=require("@babel/parser");
// 引入Babel的遍历器模块(正确包名@babel/traverse),.default是固定导出用法;核心作用:遍历AST节点+修改AST结构(AST反混淆核心步骤,比如替换混淆节点、删除死代码)
const traverse=require("@babel/traverse").default
// 引入Babel的生成器模块(正确包名@babel/generator),核心作用:将修改后的AST重新生成可读的JS代码字符串(AST处理最后一步)
const generator=require("@babel/generator").default
// 引入Babel的类型工具模块(正确包名@babel/types),核心作用:判断AST节点类型/创建新AST节点/修改节点属性(反混淆中高频用,比如判断是否是三元表达式、创建字面量节点替换混淆节点)
const types=require("@babel/types");
// const visitor={
// BinaryExpression(path){
// const {left,right, operator}=path.node;
// if ( operator==="+" && types.isStringLiteral(left) && types.isStringLiteral(right)){
// path.replaceWith(types.stringLiteral(left.value+right.value));
// }
// }
// }
// 变量名混淆
let map={}
const visitor={
Identifier(path){
let name=path.node.name,rename
if (['console','log'].includes(name)){
return;
}
if (map[name]){path.node.name=map[name]; return;}
map[name]='Ox'+parseInt(Math.random()*10000000)
path.node.name=map[name]
}
}
const code=fs.readFileSync('input.js','utf-8');
const ast=parser.parse(code,{sourceType:"unambiguous"})
traverse(ast,visitor)
const { code: output } = generator(ast, {
compact: false,
jsescOption: { minimal: true }
});
fs.writeFileSync("output.js", output);











