三层switch转一层switch的处理方法
阿里三层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 sysli = BitVec('li', 16)
s = Solver() # 创建约束求解器
Ci = li & 31
fi = li >> 5
mi = 31 & fi
bi = fi >> 5
Ai = 31 & bidef 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 resstelse: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
获取更多资讯