Exploring Abstract Syntax Tree (AST) manipulation to understand how obfuscation tools work.
The first step involves parsing JavaScript code into an AST structure:
let a,b=1;
// 注释
var obj = {
"name":1+3*2,
"func":function add(a,b){
let a1=3;
return a+b
}
}
function add(a,v){
return a+v;
}
let xx;
xx = 12;
Using Babel's parser and related utilities, we can reconstruct equivalent code:
let fs = require("fs");
let parser = require("@babel/parser");
let types = require("@babel/types");
let generator = require("@babel/generator").default;
let traverse = require("@babel/traverse").default;
let jscode = fs.readFileSync("./input.js", { encoding: "utf-8" });
let ast = parser.parse(jscode, { sourceType: "module" });
let one = types.variableDeclaration("let",[
types.variableDeclarator(types.identifier("a"),null),
types.variableDeclarator(types.identifier("b"),types.numericLiteral(120))
]);
console.log(generator(one).code);
let two = types.variableDeclaration("var",[
types.variableDeclarator(types.identifier("obj"),types.objectExpression([
types.objectProperty(types.stringLiteral("name"),types.binaryExpression("+",types.numericLiteral(1),types.binaryExpression("*",types.numericLiteral(3),types.numericLiteral(2)))),
types.objectProperty(types.stringLiteral("func"),types.functionExpression(null,[types.identifier("a"),types.identifier("b")],types.blockStatement([
types.variableDeclaration("let",[types.variableDeclarator(types.identifier("a1"),types.numericLiteral(3))]),
types.returnStatement(types.binaryExpression("+",types.identifier("a"),types.identifier("b")))
])))
]))
]);
console.log(generator(two).code);
let three = types.functionDeclaration(types.identifier("add"),[types.identifier("a"),types.identifier("v")],types.blockStatement([types.returnStatement(types.binaryExpression("+",types.identifier("a"),types.identifier("v")))]));
console.log(generator(three).code);
let four = types.variableDeclaration("let",[types.variableDeclarator(types.identifier("xx"),null)]);
console.log(generator(four).code);
let five = types.expressionStatement(types.assignmentExpression("=",types.identifier("xx"),types.numericLiteral(12)));
console.log(generator(five).code);
One approach to obufscate code uses a switch-case structure where execution order is randomized:
function sw1(){
traverse(newAst, {
FunctionDeclaration(path) {
let statments = path.node.body.body.map(function (v, i) {
return { index: i, value: v }
});
let i = statments.length;
while (i > 0) {
let j = Math.floor(Math.random() * i--);
[statments[i], statments[j]] = [statments[j], statments[i]]
}
let orderArr = [],cases = [];
statments.map(function (v, i) {
orderArr[v['index']] = i+1;
let ca = [];
ca.push(v["value"])
if(!types.isReturnStatement(v['value'])){
ca.push(types.continueStatement(null))
}
cases.push(types.switchCase(types.numericLiteral(i+1),ca));
});
let switchStatement = types.switchStatement(types.unaryExpression("+",types.memberExpression(types.identifier("arr"),types.updateExpression("++",types.identifier("index")),true)),cases);
let vardec = types.variableDeclaration('let', [types.variableDeclarator(types.identifier("arr"), types.callExpression(types.memberExpression(types.stringLiteral(orderArr.join("|")), types.identifier("split"), false), [types.stringLiteral("|")])),types.variableDeclarator(types.identifier("index"),types.numericLiteral(0))])
let whileStatement = types.whileStatement(types.unaryExpression("!",types.unaryExpression("!",types.arrayExpression([]))),types.blockStatement([switchStatement]));
path.get("body").replaceWith(types.blockStatement([vardec,whileStatement]));
}
})
}
A alternative method employs a more complex control flow obfuscaiton:
function pushArr(arr) {
let rstr;
while (true) {
rstr = Math.random().toString(36).substring(2, 10);
if (arr.indexOf(rstr) == -1) {
arr.push(rstr);
break
}
}
return rstr;
}
function sw2() {
traverse(newAst, {
FunctionDeclaration(path) {
let blockStatement = path.node.body;
let bigA = [];
pushArr(bigA)
let lastIndex = blockStatement.body.length - 1;
let createIndex = types.variableDeclaration("var", [types.variableDeclarator(types.identifier("index"), types.stringLiteral(bigA[0]))]);
let cases = [];
let index = 0;
blockStatement.body.map(function (i, v) {
let continueOrbreak = types.continueStatement(null);
if (v == lastIndex) {
continueOrbreak = types.breakStatement(null);
}
if (types.isWhileStatement(i)) {
let continueOrbreak = types.continueStatement(null);
let test = i.test;
let body = i.body.body;
let start = index;
body.map(function (i, v) {
let swcase = types.switchCase(types.stringLiteral(bigA[index]), [i, types.expressionStatement(types.assignmentExpression("=", types.identifier("index"), types.stringLiteral(pushArr(bigA)))), continueOrbreak]);
cases.push(swcase);
index++
})
let express = types.expressionStatement(types.conditionalExpression(test, types.assignmentExpression("=", types.identifier("index"), types.stringLiteral(bigA[start])), types.assignmentExpression("=", types.identifier("index"), types.stringLiteral(pushArr(bigA)))));
let swcase = types.switchCase(types.stringLiteral(bigA[index]), [express, continueOrbreak]);
cases.push(swcase);
} else {
let swcase = types.switchCase(types.stringLiteral(bigA[index]), [i, types.expressionStatement(types.assignmentExpression("=", types.identifier("index"), types.stringLiteral(pushArr(bigA)))), continueOrbreak]);
cases.push(swcase);
}
index++;
})
while (index > 0) {
let j = Math.floor(Math.random() * index--);
[cases[index], cases[j]] = [cases[j], cases[index]]
}
let switchCase = types.switchStatement(types.identifier("index"), cases);
let whileStatement = types.whileStatement(types.unaryExpression("!", types.unaryExpression("!", types.arrayExpression([]))), types.blockStatement([switchCase, types.breakStatement(null)]))
path.get("body").replaceWith(types.blockStatement([createIndex, whileStatement]));
}
});
}
sw2()
This transformation results in a complex control flow that makes reverse engineering harder:
var arg1 = '7D00060AB58C935689F6B166E65F46884E5F69AC';
var l = function () {
var _0x5e8b26 = "3000176000856006061501533003690027800375";
String['prototype']['hexXor'] = function (_0x4e08d8) {
var _0x5a5d3b = '';
for (var _0xe89588 = 0; _0xe89588 < this['length'] && _0xe89588 < _0x4e08d8['length']; _0xe89588 += 2) {
var _0x401af1 = parseInt(this['slice'](_0xe89588, _0xe89588 + 2), "16");
var _0x105f59 = parseInt(_0x4e08d8['slice'](_0xe89588, _0xe89588 + 2), "16");
var _0x189e2c = (_0x401af1 ^ _0x105f59)['toString']("16");
if (_0x189e2c['length'] == 1) {
_0x189e2c = '\x30' + _0x189e2c;
}
_0x5a5d3b += _0x189e2c;
}
return _0x5a5d3b;
};
String['prototype']['unsbox'] = function () {
var _0x4b082b = [15, 35, 29, 24, 33, 16, 1, 38, 10, 9, 19, 31, 40, 27, 22, 23, 25, 13, 6, 11, 39, 18, 20, 8, 14, 21, 32, 26, 2, 30, 7, 4, 17, 5, 3, 28, 34, 37, 12, 36];
var _0x4da0dc = [];
var _0x12605e = '';
for (var _0x20a7bf = 0; _0x20a7bf < this['length']; _0x20a7bf++) {
var _0x385ee3 = this[_0x20a7bf];
for (var _0x217721 = 0; _0x217721 < _0x4b082b['length']; _0x217721++) {
if (_0x4b082b[_0x217721] == _0x20a7bf + 1) {
_0x4da0dc[_0x217721] = _0x385ee3;
}
}
}
_0x12605e = _0x4da0dc['join']("");
return _0x12605e;
};
var _0x23a392 = arg1['unsbox']();
arg2 = _0x23a392['hexXor'](_0x5e8b26);
return arg2;
};
console.log(l());