实战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
首先感谢发文,学习了
另外还有就是,格式有点那啥...好多没处理干净的 <br> 标签,中间还有张新浪图床的表情被403了 weakptr 发表于 2021-11-5 23:05
首先感谢发文,学习了
另外还有就是,格式有点那啥...好多没处理干净的标签,中间还有张新浪图床的表 ...
0.0 我用的有道云笔记写的MD 然后复制到论坛的MD插件上没注意到这个 @a123068 图床有问题,把图片上传论坛本地吧。 好思路! 关注一下 真的强啊
页:
[1]