关注它,不迷路。
本文章中所有内容仅供学习交流,不可用于任何商业用途和非法用途,否则后果自负,如有侵权,请联系作者立即删除!
1. 需求分析
最近网络上对阿里226的分析文章突然多了起来,我也来蹭一波热点试试。我还原的是下面的这个js:
https://aeis.alicdn.com/AWSC/fireyejs/1.226.0/fireyejs.js
话不多说,直接开干。
2. 思路详解
1.拿到压缩的混淆代码,先用反混淆通用模板处理一下,将其格式化,这样我们才好分析从哪里下手。
2.格式化后,看到了大量的 void 语句:
void (3 == h ? r = U < F.length ? 7 : 6 : h < 3 ? 1 == h ? r = y < O.length ? 8 : 1 : h < 1 ? (y++, r = 16) : (U++, r = 48) : 5 == h ? r = D < T.length ? 9 : 11 : h < 5 ? r = void 0 : (D++, r = 80));
它的父节点竟然是 表达式语句,相当于这里的 void 操作符可有可无,写个插件,可以轻松还原:
const restoreUnaryExpression =
{
UnaryExpression(path)
{
let {node,parentPath} = path;
if (!parentPath.isExpressionStatement({"expression":node}) || !types.isConditionalExpression(node.argument))
{
return;
}
path.replaceWith(node.argument);
},
}
还原后的代码如下:
3 == h ? r = U < F.length ? 7 : 6 : h < 3 ? 1 == h ? r = y < O.length ? 8 : 1 : h < 1 ? (y++, r = 16) : (U++, r = 48) : 5 == h ? r = D < T.length ? 9 : 11 : h < 5 ? r = void 0 : (D++, r = 80);
3.接下来就是将上面的条件表达式语句转换为 if 语句,还原后的代码如下:
if (3 == h) {
if (U < F.length) {
r = 7;
} else {
r = 6;
}
} else {
if (h < 3) {
if (1 == h) {
if (y < O.length) {
r = 8;
} else {
r = 1;
}
} else {
if (h < 1) {
y++,
r = 16;
} else {
U++,
r = 48;
}
}
} else {
if (5 == h) {
if (D < T.length) {
r = 9;
} else {
r = 11;
}
} else {
if (h < 5) {
r = void 0;
} else {
D++,
r = 80;
}
}
}
}
4.接下来就是将if语句转换为switch语句了,不过从上面的代码可以看到,变量 h 和数字 比较,有时候在左边,有时候又在右边,不方便处理,因此写个插件将其统一一下,变量全部放到左边去,处理后的代码如下:
if (h == 3) {
if (U < F.length) {
r = 7;
} else {
r = 6;
}
} else {
if (h < 3) {
if (h == 1) {
if (y < O.length) {
r = 8;
} else {
r = 1;
}
} else {
if (h < 1) {
y++,
r = 16;
} else {
U++,
r = 48;
}
}
} else {
if (h == 5) {
if (D < T.length) {
r = 9;
} else {
r = 11;
}
} else {
if (h < 5) {
r = void 0;
} else {
D++,
r = 80;
}
}
}
}
5.这样处理后,可以将 if语句转换为 switch语句了,还原后的代码如下:
switch (h) {
case 0:
y++,
r = 16;
break;
case 1:
if (y < O.length) {
r = 8;
} else {
r = 1;
}
break;
case 2:
U++,
r = 48;
break;
case 3:
if (U < F.length) {
r = 7;
} else {
r = 6;
}
break;
case 4:
r = void 0;
break;
case 5:
if (D < T.length) {
r = 9;
} else {
r = 11;
}
break;
case 6:
D++,
r = 80;
break;
}
6.处理完毕后,发现switch嵌套了switch,是由三个变量控制的,因此可以写代码,将三重的switch语句转换为一重,方便后续继续还原:
7.switch节点没问题了,接下来就是还原 控制流了,但是在还原控制流之前,需要做一些预处理,比如还原逗号表达式,将多重嵌套的赋值语句提取出来:
x[d = d.split("").reverse().join("")]("");
......
var v = "b",l = x[p = (p += "oLtes").split("").reverse().join("")][v += "ind"](x),u = "c";
还原成:
d = d.split("").reverse().join("");
x[d]("");
......
var v = "b";
p += "oLtes";
p = p.split("").reverse().join("");
v += "ind";
var l = x[p][v](x);
总之,再去控制流之前,把一些能处理的节点,优先给他处理掉,主要是为了能使用星球里的九大节点合并算法。
8.接下来就是去除控制流的虚假分支了,大量充斥着这样的代码:
case 10561:
go = 3;
xe = 3 * go;
Fo = xe > -74;
i(38, ye, x, i, !0, V, !0);
Si = Fo ? 22049 : 1573;
break;
这里的Fo恒为 true,因此,可以改写成这样:
case 10561:
go = 3;
xe = 3 * go;
Fo = xe > -74;
i(38, ye, x, i, !0, V, !0);
Si = 22049;
break;
其他的节点是否也是如此呢?请大家自行验证。
9.接下来就是真正的去除控制流了,使用九大节点合并算法,处理后,还剩 34 个case没有被还原:
为啥没有被还原,分析了一下,原来是 一个循环里面的代码太多,合并算法不适用,因此,需要想个方法来让它适用。
case 4452:
Si = eo >= 0 ? 26306 : 12289;
break;
......
case 14752:
m = 0 === S;
if (m) {
m = 0 === j;
}
f = m;
if (f) {
f = 0 === A;
}
m = f;
Si = m ? 22880 : 13858;
break;
case 22880:
eo--;
Si = 4452;
break;
分析上面的代码,其实可以合并成这样:
不适用,因此,需要想个方法来让它适用。
case 4452:
Si = eo >= 0 ? 26306 : 12289;
break;
......
case 14752:
m = 0 === S;
if (m) {
m = 0 === j;
}
f = m;
if (f) {
f = 0 === A;
}
m = f;
if(m)
{
eo--;
continue;
}
Si = 13858;
break;
case 22880:
eo--;
Si = 4452;
break;
像这样的节点可以找规律写代码合并。这样处理后,再使用 九大节点合并算法在处理一次,就可以完全还原了。
还剩 8个 case,这就无关紧要了。
10.接下来合并字符串了,代码中有大量类似下面的代码:
var T = "167,xspulrlrlsx";
var L = "";
var D = 0;
while (D < T["length"]) {
var _ = 66 ^ T["charCodeAt"](D);
L += String["fromCharCode"](_);
D++;
}
可以说,非常的多,它其实可以被计算出来具体的值,并进行合并替换:
var L = 'stun:127.0.0.1:';
上面的代码就缩减成了一行,可以所非常的爽!
11.紧跟着,就是赋值语句的还原了,因为是反复被赋值,没法用binding来处理,可以试试星球里的脚本:
be = "<br>";
Se = be;
还原后:
be = "<br>";
Se = "<br>";
12.上面的代码处理后,还可以进一步处理,比如删除 be 这个语句变成:
Se = "<br>";
当然,这需要视情况而定。
还原后的代码如下:
拿到网上替换调试:
还原到这里基本就差不多了,还有一些小的混淆代码可以处理,不再本文中讲述。