idapython腳本編寫


api 和 例子

腳本執行方式

下面命令行

image-20201014191812578

file -> scriptcommand

image-20201014191927953

file -> script file

運行腳本文件

基礎api

idc.ScreenEA() 獲取當前光標所在行的地址, 返回一個 int 類型

MinEA() MaxEA() 獲取當前idb 的最小地址 和 最大地址

idc.SegName(ea) ea是一個變量存儲當前地址, 這個api 是獲取當前地址所在的段

idc.GetDisasm(ea) 獲取當前地址的反匯編代碼

idc.GetMnem(ea) 獲取當前地址的操作碼

idc.GetOpnd(ea,0) 獲取當前地址的操作數,第二個參數表示哪一個操作數

idc.SegStart(ea) 獲取當前地址的段起始地址

idc.SegEnd(ea) 獲取當前地址的段尾地址

idc.NextSeg(ea) 獲取下一個段的起始地址

idautils.Segments() 返回一個可迭代的對象數組

idc.GetFunctionName(func) 通過地址,獲取函數的名稱

idautils.Functions() 返回一個可迭代的函數首地址數組,

idaapi.get_func(ea) 獲取當前地址的函數首地址和尾地址

idc.NextFunction(ea) 獲取下一個函數地址

idc.PrevFunction(ea) 獲取前一個函數地址

idc.GetFunctionAttr(ea,FUNCATTR_START) 獲取一個函數的邊界地址

idc.GetDunctionAttr(ea,FUNCATTR_END) 獲取一個函數的邊界地址

idc.NextHead(當前地址,函數結尾) 獲取一個當前指令的下一行地址,第二個參數為結尾

通過 api 打印所有 段名稱 段起始地址 段結束地址

import idautils
import idc
for seg in idautils.Segments():
    print idc.SegName(seg),hex(idc.SegStart(seg)),hex(idc.SegEnd(seg))

打印所有段中的所有函數地址和名稱

import idautils
import idc
for func in idautils.Functions():
    print hex(func),idc.GetFunctionName(func)
   

獲取當前地址的函數首和尾

import idautils
import idc
ea = ida.ScreenEA()
print ea
func = idaapi.get_func(ea)
print type(func)
print "Start:0x%x , End:0x%x"%(func.startEA,func.endEA)

高級 api

idc.GetFunctionFlags(func)

擁有 9種狀態

  1. FUNC_NORET 表示某個函數是否有返回值,本身的值是1,
  2. FUN_FAR 不常見,用於標志程序是否使用分段內存, 值為2
  3. FUN_USERFAR 不常見,官方文檔描述為 "user has specified far-ness of the function" 它的值是 32
  4. FUN_LIB 表示用於尋找庫函數的代碼,它的值是4
  5. FUNC_STATIC 作用域是被該函數在編譯的是否是一個靜態函數, 在 c語言中靜態函數被默認是認為全局的,如果作者吧這個函數定義為靜態函數,那么這個函數只能被本文件中的函數訪問.利用靜態函數的判定我們可以更好地理解源代碼的結構.
  6. FUNC_FRAME 表示函數是否使用了 ebp寄存器, 使用ebp寄存器的程序一般是windows 程序
  7. FUNC_BOTTOMBP 和 FUNC_FRAME 一樣,用於跟着幀指針(ebp),它的作用是識別函數中幀指針是否等於堆棧指針 esp
  8. FUNC_HIDDEN 帶有FUNC_HIDDEN 標志的函數意味着他們是隱藏的,這個函數需要展開才能查看
  9. FUNC_THUNK 表示這個函數是否是一個 thunk 函數, thunk 函數表示的是一個簡單的跳轉函數

idautils.Funcltems(ea) 來獲取該函數中所有指令地址的集合

迭代器沒有 len() 屬性, 通過強制轉換將其放入 list中可以實現獲取指令的操作,

idc.NextHead(ea) idc.PrevHead(ea) 獲取當前地址的下一條指令 或前一個 相當於 - 當前指令長度or +當前指令長度

idc.NextAddr(ea) idc.PrevAddr(ea) 獲取當前地址的下一個地址 或前一個 相當於 -1 +1

使用 api 打印某個函數的所有匯編代碼和地址

