前言
因为刚入门js逆向
所以可能存在一些比较无语的操作
请见谅
开始
我们依然以
https://bbs.tampermonkey.net.cn/thread-3395-1-1.html最简单的为例
这节课我们尝试使用AST还原代码
AST是什么
在我们写出源代码之后
会经过词法分析,语法分析,生成一个AST树,最后由编译器编译出二进制程序,或由解释器进行运行
可以看出AST是计算机阅读源代码的语言
因为我们使用源代码进行混淆还原是极其蛋疼的一件事(也可以)
所以我们使用AST从计算机的角度来进行代码还原
使用的插件是babel
用了@babel/traverse、@babel/generator、@babel/types、@babel/parser
parser用于解析源代码到ast
generator用于从ast生成到代码
traverse用于遍历ast函数
types用于对对应的ast来进行判断以及生成ast节点
在nodejs下安装一下
npm i @babel/parser
npm i @babel/types
npm i @babel/traverse
npm i @babel/generator
然后写代码引入
const fs = require("fs");
const parser = require("@babel/parser");
const types = require("@babel/types");
const generator = require("@babel/generator").default;
const traverse = require("@babel/traverse").default;
我将混淆代码放到了fuck.js文件里,读入并且生成AST树
const fuck_js = fs.readFileSync("./fuck.js", {
encoding: "utf-8",
});
let ast = parser.parse(fuck_js);
我们先到Ast Exploer观察一下
发现_0x43f9函数解析成AST后是一个CallExpression节点
名字在CallExpression节点下的callee的name上
![图片.png](data/attachment/forum/202210/09/230106km9pdclm9h2iihvp.png)
而传入的两个参数在arguments上
![图片.png](data/attachment/forum/202210/09/230158najv4mmlvotjasys.png)
我们酝酿一下,首先找到_0x43f9函数,然后提取出来参数,最后替换回去
理论建立完毕,实战开始
traverse传入两个参数,一个是ast树,另一个是在ast由traverse遍历节点的时候,一旦发现第二个参数的对象里有对应的节点名称,就会进行回调,传给你一个path参数,path相当于两个节点之间的连接点,我们可以通过parent.node拿到连接点下方的函数,也可以随意切断下方的节点,更换为其他节点等操作。
我们通过path.node.callee.name拿到函数名字,判断是否等于_0x43f9
然后用path.node.arguments读取所有参数,并且放到数组里
const fuck_js = fs.readFileSync("./fuck.js", {
encoding: "utf-8",
});
let ast = parser.parse(fuck_js);
traverse(ast, {
CallExpression(path) {
if (path.node.callee.name === "_0x43f9") {
let arg = [];
path.node.arguments.forEach((item) => {
arg.push(item.value);
});
console.log(arg);
}
},
});
可以看到拿到了数组
![图片.png](data/attachment/forum/202210/09/231435ygzr3zuexen0n2tv.png)
然后我们的目标就变成了替换函数成字符串了
我们把原来的函数抽到nodejs里,直接在index.js里调用
因为他判断了window变量,所以我也跟随声明了一个windows变量
let window = {};
let __0xecbce = [
"w7Q+w6jDqcKnMMOewpgF",
"TsOTIcKVw4IjwpsGc2NVQR/CtQ==",
"5YiH6ZmZ54uo5pyg5YyF772HfcOr5L+Z5a6R5p+y5byO56ms",
"w4I7Ohwo",
"dXkZw4kL",
"wrnClsKNIsKX",
"54if5p6c5Y6v772NCGDkv7rlr6zmnYXlvqPnq4nvvaPov4Hor6Xmla3mjIXmi6jkuaTnm7Dlt4zkv4o=",
"wpk8WMOuQA==",
"wqojw6I=",
];
(function (_0x4a174f, _0x2b3ed7) {
var _0x51adc6 = function (_0x4c4f72) {
while (--_0x4c4f72) {
_0x4a174f["push"](_0x4a174f["shift"]());
}
};
_0x51adc6(++_0x2b3ed7);
})(__0xecbce, 0x155);
var _0x43f9 = function (_0x15b28a, _0x370d4f) {
_0x15b28a = _0x15b28a - 0x0;
var _0x958f5c = __0xecbce[_0x15b28a];
if (_0x43f9["initialized"] === undefined) {
(function () {
var _0x375bef =
typeof window !== "undefined"
? window
: typeof process === "object" &&
typeof require === "function" &&
typeof global === "object"
? global
: this;
var _0x36add6 =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
_0x375bef["atob"] ||
(_0x375bef["atob"] = function (_0x2b8d70) {
var _0x786ab7 = String(_0x2b8d70)["replace"](/=+$/, "");
for (
var _0x2efd19 = 0x0,
_0x5032a4,
_0x1a3fe4,
_0x5cb9a4 = 0x0,
_0x3d9d3c = "";
(_0x1a3fe4 = _0x786ab7["charAt"](_0x5cb9a4++));
~_0x1a3fe4 &&
((_0x5032a4 =
_0x2efd19 % 0x4 ? _0x5032a4 * 0x40 + _0x1a3fe4 : _0x1a3fe4),
_0x2efd19++ % 0x4)
? (_0x3d9d3c += String["fromCharCode"](
0xff & (_0x5032a4 >> ((-0x2 * _0x2efd19) & 0x6))
))
: 0x0
) {
_0x1a3fe4 = _0x36add6["indexOf"](_0x1a3fe4);
}
return _0x3d9d3c;
});
})();
var _0x4d9f1d = function (_0x11262d, _0x148d0f) {
var _0x31b35b = [],
_0x2d6c29 = 0x0,
_0x24b4f5,
_0x196a93 = "",
_0x56258a = "";
_0x11262d = atob(_0x11262d);
for (
var _0x3d7663 = 0x0, _0x34f9e9 = _0x11262d["length"];
_0x3d7663 < _0x34f9e9;
_0x3d7663++
) {
_0x56258a +=
"%" +
("00" + _0x11262d["charCodeAt"](_0x3d7663)["toString"](0x10))[
"slice"
](-0x2);
}
_0x11262d = decodeURIComponent(_0x56258a);
for (var _0x48101c = 0x0; _0x48101c < 0x100; _0x48101c++) {
_0x31b35b[_0x48101c] = _0x48101c;
}
for (_0x48101c = 0x0; _0x48101c < 0x100; _0x48101c++) {
_0x2d6c29 =
(_0x2d6c29 +
_0x31b35b[_0x48101c] +
_0x148d0f["charCodeAt"](_0x48101c % _0x148d0f["length"])) %
0x100;
_0x24b4f5 = _0x31b35b[_0x48101c];
_0x31b35b[_0x48101c] = _0x31b35b[_0x2d6c29];
_0x31b35b[_0x2d6c29] = _0x24b4f5;
}
_0x48101c = 0x0;
_0x2d6c29 = 0x0;
for (var _0x206f10 = 0x0; _0x206f10 < _0x11262d["length"]; _0x206f10++) {
_0x48101c = (_0x48101c + 0x1) % 0x100;
_0x2d6c29 = (_0x2d6c29 + _0x31b35b[_0x48101c]) % 0x100;
_0x24b4f5 = _0x31b35b[_0x48101c];
_0x31b35b[_0x48101c] = _0x31b35b[_0x2d6c29];
_0x31b35b[_0x2d6c29] = _0x24b4f5;
_0x196a93 += String["fromCharCode"](
_0x11262d["charCodeAt"](_0x206f10) ^
_0x31b35b[(_0x31b35b[_0x48101c] + _0x31b35b[_0x2d6c29]) % 0x100]
);
}
return _0x196a93;
};
_0x43f9["rc4"] = _0x4d9f1d;
_0x43f9["data"] = {};
_0x43f9["initialized"] = !![];
}
var _0x106a87 = _0x43f9["data"][_0x15b28a];
if (_0x106a87 === undefined) {
if (_0x43f9["once"] === undefined) {
_0x43f9["once"] = !![];
}
_0x958f5c = _0x43f9["rc4"](_0x958f5c, _0x370d4f);
_0x43f9["data"][_0x15b28a] = _0x958f5c;
} else {
_0x958f5c = _0x106a87;
}
return _0x958f5c;
};
然后我们编写一下替换代码
调用let result = _0x43f9(...arg);获取结果
然后使用path.replaceWith(types.valueToNode(result));替换path下方的ast节点
types.valueToNode(result)是生成一个ast节点
而path.replaceWith是将自身的下方节点替换为传入的节点
traverse(ast, {
CallExpression(path) {
if (path.node.callee.name === "_0x43f9") {
let arg = [];
path.node.arguments.forEach((item) => {
arg.push(item.value);
});
console.log(arg)
let result = _0x43f9(...arg);
path.replaceWith(types.valueToNode(result));
}
},
});
然后我们生成代码看看
let code = generator(ast, ).code;
fs.writeFile("./convert.js", code, (err) => {});
![图片.png](data/attachment/forum/202210/09/231808etbqvq1v1qubu1vv.png)
可以看到成功了,但是还有\u这种编码,可以改一下ast的输出参数
let code = generator(ast, {
jsescOption: { minimal: true },
}).code;
fs.writeFile("./convert.js", code, (err) => {});
发现编码转换成功了~~~~
![图片.png](data/attachment/forum/202210/09/231858zns86npvv38kf0ta.png)
结语
撒花~