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混淆流程

  1. 解析:将源代码转换为AST
  2. 转换:修改AST结构
  3. 生成:从修改后的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;

五、动态混淆技术

动态混淆是指在混淆过程中加入随机因素,使得每次生成的混淆代码都不完全相同,提高逆向难度。实现思路:

  1. 代码端:混淆函数加入不确定参数(如时间戳、用户指纹)
  2. 系统端:混淆器与Web中间件绑定,每次请求动态生成不同代码

六、总结

JavaScript AST混淆技术通过多种手段改变代码表现形式,包括:

  1. 改变代码结构(控制流平坦化、花指令)
  2. 加密常量(字符串、数值)
  3. 改变标识符(Unicode编码)
  4. 改变访问方式(对象访问、数组混淆)
  5. 添加干扰(注释、空格、无效代码)

使用Babel工具链可以自动化实现这些混淆技术。结合动态混淆可以进一步提高安全性,保护关键业务逻辑不被轻易逆向分析。

JavaScript AST混淆技术详解 一、概述 JavaScript AST混淆是一种通过修改代码的抽象语法树(AST)结构来提高代码安全性、增加逆向分析难度的技术。它通过改变代码的表现形式而不改变其功能,使得代码难以被理解和分析。 二、常见混淆方法 2.1 对象访问混淆 JavaScript有两种访问对象成员的方式: 点表示法: obj.property 方括号表示法: obj['property'] 混淆方法: 2.2 编码格式混淆 Unicode编码 Hex编码 ASCII编码 2.3 常量加密 字符串加密 数值加密 利用位异或运算的自反特性: 2.4 数组混淆 基本数组混淆 数组乱序 2.5 JSFuck混淆 使用6种字符( ( , ) , [ , ] , + , ! )编写JavaScript代码: 2.6 花指令 添加不影响运行但增加逆向工作量的垃圾代码: 二项式转函数 2.7 控制流平坦化 使用switch语句打乱代码执行顺序: 2.8 逗号表达式 利用逗号表达式的特性添加无效语句: 三、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生成代码 基本使用示例 四、AST混淆实现 4.1 改变对象访问方式 4.2 标识符Unicode编码 4.3 字符串加密 4.4 数值位异或加密 4.5 数组混淆 4.6 二项式转花指令 4.7 指定行加密 4.8 去注释和空格 五、动态混淆技术 动态混淆是指在混淆过程中加入随机因素,使得每次生成的混淆代码都不完全相同,提高逆向难度。实现思路: 代码端 :混淆函数加入不确定参数(如时间戳、用户指纹) 系统端 :混淆器与Web中间件绑定,每次请求动态生成不同代码 六、总结 JavaScript AST混淆技术通过多种手段改变代码表现形式,包括: 改变代码结构(控制流平坦化、花指令) 加密常量(字符串、数值) 改变标识符(Unicode编码) 改变访问方式(对象访问、数组混淆) 添加干扰(注释、空格、无效代码) 使用Babel工具链可以自动化实现这些混淆技术。结合动态混淆可以进一步提高安全性,保护关键业务逻辑不被轻易逆向分析。