首页
社区
课程
招聘
[原创]某bang壳的简单分析
发表于: 2023-7-15 19:24 24777

[原创]某bang壳的简单分析

2023-7-15 19:24
24777

样本名shansong,某bang的壳
本文主要是分享在逆向中遇到的对抗以及处理办法

010editor打开libDexHelper.so

一眼假的section

操作系统加载并不关心section,先直接删除掉,后面修复dynamic后再重新生成一份

一眼假的dynamic

dynamic除了vaddr,其他成员都是可以伪造欺骗的,因为操作系统也只关心dynamic的vaddr

通过分析PT_LOAD的信息,即RVAFA的转换,对dynamic的file_offset进行简单修复

跑工具重新生成了一份section后,ida已经可以正常的解析so了

简单看了一下init_arraryJNI_OnLoad方法,都是被加密的

猜测解密方法在.init方法中

暂时不去关心解密算法的实现,等.init方法运行结束后,直接dump内存,可以看到数据已经解密完成

dump内存可以写ida脚本,也可以直接使用dd来dump(注意先要使用ida把app挂起,壳对/proc/self/mem的读写做了监控)

类似于这样

实际是ida对 switch-case的识别失败

解决办法就是帮助ida正确识别 switch-case,具体可以参考这篇文章

正确修复后,ida也就能正常反编译了

代码的正常流程被switch-case打乱了

简单分析了一下汇编的代码结构,可以发现直接跳转到分发块的case块,会直接写死下一个要执行的case块

于是可以通过计算获取到目标case块的地址,将case块跳回分发块,改成直接跳转到对应的case块的地址

对于跳回def块的case块,则会通过条件执行来决定下一个要执行的case块

同样可以计算出不同条件所要跳转的case块,将对应的跳转改为条件跳转

脚本还有待完善,有一些跳转的修改可能需要手动去修改

JNI_OnLoad做了简单的测试

修改前:

修改后:

准备工作做完了,开始上frida

意料之中,存在frida检测

简单测试了一下:

看来是对frida的基本特征做了检测

对openat进行hook

定位到检测代码的位置:

用的去特征的frida-server,不知道为啥gdbus的特征仍然存在

gdbus字符串的比较在sub_9DE68进行,简单的处理一下

除了通过hook svc调用来定位,还可以通过hook pthread_create来获取创建的所有线程来定位检测

此时frida已经可以使用了

继续测试,会发现对部分libc库函数(比如open)进行hook时,一样会被检测到

猜测是存在代码的hash值校验,或比较了内存与文件中的代码是否一致

逆向分析时,肯定是少不了动态调试的,且遇到反调试肯定也是不可避免的

首先也是通过对openat进行hook来定位检测

定位到线程0xaa97c

也是很常规的检测方式

简单修改一下,JNI_OnLoad就可以正常调试了

import idautils
import idc
import idaapi
from keystone import *
 
ks = keystone.Ks(keystone.KS_ARCH_ARM64, keystone.KS_MODE_LITTLE_ENDIAN)
 
# 跳转表的信息,根据switch-case表修改
jump_table    = 0x1154B4
element_sz    = 2
element_base  = 0x3B348
element_shift = 2
 
# def块 和 分发块,根据switch-case表修改
def_block  = 0x3B330
jump_block = 0x3B338
def_init_block = -1
 
# input reg switch,根据switch-case表修改
reg_base = 129
reg_switch = reg_base + 0
 
# 当前处理的函数,根据switch-case表修改
func_addr = 0x38330
f_blocks = idaapi.FlowChart(idaapi.get_func(func_addr), flags=idaapi.FC_PREDS)
 
def get_code_refs_to_list(ea):
    result = list(idautils.CodeRefsTo(ea, True))
    return result
 
def get_block(addr, f_blocks):
    for block in f_blocks:
        if block.start_ea <= addr and addr <= block.end_ea - 4:
            return block
    return None
 
