观前提示:
本文章仅供学习交流,切勿用于非法通途,如有侵犯贵司请及时联系删除
样本:aHR0cHM6Ly9wYW4uYmFpZHUuY29tL3MvMTRWbXVfeHlDN2lGYk9qYjBtOHpYc3c/cHdkPWxpbm4=
0x1 花指令分析
大姐姐打开libsoulpower.so
直接跳转到0xaa73c
如果发现这里没被正常识别
直接在0xaa73c
处快捷键P
即可
直接F5反编译看伪代码
然后就会发现不明代码
转到汇编
这里是将R0
的值作为地址 跳转到该地址处
BX R0
那么R0
从哪里来呢 继续往上看
这里调用了sub_AECF0
并且将返回值放在了R0
BL sub_AECF0
所以真实的跳转地址可能就是在sub_AECF0
中计算得出的
继续往上看还能看到入参的操作
LDR R0, =(off_2C1B64 - 0xAA7A4) MOV R1, #0 STR R1, [SP,#160] MOV R1, #1 ADD R0, PC, R0 ; off_2C1B64
这里IDA已经帮我们识别好了伪代码 直接看就行
sub_AECF0(&off_2C1B64, 1)
进sub_AECF0
看是啥操作
int __fastcall sub_AECF0(int a1, int a2) { return *(_DWORD *)(a1 + 4 * a2); }
很简单啊 直接复现一下
0x2C1B64 + 4 * 1 = 0x2C1B68
得到0x2C1B68
后 跳转过去
002C1B68 DCD sub_AA7B8
可以看到0x2C1B68
对应的就是sub_AA7B8
所以 R0 = 0xAA7B8
而IDA并不会去执行这些操作 所以达到花指令的目标
0x2 手动去花
前面计算了R0
的真实跳转地址
拿到真实地址后就可以在0xAA7B4
处手动Patch下图0xAA7B4给错正确0xAA7B8
Patch完事之后再次F5
反编译
反编译完发现代码仅仅多了一行
转到汇编 这里同样出现BX R0
所以这个样本应该是多处插花 那就继续还原
按上述操作 找到俩个传入参数 然后手动计算出真实地址
0x2C1B64 + 4 * 2 = 0x2C1B6C
002C1B6C DCD sub_AA9F8
R0 = 0xAA9F8
继续重复操作Patch
代码又多了一点 然后不出意料 又是一处插花
LDR R0, =(off_2C1B64 - 0xAAA08) MOV R1, #3 ADD R0, PC, R0 ; off_2C1B64 BL sub_AECF0 BX R0
继续重复几次操作后 代码多了这么多 但是还没完全去花
同时在视图中可以发现ollvm 后面还不知道会出现啥 继续Patch
停!!!
怎么可能 量很大 年轻人 你把握不住的 该上脚本了!
0x3 脚本去花
首先明确要干啥先
- 找出所有的插花方法
- 找出所有插花方法的调用处
- 拿到俩个参数
- 计算正确地址
- Patch
前面我们知道 每次计算都会进入sub_AECF0
但是我猜测可能不止一个sub_AECF0
也许是同操作 但是方法名不一样 这里做个验证
拿到方法HEX01 01 90 E7 1E FF 2F E1
01 01 90 E7 LDR R0, [R0,R1,LSL#2] 1E FF 2F E1 BX LR
然后Binary search
搜索得出五个结果
查看每个方法的交叉引用都有很多处
那可以使用idautils.CodeRefsTo
遍历交叉引用
但是我担心有可能存在漏识别 所以我采用了从头到尾的遍历方法
import capstone from keystone import * import flare_emu import ida_ida import idautils import idaapi import ida_segment import idc import ida_bytes # 初始化架构和模式 CS = capstone.Cs(capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM) # 设置为详细反汇编模式 CS.detail = True # 设置反汇编跳过数据 CS.skipdata = True # 获取.text段的范围 def getAddrRange(): start = ida_ida.inf_get_min_ea() size = ida_ida.inf_get_max_ea() - start # 将地址范围限定于text节 for seg in idautils.Segments(): seg = idaapi.getseg(seg) segName = ida_segment.get_segm_name(seg) if segName == ".text": start = seg.start_ea size = seg.size() return start, size def binSearch(start, end, pattern): matches = [] addr = start if end == 0: end = idc.BADADDR if end != idc.BADADDR: end = end + 1 while True: addr = ida_bytes.bin_search(addr, end, bytes.fromhex(pattern), None, idaapi.BIN_SEARCH_FORWARD, idaapi.BIN_SEARCH_NOCASE) if addr == idc.BADADDR: break else: matches.append(addr) addr = addr + 1 return matches # 获取代码段的起始地址和长度 start, size = getAddrRange() codebytes = idc.get_bytes(start, size) sub_matches = binSearch(0, 0, "01 01 90 E7 1E FF 2F E1") disasmCodes = list(CS.disasm_lite(codebytes, start, size)) for i in range(len(disasmCodes)): (address, size, mnemonic, op_str) = disasmCodes[i] if mnemonic == 'bl': sub_addr = int(op_str.replace('#', ''), 16) if sub_addr in sub_matches: print(hex(address))
效果很不错 找出了很多的调用处
找出调用处随便跳转几个看能不能找到规律匹配参数
这里发现 参数的位置并不固定 不好做匹配
但是又可以发现 参数位置 调用位置 跳转位置 都是处于同一个block中
知道了这一点 就只需要匹配出相对应的block就可以了
def find_block(blocks, addr): for block in blocks: start_ea = block.start_ea end_ea = block.end_ea if start_ea < addr < end_ea: return block return None func = idaapi.get_func(address) if func: block = find_block(idaapi.FlowChart(func), address)
即使匹配出了地址块 也不好匹配出参数 那咋办呢
其实 这里我匹配地址块的原因就是不想再写一堆匹配代码找出R0
和R1
所以我打算交给unicorn
去做
而我又懒得写很多代码 所以 我又使用了封装好的flare_emu
例如 我匹配出了一个block 我仅需要把block的起始地址匹配到方法执行前即可
测试看结果
import flare_emu myEH = flare_emu.EmuHelper() myEH.emulateRange( startAddr=0x804DC, endAddr=0x804FC ) Out[31]: <unicorn.unicorn.Uc at 0x169c478b908> myEH.getRegVal("R0") Out[32]: 0x2c14d0 (OCSP_SIGNATURE_it + 0x2dc) myEH.getRegVal("R1") Out[33]: 1
效果完美
后面仅需自己写个计算方法 将参数传入计算即可
def calcu_addr(a, b): return a + 4 * b
得到真实跳转地址后 还得匹配出跳转地址存储的地址
而idc.print_operand
刚好能实现这个操作
idc.print_operand(0x2c14d4,0) Out[34]: 'sub_80508'
拿到这个真实地址 最后Patch就行
不过这里需要注意的是 我们不知道后面第几行是需要Patch的BX
处
但是我们前面就得到了块的起始地址 同样的也可以得到块的结束地址
只需要(结束地址-方法调用地址)/4 然后遍历就能找到BX
所在位置
但是但是 我觉得不优雅
从这张图 可以知道BX
一直在块的最后一行 那我何必去遍历呢 直接那最后一行的地址不就行了
一下子就优雅起来了
最后写下patch
code = f"B {hex(b_addr)}" b_code = generate(code, block.end_ea - 4) # Patch 1 处理花指令 ida_bytes.patch_bytes(block.end_ea - 4, bytes(b_code))
将所有代码串起来 执行测试一下效果
执行完后应用更改
最后重新打开so
跳转到最开始分析的0xaa73c
成功识别到N多行代码了
去花工作完成 处理脚本和对应的so文件放在样本中
如果你想学习关于so反混淆相关的知识
例如IDA脚本开发 字符串加密处理 花指令处理 龙龙的星球里都写得很详细
龙龙老卷王了 内容丰富 很多东西我都是跟着龙龙学的 让我受益匪浅
所以 我吹爆!
感谢各位大佬观看
感谢大佬们的文章分享
如有错误 还请海涵
共同进步 带带弟弟
点赞 在看 分享是你对我最大的支持
逆向lin狗