js安全之ast混淆
字数 1504 2025-08-29 08:31:35
JavaScript AST混淆技术详解
一、概述
JavaScript AST混淆是一种通过修改代码的抽象语法树(AST)结构来提高代码安全性、增加逆向分析难度的技术。它通过改变代码的表现形式而不改变其功能,使得代码难以被理解和分析。
二、常见混淆方法
2.1 对象访问混淆
JavaScript有两种访问对象成员的方式:
- 点表示法:
obj.property - 方括号表示法:
obj['property']
混淆方法:
// 原始代码
var k1 = new Test('k1');
console.log(k1.name);
// 混淆后
var k1 = new window['Test']('k1');
window['console']['l' + 'o' + 'g'](k1['n' + 'a' + 'm' + 'e']);
2.2 编码格式混淆
Unicode编码
// 原始代码
function Aaa(ccc){ this.name = ccc; }
var bbb = new Aaa('kk');
// Unicode编码后
function \u0041\u0061\u0061(\u0063\u0063\u0063){
this.\u006e\u0061\u006d\u0065 = \u0063\u0063\u0063;
}
var \u0062\u0062\u0062 = new \u0041\u0061\u0061('\u006b\u006b');
Hex编码
// 原始代码
var aaa = 'hello';
console['log'](aaa);
// Hex编码后
var aaa = '\x68\x65\x6c\x6c\x6f';
console['\x6c\x6f\x67'](aaa);
ASCII编码
// 使用String.fromCharCode和eval
var test = [10,32,32,...]; // ASCII码数组
eval(String.fromCharCode.apply(null, test));
2.3 常量加密
字符串加密
// 原始代码
var aaa = 'haaaaeaaaalaaaaalaaaaaoaaaaa';
console.log(aaa.replace(/a/g, ''));
// 双重base64加密后
function double_b64_decode(sss){
return atob(atob(sss));
}
var aaa = double_b64_decode('YUdGaFlXRmxZV0ZoWVd4aFlXRmhZV3hoWVdGaFlXOWhZV0ZoWVE9PQ==');
console[double_b64_decode('Y21Wd2JHRmpaUT09')](aaa.replace(/a/g, ''));
数值加密
利用位异或运算的自反特性:
// 原始代码
for(a = 3, b = 0; a > b; b++){ console.log(b); }
// 数值加密后
for(a = (28904789 ^ 23411199) - (98209009 ^ 84326486),
b = (82719280 ^ 72618394) - (27206798 ^ 19203876);
a > b; b++){
console.log(b);
}
2.4 数组混淆
基本数组混淆
// 原始代码
var currTime = new window.Date().getTime();
console.log(currTime);
// 数组混淆后
var _JMX2pS = ["".constructor.fromCharCode, "length", "split"];
function _phkzfz(str) {
var i, k, m = "";
k = str[_JMX2pS[2]](".");
for(i = 0; i < k[_JMX2pS[1]]; i++) {
m += _JMX2pS[0](k[i] ^ 0x12);
}
return m;
}
var _N2JfbZ = [_phkzfz('126.125.117'), _phkzfz('86.115.102.119'),
_phkzfz('117.119.102.70.123.127.119'), _phkzfz('113.125.124.97.125.126.119')];
var _rAr7F7 = new window[_N2JfbZ[1]]()[_N2JfbZ[2]]();
window[_N2JfbZ[3]][_N2JfbZ[0]](_rAr7F7);
数组乱序
// 数组打乱
var aaa = [1,2,3,4,5];
(function(arr, num){
for(var x = num; x > 0; x--) {
arr['push'](arr['shift']());
}
})(aaa, 7);
// 数组还原
(function(arr, num){
for(var x = num; x > 0; x--) {
arr['unshift'](arr['pop']());
}
})(aaa, 7);
2.5 JSFuck混淆
使用6种字符((,),[,],+,!)编写JavaScript代码:
// 原始代码
alert(1);
// JSFuck混淆后
[][(([0]+([[10]+([2]+(!![]+[])[0]+(!![]+[])[3]+(!![]+[])[1])]
[(([][(([0]+([[10]+([2]+(!![]+[])[0]+(!![]+[])[3]+(!![]+[])[1])]+[])[3]+
(!![]+[][(([0]+([[10]+([2]+(!![]+[])[0]+(!![]+[])[3]+(!![]+[])[1])])[10]+
([][[]]+[])[1]+([3]+(!![]+[])[0]+(!![]+[])[1]+([][[]]+[])[0]+
([][(([0]+([[10]+([2]+(!![]+[])[0]+(!![]+[])[3]+(!![]+[])[1])]+[])[3]+
(!![]+[])[0]+(!![]+[][(([0]+([[10]+([2]+(!![]+[])[0]+(!![]+[])[3]+(!![]+[])[1])])[10]+
(!![]+[])[1])]((([1]+([2]+(!![]+[])[3]+(!![]+[])[1]+(!![]+[])[0]+
([0]+([[10]+([2]+(!![]+[])[0]+(!![]+[])[3]+(!![]+[])[1])])[20]+1+
(!![]+[][(([0]+([[10]+([2]+(!![]+[])[0]+(!![]+[])[3]+(!![]+[])[1])])[20]))();
2.6 花指令
添加不影响运行但增加逆向工作量的垃圾代码:
二项式转函数
// 原始代码
var a = 3; var b = 5; var c = 7;
console.log(a + b + c);
// 花指令混淆后
function _yEMYyf(j, k, l){ return j + l; }
function _hDp7fx(j, k, l){ return _yEMYyf(l, + + k; }
function _zaApRm(j, k, l){ return _hDp7fx(k, l, j); }
console.log(_zaApRm(3, 5, 7));
2.7 控制流平坦化
使用switch语句打乱代码执行顺序:
// 原始代码
function aaa(){
var a, b, c;
a = 1;
b = a + 2;
c = b + 3;
return c + 4;
}
// 控制流平坦化后
function aaa(){
var a, b, c, d = 0, arr = '2|3|1|4'.split('|');
while(!![]){
switch(arr[d++]){
case '1': c = b + 3; continue;
case '2': a = 1; continue;
case '3': b = a + 2; continue;
case '4': return c + 4; continue;
}
break;
}
}
2.8 逗号表达式
利用逗号表达式的特性添加无效语句:
// 原始代码
function aaa(){
var a, b, c;
a = 1;
b = a + 2;
c = b + 3;
return c + 4;
}
// 逗号表达式混淆后
function aaa(){
var a, b, c, d, e;
return (c = (e = 3, (b = (d = 2, a = 1, a)), b + 2), c + 3) + 4;
}
三、AST混淆原理与工具
3.1 AST语法树
AST(Abstract Syntax Tree)是代码语法结构的树状表示,常见节点类型:
- Identifier: 标识符
- Programs: 根节点
- Functions: 函数节点
- Literals: 字面量
- RegExpLiteral: 正则表达式
- StringLiteral: 字符串
- BooleanLiteral: 布尔值
- NumericLiteral: 数字
- Statements: 语句节点
- ExpressionStatement: 表达式语句
- BlockStatement: 块语句
- ForStatement: for循环
- Declarations: 声明语句
- FunctionDeclaration: 函数声明
- VariableDeclaration: 变量声明
- Expressions: 表达式
- FunctionExpression: 函数表达式
- BinaryExpression: 二项式
- CallExpression: 调用表达式
3.2 AST混淆流程
- 解析:将源代码转换为AST
- 转换:修改AST结构
- 生成:从修改后的AST生成新代码
3.3 Babel工具链
- @babel/parser: 解析代码生成AST
- @babel/traverse: 遍历和修改AST
- @babel/types: 创建和验证AST节点
- @babel/generator: 从AST生成代码
基本使用示例
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const t = require("@babel/types");
const generator = require("@babel/generator").default;
// 1. 解析
let ast = parser.parse('var a = 1 + 1;');
// 2. 转换
const visitor = {
NumericLiteral(path) {
let value = path.node.value;
let key = parseInt(Math.random() * 999999, 10);
let cipherNum = value ^ key;
path.replaceWith(
t.binaryExpression('^', t.numericLiteral(cipherNum), t.numericLiteral(key))
);
path.skip();
}
};
traverse(ast, visitor);
// 3. 生成
let code = generator(ast).code;
console.log(code);
四、AST混淆实现
4.1 改变对象访问方式
const visitor = {
MemberExpression(path) {
if(path.node.computed == false) {
const name = path.node.property.name;
path.node.property = t.stringLiteral(name);
path.node.computed = true;
}
}
};
4.2 标识符Unicode编码
function string2unicode(str) {
let ret = "";
for(let i = 0; i < str.length; i++){
ret += "\\u" + "00" + str.charCodeAt(i).toString(16);
}
return ret;
}
const visitor = {
Identifier(path) {
const src_value = path.node.name;
path.replaceWith(t.Identifier(string2unicode(src_value)));
path.skip();
}
};
4.3 字符串加密
function double_b64_encode(sss){ return btoa(btoa(sss)); }
const visitor = {
StringLiteral(path) {
const src_value = path.node.value;
const en_Str = t.callExpression(
t.identifier('double_b64_decode'),
[t.stringLiteral(double_b64_encode(src_value))]
);
path.replaceWith(en_Str);
path.skip();
}
};
// 添加解密函数
let d_decode = t.functionDeclaration(
t.identifier('double_b64_decode'),
[t.identifier('sss')],
t.blockStatement([
t.returnStatement(
t.CallExpression(
t.identifier('atob'),
[t.CallExpression(t.identifier('atob'), [t.identifier('sss')])]
)
)
])
);
ast.program.body.unshift(d_decode);
4.4 数值位异或加密
function num2xor(num) {
let key = parseInt(Math.random() * 999999, 10);
let cipherNum = key ^ num;
return [key, cipherNum];
}
function num2add(num) {
let key = parseInt(Math.random() * 999999, 10);
let cipherNum = key - num;
return [key, cipherNum];
}
const visitor = {
NumericLiteral(path) {
const src_value = path.node.value;
let xxx = num2add(src_value);
let xxx_2 = num2xor(xxx[0]);
let xxx_3 = num2xor(xxx[1]);
path.replaceWith(
t.binaryExpression('-',
t.binaryExpression('^', t.NumericLiteral(xxx_2[0]), t.NumericLiteral(xxx_2[1])),
t.binaryExpression('^', t.NumericLiteral(xxx_3[0]), t.NumericLiteral(xxx_3[1]))
)
);
path.skip();
}
};
4.5 数组混淆
let strList = [];
const visitor = {
StringLiteral(path) {
let srcValue = double_b64_encode(path.node.value);
let index = strList.indexOf(srcValue);
if(index == -1){
let length = strList.push(srcValue);
index = length - 1;
}
path.replaceWith(
t.CallExpression(
t.identifier('double_b64_decode'),
[t.memberExpression(t.identifier('Arr'), t.numericLiteral(index), true)]
)
);
}
};
// 添加数组和解密函数
strList = strList.map(function(sss){
return t.StringLiteral(sss);
});
let var_tion = t.variableDeclaration('var', [
t.variableDeclarator(t.identifier('Arr'), t.arrayExpression(strList))
]);
ast.program.body.unshift(var_tion);
let fun_tion = t.functionDeclaration(
t.identifier('double_b64_decode'),
[t.identifier('sss')],
t.blockStatement([
t.returnStatement(
t.CallExpression(
t.identifier('atob'),
[t.CallExpression(t.identifier('atob'), [t.identifier('sss')])]
)
)
])
);
ast.program.body.unshift(fun_tion);
4.6 二项式转花指令
function randomString(len) {
len = len || 4;
var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678';
var maxPos = $chars.length;
var pwd = '';
for(i = 0; i < len; i++) {
pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd;
}
let visitor = {
BinaryExpression(path) {
let xxx = "_" + randomString(4);
let left = path.node.left;
let right = path.node.right;
let operator = path.node.operator;
let j = t.identifier('j');
let k = t.identifier('k');
path.replaceWith(
t.CallExpression(t.identifier(xxx), [left, right])
);
let newFunc = t.functionDeclaration(
t.identifier(xxx),
[j, k],
t.blockStatement([
t.returnStatement(t.binaryExpression(operator, j, k))
])
);
let rootPath = path.findParent(function(p){ return p.isProgram(); });
rootPath.node.body.unshift(newFunc);
}
};
4.7 指定行加密
const visitor = {
FunctionDeclaration(path) {
let tmp = path.node.body;
let body = tmp.body.map(function(p){
if(t.isReturnStatement(p)) { return p };
let src_code = generator(p).code;
let ciperCode = double_b64_encode(src_code);
let ciperFunc = t.callExpression(
t.identifier('double_b64_decode'),
[t.stringLiteral(ciperCode)]
);
let newFunc = t.callExpression(t.identifier('eval'), [ciperFunc]);
return t.expressionStatement(newFunc);
});
path.get('body').replaceWith(t.blockStatement(body));
}
};
// 添加解密函数
let d_decode = t.functionDeclaration(
t.identifier('double_b64_decode'),
[t.identifier('sss')],
t.blockStatement([
t.returnStatement(
t.CallExpression(
t.identifier('atob'),
[t.CallExpression(t.identifier('atob'), [t.identifier('sss')])]
)
)
])
);
ast.program.body.unshift(d_decode);
4.8 去注释和空格
let code = generator(ast, {
compact: true, // 去除空格
comments: false // 去除注释
}).code;
五、动态混淆技术
动态混淆是指在混淆过程中加入随机因素,使得每次生成的混淆代码都不完全相同,提高逆向难度。实现思路:
- 代码端:混淆函数加入不确定参数(如时间戳、用户指纹)
- 系统端:混淆器与Web中间件绑定,每次请求动态生成不同代码
六、总结
JavaScript AST混淆技术通过多种手段改变代码表现形式,包括:
- 改变代码结构(控制流平坦化、花指令)
- 加密常量(字符串、数值)
- 改变标识符(Unicode编码)
- 改变访问方式(对象访问、数组混淆)
- 添加干扰(注释、空格、无效代码)
使用Babel工具链可以自动化实现这些混淆技术。结合动态混淆可以进一步提高安全性,保护关键业务逻辑不被轻易逆向分析。