import idautils
import idc
ea = idc.ScreenEA()
print ea
start = idc.GetFunctionAttr(ea,FUNCATTR_START)
end = idc.GetFunctionAttr(ea,FUNCATTR_END)
cur_addr = start
while cur_addr < end:
    print hex(cur_addr),idc.GetDisasm(cur_addr)
    cur_addr = idc.NextHead(cur_addr,end)

枚舉所有不會 ret 的函數

import idautils
ea = idc.ScreenEA()
print ea
for func in idautils.Functions():
    flags = idc.GetFunctionFlags(func)
    if flags & FUNC_NORET:
        print hex(func),"FUNC_NORET",GetFunctionName(func)

打印一個函數中所有指令

import idautils
ea = idc.ScreenEA()
print ea
dism_addr = list(idautils.Funcltems(ea))
print type(dism_addr)
print dism_addr
for line in dism_addr:
    print hex(line),idc.GetDisasm(line)

打印所有被調用的地址

有時候我們會逆向一個加殼的代碼, 這時候知道代碼中哪里進行了動態調用對分析是非常有幫助的. 一個動態的調用可能是由 call 或者 jmp 加上一個操作數來實現的, 比如 call eax, 或者 jmp edi. 我們可以通過下面的腳本將存在相應特征的指令打印處理.

import idautils
for func in idautils.Functions():
    flags = ida.GetFunctionFlags(func)
    if flags & FUNC_LIB or flags & FUNC_THUNK:
        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))

搜索api

idc.FindBinary(當前地址,flag,searcstr,radix=16) 來進行字節或者二進制的搜索, flag 代表搜索方向或條件

  • SEARCH_UP, SEARCH_DOWN 用來指明搜索方向
  • SEARCH_NEXT 用來獲取下一個已經找到的對象
  • SEARCH_CASE 用來指明是否區分大小寫
  • SEARCH_NOSHOW 來指明是否顯示搜索的進度
  • SEARCH_UNICODE 用來將所有搜索字符串視為 Unicode

searcstr 是我們要查找的狀態,

radix 參數在寫處理器模塊時使用, 一般用不到, 留空即可


idc.FindText(當前地址,flag,從當前地址開始搜索的行數,行中的坐標,searchar)

這個搜索的是 匯編代碼中函數名稱之類的, 不是內存中的字符串


idc.isCode(f) 如果該地址為代碼則返回True

idc.isData(f) 如果該地址為數據返回true

idc.idUnknown(f) 無法鑒別是數據還是代碼則返回True

idc.isHead(f) 如果為函數開頭則返回true

idc.isTail(f) 如果為函數結尾則返回true

idc.GetFlags(地址) 以上一系列判斷函數,都不能直接傳遞地址, 需要通過GetFlags(ea) 對地址進行轉換


idc.FindCode(地址,標志) 獲取下一個代碼指令的地址, 比如我們需要獲取某一塊數據的結尾時,如果當前的 ea 是代碼地址, idc.FindCode 將返回下一個代碼指令的地址

idc.FindData(地址,標志) 獲取下一個數據塊類型地址的起始.

idc.Findlmmediate(地址,標志,值)

搜索字節

import idautils
pattern = '48 ff'   #inc rdx
addr = MinEA()
for x in range(0,5):
    addr = idc.FindBinary(addr,SEARCH_DOWN,pattern)

首先將 pattern 置為對應的字節, 然后使用 MinEA() 獲取可執行文件的最開始的地址, 然后將 idc.FindBinary 的返回結果設置為 addr 變量

搜索字符串

import idautils
pattern = "check_value"
cur_addr = MinEA()
end_addr = MaxEA()
while cur_addr<end_addr:
    cur_addr = idc.FindText(cur_addr,SEARCH_DOWN,0,0,pattern)
    if cur_addr == idc.BADADDR:
        break
    else:
        print hex(cur_addr),idc.GetDisasm(cur_addr)
    cur_addr = idc.NextHead(cur_addr)

代碼中 cur_addr 標記當前地址, end_addr 標記搜尋idb 的結束地址,通過函數FiindText 進行搜索. 我們在最后一行 cur_addr=idc.NextHead(cur_addr) 將每次操作的當前地址更新為下一行地址. 這樣就可以搜索出全部的check_value.

利用 findcode 獲取數據的結尾地址

