用Babel操作AST实现JavaScript代码的自动化生成与转换
学习本文后,你将能够开发自己的代码转换工具!
目录
环境搭建
- 安装环境
npm install @babel/parser @babel/traverse @babel/generator @babel/types
- ast转换的代码框架
const fs = require('fs');
const vm = require('node:vm');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const t = require('@babel/types');
const generator = require('@babel/generator').default;
// 读取混淆代码文件
const code = fs.readFileSync('obfuscated.js', 'utf-8');
// 解析为AST
const ast = parser.parse(code);
// TODO 修改AST的逻辑将在这里编写
// 生成新代码
const output = generator(ast);
fs.writeFileSync('clean.js', output.code);
常见节点类型
1. Identifier -> 变量名
2. CallExpression -> 函数调用
3. MemberExpression -> 对象属性访问
4. VariableDeclarator -> 变量声明
代码:修改AST的逻辑
重命名函数名
- 案例
function _0x1a2b(s) {
return atob(s);
}
console.log(_0x1a2b("SGVsbG8=")); // 输出 "Hello" - 结果
function decryptString(s) {
return atob(s);
}
console.log(_0x1a2b("SGVsbG8=")); // 输出 "Hello" - 代码实现:
//重命名加密函数
FunctionDeclaration(path) {
if (path.node.id.name==='_0x1a2b') {
path.node.id.name='decryptString';
}
}
重命名变量并修改变量值
- 案例
function _0x12ab() {
const _0x3cde = ["\\x48\\x65\\x6c\\x6c\\x6f", "\\x77\\x6f\\x72\\x6c\\x64"];
return _0x3cde[0] + " " + _0x3cde[1];
} - 结果
function _0x12ab() {
const words = ["Hello", "world"];
return words[0] + " " + words[1];
} - 代码实现:
//重命名变量并解密字符串
//变量声明节点
VariableDeclarator(path) {
if (path.node.id.name === '_0x3cde') {
path.node.id.name = 'words'; // 修改变量名
// 遍历elements数组
path.node.init.elements = path.node.init.elements.map(element => {
// 解密十六进制字符串
const decoded = element.value.replace(/\\x([0-9a-fA-F]{2})/g,
(_, hex) => String.fromCharCode(parseInt(hex, 16))
);
// 关键
return { type: 'StringLiteral', value: decoded };
});
}
},
//对象成员访问节点
MemberExpression(path) {
if(path.node.object.name==='_0x3cde'){
path.node.object.name='words';
}
}
函数调用替换
- 案例
function _0x1a2b(s) {
return atob(s);
}
console.log(_0x1a2b("SGVsbG8=")); // 输出 "Hello" - 结果
function _0x1a2b(s) {
return atob(s);
}
console.log("Hello"); // 输出 "Hello" - 代码实现:
CallExpression(path) {
if (path.node.callee.name==='_0x1a2b'&&path.node.arguments[0].type==='StringLiteral') {
//取出参数
const encryptedStr=path.node.arguments[0].value;
// 对参数进行解密
const decryptedStr=atob(encryptedStr);
// 把原来的参数调用_0x1a2b("SGVsbG8="),替换为decryptedStr,即对atob(encryptedStr)
path.replaceWith({
type:'StringLiteral',
value:decryptedStr
});
}
}
控制流扁平化还原
案例
function _0x1234() {
const _0x5678 = [2, 0, 1];
while (true) {
switch (_0x5678.shift()) {
case 0:
console.log("world");
continue;
case 1:
console.log("!");
continue;
case 2:
console.log("Hello");
continue;
}
break;
}
}结果
function _0x1234() {
console.log("Hello");
console.log("world");
console.log("!");
}AST转换逻辑
· 识别switch-case结构:找到SwitchStatement节点
· 提取case顺序:通过_0x5678数组的初始值确定执行顺序(本例顺序为2→0→1)
· 重建代码顺序:按顺序合并case块中的语句,删除switch和while结构代码实现:
FunctionDeclaration(path) {
// 1. 定位控制流数组声明
const controlFlowDecl=path.node.body.body.find(n=>
(t.isVariableDeclaration(n)&&
n.declarations[0].id.name==='_0x5678')
);
if (!controlFlowDecl) return;
// 2. 提取控制流顺序 [2, 0, 1]
const controlFlowArray = controlFlowDecl.declarations[0].init.elements
.map(e=>e.value);
// 3. 删除控制流数组声明
path.node.body.body=path.node.body.body.filter(n=>
n!==controlFlowDecl);
// 4. 提取switch语句
const switchStmt = path.node.body.body
.find(n => t.isWhileStatement(n)).body.body
.find(n => t.isSwitchStatement(n));;
// 5. 删除while语句
path.node.body.body=path.node.body.body.filter(n=>!t.isWhileStatement(n));
// 6. 建立case值到语句的映射
const caseMap=new Map();
switchStmt.cases.forEach(n=>{
caseMap.set(n.test.value,n.consequent);
});
// 7. 按控制流顺序重组语句
const orderedStatements=[];
for(const caseVal of controlFlowArray){
const stmts=caseMap.get(caseVal)
.filter(n=>!t.isContinueStatement(n));// 移除continue
orderedStatements.push(...stmts);
}
// 8. 插入到函数体头部(保留其他语句)
path.node.body.body.unshift(...orderedStatements);
}
删除未使用的变量
案例
// 原始代码
const unusedVar = "test"; // 无任何地方使用
const activeVar = "data";
console.log(activeVar);结果
const activeVar = "data";
console.log(activeVar);代码实现:
VariableDeclarator(path){
const binding = path.scope.getBinding(path.node.id.name);
// 检测变量是否未被引用
if (!binding || binding.references === 0) {
// 删除整个 VariableDeclaration(需判断是否最后一个声明)
const parent = path.parent;
if (parent.declarations.length === 1) {
// 情况1:整个 VariableDeclaration 只有一个声明 eg: const a = 1;
path.parentPath.remove();// 删除父节点(即整个声明语句)
} else {
// 情况2:声明语句中有多个变量 eg: let a = 1, b = 2;
path.remove();// 只删除当前 VariableDeclarator
}
}
}
对象属性简化
案例
const _0xabc = {
"xYz": function(s) { return s.toUpperCase(); }
};
console.log(_0xabc["xYz"]("hello")); // 输出 "HELLO"结果
const _0xabc = {
upper: function (s) {
return s.toUpperCase();
}
};
console.log(utils.upper("hello")); // 输出 "HELLO"AST转换逻辑
- 识别对象属性:找到ObjectProperty节点中的动态键(如”xYz”)
- 重命名属性和调用方式:将_0xabc[“xYz”]改为utils.upper
代码实现:
ObjectProperty(path) {
// 重命名键名
if (path.node.key.value === "xYz") {
path.node.key = t.identifier("upper"); // 改为标识符形式
}
},
MemberExpression(path) {
// 转换动态属性访问为静态
if (
t.isIdentifier(path.node.object, { name: "_0xabc" }) &&
t.isStringLiteral(path.node.property, { value: "xYz" })
) {
path.node.object.name = "utils";
path.node.property = t.identifier("upper");
path.node.computed = false; // 改为.访问方式
}
}
条件表达式优化
案例
const isVIP = !![]; // 混淆写法:!![] → true
console.log(isVIP ? "VIP User" : "Guest");结果
const isVIP = true; // 混淆写法:!![] → true
console.log("VIP User");AST转换逻辑
· 计算常量表达式:在AST遍历阶段预计算!![]的值
· 删除无效分支:根据计算结果删除false分支代码实现:
VariableDeclarator(path) {
//!![] → true
if (t.isUnaryExpression(path.node.init,{ operator: "!" })){
if (t.isUnaryExpression(path.node.init.argument,{ operator: "!" })){
// 3. 检测最内层是否为空数组 []
const arrayExpr = path.node.init.argument.argument;
if(t.isArrayExpression(arrayExpr) && arrayExpr.elements.length === 0){
path.get('init').replaceWith(t.booleanLiteral(true));
}
}
}
},
ConditionalExpression(path) {
// 步骤1:检查 test 是否为变量引用(如 isVIP)
if (!t.isIdentifier(path.node.test)) return;
const varName = path.node.test.name; // 获取变量名 'isVIP'
// 步骤2:从作用域中获取变量绑定信息
const binding = path.scope.getBinding(varName);
// 步骤3:检查绑定是否存在且是常量
if (!binding || !binding.constant) return;
// 步骤4:找到变量的声明节点
const declaration = binding.path.node;
// 步骤5:提取变量的初始值
if (t.isVariableDeclarator(declaration)){
const initValue = declaration.init.value; // true
console.log(`变量 ${varName} 的值为: ${initValue}`);
// 步骤6(可选):根据值直接替换条件表达式
path.replaceWith(
initValue ? path.node.consequent : path.node.alternate
);
}
}
表达式还原
案例
var e=840;
e= e - (-0x2 * 0x7d6 + -0xd1 * 0x14 + 0x210e);结果
var e = 840;
e = e - 270;代码实现:
// 静态计算表达式值
function evaluateExpression(node) {
if (t.isLiteral(node)) {
return node.value;
}
if (t.isUnaryExpression(node)) {
const argValue = evaluateExpression(node.argument);
if (typeof argValue === 'number') {
return node.operator === '-' ? -argValue : argValue;
}
}
if (t.isBinaryExpression(node)) {
const left = evaluateExpression(node.left);
const right = evaluateExpression(node.right);
if (typeof left === 'number' && typeof right === 'number') {
switch (node.operator) {
case '+': return left + right;
case '-': return left - right;
case '*': return left * right;
case '/': return left / right;
// 可扩展其他运算符
}
}
}
return undefined; // 无法静态计算
}BinaryExpression(path) {
const result = evaluateExpression(path.node);
if (typeof result === 'number') {
// 替换为计算结果的字面量
path.replaceWith(t.numericLiteral(result));
}
}
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 guanq的博客!