def get_next_case(start_ea, end_ea):
    next_case = -1
    ea = start_ea
    while ea < end_ea:
        mnem = idc.ida_ua.ua_mnem(ea)
        if mnem == 'MOV':
            op1 = idc.get_operand_value(ea, 0)
            op2 = idc.get_operand_value(ea, 1)
            op2_type = idc.get_operand_type(ea, 1)
            if op1 == reg_switch and op2_type == idc.o_imm:
                next_case = op2
        ea = ea + 4
    return next_case
 
def get_cond(ea):
    cond = None
    disasm = idc.GetDisasm(ea)
    if disasm.find('LT') != -1:
        cond = 'blt'
    elif disasm.find('EQ') != -1:
        cond = 'beq'
    elif disasm.find('CC') != -1:
        cond = 'bcc'
    elif disasm.find('GT') != -1:
        cond = 'bgt'
    elif disasm.find('NE') != -1:
        cond = 'bne' 
    elif disasm.find('GE') != -1:
        cond = 'bge' 
    elif disasm.find('HI') != -1:
        cond = 'bhi' 
    elif disasm.find('LE') != -1:
        cond = 'ble'
    else:
        print('unknow cond:0x%x' % ea)
    return cond
 
def get_cond_next_case(start_ea, end_ea):
    cond_case = -1
    uncond_case = -1
    cond_reg = idc.get_operand_value(end_ea - 8, 1)
    uncond_reg = idc.get_operand_value(end_ea - 8, 2)
 
    ea = start_ea
    while ea < end_ea:
        mnem = idc.ida_ua.ua_mnem(ea)
        if mnem == 'MOV':
            op1 = idc.get_operand_value(ea, 0)
            op2 = idc.get_operand_value(ea, 1)
            op2_type = idc.get_operand_type(ea, 1)
            if op1 == cond_reg and op2_type == idc.o_imm:
                cond_case = op2
            if op1 == uncond_reg and op2_type == idc.o_imm:
                uncond_case = op2
        ea = ea + 4
     
    # 部分case值的初始化,在init块中,不在对应的case块
    if cond_case == -1 or uncond_case == -1:
        #print('def_init_block 0x%x' % def_init_block)
        block = get_block(def_init_block, f_blocks)
        ea = block.start_ea
        end_ea = block.end_ea
 
        cond_flag = False
        uncond_flag = False
        if cond_case == -1:
            cond_flag = True
        if uncond_case == -1:
            uncond_flag = True
 
        while ea < end_ea:
            mnem = idc.ida_ua.ua_mnem(ea)
            if mnem == 'MOV':
                op1 = idc.get_operand_value(ea, 0)
                op2 = idc.get_operand_value(ea, 1)
                op2_type = idc.get_operand_type(ea, 1)
                if cond_flag:
                    if op1 == cond_reg and op2_type == idc.o_imm:
                        cond_case = op2
                if uncond_flag:
                    if op1 == uncond_reg and op2_type == idc.o_imm:
                        uncond_case = op2
            ea = ea + 4         
 
    if cond_reg == 160:
        cond_case = 0
    elif uncond_reg == 160:
        uncond_case = 0
    return cond_case, uncond_case
 
def do_patch(ea, opcode, src, dst):
    jump_offset = " ({:d})".format(dst - src)
    repair_opcode = opcode + jump_offset
    #print(repair_opcode)
    encoding, count = ks.asm(repair_opcode)
    idaapi.patch_byte(ea, encoding[0])
    idaapi.patch_byte(ea + 1, encoding[1])
    idaapi.patch_byte(ea + 2, encoding[2])
    idaapi.patch_byte(ea + 3, encoding[3])
 
jump_block_list = get_code_refs_to_list(jump_block)
jump_def_list = get_code_refs_to_list(def_block)
 