import idautils
ea = idc.ScreenEA()
print hex(ea),idc.GetDisasm(ea)
addr = idc.FindCode(ea,SEARCH_DOWN|SEARCH_NEXT)
print hex(addr),idc.GetDisasm(addr)

搜尋所有帶 0xa 的地址

import idautils
addr = MinEA()
while True:
    addr,operand = idc.Findlmmediate(addr,SEARCH_DOWN|SEARCH_NEXT,0X0A)
    if addr != BADADDR:
        print hex(addr),idc.GetDisasm(addr),"Operand",operand
    else:
        break

數據截取

idc.SelStart() 選中區域開始地址

idc.SelEnd() 選中區域結束地址

打印選中區域的字節

import idautils
start = idc.SelStart()
end = idc.SelEnd()

l = []
while start<end:
    l.append(idc.Byte(start))
    start += 1
print l
    

自動注釋

idc.MakeComm(地址,注釋內容str類型) 向指定地點添加注釋

idc.GetCommentEx(地址,repeatable) 獲取某地址的注釋內容, repeatable 是一個布爾值

idc.SetFunctionCmt(ea,cmt,bool) ea為函數體中任意一處指令的地址, cmt 為需要注釋的內容, bool 為布爾值, false 表示重復注釋, true 為普通注釋. 重復注釋的意思就是生效后, 其他引用的地方也會自動生成注釋

idc.GetFunctionCmt(ea,1) 獲取某個函數的注釋

自動給 xor eax,eax 類型的數據寫注釋

import idautils
for func in idautils.Functions():
    flags = idc.GetFunctionFlags(func)
    if flags & FUNC_LIB or flags & FUNC_THUNK:
        continue
    dism_addr = list(idautils.Funcltems(func))
    for ea in dism_addr:
        if idc.GetMnem(ea) == "xor":
            if idc.GetOpnd(ea,0) == idc.GetOpnd(ea,1):
                comment = "% s = 0" %(idc.GetOpnd(ea,0))
                idc.MakeComm(ea,comment)

查看剛才 xor 的注釋

import idautils
for func in idautils:
    flags = idc.GetFunctionFlags(func)
    if flags & FUNC_LIB or flags & FUNC_THUNK:
        continue
    dism_addr = list(idautils.FuncItems(func))
    for ea in dism_addr:
        if idc.GetMnem(ea) == "xor":
            if idc.GetOpnd(ea,0) == idc.GetOpnd(ea,1):
                comment = "% s = 0"%(idc.GetOpnd(ea,0))
                idc.MakeComm(ea,comment)
                print idc.GetCommentEx(ea,False)

重命名 讀取原始數據 api

idc.MakeName(ea,重命名內容) ea必須是 函數的起始地址.函數重命名,不知道放哪

idc.Byte(ea)

idc.Word(ea)

idc.Dword(ea)

idc.Qword(ea)

idc.GetFloat(ea)

idc.GetDouble(ea)

idc.GetManyBytes(ea,size,use_dbg=False) size 用於長度, use_dbg在調試內存時會用到, 一般不需要設置

將 main 函數重命名為 MAIN

import idautils
ea = idc.ScreenEA()
print hex(ea),idc.GetDisasm()
print idc.MakeName(ea,"MAIN")

總結

進一步全面了解IDAPython提供的函數可以參考官方文檔:

https://www.hex-rays.com/products/ida/support/idapython_docs/

另外有更加擴展深入的學習資料:

https://unit42.paloaltonetworks.com/using-idapython-to-make-your-life-easier-part-1/

這是一系列的文章,國內蒸米大佬曾經在wooyun drops上翻譯過,題目為IDAPython 讓你的生活更滋潤

以及一些安全研究者發出的IDAPython的實際應用文章

https://resources.infosecinstitute.com/saving-time-effort-idapython/#gref

https://www.thezdi.com/blog/2018/6/26/mindshare-variant-hunting-with-ida-python

https://www.fireeye.com/blog/threat-research/2018/01/simplifying-graphs-in-ida.html

雖然python2 語法看起來很別扭,但是自動化確實方便,加上自己由買不起 ida正版軟件,湊合用吧

在這里給合天安全實驗室打個廣告,他們的課做的挺用心的


免責聲明!

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



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