IDAPython详细版(一)

一:基础知识:

IDAPython由三个独立模块组成:

第一个是idc,它是封装IDA的IDC函数的兼容性模块。

第二个模块是idautils,这是IDA里的一个高级实用函数。

第三个是idaapi,它允许访问更多低级数据,这些数据能够被类使用通过IDA。

IDAPython的强大来自于遍历所有的指令,交叉引用地址和搜索代码或者数据。

1.基础知识

.text:00401570 lea eax,[ebp+arg_0]

.text是段名称,地址是00401570 lea是助记符,第一个操作是eax,第二个操作是[ebp+arg_0]

EA = here() == idc.get_screen_ea() 代表当前光标地址,返回整形值,就是地址0012529的十进制

IDAPython详细版(一)_第1张图片

 idc.get_inf_attr(INF_MIN_EA)获取当前最小地址

idc.get_inf_attr(INF_MAX_EA)获取当前最大地址

IDAPython详细版(一)_第2张图片

idc.get_segm_name()获取当前段名称

idc.generate_disasm_line()获取汇编代码

idc.print_insn_name()获取助记符

idc.print_oprand()获取操作符

IDAPython详细版(一)_第3张图片

idaapi.BADADDR idc.BADADDR BADADDR三个函数 验证地址是否在当前程序存在

IDAPython详细版(一)_第4张图片

二:段

遍历所有段 idautils.Segments()返回一个遍历类型对象

idc.get_next_seg(ea)获取下一个段

idc.selector_by_name(segname)返回段选择器并传递单个字符段名称的参数

idc.get_segm_by_sel(idc.selector_by_name(str,SectionName))获取一个段的开始地址

三:函数

idautils.Functions() /  idautils.Funtions(start_addr,end_addr)

idautils.Function()将返回一个已经知道的函数列表

ida.get_func_name()返回函数名称

idaapi.get_func(ea)获取函数边界

start_ea和end_ea 获取起始和结束地址

Python>for func in idautils.Functions(): print("0x%x, %s" % (func, idc.get_func_name(func)))
 
Python>
 
>>> 0x401000, sub_401000
 
>>> 0x401006, w_vfprintf
 
>>> 0x401034, _main
 
>>> …removed…
 
>>> 0x401c4d, terminate
 
>>> 0x401c53, IsProcessorFeaturePresent

访问上/下函数 idc.get_next_func(ea)和idc.get_prev_func(ea)

Python>func = idaapi.get_func(ea)
 
Python>type(func)
 
>>> 
 
Python>print("Start: 0x%x, End: 0x%x" % (func.start_ea, func.end_ea))
 
>>> Start: 0x45c7c3, End: 0x45c7cd

idc.get_func_attr(ea,FUNCATTR_START)和idc.get_func_attr(ea,FUNCATTR_END)访问函数边界

idc.generate_disasm_line(ea,0)打印当前汇编代码

idc.next_head(eax)下一条指令,直到函数结束

IDAPython详细版(一)_第5张图片

使用idautils.Functions()来获取所有已知函数列表的地址

使用idc.get_func_attr(ea,FUNCATTR_FLAGS)获取函数信息,它有9个参数

IDAPython详细版(一)_第6张图片

