Shocker 发表于 2021-8-5 13:35

基于IDA Python的OLLVM反混淆(二) 实战腾讯御安全(模拟器)

本帖最后由 Shocker 于 2021-8-10 10:33 编辑

## 前言
上一篇中介绍了IDA Python去混淆的基本思路,本篇将以腾讯免费御安全为例进行脚本反混淆.(以x86_32指令为例)
## 思路
首先分析出控制块的特征,将可能是控制块的块去掉,之后连接所有真实的块.
## 实战
实验环境:ida7.5+雷电模拟器(64位)4

Jadx打开样本,发现加载了libshellx-super.2019.so文件


IDA加载libshellx-super.2019.so,发现JNI_OnLoad被混淆


发现是个魔改的OLLVM.
### 找出疑似控制块的代码
1.
2.
3.
4.

可以分析出,上面四种类型的块与真实逻辑毫无关系

### 去混淆脚本
对疑似控制块的块不下断点,其余块结尾下断点
```
#---------------------------------------------------------------------
# Debug notification hook test
#
# This script start the executable and steps through the first five
# instructions. Each instruction is disassembled after execution.
#
# Original Author: Gergely Erdelyi <gergely.erdelyi@d-dome.net>
#
# Maintained By: IDAPython Team
#
#---------------------------------------------------------------------
from idaapi import *
import keypatch

patcher=keypatch.Keypatch_Asm()

fun_offset=0x25D0 #函数地址

class MyDbgHook(DBG_Hooks):
    """ Own debug hook class that implementd the callback functions """

    def dbg_process_start(self, pid, tid, ea, name, base, size):
      print("Process started, pid=%d tid=%d name=%s" % (pid, tid, name))
      self.dbg_process_attach(pid, tid, ea, name, base, size)

    def dbg_process_exit(self, pid, tid, ea, code):
      print("Process exited pid=%d tid=%d ea=0x%x code=%d" % (pid, tid, ea, code))
      for sub_dict in self.related_dict:
            if len(self.related_dict)==1:
            for sub_sub_dict_key in self.related_dict:
                  if idc.print_insn_mnem(sub_dict).startswith('j'):
                        disasm='jmp'+' '+hex(self.related_dict.pop())
                        patcher.patch_code(sub_dict,disasm,patcher.syntax,True,False)
               

    def dbg_process_attach(self, pid, tid, ea, name, base, size):
      print("Process attach pid=%d tid=%d ea=0x%x name=%s base=%x size=%x" % (pid, tid, ea, name, base, size))
      self.pre_blcok=None
      self.ZF_flag=None
      self.related_dict=dict()
      self.block_addr_dict=dict()
      so_base=idaapi.get_imagebase()
      self.f_blocks = idaapi.FlowChart(idaapi.get_func(so_base+fun_offset), flags=idaapi.FC_PREDS)
      for block in self.f_blocks:
            start=block.start_ea
            end=idc.prev_head(block.end_ea)
            if (idc.print_insn_mnem(start)=='cmp' and idc.print_insn_mnem(idc.next_head(start)).startswith('j')) or \
                (idc.print_insn_mnem(start)=='mov' and idc.print_insn_mnem(idc.next_head(start))=='cmp' and idc.print_insn_mnem(idc.next_head(idc.next_head(start))).startswith('j')) or \
                idc.print_insn_mnem(start)=='jmp' or \
                idc.print_insn_mnem(start)=='nop' or \
                idc.print_insn_mnem(start).startswith('cmov'):
                continue
            add_bpt(end,0,BPT_SOFT)
            while start<block.end_ea:
                if idc.print_insn_mnem(start).startswith('ret'):
                  add_bpt(start,0,BPT_SOFT)
                  break
                start=idc.next_head(start)
               
               
    def dbg_bpt(self, tid, ea):
      print ("Break point at 0x%x pid=%d" % (ea, tid))

      if not self.block_addr_dict:
            so_base=idaapi.get_imagebase()
            blocks=idaapi.FlowChart(idaapi.get_func(so_base+fun_offset), flags=idaapi.FC_PREDS)
            for block in blocks:
                start=block.start_ea
                end=idc.prev_head(block.end_ea)
                self.block_addr_dict=start

      if not self.pre_blcok==None:
            if self.pre_blcok in self.related_dict:
                ori_dict=self.related_dict
                if self.ZF_flag in ori_dict:
                  sub_set=ori_dict
                  sub_set.add(self.block_addr_dict)
                else:
                  sub_set=set()
                  sub_set.add(self.block_addr_dict)
                  ori_dict=sub_set
            else:
                # 不存在
                sub_set=set()
                sub_set.add(self.block_addr_dict)
                sub_dict={self.ZF_flag:sub_set}
                self.related_dict.update({self.pre_blcok:sub_dict})

      self.pre_blcok=ea
      self.ZF_flag = get_reg_value("ZF")


      if idc.print_insn_mnem(ea).startswith('ret'):
            return 0
      else:
            idaapi.continue_process()
      # return values:
      #   -1 - to display a breakpoint warning dialog
      #      if the process is suspended.
      #    0 - to never display a breakpoint warning dialog.
      #    1 - to always display a breakpoint warning dialog.
      return 0


# Remove an existing debug hook
try:
    if debughook:
      print("Removing previous hook ...")
      debughook.unhook()
except:
    pass

# Install the debug hook
debughook = MyDbgHook()
debughook.hook()
debughook.steps = 0

# Stop at the entry point
ep = get_inf_attr(INF_START_IP)
request_run_to(ep)

# Step one instruction
request_step_over()

# Start debugging
run_requests()
```
```
for block in self.f_blocks:
            start=block.start_ea
            end=idc.prev_head(block.end_ea)
            if (idc.print_insn_mnem(start)=='cmp' and idc.print_insn_mnem(idc.next_head(start)).startswith('j')) or \
                (idc.print_insn_mnem(start)=='mov' and idc.print_insn_mnem(idc.next_head(start))=='cmp' and idc.print_insn_mnem(idc.next_head(idc.next_head(start))).startswith('j')) or \
                idc.print_insn_mnem(start)=='jmp' or \
                idc.print_insn_mnem(start)=='nop' or \
                idc.print_insn_mnem(start).startswith('cmov'):
                continue
            add_bpt(end,0,BPT_SOFT)
            while start<block.end_ea:   #对retn块下断点
                if idc.print_insn_mnem(start).startswith('ret'):
                  add_bpt(start,0,BPT_SOFT)
                  break
                start=idc.next_head(start)
```
这里对所有疑似真实块的地址下断点
```
def dbg_process_exit(self, pid, tid, ea, code):
      print("Process exited pid=%d tid=%d ea=0x%x code=%d" % (pid, tid, ea, code))
      for sub_dict in self.related_dict:
            if len(self.related_dict)==1:
            for sub_sub_dict_key in self.related_dict:
                  if idc.print_insn_mnem(sub_dict).startswith('j'):
                        disasm='jmp'+' '+hex(self.related_dict.pop())
                        patcher.patch_code(sub_dict,disasm,patcher.syntax,True,False)
```
记录所有真实块的下一个真实块.
我这里用一个dict嵌套dict来记录,
大致是这样
{0x2638:{0:xxxxx,1:xxxxx}} 这里的0和1是我对ZF标志的判断,xxxxx是下一个真实块的首地址.
得到所有真实块的流程后,就可以进行跳转的修复
我这里仅仅只是对真实块中只有一个后继真实块.
且结尾为jxx的块做了修复