def hex_to_dec(hex_str):
    #print(hex_str)
    #print(hex_str[0])
    # 把16进制字符串转成带符号10进制
    if hex_str[0] in '0123456789':
        dec_data = int(hex_str, 16)
    else:
        # 负数算法
        width = 32  # 16进制数所占位数
        d = 'FFFF' + hex_str
        dec_data = int(d, 16)
        if dec_data > 2 ** (width - 1) - 1:
            dec_data = 2 ** width - dec_data
            dec_data = 0 - dec_data
    return dec_data
 
def do_B_block(addr, cond):
    block = get_block(addr, f_blocks)
    if block is None:
        return
 
    next_case = get_next_case(block.start_ea, block.end_ea)
    if next_case == -1:
        return
     
    if element_sz == 1:
        case_data = idc.get_wide_byte(jump_table + next_case)
        if case_data > 0x7f:
            case_data = hex_to_dec(hex(case_data)[2:])
        jmp_off = case_data * (2 * element_shift)
        jmp_addr = jmp_off + element_base
    elif element_sz == 2:
        case_data = idc.get_wide_word(jump_table + next_case * 2)
        if case_data > 0x7fff:
            case_data = hex_to_dec(hex(case_data)[2:])
        jmp_off = case_data * (2 * element_shift)
        jmp_addr = jmp_off + element_base
 
    print('jump_block_list->addr: 0x%x, next_case: %d, jmp_addr: 0x%x' % (addr, next_case, jmp_addr))
     
    if cond == 'cbnz':
        reg_cmp = idc.get_operand_value(addr - 4, 0)
        cond = "cbnz x{:d}, ".format(reg_cmp - reg_base)
    elif cond == 'cbz':
        reg_cmp = idc.get_operand_value(addr - 4, 0)
        cond = "cbz x{:d}, ".format(reg_cmp - reg_base)
 
    do_patch(addr, cond, addr, jmp_addr)
 
 
# 处理 jump_block
for addr in jump_block_list:
    mnem = idc.ida_ua.ua_mnem(addr)
 
    if mnem == 'B':
        do_B_block(addr, 'b')
    elif mnem == 'TBZ':
        do_B_block(addr, 'b')
    elif mnem == 'CBNZ':
        do_B_block(addr, 'cbnz')
    elif mnem == 'CBZ':
        do_B_block(addr, 'cbz')
    else:
        print('unknow jump_block:0x%x' % addr)
 
 
def do_cond_block(addr, ins):
    cond = get_cond(addr - 4)
    if cond is None:
        print('unkown cond 0x%x' % addr)
        return
 
    block = get_block(addr, f_blocks)
    if block is None:
        return
     
    cond_case, uncond_case = get_cond_next_case(block.start_ea, block.end_ea)
    if cond_case == -1 or uncond_case == -1:
        return
     
    if mnem == 'CSINC':
        uncond_case = uncond_case + 1
 
    cond_jmp_addr = -1
    uncond_jmp_addr = -1
    if element_sz == 1:
        case_data = idc.get_wide_byte(jump_table + cond_case)
        if case_data > 0x7f:
            case_data = hex_to_dec(hex(case_data)[2:])
        jmp_off = case_data * (2 * element_shift)
        cond_jmp_addr = jmp_off + element_base
 
        case_data = idc.get_wide_byte(jump_table + uncond_case)
        if case_data > 0x7f:
            case_data = hex_to_dec(hex(case_data)[2:])
        jmp_off = case_data * (2 * element_shift)
        uncond_jmp_addr = jmp_off + element_base
    elif element_sz == 2:
        case_data = idc.get_wide_word(jump_table + cond_case * 2)
        if case_data > 0x7fff:
            case_data = hex_to_dec(hex(case_data)[2:])
        jmp_off = case_data * (2 * element_shift)
        cond_jmp_addr = jmp_off + element_base
 
        case_data = idc.get_wide_word(jump_table + uncond_case * 2)
        if case_data > 0x7fff:
            case_data = hex_to_dec(hex(case_data)[2:])
        jmp_off = case_data * (2 * element_shift)
        uncond_jmp_addr = jmp_off + element_base
 
    print('jump_def_list->addr: 0x%x, cond_case: %d, cond_jmp_addr: 0x%x, uncond_case: %d, uncond_jmp_addr: 0x%x' %
        (addr, cond_case, cond_jmp_addr, uncond_case, uncond_jmp_addr))
     
    do_patch(addr - 4, cond, addr - 4, cond_jmp_addr)
    do_patch(addr, 'b', addr, uncond_jmp_addr)
 
 
