在逆向工程過程中,您可能會遇到可用工具尚不支持您正在使用的架構的情況。
在本文中,我們將探討這樣的案例並展示擴展IDA 功能的示例。我們探索如何實現IDA 插件來反彙編新的Xtensa 指令。
為什麼要創建自定義IDA 插件?在我們之前的一篇文章中,我們討論了研究固件架構的重要性。在這種情況下,我們設法找到了一個支持我們需要分析的設備架構的反彙編工具。現在,我們想要解決逆向工程工具不支持您在項目中使用的架構的問題。讓我們以IDA 為例來探討本文。
交互式反彙編器(IDA)是一種軟件反彙編器,可從機器可執行代碼生成彙編語言源代碼並執行自動代碼分析。該逆向工程工具通過眾多插件提供廣泛的反彙編和調試功能。
IDA 還使用一組稱為處理器模塊的插件將原始字節代碼轉換為反彙編文本。每個插件都是針對特定的硬件架構設計的。
由於反彙編插件的可用性很大程度上取決於架構的流行程度,因此某些處理器模塊的更新頻率比其他模塊要高。而對新指令的支持可能需要IDA 開發人員花一些時間才能實現。
那麼,如果您當前正在使用的架構沒有插件,您該怎麼辦?
好消息是,無需等待IDA 開發人員實現對特定指令集的支持。您可以自己創建自定義IDA 插件,也可以使用Python 通過IDA SDK 實現相關插件。
讓我們探討一個實現逆向工程IDA 插件的示例,並使用新的Xtensa 架構指令,在撰寫本文時,IDA 7.7 尚不支持這些指令。由於這些指令未在IDA 中反彙編,因此您將它們視為原始字節:
屏幕截圖1. Xtensa 指令在IDA 中顯示為原始字節
但如果您使用其他支持反彙編新Xtensa指令的軟件,例如Lauterbach Trace32模擬器,您可以看到這些字節是商無符號(QUOU)指令:
屏幕截圖2.Xtensa 指令看起來像Lauterbach Trace32 模擬器中的QUOU 指令
一旦知道這些字節是什麼,您就可以找到QUOU 指令的描述並實現IDA 插件來擴展現有處理器模塊的功能。讓我們探討一下如何做到這一點。
使用新插件向IDA 處理器模塊添加指令讓我們使用NECromancer插件,它為NEC V850 CPU 擴展了IDA 的處理器模塊。
使用此插件的目標是掛鉤處理器模塊的事件處理程序並執行您自己的處理例程而不是現有的處理例程。該插件將允許處理器模塊處理默認情況下無法處理的未知指令。
讓我們看一下一個空插件。以下是在IDA 引擎中註冊插件並掛接處理器模塊所需的最少代碼:
(Python)
classXtensaESP(plugin_t):flags=PLUGIN_PROC|PLUGIN_HIDEcomment=''wanted_hotkey=''help='AddssupportforadditionalXtensainstructions'wanted_name='XtensaESP'def__init__(self):self.prochook=Nonedefinit(self):ifph_get_id()!=PLFM_XTENSA:returnPLUGIN_SKIPself.prochook=xtensa_idp_hoo k_t()self.prochook.hook()print('%sinitialized.'%XtensaESP.wanted_name)returnPLUGIN_KEEPdefrun(self,arg):passdefterm(self):ifself.prochook:self.prochook.unhook()#--------------------------------------------------------------------------defPLUGIN_ENTRY():returnXtensaESP()為了確保IDA 僅在加載Xtensa CPU 處理器模塊時運行該插件,該插件會執行以下檢查:
ifph_get_id()!=PLFM_XTENSANECromancer 插件還需要xtensa_idp_hook_t掛鉤類來安裝處理器模塊事件的處理程序。鉤子類主體如下所示:
classxtensa_idp_hook_t(IDP_Hooks):def__init__(self):IDP_Hooks.__init__(self)defev_ana_insn(self,insn):passdefev_out_mnem(self,outctx):passdefev_out_operand(self,outctx,op):pass該代碼片段的關鍵元素是:
ev_ana_insn方法,幫助您分析字節碼並創建指令類
ev_out_mnem方法,允許您創建指令的可視化表示,即生成反彙編文本
ev_out_operand方法,實現指令操作數生成為文本以便反彙編
讓我們一一實現這三種方法。
1. 實現ev_ana_insn方法使用NECromancer 插件的目標是添加對QUOU(無符號商)指令的支持。這意味著您需要知道CPU 實際上如何解析表示QUOU 指令的字節。
您可以在Xtensa 指令集架構(ISA) 參考手冊[PDF]中找到此信息:
指令詞:
所需的配置選項:32 位整數除法選項。
彙編語法:QUOU ar, as, at
說明:將地址寄存器的內容除以地址寄存器的內容QUOU進行32 位無符號除法,並將商寫入地址寄存器。如果地址寄存器的內容引發整數除以零異常而不是寫入結果。 asatarat0, QUOU
在這種特殊情況下,您不需要詳細了解指令的作用。目標是了解CPU 如何知道一組字節實際上是QUOU 指令。
IDA 將這條QUOU 指令顯示為字節序列:0xC0,0x22,0xC2。指令的第一個字節0xC0——在文檔中表示如下:
讓我們解釋一下這意味著什麼:
1、標記為的頂部四個字節t的值為0xC。
2、低四個字節始終等於0。
3、的值t是用作指令第三個參數的寄存器的索引。
4、0xC=12,這意味著第三個參數是a12。
指令的第二個字節指定了另外兩個標記為r和的參數s:
在我們的例子中,第二個字節是0x22,這意味著r=0x2和s=0x2。因此,第一和第二操作數都是a2。
最後,第三個字節是0xC2。根據文檔,它始終是一個常量:
由於1100 0010=0xC2,您可以使用該字節來識別QUOU 指令。
現在,一切準備就緒,可以開始實現ev_ana_insn方法了。
首先,創建新的指令ID,這將允許IDA引擎區分新指令和現有指令:
classNewInstructions:(NN_quou,NN_last)=range(CUSTOM_INSN_ITYPE,CUSTOM_INSN_ITYPE+2)其次,讓我們使用從值開始的IDCUSTOM_INSN_ITYPE。
最後,運行指令分析方法,如下所示:
defev_ana_insn(self,insn):buf=get_bytes(insn.ea,3)ifbuf[2]==0xC2and(buf[0]0xF)==0:insn.itype=NewInstructions.NN_quouinsn.size=3returnTruereturnFalse我們來解釋一下這段代碼的作用:
get_bytes()函數從IDA 期望的下一條指令的地址處的二進製文件中讀取原始字節。
然後,它檢查指令字節是否實際上看起來像QUOU 指令。
在這種情況下,我們檢查第三個字節0xC2和較低的4 個字節是否0在第一個字節中,如文檔中所定義。最後,您需要使用有關QUOU 指令的信息填充ev_ana_insn方法的insn參數:至少指定指令ID 和指令大小(以字節為單位)。
然後,如果ev_ana_insn方法能夠在建議地址找到指令,則它必須返回True ;否則,它必須返回False。
即使您將努力保持在絕對最低限度(如上所示),IDA 也已經能夠識別新指令。但我們還想向您展示如何讓IDA 也了解指令參數,否則指令將顯示為沒有參數。為此,您需要對ev_ana_insn()方法進行改進:
defev_ana_insn(self,insn):buf=get_bytes(insn.ea,3)ifbuf[2]==0xC2and(buf[0]0xF)==0:insn.itype=NewInstructions.NN_quouinsn.size=3insn.Op1.type=o_reginsn.Op1.reg=buf[1]4insn.Op2.type=o_reginsn.Op2.reg=buf[1]0xFinsn.Op3.type=o_reginsn.Op3.reg=buf[0]4returnTruereturnFalse這段新代碼實現了指令參數的定義。這些是代碼從指令字節中提取的r、s和值。
解析完成後,就可以設置反彙編文本的輸出了。
2. 實現ev_out_mnem方法要生成反彙編文本,您可以完全重用ev_out_mnem方法的NECromancer 插件的現有代碼:
DEBUG_PLUGIN=TrueNEWINSN_COLOR=COLOR_MACROifDEBUG_PLUGINelseCOLOR_INSNclassNewInstructions:(NN_quou,NN_last)=range(CUSTOM_INSN_ITYPE,CUSTOM_INSN_IT YPE+2)lst={NN_quou:'quou'}defev_out_mnem(self,outctx):insntype=outctx.insn.itypeglobalNEWINSN_COLORif(insntype=CUSTOM_INSN_ITYPE)and(insntypeinN ewInstructions.lst):mnem=NewInstructions.lst[insntype]outctx.out_tagon(NEWINSN_COLOR)outctx.out_line(mnem)outctx.out_tagoff(NEWINSN_COLOR)#TODO:howcanMNEM_widthbedeterminedprogrammatically?MNEM_WIDTH=8width=max(1,MNEM_WIDTH-len(mnem))outctx.out_line(''*width)returnTruereturnFalse我們來解釋一下這個例子的要點:
ev_out_mnem從NewInstructions類獲取指令名稱並andoutctx.out_line在IDA 反彙編窗口中顯示文本。
標誌DEBUG_PLUGIN改變文本的顏色。您可以將其設置為默認顏色或宏的顏色,以使新指令脫穎而出,這在調試插件時非常方便。
ev_out_mnem輸出八個空格,為引擎輸出指令參數做好準備。因此,所有內容——代碼、空格、代碼註釋——都將被對齊。
3. 實現ev_out_operand方法要實現指令操作數的生成,您還可以重用ev_out_operand方法的現成代碼:
defev_out_operand(self,outctx,op):insn=outctx.insnifinsn.itypein[NewInstructions.NN_ld_hu,NewInstructions.NN_st_h]:ifop.type==o_displ:outctx.out_value(op,OOF_ADDR)outctx.out_register(ph_get_regnames()[op.reg])returnTruereturnFalse此代碼檢查該指令是否是您之前添加的指令。如果指令包含參數,則需要打印操作數。然後,您將獲得寄存器的名稱,插件將打印它。
現在,所有準備工作都已完成,您可以開始測試您創建的插件。
測試插件將插件放在\IDA\plugins目錄中,以便IDA 可以運行它。然後,加載包含未定義字節的IDA數據庫,將光標置於其上,然後按C創建指令:
屏幕截圖3. 在IDA 中創建新的QUOU 指令
添加對QUOU 指令的支持後,IDA 每當遇到該指令時都會自動識別該指令。此處指令的顏色與其他指令的顏色不同,因為我們DEBUG_PLUGIN在此示例中啟用了該標誌。如果您決定禁用該標誌,指令的顏色將與代碼的其餘部分相同。
我們示例的完整NECromancer 插件源代碼如下:
fromida_linesimportCOLOR_INSN,COLOR_MACROfromida_idpimportCUSTOM_INSN_ITYPE,IDP_Hooks,ph_get_regnames,ph_get_id,PLFM_XTENSAfromida_bytesimportget_bytesfromida_idaapiimportplugin_t,PLUGIN_PROC,PLUGIN_HIDE,PLUGIN_SKIP,PLUGIN_KEEPfromida_uaimporto_displ,o_reg,o_ imm,dt_dword,OOF_ADDRfromstructimportunpackDEBUG_PLUGIN=TrueNEWINSN_COLOR=COLOR_MACROifDEBUG_PLUGINelseCOLOR_INSNclassNewInstructions:(NN_quou,NN_muluh)=range(CUSTOM_INSN_ITYPE,CUSTOM_INSN_ITYPE+2)lst={NN_quou:'quou',NN_muluh:'muluh'}#------------ --------------------------------------------------------------classxtensa_idp_hook_t(IDP_Hooks):def__init__(self):IDP_Hooks.__init__(self)defdecode_instruction(self,insn):buf=get_bytes(insn.ea,3)#print('%08Xbytes%X%X%X'%(insn.ea,buf[2],buf[1],buf[ 0]))ifbuf[2]==0xC2and(buf[0]0xF)==0:insn.itype=NewInstructions.NN_quouinsn.size=3insn.Op1.type=o_reginsn.Op1.reg=buf[1]4insn.Op2.type=o_reginsn.Op2.reg=buf[1]0xFinsn.Op3.type=o_reginsn.Op3.reg=buf[0]4returnTrueifbuf[2]==0xA2and(buf[0]0xF)==0:insn.ityp e=NewInstructions.NN_muluhinsn.size=3insn.Op1.type=o_reginsn.Op1.reg=buf[1]4insn.Op2.type=o_reginsn.Op2.reg=buf[1]0xFinsn.Op3.type=o_reginsn.Op3.reg=buf[0]4returnTruereturnFalsedefev_ana_insn(self,insn):returnself.decode_instruction(insn)defev_out_mnem(se lf,outctx):insntype=outctx.insn.itypeglobalNEWINSN_COLORif(insntype=CUSTOM_INSN_ITYPE)and(insntypeinNewInstructions.lst):mnem=NewInstructions.lst[insntype]outctx.out_tagon(NEWINSN_COLOR)outctx.out_line(mnem)outctx.out_tagoff(NEWINSN_COLOR)#TODO:ho wcanMNEM_widthbedeterminedprogrammatically?MNEM_WIDTH=8width=max(1,MNEM_WIDTH-len(mnem))outctx.out_line(''*width)returnTruereturnFalsedefev_out_operand(self,outctx,op):insn=outctx.insnifinsn.itypein[NewInstructions.NN_ld_hu,NewInstructions.NN_st_h]:if op.type==o_displ:outctx.out_value(op,OOF_ADDR)outctx.out_register(ph_get_regnames()[op.reg])returnTruereturnFalse#--------------------------------------------------------------------------classXtensaESP(plugin_t):flags=PLUGIN_PROC|PLUGIN_HIDEcomment=''
Recommended Comments