IDAPython脚本编写指南(二)

时间:2020-07-11
本文章向大家介绍IDAPython脚本编写指南(二),主要包括IDAPython脚本编写指南(二)使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

IDAPython脚本编写指南(二)

关于指令

在上一篇已经学会了使用函数,现在可以继续来学习指令了,如果我们有一个函数的地址,我们可以使用idautils.FuncItems(ea) 来获得所有地址的列表。

Python>dism_addr = list(idautils.FuncItems(here()))
Python>type(dism_addr)
<type 'list'>
Python>print dism_addr
[4199264L, 4199265L, 4199267L, 4199268L, 4199275L, 4199276L, 4199278L, 4199281L, 4199284L, 4199290L, 4199293L, 4199299L, 4199302L, 4199307L, 4199310L, 4199316L, 4199318L, 4199325L, 4199327L, 4199329L, 4199330L, 4199335L, 4199336L, 4199337L, 4199339L, 4199344L, 4199347L, 4199350L, 4199352L, 4199354L, 4199356L, 4199358L, 4199364L, 4199366L, 4199369L, 4199372L, 4199377L, 4199379L, 4199383L, 4199386L, 4199389L, 4199391L, 4199393L, 4199397L, 4199400L, 4199402L, 4199403L, 4199406L, 4199408L, 4199410L, 4199412L, 4199413L, 4199414L, 4199417L, 4199418L, 4199423L, 4199429L, 4199434L, 4199437L, 4199439L, 4199441L, 4199444L, 4199446L, 4199450L, 4199452L, 4199456L, 4199460L, 4199463L, 4199465L, 4199466L, 4199467L]
Python>for line in dism_addr: print hex(line),idc.generate_disasm_line(line,0)
    ....

idautils.FuncItems(ea)返回一个 iterator type类型,但它被强制转换为一个list。该list按连续顺序包含每个指令的起始地址。现在我们已经有了一个遍历段、函数和指令的良好基础,下面让我们展示一个有用的例子。有时,当逆向打包代码时,只知道在某个地方动态调试是很有用的。动态调试可以调试调用call和跳转jmp,例如call eax 或者 jmp edi

python>for func in idautils.Functions():   # 获取已知函数list
    flags = idc.get_func_attr(func,FUNCATTR_FLAGS)   # 获取函数的标志
    if flags & FUNC_LIB or flags & FUNC_THUNK:       # 标志是否是FUNC_LIB 或者 FUNC_FLAGS
        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))

我们使用idautils.Functions()来获得所有已知函数的list,对于每个函数,我们通过调用idc.get_func_attr(ea, FUNCATTR_FLAGS)检索函数标志。如果函数是库代码或thunk函数,则传递该函数。接下来,我们调用idautil.funcitems()来获取函数中的所有地址。我们使用for循环遍历list。因为我们只对calljmp指令感兴趣,所以我们需要通过调用idc.print_insn_mnem()来获得助记符。然后,我们使用一个简单的字符串比较来检查助记符。如果助记符是calljmp,我们通过调用idc.get_operand_type(ea,n)来获得操作数类型。这个函数返回一个内部称为op_t.type的整数。此值可用于确定操作数是否是寄存器、内存引用等。然后检查op_t.type是一个寄存器。如果是,则打印该行。将idautil.funcitems()的返回值转换成列表是很有用的,因为迭代器没有len()这样的对象。通过将它转换为一个list,我们可以很容易地获得一个函数中的行数或指令数。

Python>ea = here()
Python>len(idautils.FuncItems(ea))
Traceback (most recent call last):
  File "<string>", line 1, in <module>
TypeError: object of type 'generator' has no len()
Python>len(list(idautils.FuncItems(ea)))
49

在前面的示例中,我们使用了一个包含函数中所有地址的list。我们遍历每个实例以访问下一条指令。如果我们只有一个地址而且想要获得下一条指令,该怎么办?要,我们可以使用idc.next_head(ea)移动到下一个指令地址,并使用idc.prev_head(ea)获得前一个指令地址。这些函数得到的是下一条指令的开始的位置,而不是下一个地址。要获取下一个地址,我们使用idc.next_addr(ea),要获取前一个地址,我们使用idc.prev_head(ea)

ea = here()
print hex(ea),idc.generate_disasm_line(ea,0)
next_instr = idc.next_head(ea)
print hex(next_instr),idc.generate_disasm_line(next_instr,0)
prev_instr = idc.prev_head(ea)
print hex(prev_instr),idc.generate_disasm_line(prev_instr,0)
print hex(idc.next_addr(ea))
print hex(idc.prev_addr(ea))

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

Python>JMPS = [idaapi.NN_jmp,idaapi.NN_jmpfi,idaapi.NN_jmpni]
Python>CALLS = [idaapi.NN_call,idaapi.NN_callfi,idaapi.NN_callni]
# 使用另外一种表示方法来表示上面相同的示例
for func in idautils.Functions():
    flags = idc.get_func_attr(func,FUNCATTR_FLAGS)
    if flags & FUNC_LIB or flags & FUNC_THUNK:  # 忽略库函数和thunk
        continue
    dism_addr = list(idautils.FuncItems(func))
    for line in dism_addr:
        idaapi.decode_insn(line)
        if idaapi.cmd.itype in CALLS or idaapi.cmd.itype in JMPS:
            if idaapi.cmd.Op1.type == o_reg:
                print "0x%x %s" % (line,idc.generate_disasm_line(line,0))