for func in idautils.Functions():
 
           flags = idc.get_func_attr(func,FUNCATTR_FLAGS)
 
           if flags & FUNC_NORET:                 #无返回标志的函数
 
               print("0x%x FUNC_NORET" % func)
 
           if flags & FUNC_FAR:                   # 这个标志很少出现,除非逆向软件使用分段内存。它的内部表示为一个整数 2。
 
               print("0x%x FUNC_FAR" % func)
 
           if flags & FUNC_LIB:  #此标志用于查找库代码,在内部表示为整数4。
 
               print("0x%x FUNC_LIB" % func)
 
           if flags & FUNC_STATIC: #此标志用于标识基于静态ebp框架的库函数
 
               print("0x%x FUNC_STATIC" % func)
 
           if flags & FUNC_FRAME:  # 这个标志表明该函数使用帧指针 EBP。使用帧指针的函数通常以设置堆栈框架的标准函数序言开始。
 
               print("0x%x FUNC_FRAME" % func)
 
           if flags & FUNC_USERFAR:    #这个标志是罕见的,hexrays 描述标志为“用户指定了函数距离”。它的内部值为 32。
 
               print("0x%x FUNC_USERFAR" % func)
 
           if flags & FUNC_HIDDEN:  #函数带 FUNC_HIDDEN 标志意味着他们是隐藏的将需要扩展到视图。如果我们转到一个被标记为隐藏的函数的地址,它会自动扩展。
 
               print("0x%x FUNC_HIDDEN" % func)
 
           if flags & FUNC_THUNK: #这标志标识函数是 thunk 函数。一个简单的功能是跳到另一个函数。
 
               print("0x%x FUNC_THUNK" % func)
 
           if flags & FUNC_BOOTOMBP:          # 此标志用于跟踪帧指针。标识指针指向堆栈指针的函数
 
               print("0x%x FUNC_BOTTOMBP" % func)

 四:提取函数参数

在IDAPython中,提取函数参数并不总是一项简单的任务。在许多情况下,需要标识函数的调用约定,并且必须使用反向跟踪或者类似的技术手动解析参数。由于大量的调用约定,这在一般情况下并不是总是可行的。IDAPython包含一个名为idaapi.get_arg_addrs(ea)函数,能够识别被调用函数的原型。以下就是识别了一个函数的七个参数地址:

IDAPython详细版(一)_第7张图片

 IDAPython详细版(一)_第8张图片

 五:指令

 本节学习在函数中访问的指令

如果我们有一个函数的地址,我们能够使用idautils.Funcltems(ea)获取列表中所有地址(前提是必须在函数内,会显示改函数所有指令的地址)

idautils.Funcltems(ea)实际返回一个迭代器类型但是被强制转成一个list。该列表将包含顺序连续的每个指令的起始地址。

IDAPython详细版(一)_第9张图片

dism_addr = list(idautils.FuncItems(here()))
print(dism_addr)

for line in dism_addr:
    print("0x%x %s" % (line,idc.generate_disasm_line(line,0)))

 

以下是一个例子

我们调用 idautils.Functions()去获取所有已知函数列表。每个函数我们通过调用get_func_attr(func, FUNCATTR_FLAGS)检索函数的标志

如果这个函数是库代码或者 thunk 函数这个函数被跳过。接下来我们调用 idautils.FuncItems(ea)来获取在函数里的所有的指令地址

使用 for 循环循环遍历列表。由于我们只对 call 和 jmp 指令感兴趣我们需要通过调用 idc.print_insn_mnem(ea)(获取助记符)

然后我们用一个简单的字符串比较检查法。如果是一个 jump 或 call 我们通过操作的类型调用idc.get_operand_type(ea, n)。这个函数将返回一个整数是内部调用 op_t.type。这个值可以用来确定如果操作数是一个寄存器、内存引用等

然后我们检查 op_t.type 是一个寄存器。通过 idc.generate_disasm_line(line,0)打印汇编。

 

for func in idautils.Functions():
 
    flags = idc.get_func_attr(func, FUNCATTR_FLAGS)
 
    if flags & FUNC_LIB or flags & FUNC_THUNK:
 
       continue
 
    dism_addr = list(idautils.FuncItems(func))
 
    for line in dism_addr:  #循环每条指令访问下一条指令
 
          m = idc.print_insn_mnem(line)
 
          if m == 'call' or m == 'jmp':
 
                   op = idc.get_operand_type(line, 0)
 
                   if op == o_reg:
 
                      print "0x%x %s" % (line, idc.generate_disasm_line(line,0))
 
