阿里三层switch转一层switch的处理方法
如下所示的混淆代码,在淘系140、227等滑块的代码经过一些转化后得到。
这些转化包括:
//去自执行避免变量污染
traverse(ast, { UnaryExpression: renameScopIdn })
//去自执行
traverse(ast, { UnaryExpression: movezishixing })
//三目运算转if-else
traverse(ast, { ConditionalExpression: condition2ifelse })
//例如a&&console.log(b) 转if-no-else
traverse(ast, { LogicalExpression: Logic2if })
//将9==Ai,转成Ai==9,放在左边
traverse(ast, { BinaryExpression: putIdenLeft })
// Ai > 9 Ai < 10 转 Ai == x
traverse(ast, { BinaryExpression: ifLeGe2Eq })
//从switchCase入口是很有用的
//if嵌套转switch
//if嵌套能转switch的究极原因在于,它是通过一些混淆逻辑得到的代码,肉眼分析就会发现,每个代码块总是对应了条件变量等于某个值时,才会进入
traverse(ast, { SwitchCase: if2switch })
通过上面的转化得到了三层嵌套的switch,这篇我们重点看看这个转化:
try {
for (var li = 16996; void 0 !== li;) {
var Ci = 31 & li,
fi = li >> 5,
mi = 31 & fi,
bi = fi >> 5,
Ai = 31 & bi;
switch (Ci) {
case 0:
switch (mi) {
case 0:
switch (Ai) {
case 12:
N = Se[vo], Q = N[Z](), li = Q ? 11460 : 1475;
break;
case 5:
li = 8804;
break;
case 2:
W = $ % 128, ie = [], M = W + 128, _ = $ - W, W = _ / 128, _ = 127 & W, ie.push(M, _), se = ie, li = 16390;
break;
case 0:
Dn.push(0), li = 11522;
break;
case 1:
L = mo, li = 24641;
break;
case 3:
Oe = K, li = 20257;
break;
case 4:
_ = 0 !== se.length, I = je, li = _ ? 22694 : 16963;
break;
……
……
……
……
通过上面可以看出,在满足
var Ci = 31 & li,
fi = li >> 5,
mi = 31 & fi,
bi = fi >> 5,
Ai = 31 & bi
并且 Ci===0 && mi ===0 && Ai ===0
的情况下进入第一个代码块,眼看好像很多变量,实际上都是由li运算得来,因此就是求满足这些约束的条件下的li的值。
然后最终转化为:
switch(li){
case x:
…………
…………
}
那么怎么求解嘞?
嘿嘿,下面提供两种思路:
1.穷举
通过for循环,穷举0到999999(一个较大的数)的范围内,看看是否有满足上面条件的li的值
代码示例:
function iter() {
here: for (let li = 0; li < 999999; li++) {
var Ci = 31 & li,
fi = li >> 5,
mi = 31 & fi,
bi = fi >> 5,
Ai = 31 & bi;
for (let con1 = 0; con1 < 26; con1++) {
for (let con2 = 0; con2 < 26; con2++) {
for (let con3 = 0; con3 < 26; con3++) {
if (Ci === con1 && mi === con2 && Ai === con3) {
//记录下来li和对应代码块的映射关系,后续重建switch即可
console.log(con1, con2, con3, "<==>", li)
// if (li === 16996) break here
}
}
}
}
}
}
iter()
输出:
……
21 18 16 <==> 16981
22 18 16 <==> 16982
23 18 16 <==> 16983
24 18 16 <==> 16984
25 18 16 <==> 16985
0 19 16 <==> 16992
1 19 16 <==> 16993
2 19 16 <==> 16994
3 19 16 <==> 16995
4 19 16 <==> 16996
……
可以看到,还是挺快的就得出结果了。
值得注意的是,你会发现为什么我只遍历0到26的范围,这个的话取决于程序三层switch的case的范围有多大,当然你也可以写ast程序去搜集这三个的变化范围
2.约束求解
作者推荐使用这种方式,想到这个是因为作者的毕业设计使用的就是这个技术,如今没想到在js逆向上还能排上用场
我们直接使用z3-solver
进行约束求解得到一个满足条件的解即可,穷举些许显得low
如下我们直接使用Z3对收集到的情况进行求解:
import json
from z3 import *
import sys
li = BitVec('li', 16)
s = Solver() # 创建约束求解器
Ci = li & 31
fi = li >> 5
mi = 31 & fi
bi = fi >> 5
Ai = 31 & bi
def get_ans(n1, n2, n3):
s.add(Ci == n1) # 添加约束条件
s.add(mi == n2) # 添加约束条件
s.add(Ai == n3) # 添加约束条件
if s.check() == sat: # 检测是否有解
# result = s.model()
resst = s.model().eval(li).as_string() # 若有解则得出解,注意这里的解是等式
s.reset()
return resst
else:
print('no result') # 无解
for n1 in range(26):
for n2 in range(26):
for n3 in range(26):
ans = get_ans(n1, n2, n3)
print(n1,n2,n3,"<==>",ans)
总结
两种方式的话,速度我没有进行比较,甚至目前cpu情况下,穷举更快一些,为什么提出第二个方法呢?
实际上我在设想,我们没必要写前面所说那些复杂的ast还原插件,写了费劲巴拉调试了很久,才得到,三层switch嵌套,最后来转一层,如果我们能通过程序分析的策略往深度分支进行探索,一路上不断收集约束集合,直到最深的代码块(没有子分支),此时将收集到的约束进行求解,即可一步到位直接得到li和最终执行代码块的关系,直接就从混淆的代码得到了一层switch
上述方法的难点在于约束收集,纵观基于js写的符号执行引擎,ExpoSE算一个,但是文档稀少,安装编译都成问题,其次这些符号执行引擎都是动态符号执行,需要程序能够运行的情况下设计实现的,但是我们的被混淆的js代码一般都是不能直接运行的,虽然我们可以通过一些操作,比如将最主要的需要还原的代码抠出来,然后将不能执行的代码块暂时替换成能够运行的代码,但是映射关系需要留存好,后续还原用。但是这些为了能够使用动态符号执行思路的前期操作,比起直接写ast插件的方式,工作量也不一定小,所以解混淆嘛!本着简单直接的方式,所以还是目前建议使用ast插件的方式,进行,因为动态符号执行还涉及到代码插装等,写论文的水平了,已经是……
记得加入我们的学习群,更多知识尽在我的知识星球:
我的星球https://t.zsxq.com/125umU2l8
qq群 961566389
获取更多资讯