# 处理 def_block
for addr in jump_def_list:
    #print('jump_def_list->addr: 0x%x' % addr)
     
    if addr + 4 == def_block:
        def_init_block = addr
        # print('def_init_block 0x%x' % def_init_block)
        continue
     
    mnem = idc.ida_ua.ua_mnem(addr)
    if mnem != 'B':
        continue
     
    mnem = idc.ida_ua.ua_mnem(addr - 4)
 
    if mnem == 'CSEL':
        do_cond_block(addr, 'CSEL')
    elif mnem == 'CSINC':
        do_cond_block(addr, 'CSINC')
import idautils
import idc
import idaapi
from keystone import *
 
ks = keystone.Ks(keystone.KS_ARCH_ARM64, keystone.KS_MODE_LITTLE_ENDIAN)
 
# 跳转表的信息,根据switch-case表修改
jump_table    = 0x1154B4
element_sz    = 2
element_base  = 0x3B348
element_shift = 2
 
# def块 和 分发块,根据switch-case表修改
def_block  = 0x3B330
jump_block = 0x3B338
def_init_block = -1
 
# input reg switch,根据switch-case表修改
reg_base = 129
reg_switch = reg_base + 0
 
# 当前处理的函数,根据switch-case表修改
func_addr = 0x38330
f_blocks = idaapi.FlowChart(idaapi.get_func(func_addr), flags=idaapi.FC_PREDS)
 
def get_code_refs_to_list(ea):
    result = list(idautils.CodeRefsTo(ea, True))
    return result
 
def get_block(addr, f_blocks):
    for block in f_blocks:
        if block.start_ea <= addr and addr <= block.end_ea - 4:
            return block
    return None
 
def get_next_case(start_ea, end_ea):
    next_case = -1
    ea = start_ea
    while ea < end_ea:
        mnem = idc.ida_ua.ua_mnem(ea)
        if mnem == 'MOV':
            op1 = idc.get_operand_value(ea, 0)
            op2 = idc.get_operand_value(ea, 1)
            op2_type = idc.get_operand_type(ea, 1)
            if op1 == reg_switch and op2_type == idc.o_imm:
                next_case = op2
        ea = ea + 4
    return next_case
 
def get_cond(ea):
    cond = None
    disasm = idc.GetDisasm(ea)
    if disasm.find('LT') != -1:
        cond = 'blt'
    elif disasm.find('EQ') != -1:
        cond = 'beq'
    elif disasm.find('CC') != -1:
        cond = 'bcc'
    elif disasm.find('GT') != -1:
        cond = 'bgt'
    elif disasm.find('NE') != -1:
        cond = 'bne' 
    elif disasm.find('GE') != -1:
        cond = 'bge' 
    elif disasm.find('HI') != -1:
        cond = 'bhi' 
    elif disasm.find('LE') != -1:
        cond = 'ble'
    else:
        print('unknow cond:0x%x' % ea)
    return cond
 
