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為基址。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM