溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

如何使用IDAPython尋找漏洞

發(fā)布時(shí)間:2021-12-04 10:52:35 來源:億速云 閱讀:190 作者:柒染 欄目:網(wǎng)絡(luò)安全

今天就跟大家聊聊有關(guān)如何使用IDAPython尋找漏洞,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

概覽

IDAPython是一個(gè)強(qiáng)大的工具,可用于自動化繁瑣復(fù)雜的逆向工程任務(wù)。雖然已經(jīng)有很多關(guān)于使用IDAPython來簡化基本的逆向工程的文章,但是很少有關(guān)于使用IDAPython來幫助審查二進(jìn)制文件以發(fā)現(xiàn)漏洞的文章。因?yàn)檫@不是一個(gè)新想法(HalvarFlake在2001年提出了關(guān)于使用IDA腳本自動化漏洞研究的文章),所以沒有更多關(guān)于這個(gè)主題的文章是有點(diǎn)令人驚訝的。這可能部分是因?yàn)樵诂F(xiàn)代操作系統(tǒng)上執(zhí)行利用操作所需的復(fù)雜性日益增加。但是,能夠?qū)⒉糠致┒囱芯窟^程自動化仍然很有價(jià)值。

我們將開始介紹如何使用基本的IDAPython技術(shù)來檢測危險(xiǎn)的代碼,它們常常導(dǎo)致堆棧緩沖區(qū)溢出。我將使用 http://pwnable[.]kr 中的“ascii_easy”二進(jìn)制文件自動檢測基本堆棧緩沖區(qū)溢出。雖然這個(gè)二進(jìn)制文件足夠小,可以完全手動逆向,但它是一個(gè)很好的示例,可以將相同的IDAPython技術(shù)應(yīng)用到更大、更復(fù)雜的二進(jìn)制文件中。

開始

在開始編寫IDAPython之前,我們必須首先確定希望腳本查找什么內(nèi)容。在本例中,我選擇了具有最簡單類型漏洞之一的二進(jìn)制文件,這是由使用“strcpy”將用戶控制的字符串復(fù)制到堆棧緩沖區(qū)所造成的堆棧緩沖區(qū)溢出。既然我們已經(jīng)知道了我們要尋找什么,我們就可以開始考慮如何自動查找這些類型的漏洞了。

為了達(dá)到目的,我們將把它分成兩個(gè)步驟:

  1. 查找可能導(dǎo)致堆棧緩沖區(qū)溢出的所有函數(shù)調(diào)用(在本例中是”strcpy”)

  2. 分析函數(shù)調(diào)用的使用以確定使用是否符合條件(可能導(dǎo)致可利用的溢出)

查找函數(shù)調(diào)用

為了找到對“strcpy”函數(shù)的所有調(diào)用,我們必須首先定位“strcpy”函數(shù)本身。使用IDAPython API提供的功能很容易做到這一點(diǎn)。使用下面的代碼,我們可以打印出二進(jìn)制文件中的所有函數(shù)名:

for functionAddr in Functions():    
   print(GetFunctionName(functionAddr))

在ascii_easy二進(jìn)制文件上運(yùn)行這個(gè)IDAPython腳本會給出以下輸出。我們可以看到所有的函數(shù)名都打印在IDA Pro的輸出窗口中。

如何使用IDAPython尋找漏洞

接下來,我們添加代碼來過濾函數(shù)列表,以便找到我們感興趣的‘strcpy’函數(shù)。簡單的字符串比較將在這里發(fā)揮作用。由于我們通常處理的函數(shù)類似,但由于導(dǎo)入函數(shù)的命名方式略有不同(例如示例程序中的“strcpy” vs“_strcpy”),所以最好檢查子串,而不是確切的字符串。

在前面的代碼的基礎(chǔ)上,我們現(xiàn)在有了以下代碼:

for functionAddr in Functions():    
    if “strcpy” in GetFunctionName(functionAddr):        
        print hex(functionAddr)