def get_cond_next_case(start_ea, end_ea):
    cond_case = -1
    uncond_case = -1
    cond_reg = idc.get_operand_value(end_ea - 8, 1)
    uncond_reg = idc.get_operand_value(end_ea - 8, 2)
 
    ea = start_ea
    while ea < end_ea:
        mnem = idc.ida_ua.ua_mnem(ea)
        if mnem == 'MOV':
            op1 = idc.get_operand_value(ea, 0)
            op2 = idc.get_operand_value(ea, 1)
            op2_type = idc.get_operand_type(ea, 1)
            if op1 == cond_reg and op2_type == idc.o_imm:
                cond_case = op2
            if op1 == uncond_reg and op2_type == idc.o_imm:
                uncond_case = op2
        ea = ea + 4
     
    # 部分case值的初始化,在init块中,不在对应的case块
    if cond_case == -1 or uncond_case == -1:
        #print('def_init_block 0x%x' % def_init_block)
        block = get_block(def_init_block, f_blocks)
        ea = block.start_ea
        end_ea = block.end_ea
 
        cond_flag = False
        uncond_flag = False
        if cond_case == -1:
            cond_flag = True
        if uncond_case == -1:
            uncond_flag = True
 
        while ea < end_ea:
            mnem = idc.ida_ua.ua_mnem(ea)
            if mnem == 'MOV':
                op1 = idc.get_operand_value(ea, 0)
                op2 = idc.get_operand_value(ea, 1)
                op2_type = idc.get_operand_type(ea, 1)
                if cond_flag:
                    if op1 == cond_reg and op2_type == idc.o_imm:
                        cond_case = op2
                if uncond_flag:
                    if op1 == uncond_reg and op2_type == idc.o_imm:
                        uncond_case = op2
            ea = ea + 4         
 
    if cond_reg == 160:
        cond_case = 0
    elif uncond_reg == 160:
        uncond_case = 0
    return cond_case, uncond_case
 
def do_patch(ea, opcode, src, dst):

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 17
支持
分享
最新回复 (14)
雪    币: 2575
活跃值: (502)
能力值: ( LV2,RANK:85 )
在线值:
发帖
回帖
粉丝
2
最近正在学习APP脱壳
2023-7-15 19:39
0
雪    币: 3059
活跃值: (30876)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
感谢分享
2023-7-15 23:04
1
雪    币: 1571
活跃值: (1653)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
学习了  感谢老哥分享
2023-7-16 14:34
0
雪    币: 3366
活跃值: (14033)
能力值: ( LV9,RANK:230 )
在线值:
发帖
回帖
粉丝
5
文章写的不错,为啥不用xposed直接去hook呢... 看你主要目的就是为了去hook 。frida特征太多 。
2023-7-17 19:47
0
雪    币: 6573
活跃值: (3888)
能力值: (RANK:200 )
在线值:
发帖
回帖
粉丝
6
感谢分享
2023-7-20 11:13
0
雪    币: 6166
活跃值: (4937)
能力值: ( LV10,RANK:160 )
在线值:
发帖
回帖
粉丝
8
mark
2023-8-16 17:46
1
雪    币: 3222
活跃值: (4932)
能力值: ( LV9,RANK:160 )
在线值:
发帖
回帖
粉丝
9
直接把section header全部抹0,ida也能解析,用的dynamic segment。
2023-12-9 17:36
0
雪    币: 3518
活跃值: (4789)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
10
乐子人 直接把section header全部抹0,ida也能解析,用的dynamic segment。
是的,ida确实可以拿dynamic去解析,怕就怕是个伪造的dynamic
2023-12-9 18:07
0
雪    币: 2159
活跃值: (4097)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
11
2beNo2 是的,ida确实可以拿dynamic去解析,怕就怕是个伪造的dynamic[em_27]
牛逼
2023-12-9 23:27
0
雪    币: 189
活跃值: (1605)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
楼主 用什么hook openat?
2024-1-12 00:07
0
雪    币: 3836
活跃值: (4142)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
感谢分享
2024-1-12 00:55
0
雪    币: 12
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
看机会吗大佬
2024-10-21 16:17
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
大佬有联系方式吗
2024-10-22 00:52
0
游客
登录 | 注册 方可回帖
返回
//