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的十进制
idc.get_inf_attr(INF_MIN_EA)获取当前最小地址
idc.get_inf_attr(INF_MAX_EA)获取当前最大地址
idc.get_segm_name()获取当前段名称
idc.generate_disasm_line()获取汇编代码
idc.print_insn_name()获取助记符
idc.print_oprand()获取操作符
idaapi.BADADDR idc.BADADDR BADADDR三个函数 验证地址是否在当前程序存在
遍历所有段 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)下一条指令,直到函数结束
使用idautils.Functions()来获取所有已知函数列表的地址
使用idc.get_func_attr(ea,FUNCATTR_FLAGS)获取函数信息,它有9个参数
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)函数,能够识别被调用函数的原型。以下就是识别了一个函数的七个参数地址:
本节学习在函数中访问的指令
如果我们有一个函数的地址,我们能够使用idautils.Funcltems(ea)获取列表中所有地址(前提是必须在函数内,会显示改函数所有指令的地址)
idautils.Funcltems(ea)实际返回一个迭代器类型但是被强制转成一个list。该列表将包含顺序连续的每个指令的起始地址。
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