原文地址:https://zhuoyue360.com/jsnx/110.html
1. 期望
这是一个瑞数5代解混淆的案例,我们本章节需要做的是把if节点
的内容转换成switch-case
内容.以此来熟悉AST对JS混淆的对抗.
原始代码:
function whileState() {
while (1) {
aV = cA[wU++];
if (aV < 4) {
if (aV < 1) {
zT = window, kD = String, bO = Array, xX = document, nZ = Date;
} else if (aV < 2) {
iG = zT['ab'] = {};
} else if (aV < 3) {
iG = zT['ab'];
} else {
mM = !iG;
}
} else {
if (aV < 5) {
xT(0);
} else if (aV < 6) {
if (!mM) wU += 1;
} else if (aV < 7) {
lG = [4, 16, 64, 256, 1024, 4096, 16384, 65536];
} else {
return;
}
}
}
}
期望代码:
function whileState() {
while (1) {
switch (cA[wU++]) {
case 0:
zT = window, kD = String, bO = Array, xX = document, nZ = Date;
break;
case 1:
iG = zT['ab'] = {};
break;
case 2:
iG = zT['ab'];
break;
case 3:
mM = !iG;
break;
case 4:
xT(0);
break;
case 5:
if (!mM) wU += 1;
break;
case 6:
lG = [4, 16, 64, 256, 1024, 4096, 16384, 65536];
break;
case 7:
return;
break;
}
}
}
2. 思路分析
首先,我们需要明确一点,aV
的索引是从0开始的,它是不可能为负数的.
那么也就可以有如下的转换:
if (aV < 1) {
zT = window, kD = String, bO = Array, xX = document, nZ = Date;
}
转换成
if (aV == 0) {
zT = window, kD = String, bO = Array, xX = document, nZ = Date;
}
这是蔡老板所说的夹逼原理
,奈何文化低,我不懂.知道有这么一个回事就行.
思路如下(更加详细的看代码注释):
while
循环的参数是NumericLiteral
,且内容为1
.- body中只有2个节点
- 提取出
aV
- 找到
WhileStatement
- 枚举
WhileStatement
下的IfStatement
节点. left
的name
应该为我们提取出的aV
operator
为<
right
类型不能为IfStatement
, 因为它有嵌套.- 记录下了所有符合条件的body
- 生成
switch
节点
3. 代码
function collectSwitchCase(whilePath,name){
// 菜老板知识星球获得.
let ifNodes = [];
// 遍历WhilePath
whilePath.traverse({
"IfStatement"(path)
{
//遍历所有的ifStatement;
let {test,consequent,alternate} = path.node; //获取子节点
let {left,operator,right} = test; // 必定是BinaryExpression
if (!types.isIdentifier(left,{name:name}) || operator != '<' || !types.isNumericLiteral(right))
{//条件过滤
return;
}
let value = right.value;
//保存整个body,记得生成switchCase节点的时候加上break节点。
ifNodes[right.value-1] = consequent.body;
if (!types.isIfStatement(alternate))
{
ifNodes[right.value] = alternate.body; //最后一个else,其实就是上一个else-if 的 test.right的值
}
}
})
return ifNodes;
}
const if2switchReplace = {
WhileStatement(path){
let {test,body} = path.node;
// `while`循环的参数是`NumericLiteral` ,且内容为`1`. body中只有2个节点
if(!types.isNumericLiteral(test,{value:1}) || body.body.length != 2){
return
}
// 判断while循环格式, 条件过滤
let blockBody = body.body;
if (!types.isExpressionStatement(blockBody[0]) || !types.isIfStatement(blockBody[1]))
{
return;
}
// left 左边的节点就是我们需要的变量名
let {left,right} = blockBody[0].expression; //或者左右节点 aV = cA[wU++];
let name = left.name;
// 获取到了变量名称后, 就需要收集使用了aV的case
let ifNodes = collectSwitchCase(path,name); //收集case
//无case,直接返回。
if (ifNodes.length == 0) return;
let len = ifNodes.length;
for (let i=0; i < len; i++)
{
//每一个case最后都加break
ifNodes[i].push(types.BreakStatement());
ifNodes[i] = types.SwitchCase(test = types.valueToNode(i),consequent = ifNodes[i]); //生成SwitchCase节点
}
//生成SwitchCase节点
let switchNode = types.SwitchStatement(right,ifNodes);
path.node.body.body = [switchNode]; //最后的while节点只有一个Switch Node;
}
}
traverse(ast, if2switchReplace);