首页
社区
课程
招聘
[原创]某手so跳转表修复
2022-2-20 14:43 45976

[原创]某手so跳转表修复

2022-2-20 14:43
45976

最近打算做安全sdk,由于以前只想着攻没想过防,所以也没积累下什么资料,于是只好从其他app借鉴一下。

在分析某手的时候,其so中大量使用了跳转表,刚开始的时候没在意,以为只是部分函数使用了,因此图省事直接手动修复。

结果越搞越不对劲,发现到处都是这玩意儿,于是准备分析下逻辑,使用脚本一次性全部修复下。


分析

1、每次跳转的起始代码都是一样的(标记为1的块),保存寄存器,获取本次跳转在跳转表的索引,然后跳到跳转表的起始位置。

2、每个函数都有一个跳转表,最开始为一条blx指令(标记为2),均跳转到0x7584(标记为4的块)。

3、每个跳转表的blx指令之后是一个数组(标记为3的块),每个元素为跳转的偏移。

4、所有跳转均在0x7584实现(标记为4的块)。

4.1、因为步骤2使用的blx,所以此时lr寄存器为偏移数组的起始地址(标记为3的块);

4.2、步骤1获取到的跳转表索引,加上步骤4.1获取的跳转表数组起始地址,能够获取到跳转的实际地址;

4.3、恢复寄存器,实现跳转。


脚本

1、查找ldr指令,获取本次跳转在跳转表的索引。

2、获取跳转表的位置。

3、检查跳转表起始内容是否为blx指令,是否跳到0x7584。

4、计算跳转位置并修复。

5、因为修复后可能导致之前被识别为数据的内容转换成指令,所以还需要循环多次修复,直到遍历所有指令后都不需要修复才结束。

import keystone

import idc

arch = keystone.KS_ARCH_ARM
mode = keystone.KS_MODE_THUMB
ks = keystone.Ks(arch, mode)


def is_ldr(ea):
    ldr_addr = ea
    ldr_flags = idc.get_full_flags(ldr_addr)
    if not idc.is_code(ldr_flags):
        return False, None

    ldr_op = idc.print_insn_mnem(ldr_addr)
    if ldr_op != 'LDR':
        return False, None

    ldr_opt0 = idc.get_operand_type(ldr_addr, 0)
    ldr_op0 = idc.print_operand(ldr_addr, 0)
    if ldr_opt0 != idc.o_reg or ldr_op0 != 'R0':
        return False, None

    ldr_opt1 = idc.get_operand_type(ldr_addr, 1)
    if ldr_opt1 != idc.o_mem:
        return False, None

    ldr_opv1 = idc.get_operand_value(ldr_addr, 1)
    return True, ldr_opv1


def is_bl(ea):
    bl_addr = ea
    bl_flags = idc.get_full_flags(bl_addr)
    if not idc.is_code(bl_flags):
        return False, None

    bl_op = idc.print_insn_mnem(bl_addr)
    if bl_op != 'BL':
        return False, None

    bl_opt0 = idc.get_operand_type(bl_addr, 0)
    if bl_opt0 != idc.o_near:
        return False, None

    blx_addr = idc.get_operand_value(bl_addr, 0)
    return True, blx_addr


def is_jump_table(ea):
    blx_addr = ea
    blx_flags = idc.get_full_flags(blx_addr)
    if not idc.is_code(blx_flags):
        return False, None

    blx_op = idc.print_insn_mnem(blx_addr)
    if blx_op != 'BLX':
        return False, None

    blx_opt0 = idc.get_operand_type(blx_addr, 0)
    if blx_opt0 != idc.o_near:
        return False, None

    route_addr = idc.get_operand_value(blx_addr, 0)
    return True, route_addr


def check_code_items(ea, t_flag):
    addr = ea
    flags = idc.get_full_flags(ea)
    if not idc.is_head(flags):
        idc.del_items(idc.get_item_head(ea))
        addr = idc.next_head(ea)

    while addr != idc.BADADDR:
        flags = idc.get_full_flags(addr)
        if not idc.is_code(flags) or idc.get_sreg(addr, 't') != t_flag:
            idc.del_items(addr)
            addr = idc.next_head(addr)
        else:
            break

    if idc.get_sreg(ea, 't') != t_flag:
        idc.split_sreg_range(ea, 't', t_flag)


def patch_jump_addr(ldr_addr, tb_addr, ldr_src_val_addr):
    push_addr = idc.prev_head(ldr_addr)
    bl_addr = idc.next_head(ldr_addr)
    patch_addr = push_addr
    pc = patch_addr + 4
    if pc & 0b11:
        pc &= 0xfffffffc
    ldr_pc_offset = ldr_src_val_addr - pc

    idc.del_items(push_addr)
    idc.del_items(ldr_addr)
    idc.del_items(bl_addr)

    ldr_src_flags = idc.get_full_flags(ldr_src_val_addr)
    if not idc.is_dword(ldr_src_flags):
        idc.del_items(ldr_src_val_addr, 0, 4)
        idc.create_dword(ldr_src_val_addr)
    tb_index = idc.get_wide_dword(ldr_src_val_addr)
    index_addr = tb_addr + tb_index * 4
    index_flags = idc.get_full_flags(index_addr)
    if not idc.is_dword(index_flags):
        idc.del_items(index_addr, 0, 4)
        idc.create_dword(index_addr)
    offset = idc.get_wide_dword(index_addr)
    jmp_addr = tb_addr + offset

    code = 'ldr pc,[pc,' + hex(ldr_pc_offset) + ']'
    encoding, count = ks.asm(code, patch_addr)
    if not encoding:
        print 'asm error', hex(patch_addr)
        return

    for item in enumerate(encoding):
        idc.patch_byte(patch_addr + item[0], item[1])
    idc.patch_dword(ldr_src_val_addr, jmp_addr)

    check_code_items(jmp_addr, idc.get_sreg(patch_addr, 't'))
    idc.create_insn(patch_addr)
    idc.auto_wait()


