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

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

2022-2-20 14:43
47393

最近打算做安全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当前分析出的所有指令,所以未被识别成指令的部分不会修复。

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



[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2022-2-22 01:02 被卧勒个槽编辑 ,原因: 修复脚本bug
上传的附件:
收藏
免费 8
支持
分享
最新回复 (15)
雪    币: 576
活跃值: (2035)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享 mark
2022-2-20 14:47
0
雪    币: 2914
活跃值: (4946)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
3
mark
2022-2-21 09:29
0
雪    币: 6197
活跃值: (4629)
能力值: ( LV9,RANK:185 )
在线值:
发帖
回帖
粉丝
4

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

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

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

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