三层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 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获取更多资讯

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星云牛马

帮到您的话,可否请我喝杯咖啡

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值