这两天看了什么是控制流平坦化,又研究了下ast语法数,如何把混淆乱序后的代码恢复之前的样子。
知识点:1.js包为 recast
2.Recast除了parse/print/builder以外,
还有三项主要功能: run: 通过命令行读取js文件,并转化成ast以供处理。
tnt(recast.types.namedTypes): 通过assert()和check(),可以验证ast对象的类型。
visit: 遍历ast树,获取有效的AST对象并进行更改。
下面举例一个案例:
通过 https://obfuscator.io/ 将原代码混淆乱序后输出,复制粘贴到饭碗里,再格式化得到:
function test() {
var _0x238e3e = {
'puzUT': '4|3|2|1|0', 'Rgtsl': function (_0x3a9ad8, _0x312b1a) {
return _0x3a9ad8 < _0x312b1a;
}, 'HFHYB': function (_0x13ea88, _0x2533df) {
return _0x13ea88 + _0x2533df;
}
};
var _0x388255 = _0x238e3e['puzUT']['split']('|');
var _0xfbda52 = 0x0;
while (!![]) {
switch (_0x388255[_0xfbda52++]) {
case'0':
return _0x2ce383;
case'1':
for (var _0x1fa16b = 0x0; _0x238e3e['Rgtsl'](_0x1fa16b, _0x3c5b0c['length']); _0x1fa16b++) {
_0x2ce383['push'](String['fromCharCode'](_0x3c5b0c[_0x1fa16b]));
}
continue;
case'2':
var _0x2ce383 = [];
continue;
case'3':
_0x3c5b0c[0x0] = _0x238e3e['HFHYB'](_0x3c5b0c[0x0], 0x1);
continue;
case'4':
var _0x3c5b0c = [0x67, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64];
continue;
}
break;
}
}
var s = test();
console['log'](s['join'](''));
在将上方代码粘贴到 https://astexplorer.net/,可以看到ast语法树
下面就是代码环节,如何将乱序后的代码有序输出。
将混淆乱序后的代码命名为demo.js,再命名一个read.js写入以下代码:
const recast = require("recast");
const TNT = recast.types.namedTypes;
const {
expressionStatement,
memberExpression,
identifier: id,
callExpression,
stringLiteral //字符串
} = recast.types.builders;
recast.run(function (ast, printSource) {
recast.visit(ast, {
visitSwitchCase(node) {
var codes = "";
// console.log(node.value.consequent.length) // 打印case:下的有几条语句 1 2 2 2 2
for (var i = 0; i < node.value.consequent.length; i++) {
// console.log(node.value.consequent[i]) // SwitchCase节点
// 检测语句是否有continue,即检测节点中是否有 ContinueStatement
if (TNT.ContinueStatement.check(node.value.consequent[i])) {
continue
}
aaa = recast.print(node.value.consequent[i]).code // 将ast组装成能执行的代码
codes += aaa ; // 这句貌似没啥用??
// console.log(codes) //打印每条语句
// 等效于上面1行
// printSource(node.value.consequent[i]);console.log('\n'); // 输出AST对象对应的源码
}
// printSource(stringLiteral(codes));console.log('\n');
exp = expressionStatement(callExpression(memberExpression(id('console'), id('1og')), [stringLiteral(codes)]));
// console.log(recast.print(exp).code) //打印 console.1og(".......");
node.value.consequent.unshift(exp); // 向数组的开头添加一个或更多元素 consequent是一个数组,相当于在原代码中增加exp
return false
}
});
// console.log(recast.print(ast).code)
printSource(ast)
});
命令行执行 node read demo.js
输出得到并命名为demo-2.js:
function test() {
var _0x238e3e = {
'puzUT': '4|3|2|1|0', 'Rgtsl': function (_0x3a9ad8, _0x312b1a) {
return _0x3a9ad8 < _0x312b1a;
}, 'HFHYB': function (_0x13ea88, _0x2533df) {
return _0x13ea88 + _0x2533df;
}
};
var _0x388255 = _0x238e3e['puzUT']['split']('|');
var _0xfbda52 = 0x0;
while (!![]) {
switch (_0x388255[_0xfbda52++]) {
case '0':
console.log("return _0x2ce383;");
return _0x2ce383;
case '1':
console.log(
"for (var _0x1fa16b = 0x0; _0x238e3e['Rgtsl'](_0x1fa16b, _0x3c5b0c['length']); _0x1fa16b++) {\r\n_0x2ce383['push'](String['fromCharCode'](_0x3c5b0c[_0x1fa16b]));}");
for (var _0x1fa16b = 0x0; _0x238e3e['Rgtsl'](_0x1fa16b, _0x3c5b0c['length']); _0x1fa16b++) {
_0x2ce383['push'](String['fromCharCode'](_0x3c5b0c[_0x1fa16b]));
}
continue;
case '2':
console.log("var _0x2ce383 = [];");
var _0x2ce383 = [];
continue;
case '3':
console.log("_0x3c5b0c[0x0] = _0x238e3e['HFHYB'](_0x3c5b0c[0x0], 0x1);");
_0x3c5b0c[0x0] = _0x238e3e['HFHYB'](_0x3c5b0c[0x0], 0x1);
continue;
case '4':
console.log(
"var _0x3c5b0c = [0x67, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64];");
var _0x3c5b0c = [0x67, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64];
continue;
}
break;
}
}
var s = test();
console['log'](s['join'](''));
运行demo-2.js得到:
var _0x3c5b0c = [0x67, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64];
_0x3c5b0c[0x0] = _0x238e3e['HFHYB'](_0x3c5b0c[0x0], 0x1);
var _0x2ce383 = [];
for (var _0x1fa16b = 0x0; _0x238e3e['Rgtsl'](_0x1fa16b, _0x3c5b0c['length']); _0x1fa16b++) {
_0x2ce383['push'](String['fromCharCode'](_0x3c5b0c[_0x1fa16b]));
}
return _0x2ce383;
再将demo-2.js修改为:
function test() {
var _0x238e3e = {
'puzUT': '4|3|2|1|0', 'Rgtsl': function (_0x3a9ad8, _0x312b1a) {
return _0x3a9ad8 < _0x312b1a;
}, 'HFHYB': function (_0x13ea88, _0x2533df) {
return _0x13ea88 + _0x2533df;
}
};
var _0x3c5b0c = [0x67, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64];
_0x3c5b0c[0x0] = _0x238e3e['HFHYB'](_0x3c5b0c[0x0], 0x1);
var _0x2ce383 = [];
for (var _0x1fa16b = 0x0; _0x238e3e['Rgtsl'](_0x1fa16b, _0x3c5b0c['length']); _0x1fa16b++) {
_0x2ce383['push'](String['fromCharCode'](_0x3c5b0c[_0x1fa16b]));
}
return _0x2ce383;
}
var s = test();
console['log'](s['join'](''));
输出得到 hello world,相比混淆乱序的更方便调试。
法二:
直接在demo.js中修改代码,无需read.js。demo.js代码如下:
const sss = `function test() {
var _0x238e3e = {
'puzUT': '4|3|2|1|0', 'Rgtsl': function (_0x3a9ad8, _0x312b1a) {
return _0x3a9ad8 < _0x312b1a;
}, 'HFHYB': function (_0x13ea88, _0x2533df) {
return _0x13ea88 + _0x2533df;
}
};
var _0x388255 = _0x238e3e['puzUT']['split']('|');
var _0xfbda52 = 0x0;
while (!![]) {
switch (_0x388255[_0xfbda52++]) {
case'0':
return _0x2ce383;
case'1':
for (var _0x1fa16b = 0x0; _0x238e3e['Rgtsl'](_0x1fa16b, _0x3c5b0c['length']); _0x1fa16b++) {
_0x2ce383['push'](String['fromCharCode'](_0x3c5b0c[_0x1fa16b]));
}
continue;
case'2':
var _0x2ce383 = [];
continue;
case'3':
_0x3c5b0c[0x0] = _0x238e3e['HFHYB'](_0x3c5b0c[0x0], 0x1);
continue;
case'4':
var _0x3c5b0c = [0x67, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x77, 0x6f, 0x72, 0x6c, 0x64];
continue;
}
break;
}
}
var s = test();
console['log'](s['join'](''));`
const recast = require("recast");
// // 用 parse 解析成 ast树
const ast = recast.parse(sss)
const TNT = recast.types.namedTypes;
const {
expressionStatement,
memberExpression,
identifier: id,
callExpression,
stringLiteral //字符串
} = recast.types.builders;
recast.visit(ast, {
visitSwitchCase(node) {
var codes = "";
// console.log(node.value.consequent.length) // 打印case:下的有几条语句 1 2 2 2 2
for (var i = 0; i < node.value.consequent.length; i++) {
// console.log(node.value.consequent[i]) // SwitchCase节点
// 检测语句是否有continue,即检测节点中是否有 ContinueStatement
if (TNT.ContinueStatement.check(node.value.consequent[i])) {
continue
}
aaa = recast.print(node.value.consequent[i]).code // 将ast组装成能执行的代码
codes += aaa; // 这句貌似没啥用??
// console.log(codes) //打印每条语句
// 等效于上面1行
// printSource(node.value.consequent[i]);console.log('\n'); // 输出AST对象对应的源码
}
// printSource(stringLiteral(codes));console.log('\n'); //"......"
exp = expressionStatement(callExpression(memberExpression(id('console'), id('1og')), [stringLiteral(codes)]));
// console.log(recast.print(exp).code) //打印 console.1og(".......");
node.value.consequent.unshift(exp); // 向数组的开头添加一个或更多元素,consequent是一个数组,相当于在原代码中增加exp语句
return false
}
});
console.log(recast.print(ast).code)
运行得到:也就是demo-2.js的内容,再运行,就得到有序的代码。