样本:aHR0cHM6Ly9wYW4uYmFpZHUuY29tL3MvMTRWbXVfeHlDN2lGYk9qYjBtOHpYc3c/cHdkPWxpbm4=
大姐姐打开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并不会去执行这些操作 所以达到花指令的目标
前面计算了R0
的真实跳转地址
拿到真实地址后就可以在0xAA7B4
处手动Patch了
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
怎么可能 量很大 年轻人 你把握不住的 该上脚本了!
首先明确要干啥先
前面我们知道 每次计算都会进入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脚本开发 字符串加密处理 花指令处理 龙龙的星球里都写得很详细
龙龙老卷王了 内容丰富 很多东西我都是跟着龙龙学的 让我受益匪浅
所以 我吹爆!