输出和前面的示例相同,前两行将jmpcall放入连个lists中,由于我们没有使用助记符字符串的表示形式。我们需要认识到,助记符(例如calljmp)可以有多个值。例如:jmp可以使用idaapi.NN_jmp表示跳转,idaapi.NN_jmpfi表示间接远跳,或者idaapi.NN_jmpni 表示间接近跳,X86X64指令类型都以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>dir(idaapi.cmd)
['Op1', 'Op2', 'Op3', 'Op4', 'Op5', 'Op6', 'Operands', '__class__', '__del__', '__delattr__', '__dict__', '__doc__', '__format__', '__get_auxpref__', '__get_operand__', '__get_ops__', '__getattribute__', '__getitem__', '__hash__', '__init__', '__iter__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__set_auxpref__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__swig_destroy__', '__weakref__', 'add_cref', 'add_dref', 'add_off_drefs', 'assign', 'auxpref', 'create_op_data', 'create_stkvar', 'cs', 'ea', 'flags', 'get_canon_feature', 'get_canon_mnem', 'get_next_byte', 'get_next_dword', 'get_next_qword', 'get_next_word', 'insnpref', 'ip', 'is_canon_insn', 'is_macro', 'itype', 'ops', 'segpref', 'size', 'this', 'thisown']

可以从dir()命令查看到idaapi.cmd有很多的属性,操作数类型通过idaapi.cmd.Op1.type访问。请注意,操作数索引从1开始,不同于IDC中get_operand_type(ea,n)的从0开始。

操作数

操作数类型是常用的,所以最好遍历所有类型。如前所述,我们可以使用idc.get_operand_type(ea,n)来获取操作数类型。ea是地址,n是索引。有八种不同类型的操作数类型。

o_void

当一个指令没有任何操作数时返回0。

Python>ea = here()
Python>print hex(ea), idc.generate_disasm_line(ea,0)
0x40142bL retn
Python>print idc.get_operand_type(ea,0)
0

o_reg

如果操作数是常规寄存器时,它将返回1。

Python>ea = here()
Python>print hex(ea), idc.generate_disasm_line(ea,0)
0x401429L pop     ebx
Python>print idc.get_operand_type(ea,0) # 操作数是一个寄存器时返回1
1

o_mem

如果操作数是直接内存引用,它将返回2。这种类型对于查找对数据的引用很有用。

Python>ea = here()
Python>print hex(ea), idc.generate_disasm_line(ea,0)
0x401364L cmp     dword_406C80, 0
Python>print idc.get_operand_type(ea,0)
2

o_phrase

如果操作数包含基址寄存器和/或标志寄存器,则返回此操作数。这个值在内部表示为3。

Python>print hex(ea), idc.generate_disasm_line(ea,1)
0x4013b0L mov     al, [eax+ebx*2]
Python>print idc.get_operand_type(ea,1)
3

o_displ

如果操作数由寄存器和一个数字偏移时,则返回4。偏移是一个整数值,比如0x18。当一条指令访问一个结构中的值时,通常会出现这种情况。

Python>print hex(ea), idc.generate_disasm_line(ea,0)
0x40132dL lea     edx, [esp+28h+Msg]
Python>print idc.get_operand_type(ea,1)
4

o_imm

当操作数是立即数时,返回5

Python>print hex(ea), idc.generate_disasm_line(ea,0)
0x401358L add     esp, 1Ch
Python>print idc.get_operand_type(ea,1)
5

o_far

这个操作数在逆向x86或者x64时很不常见,用于查找当前远跳地址的操作数,返回6

o_fear

这个操作数在逆向x86x86_64时不是很常见。用于查找近跳地址的操作数,返回7

一个例子

有时,当逆向可执行文件的内存dump时,操作数不能被识别为偏移


seg000:00BC1388 push 0Ch
seg000:00BC138A push 0BC10B8h
seg000:00BC138F push [esp+10h+arg_0]
seg000:00BC1393 call ds:_strnicmp

push进的第二个数值是内存偏移,如果我们右键点击它,把它变成一个数据类型,我们会看到一个字符串的偏移量。我们完全可以将这个过程自动化。

# 当操作数是立即数时

min = idc.get_inf_attr(INF_MIN_EA)
max = idc.get_inf_attr(INF_MAX_EA)
# 对于每个已知的函数
for func in idautils.Functions():
    flags = idc.get_func_attr(func,FUNCATTR_FLAGS)
    #忽略库函数和thunk
    if flags & FUNC_LIB or flags & FUNC_THUNK:
        continue
    dism_addr = list(idautils.FuncItems(func))
    for curr_addr in dism_addr:
        if idc.get_operand_type(curr_addr,0) == 5 and \
                (min < idc.get_operand_value(curr_addr,0) < max):
            idc.OpOff(curr_addr,0,0)
        if idc.get_operand_type(curr_addr,1) == 5 and \
                (min < idc.get_operand_value(curr_addr,1) < max):
            idc.op_plain_offset(cur_addr,1,0)

运行以上代码后,我们可以看到以下字符

seg000:00BC1388 push 0Ch
seg000:00BC138A push offset aNtoskrnl_exe ; "ntoskrnl.exe"
seg000:00BC138F push [esp+10h+arg_0]
seg000:00BC1393 call ds:_strnicmp

一开始,我们通过调用idc.get_inf_attr(INF_MIN_EA)idc.get_inf_attr(INF_MAX_EA)来获得最小和最大地址函数或指令,检查操作数类型是否为o_imm(5),找到这个值后,就通过调用idc.get_operand_value(ea,n)来读取该值,如果值在最小和最大地址的范围内,使用idc.op_plain_offset(ea, n, base) 将操作数转换为偏移量,第一个参数ea是地址,n是操作数索引,base是基址例子中是以0为基址。

原文地址:https://www.cnblogs.com/TJTO/p/13284864.html