a123068 发表于 2021-11-5 18:44

实战IDA python手动脱壳

本帖最后由 a123068 于 2021-11-7 01:24 编辑

样本地址:


```
https://wwc.lanzoui.com/i7dwlw659jg
密码:be8z
```

Android App在进程启动之后会首先调用Application.attachBaseContext() 给ContextWrapper指定真正的context对象,加固的App也是在这里做真正的Application的创建和还原的。
发现在该App中最终会调用**MyWrapperProxyApplication**中的**initProxyApplication**方法


```
protected void initProxyApplication(Context arg3) {
      ...
      ...
      if(Util.CPUABI == "x86") {
            System.load(arg3.getFilesDir().getAbsolutePath() + "/prodexdir/" + Util.libname);
      }
      else {
            System.loadLibrary(Util.libname);
      }
    }
```

在这里壳加载了一个libshell-super.2019.so ,使用IDA打开这个so有init_array段所以先看看init_array

!(https://z3.ax1x.com/2021/11/05/InU6MT.png)

emm。。。init_array全是做字符串解密的,暂时不关心这个地方,因为待会儿要动态调试直接在内存中查看就行了。

直接找到JNI_Onload
!(https://z3.ax1x.com/2021/11/05/Ina21P.png)
**OLLVM 的控制流平坦化**

所谓控制流平坦化通俗点来说就是通过循环和判断把真实代码块给串联起来,去混淆的主要方法是区分真实块和虚假块。
<br>
真实块和虚假块的特征还是比较明显的,虚假块一般只包含赋值,比较,条件跳转等指令,而真实块的指令更为复杂,从序言块可以看出在进入主分发器之前是需要对当前寄存器值做保存,在走到真实块的时候再读出来,那么我们可以通过动态IDA Python 动态调试记录过滤掉所有分发器块 得到部分未做区分的虚假块和真实块,当然这样做只能得到**大部分**的真实块地址,比如某一块判断满足某个条件执行,而我们在调试的时候可能永远都不会满足这个条件那么对于真实的情况来讲,我们这部分代码就有可能丢失,具体涉及到算法还原还是建议通过符号执行来实现去混淆

```
from idaapi import *

func_offset = 0x3894
real_cmd = ['LDR', 'STR', 'BL', 'BLR', 'RET']

class MyHook(DBG_Hooks):

    def __init__(self):
      DBG_Hooks.__init__(self)
      self.base_addr = 0x0
      self.fun_jni_onload_addr = 0

    def dbg_library_load(self, pid, tid, ea, modinfo_name, modinfo_base, modinfo_size):
      if modinfo_name.find('libshell-super.2019.so') != -1:
            self.base_addr = modinfo_base
            self.fun_jni_onload_addr = modinfo_base + func_offset
            add_bpt(self.fun_jni_onload_addr)
            print('start base=0x%x' % (self.fun_jni_onload_addr))
      return

    def dbg_bpt(self, tid, bptea):
      print("bpt ea--->0x%x" % (bptea))
      if bptea == self.fun_jni_onload_addr:
            auto_make_code(bptea)
            add_func(bptea, BADADDR)
            auto_wait()
            fun = get_func(self.fun_jni_onload_addr)
            #获取所有块
            blocks = FlowChart(fun)
            for block in blocks:
                #如果是虚假块则跳过 在所有的真实块
                if self.isVMBody(block):
                  continue
                else:
                  add_bpt(block.end_ea - get_item_size(bptea))
                  add_bpt(block.start_ea)

      continue_process()

      return 0

    def isVMBody(self, block):
      index = block.start_ea
      while index <= block.end_ea - get_item_size(index):
            mnem = print_insn_mnem(index)
            index = index + get_item_size(block.start_ea)
            try:
                if real_cmd.index(mnem) != -1:
                  return False
            except:
                continue
      return True

try:
    if hook:
      hook.unhook()
except:
    pass

hook = MyHook()
hook.hook()

```
IDA 动态挂接上之后跑起来得到一组块的地址


```
bpt ea--->0x76becd0894
bpt ea--->0x76becd090c
...
...
...
bpt ea--->0x76becd2934
bpt ea--->0x76becd2948
bpt ea--->0x76becd294c
```
其中任然有不少的虚假块,类似这样的块

```
.text:00000076BECD26E8 ADRP            X8, #off_76BED1FEA0@PAGE
.text:00000076BECD26EC LDR             X8,
.text:00000076BECD26F0 ADRP            X9, #off_76BED1FEF0@PAGE
.text:00000076BECD26F4 LDR             W8,
.text:00000076BECD26F8 LDR             X9,
.text:00000076BECD26FC SUB             W10, W8, #1
.text:00000076BECD2700 LDR             W9,
.text:00000076BECD2704 MUL             W8, W10, W8
.text:00000076BECD2708 TST             W8, #1
.text:00000076BECD270C MOV             W8, #0x2FF3
.text:00000076BECD2710 CCMP            W9, #0xA, #8, NE
.text:00000076BECD2714 MOV             W9, #0x8DC4
.text:00000076BECD2718 MOVK            W8, #0x99FE,LSL#16
.text:00000076BECD271C MOVK            W9, #0x6800,LSL#16
.text:00000076BECD2720 CSEL            W20, W8, W9, LT
.text:00000076BECD2724 B               loc_76BECD091C
```

通过keypather或者直接使用capstone把这些块串起来,这些虚假块已经不影响我们对整个程序的分析了。


原始的伪代码
!(https://z3.ax1x.com/2021/11/05/InwzWR.png)

处理后的伪代码
!(https://z3.ax1x.com/2021/11/05/Infu1U.png)

经过一系列的代码追踪,这个壳在JNI_Onload中做了一系列的初始化函数后跳转到了sub_12DB4,解密dex地方。
通过在解密后dump dex可以发现 这个壳其实是做了指令抽取的,所谓指令抽取就是把CodeItem给填充成空指令在运行的时候再回填回来。然后我找到了回填CodeItem的函数sub_17A38,然后发现这个函数也是被混淆过的,emm 不过我们不关心这个函数,直接F4跳过 然后对内存中的dex进行dump获取到最终被修复后的dex

补充说明:中间有一些分析的地方不是很完善后续回补充上来

修复后的Dex

weakptr 发表于 2021-11-5 23:05

首先感谢发文,学习了

另外还有就是,格式有点那啥...好多没处理干净的 <br> 标签,中间还有张新浪图床的表情被403了

a123068 发表于 2021-11-5 23:10

weakptr 发表于 2021-11-5 23:05
首先感谢发文,学习了

另外还有就是,格式有点那啥...好多没处理干净的标签,中间还有张新浪图床的表 ...

0.0 我用的有道云笔记写的MD 然后复制到论坛的MD插件上没注意到这个

Hmily 发表于 2021-11-6 21:20

@a123068 图床有问题,把图片上传论坛本地吧。

wzwzaozao 发表于 2021-11-7 01:04

好思路!

qtfreet00 发表于 2021-11-17 15:51

关注一下

hiodis 发表于 2022-1-6 15:42

真的强啊
页: [1]
查看完整版本: 实战IDA python手动脱壳