>>>0x43ebde call eax ; VirtualProtect

 

如果只有一个地址,该怎么访问下一个指示呢?

移动到下一条指令的地址可以使用 idc.next_head(ea)和idc.prev_head(ea)获得前后一条指令地址 。这些功能将得到下一个指令的起始地址而不是下一个地址。

得到下一个地址我们使用 idc.next_addr(ea),得到前一个地址我们使用 idc.prev_addr(ea)

ea = here()
print("0x%x %s" % (ea, idc.generate_disasm_line(ea,0)))
>>> 0x10004f24 call    sub_10004f32
next_instr = idc.next_head(ea)
print("0x%x %s" % (ea, idc.generate_disasm_line(next_instr,0)))
>>> 0x10004f29 mov  [esi],eax
prev_instr = idc.prev_head(ea)
print("0x%x %s" % (ea, idc.generate_disasm_line(prev_instr,0)))
>>>0x10004f1e mov [esi+98h], eax
print ("0x%x" % idc.next_addr(ea))
>>> 0x10004f25                                #注意和next_head的区别
print ("0x%x" % idc.prev_addr(ea))
>>>0x10004f23

在动态调试的示例中,IDAPython代码依赖于使用jmp和call的字符串比较,我们也可以使用idaapi.decode_insn(ea)来解码指令,而不是使用字符串比较,对一条指令进行解码是更加好的方法,因为使用整型指令表示可以更快、更少出错。不幸的是,整数表示是特定于IDA的,无法方便的移植到其它反汇编工具,下面是使用idaapi.decode_insn(ea并比较整数表示形式的相同示例

idaapi.decode_insn(insn_t, ea) 解码指令;

第一个参数是来自ida_ua的insn_t类,通过调用ida_ua.insn_t()获得。一旦idaapi.decode_insn函数调用,insn_t类就被属性填充。

第二个参数是被分析的地址。

前两行将jmp和call放入lists中,由于我们没有使用助记符字符串的表示形式。我们需要认识到,助记符(例如call和jmp)可以有多个值。

例如:jmp可以使用idaapi.NN_jmp表示跳转,idaapi.NN_jmpfi表示间接远跳,或者idaapi.NN_jmpni 表示间接近跳,X86和X64指令类型都以NN开头。

找到这超过1700多个指令类型,我们可以在命令行中执行[name for name in dir(idaapi) if “NN”],或者在IDA的SDK文件allins.hpp中查看它们。一旦我们在列表中有了指令,我们使用idautil . functions()和get_func_attr(ea, FUNCATTR_FLAGS)的组合来获得所有适用的函数,同时忽略库和thunks。我们通过调用idautil.funcitems (ea)来获取函数中的每条指令。这是调用新引入的函数idaapi.decode_insn(ea)的地方。这个函数找到我们想要解码指令的地址,一旦解码成功,我们可以通过idaapi.cmd访问指令的不同属性。

Python>JMPS = [idaapi.NN_jmp, idaapi.NN_jmpfi, idaapi.NN_jmpni]
 
Python>CALLS = [idaapi.NN_call, idaapi.NN_callfi, idaapi.NN_callni]
 
Python>for func in idautils.Functions():
 
          flags = idc.get_func_attr(func, FUNCATTR_FLAGS)
 
          if flags & FUNC_LIB or flags & FUNC_THUNK:
 
             continue
 
          dism_addr = list(idautils.FuncItems(func))
 
          for line in dism_addr:
 
             ins = ida_ua.insn_t()
 
             idaapi.decode_insn(ins, line)
 
             if ins.itype in CALLS or ins.itype in JMPS:
 
                if ins.Op1.type == o_reg:   #这就找到了call 和 jmp指令地址
 
                   print("0x%x %s" % (line, idc.generate_disasm_line(line, 0))
 
Python>
 
>>> 0x43ebde call eax ; VirtualProtect

你可能感兴趣的:(逆向,python)