如果要分析的二进制文件比较大,又要知道某些指令的个数,或者查找某种含有特征的指令,这个时候肉眼去搜索显然是比较耗时的,这个时候就可以充分体验到IDA python脚本的便利;
这里要讲到idautils.FuncItems(ea)这个函数,idautils.FuncItems(ea)实际上返回的是一个迭代器,但是我们将它强制转换为 list 类型。这个 list 以一种连续的方式存储着所有指令的起始地址。现在我们已经完全掌握了如何循环遍历程序中的段,函数和指令,那我们就开始 show 一个非常有用的例子。有时候我们会逆向一个加壳的代码,这时知道代码中哪里进行了动态调用对分析是非常有帮助的。一个动态的调用可能是由 call 或者 jump 加上一个操作数来实现的,比如说 call eax,或者 jmp edi。
Python脚本实现描述功能:
import idautils
def example_1():
for func in idautils.Functions():
flags = idc.GetFunctionFlags(func)
if flags & FUNC_LIB or flags & FUNC_THUNK:
print hex(func), "FUNC_LIB", GetFunctionName(func)
continue
dism_addr = list(idautils.FuncItems(func))
for line in dism_addr:
m = idc.GetMnem(line)
if m == 'call' or m == 'jmp':
op = idc.GetOpType(line, 0)
if op == o_reg:
print "0x%x %s" % (line, idc.GetDisasm(line))
def main():
example_1()
if __name__ == "__main__":
main()
使用 idautils.Functions()获取所有的函数,然后利用 idc.GetFunctionFlags(ea)获取每个函数的标志,如果这个函数是库函数或者这个函数是 thunk 函数,那我们就跳过对它的处理,接下来我们使用idautils.FuncItems(ea) 获取函数中每个指定的起始地址。然后使用
idc.GetMnem(ea)来获取每条指令的操作符,判断操作符是否为 call 或者 jmp。如果是的话,我们就使用 idc.GetOpType(ea, n)来获取操作数,这个函数返回的一个 opt.type 类型的数值(int 类型),这个数值可表示的类型为寄存器,内存引用等等;
效果(op==o_reg):
#call register
0xffffffff81c54e11 call r13
0xffffffff81c54e6c call r12
0xffffffff81c5ad0f call r12
0xffffffff81c5ae03 call r13
0xffffffff81c61e7e call rax
0xffffffff81c66a72 call r12
0xffffffffff600053 call rax ; qword_FFFFFFFFFF6001A0
这里对操作数进行解释一下:
o_void
如果指令没有任何操作数,它将返回 0;
o_reg
如果操作数是寄存器,则返回这种类型,它的值为 1;
//jmp to register
0xffffffff8102cba5 jmp rax
0xffffffff81431300 jmp rcx
0xffffffff8143132c jmp rax
o_mem
如果操作数是直接寻址的内存,那么返回这种类型,它的值是 2,这种类型对寻找 DATA的引用非常有帮助。
0xffffffff81c3219b jmp ds:off_FFFFFFFF81602A40[rax*8]; switch jump
0xffffffff81c375c4 jmp ds:off_FFFFFFFF816097A0[rax*8]; switch jump
0xffffffff81c3828a jmp ds:off_FFFFFFFF816097C8[rax*8]; switch jump
0xffffffff81c3ce16 jmp ds:off_FFFFFFFF81609940[rax*8]; switch jump
0xffffffff81c5def2 jmp ds:off_FFFFFFFF816476E8[r15*8]; switch jump
o_phrase
如果操作数是利用基址寄存器和变址寄存器的寻址操作的话,那么返回该类型,值为 3;
0xffffffff8118c548 mov [rsp+10h+var_10], rbx
0xffffffff8118c5f8 mov [rsp+10h+var_10], rbx
0xffffffff8118c6a8 mov [rsp+10h+var_10], rbx
0xffffffff8118c758 mov [rsp+10h+var_10], rbx
0xffffffff8118c808 mov [rsp+10h+var_10], rbx
0xffffffff8118c8b8 mov [rsp+10h+var_10], rbx
o_displ
如果操作数是利用寄存器和位移的寻址操作的话,返回该类型,值为 4;
0xffffffff81075e44 mov dword ptr [rax+1000h], 0
0xffffffff81075e4e mov dword ptr [rax+1004h], 0
0xffffffff81075ed8 mov [rbp+var_18], rbx
0xffffffff81075edc mov [rbp+var_10], r12
0xffffffff81075ee0 mov [rbp+var_8], r13
o_imm
如果操作数是一个确定的数值的话,那么返回类型,值为 5;
o_f ar
这种返回类型在 x86 和 x86_64 的逆向中不常见。它用来判断直接访问远端地址的操作数,值为 6;
o_near
这种返回类型在 x86 和 x86_64 的逆向中不常见。它用来判断直接访问近端地址的操作数,值为 7;