使用IDAPython自動對映二進位制檔案替換預設函式名
前言
這篇文章簡要介紹了我編寫的一個指令碼,該指令碼用除錯輸出的名稱替換了IDA中的預設函式名,希望它能為你建立自己的函式名提供基本知識。
免責宣告:這是我寫的一個小指令碼的解釋,它幫助我可以在幾秒內(而不是數週)對映大型二進位制檔案。我鼓勵任何人修改指令碼以供自己使用。我將這段程式碼用於我自己的私人研究——如果你發現它有用或者修復了一個bug,那就要買瓶啤酒好好謝謝我了。
存在的問題
我遇到的主要問題是我需要對映一個沒有任何符號的大型二進位制檔案。對於二進位制檔案的第一個對映,我只有一個有限的時間框架,所以我必須找到一個更有效的方法來做到這一點。我非常喜歡為IDA編寫指令碼,尤其是對映部分,這也是我在此情況下所做的。為了自動化對映過程,我使用了一個簡單的方式:檢視是否有任何除錯輸出——幸運的是,二進位制檔案有很多除錯輸出。
例項分析
從裝配方面來看,除錯輸出真是一個寶藏。它可以顯示函式的用途,還可以顯示真正的檔名,這有助於理解此函式所屬的模組。值得注意的是,我最初研究的程式碼是在x64 OS上執行的8086程式集,而大多數函式都使用fastcall呼叫約定,因此我在我的文章中使用fastcall作為示例。
圖1:除錯輸出帶指示性錯誤字串
圖2:使用原始檔名除錯輸出
查詢日誌函式名稱
由於這段程式碼有太多的除錯輸出,我決定寫一些東西來處理它們。有幾種方法可以找出哪些函式處理除錯輸出,其中一種方法是根據其內部的libc函式呼叫或行為來查詢這些函式,這是一種比較複雜和耗時的方法,但它看起來更優雅。第二種方式是快速且粗暴的,特別是當你沒有很多時間又急需時,我建議你使用它。在這種情況下,只需檢視可執行檔案中的字串並找到可疑的除錯輸出,在找到它們之後,檢視一些函式是否將它們作為引數接收。如果使用除錯輸出作為引數重複呼叫函式,那麼你可以在指令碼中使用它。在建立指令碼之前,我發現大約有10個不同的函式正在處理除錯輸出,並且我還發現了暫存器中的字串引數儲存在其中。
我的解決方案
我們的目標是根據除錯輸出更改IDA的預設函式名稱。
例如:
圖3:使用指令碼更改函式名前後
接下來我將闡明指令碼的不同部分。
把它們放在一起
正如我所說的,至少有兩種方法可以找到呼叫的日誌函式,一個懶人方案,一個非懶人方案。
懶人方案
遍歷所有程式集並查詢“call”指令,然後查詢帶有日誌函式名稱的引數。我決定將函式名稱組織為全域性字典的一部分:
FUNCTIONS_REGISTERS = {Function_Name:Register, Function_Name_1, Register_1... }
函式名稱作為鍵,它們的值是除錯輸出的相關暫存器。例如:
FUNCTIONS_REGISTERS = {'g_WriteLogFile': 'rdx', 'g_LogError': 'rdx'}
我為該部分編寫的指令碼如下:
curr_addr = MinEA() end = MaxEA() while curr_addr < end: if curr_addr == idc.BADADDR: break elif idc.GetMnem(curr_addr) == 'call': if idc.GetOpnd(curr_addr, 0) in FUNCTIONS_REGISTERS.keys(): pass
非懶人方案
我想到的不那麼懶惰的方法是將xref用於找到的相關函式。通過這種方式,我使用了相同的函式名字典。在這裡,我所做的是找到每個函式的外部參照地址,即函式呼叫的地址。
for function_name in FUNCTIONS_REGISTERS.keys(): func_addr = idc.LocByName(function_name) a = idautils.XrefsTo(func_addr, 1) for xref in a: curr_addr = xref.frm# ea in func if curr_addr == idc.BADADDR: pass
獲取函式引數
這些函式中包含在呼叫指令之前分配的暫存器中儲存的除錯輸出。因為我有呼叫指令本身的地址,所以我需要向後查詢,並從呼叫指令地址開始找到相關的暫存器值。
獲取暫存器分配的地址名稱的程式碼如下:
def get_string_for_function(call_func_addr, register): """ :param start_addr: The function call address :return: the string offset name from the relevant register """ cur_addr = call_func_addr start_addr = idc.GetFunctionAttr(cur_addr, idc.FUNCATTR_START) cur_addr = idc.PrevHead(cur_addr) # go through previous opcodes looking for assignment to the register while cur_addr >= start_addr: if idc.GetMnem(cur_addr)[:3] == "lea" and idc.GetOpnd(cur_addr, 0) == register: str_func = idc.GetOpnd(cur_addr, 1) return str_func cur_addr = idc.PrevHead(cur_addr) return str_func
我們有除錯輸出地址了,現在我們需要考慮如何得到它引用的實際字串。下面的程式碼顯示了它是如何完成的:(例如:更改“aErrorSavingFil”->“Error saving file %1”。我們可以通過簡單地從其名稱中提取地址然後獲取儲存在其中的字串來實現。)
func_name = idc.GetString(idc.LocByName(addr)
從除錯輸出到函式名
在更改函式名稱之前,我們應該稍微修改除錯輸出格式,因為要呈現的最終函式名稱應該是乾淨且可讀的,因此我在指令碼中建立了一個函式。
免責宣告:我在這裡介紹的函式不是我使用的整個函式,它只對除錯輸出進行了一般性更改,如果你想為自己建立這樣的指令碼,你應該編寫一個函式來更改除錯中的相關部分輸出格式。
在此函式中,還從地址名稱中提取除錯輸出字串。
def get_fixed_source_filename(addr): """ :param addr: The address of the source filename string :return: The fixed source filename's string """ func_name = idc.GetString(idc.LocByName(addr)).replace("/", "_").replace(" ", "_") func_name = "AutoFunc_" + func_name # if the debug print is a path, delete the extension if func_name.endwith(".c") or func_name.endwith(".h"): func_name = func_name[:-2] # you can add whatever you want here in order to have your preferred function name return func_name
更改函式名稱
更改函式名是指令碼的最後一部分,可以通過執行以下命令輕鬆完成:
idaapi.set_name(function_start, new_filename, idaapi.SN_FORCE)
值得注意的是,idaapi.SN_FORCE標誌只能用於IDA 7及更高版本。
錯誤的處理
由於我有一個大型的二進位制檔案,所以我偶爾會發現一些除錯函式的不同點,雖然在99.9%的情況下不會發生錯誤,但我也不能忽略其可能性。即使發生了一些錯誤,指令碼也會繼續在其他所有的函式上執行,不過我還是想跟蹤錯誤並更改失敗的函式名稱。
發生這些錯誤時,訊息將顯示在輸出視窗中: