IDA python script cheatsheet - aeskkkey/reverse GitHub Wiki

个人笔记,转载请注明出处。


ida8.0, arm64, 仅供api使用参考

常用代码

# so开始地址
idc.get_inf_attr(idc.INF_MIN_EA)
# so结束地址
idc.get_inf_attr(idc.INF_MAX_EA)


# 遍历函数的所有引用
ea = ida_xref.get_first_cref_to(ea)
while ea != idc.BADADDR:
  	ea = ida_xref.get_next_cref_to(decode_string_func, ea)
# 或者
for ea in idautils.CodeRefsTo(ea, 1):
  	print(hex(ea))
# 或者idautils.XrefsTo(ea, 1)


# 获取某地址指令字符串,带备注的
insn = idc.GetDisasm(addr) # 例如返回 insn="MOV X1, #0x35; msg"


# 获取某地址指令的操作数,例如 MOV X1, #0x35
x1 = idc.get_operand_value(addr, 0) # x1 = 83,指寄存器X1/W1
imm = idc.get_operand_value(addr, 1) # imm = 0x35,指立即数值
t = idc.get_operand_type(addr, 1) # t = 1 = idc.o_reg,代表寄存器


# 遍历段
def iter_seg():
    addr = idc.get_first_seg()
    while addr != idc.BADADDR:
        print('seg', f'{hex(addr)}-{hex(idc.get_segm_end(addr))}', idc.get_segm_name(addr))
        addr = idc.get_next_seg(addr)
# 或者idautils.Segments()
# 获取段
text_seg = ida_segment.get_segm_by_name(".text")
# 有时候dump出来的so没有修复text段
start = text_seg.start_ea if text_seg else idc.get_inf_attr(idc.INF_MIN_EA)
end = text_seg.end_ea if text_seg else idc.get_inf_attr(idc.INF_MAX_EA)


def get_top_ref(top=5):
    """
    获取各个函数引用次数
    :param top: 引用次数前n个 
    :return: 返回排序后的(ea, count) 列表
    """
    funcs = list(idautils.Functions())
    m = {}
    for ea in funcs:
        for xref in idautils.XrefsTo(ea):
            m.setdefault(ea, 0)
            m[ea] += 1
    sm = sorted(m.items(), key=lambda x: x[1], reverse=True)
    return sm[:top]
  
 
def get_called_func(func_ea):
    """
    获取函数里调用的其他函数
    :param func_ea: 函数地址
    :return: 调用的函数地址-名称 dict
    """
    called_funcs = {}
    ea = func_ea
    while ea != idc.find_func_end(func_ea):
        try:
            if idc.get_operand_type(ea, 0) == idc.o_near:
                called_funcs[ea] = idc.get_func_name(idc.get_operand_value(ea, 0))
            ea = idc.next_head(ea)
        except Exception as e:
            print(ea, e)
    return called_funcs
  
 
def get_all_switch():
    """
    遍历所有的jump table
    :return: 返回dict,key是table地址,value是(case数量, 各case长度)
    """
    text_seg = idaapi.get_segm_by_name('.text')
    jump_table = dict()
    # iterate through all items within the segment
    for head_ea in idautils.Heads(text_seg.start_ea, text_seg.end_ea):
        if idc.is_code(ida_bytes.get_flags(head_ea)):
            switch_info = idaapi.get_switch_info(head_ea)
            if switch_info and switch_info.jumps != 0:
                loc = switch_info.jumps
                element_num = switch_info.get_jtable_size()
                element_size = switch_info.get_jtable_element_size()
                jump_table[loc] = (element_num, element_size)
                # for num in range(0, element_num):
                #     table_entry = loc + num * element_size
                #     print(hex(loc), hex(table_entry), element_size)
    return jump_table
  
  

def find_svc():
    """ARM64 svc code: 01 00 00 D4"""
    text_seg = ida_segment.get_segm_by_name(".text")
    start = text_seg.start_ea if text_seg else idc.get_inf_attr(idc.INF_MIN_EA)
    end = text_seg.end_ea if text_seg else idc.get_inf_attr(idc.INF_MAX_EA)
    ret = []
    for ea in range(start, end, 4):
        if ida_bytes.get_dword(ea) == 0xd4000001:
            for i in range(0, 0x40, 0x4):
                if idaapi.is_func(idaapi.get_flags(ea)):
                    break
                if idc.get_operand_type(ea - i, 0) == idc.o_reg and idc.get_operand_value(ea - i, 0) == 0x89:
                    if idc.get_operand_type(ea - i, 1) == idc.o_imm:  # MOV X8, 0x1;
                        svc_num = idc.get_operand_value(ea - i, 1)
                        print(hex(ea), 'syscall', f'num_{svc_num}')
                        ret.append(ea)
                        break
                    if idc.get_operand_type(ea - i, 1) == idc.o_reg:  # MOV X8, X0
                        print('[find_svc]', hex(ea), 'syscall', f'reg_x{idc.get_operand_value(ea - i, 1) - 0x81}')
                        ret.append(ea)
                        break
    return ret

ida结合emu

emu基本基于unicorn实现,可以直接python代码手搓模拟器,也可以用

  • uEmu,适合在ida里用模拟器调试
  • flare-emu,适合在py脚本里执行。

用途:

  • 修复简单混淆,例如简单的br跳转,lr跳转。
  • 主动调用字符串解密函数,还原明文。

例子:修复块内计算的BR寄存器跳转,仅限于简单混淆

g_eh = flare_emu.EmuHelper()

def arm642bin(arm_code, addr=0):
    ks = keystone.Ks(keystone.KS_ARCH_ARM64, keystone.KS_MODE_LITTLE_ENDIAN)
    asm = ks.asm(arm_code, addr=addr)
    bs = asm[0]
    return bs[0] | bs[1] << 8 | bs[2] << 16 | bs[3] << 24


def fix_br(ea):
    idc.get_manual_insn(ea)
    match = re.match(r'BR\s+(X\d+)', idc.GetDisasm(ea))
    if match:
        reg = match.group(1)
    else:
        return
    g_eh.emulateRange(startAddr=idaapi.get_func(ea).start_ea, endAddr=ea, count=1000)
    ret = g_eh.getRegVal(reg)
    if ret != 0:
        print(reg, '=', hex(ret))
        idc.patch_dword(ea, arm642bin(arm_code=f"B {hex(ret)}", addr=ea))  # patch br


def patch_all_br():
    for ea in list(idautils.Functions()):
        end = idaapi.get_func(ea).end_ea - 4
        if re.match(r'BR\s+X(\d+)', idc.GetDisasm(end)) and 0x20 < end - ea < 0x200 and \
                ida_bytes.is_data(ida_bytes.get_full_flags(end + 4)):
            print(hex(end), idc.GetDisasm(end))
            try:
                fix_br(end)
            except Exception as e:
                print(hex(ea), 'error', e)