def func_patch():
    time = 0
    count = -1
    while count != 0:
        time += 1
        count = 0
        print 'time:', time,
        idc.auto_wait()

        ins_addr = idc.next_head(0)
        while ins_addr != idc.BADADDR:
            succ, ldr_src_val_addr = is_ldr(ins_addr)
            if not succ:
                ins_addr = idc.next_head(ins_addr)
                continue

            ldr_addr = ins_addr
            ins_addr = idc.next_head(ldr_addr)
            succ, blx_addr = is_bl(ins_addr)
            if not succ:
                continue

            succ, route_addr = is_jump_table(blx_addr)
            if not succ or route_addr != 0x7584:
                continue

            tb_addr = blx_addr + 4
            patch_jump_addr(ldr_addr, tb_addr, ldr_src_val_addr)
            count += 1
        print 'count:', count


print 'start...'
func_patch()
print 'end...'


结果

脚本执行后可以看到,总共遍历了16次。

JNI_OnLoad修复后的内容如下。


未修复处理 

因为修复脚本是遍历ida当前分析出的所有指令,所以未被识别成指令的部分不会修复。

如果分析过程中,手动将数据转换成指令后,存在未修复的跳转,只需要再执行一次脚本即可。



[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

最后于 2022-2-22 01:02 被卧勒个槽编辑 ,原因: 修复脚本bug
上传的附件:
收藏
点赞8
打赏
分享
最新回复 (15)
雪    币: 576
活跃值: (2035)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
kakasasa 2022-2-20 14:47
2
0
感谢分享 mark
雪    币: 2428
活跃值: (4196)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
windy_ll 2 2022-2-21 09:29
3
0
mark
雪    币: 6193
活跃值: (4457)
能力值: ( LV9,RANK:185 )
在线值:
发帖
回帖
粉丝
卧勒个槽 4 2022-2-21 15:07
4
0

帖子附件中的修复脚本有点问题,当跳转距离大于256的时候,生成的跳转指令是错误的,帖子中的代码已经修复,该条评论的附件是新的脚本。

最后于 2022-2-22 01:03 被卧勒个槽编辑 ,原因:
上传的附件:
雪    币: 2710
活跃值: (1651)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
乐活 2022-2-21 17:50
5
0
感谢
雪    币: 869
活跃值: (26)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mb_gvfnvvlv 2022-2-28 22:13
6
0
大佬 有兴趣解决一下ks直播间的风控吗 预算充足
详聊Q516-862-662
雪    币: 6193
活跃值: (4457)
能力值: ( LV9,RANK:185 )
在线值:
发帖
回帖
粉丝
卧勒个槽 4 2022-3-1 00:44
7
0
mb_gvfnvvlv 大佬 有兴趣解决一下ks直播间的风控吗 预算充足 详聊Q516-862-662
不接私活哈,
雪    币: 121
活跃值: (1517)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xxRea 2022-3-9 18:18
8
0
感谢分享 mark
雪    币: 5645
活跃值: (6553)
能力值: ( LV15,RANK:531 )
在线值:
发帖
回帖
粉丝
houjingyi 11 2022-3-14 09:27
9
0
才看到你这篇文章,我刚刚分析了美团,也是这种跳转表
雪    币: 2714
活跃值: (1497)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
Vn小帆 2022-4-9 01:58
10
0
这种 跳转 主要还是 对抗  静态分析
雪    币: 167
活跃值: (851)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
BestToYou 2022-4-27 23:04
11
0

你好,请教一下,下用脚本修复后
void JNI_OnUnLoad()
{
  ;
}

最后于 2022-4-27 23:33 被BestToYou编辑 ,原因:
雪    币: 6193
活跃值: (4457)
能力值: ( LV9,RANK:185 )
在线值:
发帖
回帖
粉丝
卧勒个槽 4 2022-4-27 23:57
12
0
BestToYou 你好,请教一下,下用脚本修复后void JNI_OnUnLoad(){  ;}
应该是函数的地址范围不对,你手动改下
雪    币: 167
活跃值: (851)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
BestToYou 2022-5-21 01:04
13
0
大佬再请教下函数尾部是如何确定的 感激不尽
雪    币: 1699
活跃值: (760)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
wx_Dispa1r 2022-7-20 11:28
14
0
mark
雪    币: 10
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
bamboogo 2023-2-27 11:04
15
0
Mark
雪    币: 398
活跃值: (1324)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
4Chan 2024-1-3 11:23
16
0
我反而是第一个压缩包的py能跑,补档的压缩包不能跑
游客
登录 | 注册 方可回帖
返回