現(xiàn)在我們找到了要找的函數(shù),我們必須確定所有調(diào)用它的位置。這涉及到幾個(gè)步驟。首先,我們得到所有對“strcpy”的交叉引用,然后檢查每個(gè)交叉引用,找出哪些交叉引用是實(shí)際的`strcpy’函數(shù)調(diào)用。把所有這些放在一起,我們就會得到下面這段代碼:

for functionAddr in Functions():    
    # Check each function to look for strcpy        
    if "strcpy" in GetFunctionName(functionAddr): 
        xrefs = CodeRefsTo(functionAddr, False)                
        # Iterate over each cross-reference
        for xref in xrefs:                            
            # Check to see if this cross-reference is a function call                            
            if GetMnem(xref).lower() == "call":           
                print hex(xref)

對ascii_easy二進(jìn)制文件運(yùn)行這個(gè)命令將生成二進(jìn)制文件中所有的“strcpy”調(diào)用。結(jié)果如下:

如何使用IDAPython尋找漏洞

函數(shù)調(diào)用分析

現(xiàn)在,通過上面的代碼,我們知道如何在程序中獲取所有調(diào)用的地址。雖然在ascii_easy應(yīng)用程序中,只有一個(gè)對“strcpy”的調(diào)用(碰巧它也是易受攻擊的),但許多應(yīng)用程序都會有大量對“strcpy”的調(diào)用(大量的調(diào)用并不容易受到攻擊),因此我們需要某種方法來分析對“strcpy”的調(diào)用,以便對更容易受到攻擊的函數(shù)調(diào)用進(jìn)行優(yōu)先級排序。

可利用緩沖區(qū)溢出的一個(gè)常見特征是,它們常常涉及堆棧緩沖區(qū)。雖然利用堆和其他地方的緩沖區(qū)溢出是可能的,但是堆棧緩沖區(qū)溢出是一種更簡單的利用途徑。

這涉及到對strcpy函數(shù)的目標(biāo)參數(shù)的一些分析。我們知道目標(biāo)參數(shù)是strcpy函數(shù)的第一個(gè)參數(shù),我們可以從函數(shù)調(diào)用的反匯編中找到這個(gè)參數(shù)。以下是對strcpy調(diào)用的反匯編。

如何使用IDAPython尋找漏洞

在分析上面的代碼時(shí),有兩種方法可以找到_strcpy函數(shù)的目標(biāo)參數(shù)。第一種方法是依賴自動IDA Pro分析,它自動注釋已知的函數(shù)參數(shù)。正如我們在上面的截圖中所看到的,IDA Pro自動檢測到了_strcpy函數(shù)的“dest”參數(shù),并在將參數(shù)推送到堆棧中的指令處用注釋將其標(biāo)記為dest參數(shù)。

檢測函數(shù)參數(shù)的另一種簡單方法是向后移動匯編代碼,從函數(shù)調(diào)用開始尋找“push”指令。每當(dāng)我們找到一條指令,我們就可以增加一個(gè)計(jì)數(shù)器,直到找到我們正在尋找的參數(shù)的索引為止。在這種情況下,由于我們正在尋找恰巧是第一個(gè)參數(shù)的“dest”參數(shù),該方法將在函數(shù)調(diào)用之前的“push”指令的第一個(gè)實(shí)例處停止。

在這兩種情況下,當(dāng)我們向后遍歷代碼時(shí),我們必須小心識別破壞順序代碼流的某些指令。諸如“ret”和“jmp”之類的指令會導(dǎo)致代碼流的更改,從而難以準(zhǔn)確識別參數(shù)。此外,我們還必須確保不會在當(dāng)前函數(shù)的開始處向后遍歷代碼?,F(xiàn)在,我們將在搜索參數(shù)時(shí)簡單地識別非順序代碼流的實(shí)例,如果找到任何非順序代碼流實(shí)例,則停止搜索。

我們將使用第二種方法查找參數(shù)(尋找被推到堆棧中的參數(shù))。為了以這種方式幫助我們找到參數(shù),我們應(yīng)該創(chuàng)建一個(gè)幫助函數(shù),這個(gè)函數(shù)將從函數(shù)調(diào)用的地址向后跟蹤推送到堆棧中的參數(shù),并返回與指定參數(shù)對應(yīng)的操作數(shù)。

因此,對于上面調(diào)用ascii_easy中的_strcpy的示例,我們的幫助函數(shù)將返回值“eax”,因?yàn)椤癳ax”寄存器在將strcpy作為參數(shù)推送到堆棧中時(shí),存儲它的目標(biāo)參數(shù)為_strcpy。結(jié)合使用一些基本的python和IDAPython API,我們可以構(gòu)建一個(gè)函數(shù)來實(shí)現(xiàn)這一點(diǎn),如下所示。

def find_arg(addr, arg_num):
   # Get the start address of the function that we are in
   function_head = GetFunctionAttr(addr, idc.FUNCATTR_START)    
   steps = 0
   arg_count = 0
   # It is unlikely the arguments are 100 instructions away, include this as a safety check
   while steps < 100:    
       steps = steps + 1
       # Get the previous instruction
       addr = idc.PrevHead(addr)  
       # Get the name of the previous instruction
       op = GetMnem(addr).lower()         
       # Check to ensure that we haven’t reached anything that breaks sequential code flow        
       if op in ("ret", "retn", "jmp", "b") or addr < function_head:           return
       if op == "push":
           arg_count = arg_count + 1
           if arg_count == arg_num:               # Return the operand that was pushed to the stack
               return GetOpnd(addr, 0)

使用這個(gè)幫助函數(shù),我們能夠確定在調(diào)用_strcpy之前使用了“eax”寄存器來存儲目標(biāo)參數(shù)。為了確定eax在被推入堆棧時(shí)是否指向堆棧緩沖區(qū),我們現(xiàn)在必須繼續(xù)嘗試跟蹤“eax”中的值來自何處。為了做到這一點(diǎn),我們使用了類似于以前幫助函數(shù)中使用的搜索循環(huán):

# Assume _addr is the address of the call to _strcpy # Assume opnd is “eax” # Find the start address of the function that we are searching infunction_head = GetFunctionAttr(_addr, idc.FUNCATTR_START)
addr = _addr 
while True:
   _addr = idc.PrevHead(_addr)
   _op = GetMnem(_addr).lower()    
   if _op in ("ret", "retn", "jmp", "b") or _addr < function_head:       break
   elif _op == "lea" and GetOpnd(_addr, 0) == opnd:       # We found the destination buffer, check to see if it is in the stack
       if is_stack_buffer(_addr, 1):           print "STACK BUFFER STRCOPY FOUND at 0x%X" % addr           break
   # If we detect that the register that we are trying to locate comes from some other register
   # then we update our loop to begin looking for the source of the data in that other register
   elif _op == "mov" and GetOpnd(_addr, 0) == opnd:
       op_type = GetOpType(_addr, 1)       if op_type == o_reg:
           opnd = GetOpnd(_addr, 1)
           addr = _addr       else:           break

在上面的代碼中,我們通過匯編代碼執(zhí)行向后搜索,查找保存目標(biāo)緩沖區(qū)的寄存器獲取其值的指令。代碼還執(zhí)行許多其他檢查,比如檢查,以確保我們沒有搜索過函數(shù)的開始,也沒有執(zhí)行任何可能導(dǎo)致代碼流更改的指令。代碼還試圖追溯任何其他寄存器的值,這些寄存器可能是我們最初搜索的寄存器的來源。例如,代碼試圖說明下面演示的情況。

... lea ebx [ebp-0x24] 
... mov eax, ebx
...
push eax
...

此外,在上面的代碼中,我們引用了函數(shù)is_stack_buffer()。這個(gè)函數(shù)是這個(gè)腳本的最后一部分,在IDA API中沒有定義。這是一個(gè)額外的幫助函數(shù),我們將編寫它來幫助我們尋找bug。這個(gè)函數(shù)的目的非常簡單:給定指令的地址和操作數(shù)的索引,報(bào)告變量是否是堆棧緩沖區(qū)。雖然IDA API沒有直接為我們提供這種功能,但它確實(shí)為我們提供了通過其他方式檢查這一功能的能力。使用get_stkvar函數(shù)并檢查結(jié)果是否為None或?qū)ο?,我們能夠有效地檢查操作數(shù)是否是堆棧變量。我們可以在下面的代碼中看到我們的幫助函數(shù):

def is_stack_buffer(addr, idx):
   inst = DecodeInstruction(addr)   return get_stkvar(inst[idx], inst[idx].addr) != None

請注意,上面的幫助函數(shù)與IDA7 API不兼容。在我們的下一篇博文中,我們將介紹一種新的方法來檢查參數(shù)是否是堆棧緩沖區(qū),同時(shí)保持與所有最新版本的IDA API的兼容性。

現(xiàn)在,我們可以將所有這些放到一個(gè)腳本中,如下所示,以便找到使用strcpy的所有實(shí)例,以便將數(shù)據(jù)復(fù)制到堆棧緩沖區(qū)中。有了這些,我們就可以將這些功能擴(kuò)展到除了strcpy之外,還可以擴(kuò)展到類似的功能,如strcat、printf等(請參閱 Microsoft禁止的函數(shù)列表 ),以及向我們的腳本添加額外的分析。這個(gè)腳本的完整版在文章的底部可以找到。運(yùn)行腳本可以成功地找到易受攻擊的strcpy,如下所示。

如何使用IDAPython尋找漏洞

腳本

def is_stack_buffer(addr, idx):
   inst = DecodeInstruction(addr)   return get_stkvar(inst[idx], inst[idx].addr) != None def find_arg(addr, arg_num):
   # Get the start address of the function that we are in
   function_head = GetFunctionAttr(addr, idc.FUNCATTR_START)    
   steps = 0
   arg_count = 0
   # It is unlikely the arguments are 100 instructions away, include this as a safety check
   while steps < 100:    
       steps = steps + 1
       # Get the previous instruction
       addr = idc.PrevHead(addr)  
       # Get the name of the previous instruction        
       op = GetMnem(addr).lower() 
       # Check to ensure that we havent reached anything that breaks sequential code flow        
       if op in ("ret", "retn", "jmp", "b") or addr < function_head:            
           return
       if op == "push":
           arg_count = arg_count + 1
           if arg_count == arg_num:               #Return the operand that was pushed to the stack 
               return GetOpnd(addr, 0) 
for functionAddr in Functions():   # Check each function to look for strcpy
   if "strcpy" in GetFunctionName(functionAddr): 
       xrefs = CodeRefsTo(functionAddr, False)       # Iterate over each cross-reference
       for xref in xrefs:           # Check to see if this cross-reference is a function call
           if GetMnem(xref).lower() == "call":               # Since the dest is the first argument of strcpy
               opnd = find_arg(xref, 1) 
               function_head = GetFunctionAttr(xref, idc.FUNCATTR_START)
               addr = xref
               _addr = xref                
               while True:
                   _addr = idc.PrevHead(_addr)
                   _op = GetMnem(_addr).lower()                    
                   if _op in ("ret", "retn", "jmp", "b") or _addr < function_head:                       break
                   elif _op == "lea" and GetOpnd(_addr, 0) == opnd:                       # We found the destination buffer, check to see if it is in the stack
                       if is_stack_buffer(_addr, 1):                           print "STACK BUFFER STRCOPY FOUND at 0x%X" % addr                            break
                   # If we detect that the register that we are trying to locate comes from some other register
                   # then we update our loop to begin looking for the source of the data in that other register
                   elif _op == "mov" and GetOpnd(_addr, 0) == opnd:
                       op_type = GetOpType(_addr, 1)                       if op_type == o_reg:
                           opnd = GetOpnd(_addr, 1)
                           addr = _addr                       else:                           break

看完上述內(nèi)容,你們對如何使用IDAPython尋找漏洞有進(jìn)一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI