基于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/ @Shocker 图片有问题,没上传成功?
看了下你两篇帖子图片都有问题,不要用引用图片地址,直接在markdown文章里用disucz的贴图方法就行,看这个演示https://www.52pojie.cn/misc.php?mod=faq&action=faq&id=29&messageid=36 学习,LZ内容不错!!! 图片丢失,麻烦补一下 感谢发布原创作品,吾爱破解论坛因你更精彩! 谢谢分享 谢谢分享,学习来了 感谢分享 学习了,很有作用 看大佬操作