您好,登錄后才能下訂單哦!
今天就跟大家聊聊有關如何使用IDAPython尋找漏洞,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
IDAPython是一個強大的工具,可用于自動化繁瑣復雜的逆向工程任務。雖然已經有很多關于使用IDAPython來簡化基本的逆向工程的文章,但是很少有關于使用IDAPython來幫助審查二進制文件以發現漏洞的文章。因為這不是一個新想法(HalvarFlake在2001年提出了關于使用IDA腳本自動化漏洞研究的文章),所以沒有更多關于這個主題的文章是有點令人驚訝的。這可能部分是因為在現代操作系統上執行利用操作所需的復雜性日益增加。但是,能夠將部分漏洞研究過程自動化仍然很有價值。
我們將開始介紹如何使用基本的IDAPython技術來檢測危險的代碼,它們常常導致堆棧緩沖區溢出。我將使用 http://pwnable[.]kr 中的“ascii_easy”二進制文件自動檢測基本堆棧緩沖區溢出。雖然這個二進制文件足夠小,可以完全手動逆向,但它是一個很好的示例,可以將相同的IDAPython技術應用到更大、更復雜的二進制文件中。
在開始編寫IDAPython之前,我們必須首先確定希望腳本查找什么內容。在本例中,我選擇了具有最簡單類型漏洞之一的二進制文件,這是由使用“strcpy”將用戶控制的字符串復制到堆棧緩沖區所造成的堆棧緩沖區溢出。既然我們已經知道了我們要尋找什么,我們就可以開始考慮如何自動查找這些類型的漏洞了。
為了達到目的,我們將把它分成兩個步驟:
查找可能導致堆棧緩沖區溢出的所有函數調用(在本例中是”strcpy”)
分析函數調用的使用以確定使用是否符合條件(可能導致可利用的溢出)
為了找到對“strcpy”函數的所有調用,我們必須首先定位“strcpy”函數本身。使用IDAPython API提供的功能很容易做到這一點。使用下面的代碼,我們可以打印出二進制文件中的所有函數名:
for functionAddr in Functions(): print(GetFunctionName(functionAddr))
在ascii_easy二進制文件上運行這個IDAPython腳本會給出以下輸出。我們可以看到所有的函數名都打印在IDA Pro的輸出窗口中。
接下來,我們添加代碼來過濾函數列表,以便找到我們感興趣的‘strcpy’函數。簡單的字符串比較將在這里發揮作用。由于我們通常處理的函數類似,但由于導入函數的命名方式略有不同(例如示例程序中的“strcpy” vs“_strcpy”),所以最好檢查子串,而不是確切的字符串。
在前面的代碼的基礎上,我們現在有了以下代碼:
for functionAddr in Functions(): if “strcpy” in GetFunctionName(functionAddr): print hex(functionAddr)
現在我們找到了要找的函數,我們必須確定所有調用它的位置。這涉及到幾個步驟。首先,我們得到所有對“strcpy”的交叉引用,然后檢查每個交叉引用,找出哪些交叉引用是實際的`strcpy’函數調用。把所有這些放在一起,我們就會得到下面這段代碼:
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二進制文件運行這個命令將生成二進制文件中所有的“strcpy”調用。結果如下:
現在,通過上面的代碼,我們知道如何在程序中獲取所有調用的地址。雖然在ascii_easy應用程序中,只有一個對“strcpy”的調用(碰巧它也是易受攻擊的),但許多應用程序都會有大量對“strcpy”的調用(大量的調用并不容易受到攻擊),因此我們需要某種方法來分析對“strcpy”的調用,以便對更容易受到攻擊的函數調用進行優先級排序。
可利用緩沖區溢出的一個常見特征是,它們常常涉及堆棧緩沖區。雖然利用堆和其他地方的緩沖區溢出是可能的,但是堆棧緩沖區溢出是一種更簡單的利用途徑。
這涉及到對strcpy函數的目標參數的一些分析。我們知道目標參數是strcpy函數的第一個參數,我們可以從函數調用的反匯編中找到這個參數。以下是對strcpy調用的反匯編。
在分析上面的代碼時,有兩種方法可以找到_strcpy函數的目標參數。第一種方法是依賴自動IDA Pro分析,它自動注釋已知的函數參數。正如我們在上面的截圖中所看到的,IDA Pro自動檢測到了_strcpy函數的“dest”參數,并在將參數推送到堆棧中的指令處用注釋將其標記為dest參數。
檢測函數參數的另一種簡單方法是向后移動匯編代碼,從函數調用開始尋找“push”指令。每當我們找到一條指令,我們就可以增加一個計數器,直到找到我們正在尋找的參數的索引為止。在這種情況下,由于我們正在尋找恰巧是第一個參數的“dest”參數,該方法將在函數調用之前的“push”指令的第一個實例處停止。
在這兩種情況下,當我們向后遍歷代碼時,我們必須小心識別破壞順序代碼流的某些指令。諸如“ret”和“jmp”之類的指令會導致代碼流的更改,從而難以準確識別參數。此外,我們還必須確保不會在當前函數的開始處向后遍歷代碼。現在,我們將在搜索參數時簡單地識別非順序代碼流的實例,如果找到任何非順序代碼流實例,則停止搜索。
我們將使用第二種方法查找參數(尋找被推到堆棧中的參數)。為了以這種方式幫助我們找到參數,我們應該創建一個幫助函數,這個函數將從函數調用的地址向后跟蹤推送到堆棧中的參數,并返回與指定參數對應的操作數。
因此,對于上面調用ascii_easy中的_strcpy的示例,我們的幫助函數將返回值“eax”,因為“eax”寄存器在將strcpy作為參數推送到堆棧中時,存儲它的目標參數為_strcpy。結合使用一些基本的python和IDAPython API,我們可以構建一個函數來實現這一點,如下所示。
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)
使用這個幫助函數,我們能夠確定在調用_strcpy之前使用了“eax”寄存器來存儲目標參數。為了確定eax在被推入堆棧時是否指向堆棧緩沖區,我們現在必須繼續嘗試跟蹤“eax”中的值來自何處。為了做到這一點,我們使用了類似于以前幫助函數中使用的搜索循環:
# 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
在上面的代碼中,我們通過匯編代碼執行向后搜索,查找保存目標緩沖區的寄存器獲取其值的指令。代碼還執行許多其他檢查,比如檢查,以確保我們沒有搜索過函數的開始,也沒有執行任何可能導致代碼流更改的指令。代碼還試圖追溯任何其他寄存器的值,這些寄存器可能是我們最初搜索的寄存器的來源。例如,代碼試圖說明下面演示的情況。
... lea ebx [ebp-0x24] ... mov eax, ebx ... push eax ...
此外,在上面的代碼中,我們引用了函數is_stack_buffer()。這個函數是這個腳本的最后一部分,在IDA API中沒有定義。這是一個額外的幫助函數,我們將編寫它來幫助我們尋找bug。這個函數的目的非常簡單:給定指令的地址和操作數的索引,報告變量是否是堆棧緩沖區。雖然IDA API沒有直接為我們提供這種功能,但它確實為我們提供了通過其他方式檢查這一功能的能力。使用get_stkvar函數并檢查結果是否為None或對象,我們能夠有效地檢查操作數是否是堆棧變量。我們可以在下面的代碼中看到我們的幫助函數:
def is_stack_buffer(addr, idx): inst = DecodeInstruction(addr) return get_stkvar(inst[idx], inst[idx].addr) != None
請注意,上面的幫助函數與IDA7 API不兼容。在我們的下一篇博文中,我們將介紹一種新的方法來檢查參數是否是堆棧緩沖區,同時保持與所有最新版本的IDA API的兼容性。
現在,我們可以將所有這些放到一個腳本中,如下所示,以便找到使用strcpy的所有實例,以便將數據復制到堆棧緩沖區中。有了這些,我們就可以將這些功能擴展到除了strcpy之外,還可以擴展到類似的功能,如strcat、printf等(請參閱 Microsoft禁止的函數列表 ),以及向我們的腳本添加額外的分析。這個腳本的完整版在文章的底部可以找到。運行腳本可以成功地找到易受攻擊的strcpy,如下所示。
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
看完上述內容,你們對如何使用IDAPython尋找漏洞有進一步的了解嗎?如果還想了解更多知識或者相關內容,請關注億速云行業資訊頻道,感謝大家的支持。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。