### IDA动态调试
1. 打开模拟器,将调试文件传进模拟器,执行
```
adb shell /data/local/tmp/android_x86_server
```
2. 端口转发
```
adb forward tcp:23946 tcp:23946
```
3. 打开ddms
4. 调试模式启动app
```
adb shell am start -D -n com.shocker.jiagutest/.MainActivity
```
5. IDA先载入脚本,后附加到app上


6. jdb方式启动
```
jdb -connect com.sun.jdi.SocketAttach:hostname=localhost,port=8700
```
7. F9运行直到运行至retn,继续F9
8. 退出程序

9. 将Patch后的so拖进apk对应的目录中,重装app(因为patch后有些块会连在一起,所以需要再次执行脚本)
10. 删除所有断点,或者重新载入IDA,执行一遍脚本.
11. 运行ida-nop脚本,将没执行的块全部nop.
## 结果
去混淆后的结果

F5后的结果


相关示例代码见
https://github.com/PShocker/de-ollvm/

Hmily 发表于 2021-8-6 07:48

@Shocker 图片有问题,没上传成功?

看了下你两篇帖子图片都有问题,不要用引用图片地址,直接在markdown文章里用disucz的贴图方法就行,看这个演示https://www.52pojie.cn/misc.php?mod=faq&action=faq&id=29&messageid=36

nevinhappy 发表于 2021-8-5 15:58

学习,LZ内容不错!!!

Li1y 发表于 2021-8-5 15:58

图片丢失,麻烦补一下

GuXing 发表于 2021-8-5 22:42

感谢发布原创作品,吾爱破解论坛因你更精彩!

goda 发表于 2021-8-5 23:45

谢谢分享

tzlqjyx 发表于 2021-8-6 07:26

谢谢分享,学习来了

无言Y 发表于 2021-8-6 08:16

感谢分享

fhb780621 发表于 2021-8-6 08:37

学习了,很有作用

LiZan 发表于 2021-8-6 10:11

看大佬操作
页: [1] 2 3 4 5
查看完整版本: 基于IDA Python的OLLVM反混淆(二) 实战腾讯御安全(模拟器)