免责声明:
本篇文章仅作为学术研究和安全交流使用,切勿用于商业用途。如因违反规定产生任何法律纠纷,本人概不负责。如果本篇文章影响到官方利益,请添加文末的作者微信号告知,本人会在第一时间将文章删除。
一.前言
看了大佬的 JS逆向之字节系列某量算数jsvmp算法分析及深度还原 这篇文章后,也想照着学一波,看到这里时,发现他将 if 语句转换成了switch语句,再进行分析:
群里问过大佬怎么弄的,他说是手动还原的,因为手动要比写还原插件的时间少很多。
碰巧群里有另外一个兄弟用AST进行了还原,学习了一波,思路已掌握。本文的大部分思路也来自他的代码,在此感谢。
二.分析
把网站上面的代码复制下来,并保存为 acrawler.js,逐个进行分析。
语句①:
if ((A = x) > 11)
throw S[R--];
没有括号,给它加上:
if ((A = x) > 11)
{
throw S[R--];
}
语句②:
(A = x) > 10 ? S[++R] = void 0 : A > 1 ? (C = S[R--],S[R] = S[R] >= C) : A > -1 && (S[++R] = null);
条件表达式,转成 if 语句:
if ((A = x) > 10) {
S[++R] = void 0;
} else {
if (A > 1) {
C = S[R--],
S[R] = S[R] >= C;
} else {
if (A > -1) {
S[++R] = null;
}
}
}
语句③:
A > 5 && (S[R] = h(S[R]))
逻辑表达式,转成 if 语句:
if (A > 5) {
S[R] = h(S[R]);
}
经过这些还原步骤后,做成了统一的代码,就可以愉快的进行插桩了。
问提来了,为什么要插桩,哪些地方需要插?
三.插桩
经过上面的那些步骤后,我们来看代码
在插桩获取 j的值之前,我们要知道,哪些地方才需要获取j的值。
答案: 每个 if 语句里面参与加密计算的语句。
语句①:
if (x >>= 2, A < 1) {
A = 3 & x;
.....
}
这一层还在外层,无法确定 j的值,因此不需要插桩。
语句②:
f((A = x) > 10)
{
S[++R] = void 0;
}
包含了加密相关的代码,S[++R] = void 0;,需要进行插桩,获取 j的值。
f((A = x) > 10)
{
mxt_("mxt_5448_1", j);
S[++R] = void 0;
}
添加的这一行代码,用于保存到 case_node_map 变量中:
var case_node_map = new Map();
window.case_node_map = case_node_map;
function mxt_(sign, step) {
case_node_map.set(sign, step)
}
同理,语句③、④、⑥一样需要进行插桩获取j的值,语句⑤则不用。
经过这样一波分析后,找到了规律,类似于下面的if语句:
if ((A = x) > 10) {
S[++R] = void 0;
} else {
if (A > 1) {
C = S[R--],
S[R] = S[R] >= C;
} else {
if (A > -1) {
S[++R] = null;
}
}
}
及其兄弟节点,都需要进行插桩:
if ((A = x) > 11) {
throw S[R--];
}
//下面是兄弟节点:
if (A > 7) {
for (C = S[R--],
z = v(b, O),
A = "",
P = i.q[z][0]; P < i.q[z][1]; P++)
A += String.fromCharCode(r ^ i.p[P]);
O += 4,
S[R--][A] = C;
} else {
if (A > 5) {
S[R] = h(S[R]);
}
}
分析后写下 AST代码,生成插桩后的代码,部分截图如下:
经过这一波插桩后,就可以使用reres插件替换网页上的js,从而获取case_node_map,及保存了 节点位置和j的值,便于还原分析。
四.获取j的值
直接使用Reres插件,将整个js文件进行映射,得到 case_node_map 变量的值:
这里说明一下,映射后,可能得到的size不一样,正常是 126 个 元素。
再把这个map转换为Array:
var arr = Array.from(case_node_map);
copy(arr);
把这个数组复制到ast文件中,然后进行比对,生成switch节点即可。部分代码:
if (case_node_map.has(tmp)) {
let i = case_node_map.get(tmp);
if (!types.isReturnStatement(consequent.body[consequent.body.length - 1])) {
consequent.body.push(types.BreakStatement())
}
caseNodes[i] = types.SwitchCase(types.NumericLiteral(i), consequent.body);
}
完整代码我会放在星球里,需要的可以自取。
感谢阅读。
另外我建了个AST的交流群,想进群的朋友可以加我微信。