怎么做網(wǎng)站動態(tài)地圖google關(guān)鍵詞挖掘工具
Ghidra101再入門(上?)-Ghidra架構(gòu)介紹
最近有群友問我,說:“用了很多年的IDA,最近想看看Ghidra,這應(yīng)該怎么進(jìn)行入門?“這可難到我了。。
我發(fā)現(xiàn),市面上雖然介紹Ghidra怎么用的文章和書籍很多,但是結(jié)構(gòu)化介紹Ghidra本身以及它的架構(gòu)的文章很少(幾乎沒有)。對于熟悉IDA的群友,反編譯器的使用肯定不存在問題,他們需要的是更加深層的知識。
那我就小寫幾篇文章(希望不會挖坑不填);這一篇,就從先Ghidra的架構(gòu)開始說起吧,希望能給已經(jīng)熟悉或者正在研究靜態(tài)逆向的朋友們些許幫助。
——————————————————————
這篇文章將討論下面這些問題,如果有寫的不清楚/不對的地方,希望大家告訴我:
- 為什么要整理這些知識?
- 什么是反編譯?
- Ghidra反編譯器的主要架構(gòu)是什么樣?
- Ghidra的主要模塊有哪些,都是干啥用的?
- Ghidra這套框架牛逼在哪里?在哪里有很大的缺陷?
- Ghidra相比于IDA有什么區(qū)別/優(yōu)點/缺點?
- 為什么Ghidra會設(shè)計成這個樣子?
- 其它。。。。?
1、為什么要整理這方面的知識?
學(xué)習(xí)靜態(tài)逆向,大家都是從使用IDA/Ghidra這類反編譯器的使用開始。然后逐步的深入到各種復(fù)雜的場景。
現(xiàn)代靜態(tài)逆向的工作越來越復(fù)雜,對抗越發(fā)深入;梳理反編譯器架構(gòu)的知識,學(xué)習(xí)程序分析技術(shù),在日常工作生活中,一旦反編譯器出現(xiàn)什么問題,能夠知道如何對反編譯器進(jìn)行修改與優(yōu)化,多一條解決問題的路子。
可惜,市面上能夠供我們學(xué)習(xí)的反編譯框架并不多,反編譯軟件開發(fā)的難度高、工作量大(IDA和Ghidra都已經(jīng)持續(xù)開發(fā)了近30年),IDA對外提供的資源又非常的少。幸好Ghidra開源了,給我們提供了一個了解這方面知識的門道。
2、什么是反編譯?
所謂"反編譯"有廣義+狹隘的兩層含義;
從廣義上,反編譯指的是將低級語言、機器語言轉(zhuǎn)換成高級語言一系列技術(shù)的集合。技術(shù)落地的產(chǎn)品就是"反編譯器"。
當(dāng)然,這篇文章中的反編譯更加接地氣一點;所謂反編譯-~反編譯~~反的是什么呢?當(dāng)然是編譯器啊!
所以,從狹隘的角度來看,反編譯技術(shù)指的是識別編譯器優(yōu)化算法特征,基于程序分析技術(shù)編寫反向算法,從編譯產(chǎn)物中還原被編譯器隱藏的高層次抽象。
這種反向算法的集大成者,我們就稱為"反編譯器"!因此,這篇文章就是參考Ghidra,介紹這一集大成者的主要架構(gòu)與功能。
如果從正向?qū)懘a的角度來看,整個Ghidra對應(yīng)的應(yīng)該是IDE(集成開發(fā)環(huán)境),Ghidra中的反編譯器對應(yīng)的是gcc這類編譯器。
3、Ghidra整體架構(gòu)/主要模塊簡述
如下圖,這是我大概畫的Ghidra反編譯器主要模塊結(jié)構(gòu)。
我畫的比較粗糙,一定會有比較多的遺漏的點。先簡單介紹一下各個子模塊的作用,從上到下,從左到右:
- Ghidra反編譯框架插件:可以在Ghidra的tool目錄中找到這些功能,和IDA中的plugin功能類似,Ghidra提供了一套專門的腳本管理頁面。
- Ghidra配套腳本:用于兼容多個平臺、headless模式等等的打包后的腳本。
- Ghidra-Java-Frameword軟件主程序:Ghidra的主程序,包含了絕大多數(shù)的組件。
- SoftwareModeling上層二進(jìn)制抽象:Java代碼中對于二進(jìn)制的抽象,包括了反匯編、中間語言、函數(shù)CFG、二進(jìn)制內(nèi)存模型等等,逆向工程的主要工作之一,就是從二進(jìn)制中還原這些上層信息。
- Ghidra-Decompiler反編譯進(jìn)程:Ghidra的"F5"功能,將中間語言轉(zhuǎn)換為類C的偽代碼,他是一個完全獨立的模塊,與Java主進(jìn)程通過自定義的”字符流“交互。
- Ghidra-Decompiler命令行程序:Ghidra的"F5"功能完全獨立,因此它可以自己以命令行形式啟動并加載二進(jìn)制,獨立的完成反編譯的工作。
- Sleigh-Processor編譯器:將Sleigh編譯成.sla的支持組件
- Sleigh-Processor描述模塊:Ghidra的核心功能,用于支持x86、arm、mips等數(shù)十種指令集架構(gòu);GCC、MSVC、GO、Java等多種編譯器的"描述模塊"。
- Ghidra開發(fā)者套裝:包括Sleigh以及Ghidra插件的eclipse-IDE開發(fā)支持套件,但是我用IDEA+vscode(喂喂,eclipse都啥時候的老古董了啊)
下面的重心放在三個最重要的組件:Ghidra-Framework主程序、Decompiler反編譯器、Sleigh-Processor指令集架構(gòu)描述配置。
4、Ghidra-Framework軟件主程序:
這個組件是Ghidra的主程序,使用Java編寫,也就是我們反編譯時所使用的Ghidra那一整套界面(Ghidra啥時候找個產(chǎn)品經(jīng)理和設(shè)計師,把你那破UI修一下啊)
主程序的功能十分的復(fù)雜,這里只說兩個關(guān)鍵模塊:
4-1、Analysis分析器:
與IDA不同,Ghidra雖然負(fù)責(zé)載入可執(zhí)行文件,但是對于函數(shù)識別、反匯編、棧平衡、指令等等解析與處理,都需要分析器完成(IDA會自動的完成這部分工作)。
可以說,Analysis模塊除了不負(fù)責(zé)"F5",其它啥功能都要做;
但對于反編譯器,核心的Analysis有那么幾個:
- 匹配查找函數(shù)頭(function start search):通過編譯器的配置以及特定的二進(jìn)制信息查找函數(shù)起始位置,配置文件可以在Processor中的xxxxpattern.xml中找到
- 遞歸下降反編譯(Symbol/Disassemble/Subroutine):從已知的函數(shù)頭執(zhí)行遞歸下降的反編譯,已知的函數(shù)頭通過匹配查找、符號信息、start函數(shù)入口等等方法獲取。
- No-Return函數(shù)處理(No-Return Function):通過規(guī)則識別并傳播標(biāo)記no-return函數(shù)。
- 數(shù)據(jù)引用(Reference):反編譯過程中對于數(shù)據(jù)地址、字符串、常量的引用信息
4-2、SoftwareModeeling二進(jìn)制抽象:
這個模塊是上層java進(jìn)程中的二進(jìn)制抽象,所謂"二進(jìn)制的抽象"指的是從二進(jìn)制中還原出高層的信息,包括:二進(jìn)制內(nèi)存結(jié)構(gòu)、函數(shù)、導(dǎo)入表、導(dǎo)出表、反匯編指令、中間語言、數(shù)據(jù)與交叉引用等等。
因此反編譯器的界面和寫代碼時的IDE很相似(個人看法)。
5、Sleigh-Processor指令集架構(gòu)描述
這個模塊是Ghidra最牛逼的東西,它繼承了反編譯技術(shù)奠基者Cifuentes, C博士和M. Van Emmerik博士的QUBT框架。
在反編譯器開發(fā)的過程總,最麻煩的事情之一,就是對不同指令集架構(gòu)、編譯器、ISA的適配;并且這個工作還沒有討巧的辦法,就是純純的苦力活。
例如,在IDA中如果需要支持一個新的指令集,就必須編寫一個叫做processor的模塊。而在Ghidra中"不需要寫代碼"通過這一套描述語言實現(xiàn)了多架構(gòu)的支持功能。
5-1、binary-rewrite技術(shù)簡述
在上個世紀(jì)末,操作系統(tǒng)、指令集、ISA并沒有現(xiàn)在這么統(tǒng)一。
那時候的程序員也遇到了多架構(gòu)的麻煩:給特定平臺開發(fā)的程序換個平臺就用沒法用了,需要重新寫。
學(xué)術(shù)界就提出了"Binary-rewrite"技術(shù),通過一套系統(tǒng)將程序從一個平臺轉(zhuǎn)換為另一個平臺(例如將C語言編寫的程序,轉(zhuǎn)換到JVM上運行)
UQBT框架實現(xiàn)了將二進(jìn)制轉(zhuǎn)換為中間語言,再將中間語言重編譯成其它平臺的能力。
?
為了降低從二進(jìn)制到中間語言的轉(zhuǎn)換成本,UQBT框架提供了一種叫做"通用描述語言Semantic Syntax Language (SSL)"的描述類型語言
通過寫對指令集、操作系統(tǒng)、調(diào)用約定的描述配置,不用寫代碼,就可以實現(xiàn)從二進(jìn)制到中間語言的轉(zhuǎn)換。
5-2、Ghidra-Sleigh描述語言概述
同樣,Ghidra這套描述語言的原型來自"Binary-rewrite"技術(shù)中的SSL類描述語言。
介紹Sleigh編寫與適配指令集的文章已經(jīng)很多了,這里我也就簡單的介紹一下,有需要可以去看Ghidra的官方文檔。
總得來說,一套完整的Processor描述應(yīng)該由這么幾部分組成:
- 語言總描述(.ldefs):描述這是什么架構(gòu)、大小端、編譯器等等
- ISA描述(.slaspec/.sinc/.pspec):最主要的模塊,描述指令集、寄存器、中間語言對應(yīng)關(guān)系等等復(fù)雜的情況
- 編譯器/調(diào)用約定描述(.cspec):主要是描述不同編譯器的調(diào)用約定,比如x86就有MSVC、GCC、Go等很多種可能的編譯器,不同編譯器都有單獨的配置。
- 函數(shù)匹配(xxxpattern.xml):二進(jìn)制形式的函數(shù)匹配,一般是用于匹配函數(shù)頭。
5-3、對比IDA-Processor,Sleigh的優(yōu)勢是什么?
這是個很有意思的問題,我也不是編譯領(lǐng)域的專家,如果說的不對麻煩大佬們指教指教。
在最初的binary-rewrite的QUBT論文中,作者提出了"相比于編寫Processor代碼,使用SSL描述語言工作量能減少一個數(shù)量級"的觀點。
但是,在我把兩邊都嘗試過之后,發(fā)現(xiàn)其實對于不熟悉的新手,兩邊的學(xué)習(xí)成本都很高。
IDA需要了解清楚Processor框架中,并且IDA的資料很少。還有一點,IDA缺少很多通用能力,比如,IDA似乎沒有提供通用間接跳轉(zhuǎn)處理的代碼,恢復(fù)switch間接跳轉(zhuǎn)的代碼是非常難寫的。
而Ghidra-Sleigh等于重新學(xué)習(xí)了一種語言,不過由于它和指令集架構(gòu)很像,所以沒有那么困難。并且Ghidra的反編譯做了很多的工作,開發(fā)者不需要考慮別的,只需要”描述清楚“,剩下的交給反編譯就行了。
所以,我個人覺得,在Processor編寫中并不能減少什么工作量,還是一個體力工作。Sleigh的優(yōu)勢在于他的格式很規(guī)范,誰寫都一樣,因此寫問題也很容易修。并且反編譯器承擔(dān)了很多麻煩的工作,我們只需要按部就班的"描述"就好了。
6、Decompiler反編譯器
如果對反編譯的實現(xiàn)細(xì)節(jié)有興趣,可以先看看官方文檔,(或者等我下一篇文章,但是不知道什么時候填好坑)
Ghidra提供了和IDA-F5同樣的將中間語言"lift"到類C語言偽代碼的功能。
但是和IDA的架構(gòu)不同,Ghidra的反編譯器是一個完全獨立的進(jìn)程,并且是一個完全獨立的模塊。而IDA中反編譯模塊是集成在主程序中。
并且Ghidra反編譯器在設(shè)計時就是"架構(gòu)無關(guān)"的,無論是什么指令集(哪怕是VMP),只要寫好中間語言p-code的轉(zhuǎn)換功能,Ghidra都能把他翻譯成類C的偽代碼。你看看隔壁IDA,一個指令集的F5支持都能賣個好幾萬。
6-1、完全獨立反編譯器的架構(gòu)缺陷
完全獨立的反編譯器/進(jìn)程,意思是反編譯器可以獨立編譯并運行,上層Java應(yīng)用通過開啟進(jìn)程并使用字符流與反編譯進(jìn)程進(jìn)行交互。
但是,這種做法引入了很多的架構(gòu)問題(我個人看法啊):
- 進(jìn)程間通信會有很多額外的開銷。
- 主Java程序和反編譯模塊
- 主Java程序和和反編譯模塊需要同時維護(hù)兩套"二進(jìn)制抽象";無法互相復(fù)用。
- 每當(dāng)反編譯需要上層的數(shù)據(jù)(比如我手動修改了函數(shù)簽名),只能通過進(jìn)程間通信獲得,這使得類似功能開發(fā)十分的麻煩。
- 主Java功能和native反編譯功能同時實現(xiàn)了一些功能類似的分析,無法重用代碼。
- 無法展示反編譯結(jié)果與匯編/中間語言的對應(yīng)關(guān)系。
- Ghidra的字符流好奇怪,比較難看懂。
可以說,所有的麻煩,都是因為兩邊完全獨立導(dǎo)致沒法共享資源導(dǎo)致的。
6-2、從架構(gòu)上對比IDA與Ghidra反編譯的F5能力
個人感覺,無論是從反編譯的效率還是準(zhǔn)確率來說,毫無疑問,IDA就是這個領(lǐng)域的"行業(yè)標(biāo)準(zhǔn)"
但是如果需要重頭支持一套新的指令集,那我一定選擇Ghidra,畢竟IDA沒有F5給我用。
大家都知道,IDA的反編譯中層優(yōu)化使用了8層轉(zhuǎn)換,每向上一層中間語言就能縮減一部分,從下到上依次為:
- MMAT_GENERATED:類似于匯編
- MMAT_PREOPTIMIZED:在basic-block上構(gòu)造def+use+kill關(guān)系
- MMAT_LOCOPT:在basic-block完成局部優(yōu)化,構(gòu)造CFG,處理基于規(guī)則的窺孔優(yōu)化
- MMAT_CALLS:基于調(diào)用約定識別函數(shù)調(diào)用、參數(shù)、返回值
- MMAT_GLBOPT1:將call的各種block進(jìn)行合并,全局優(yōu)化,全局死代碼消除
- MMAT_GLBOPT2:全局優(yōu)化,全局常量傳播與條件優(yōu)化
- MMAT_GLBOPT3:全局窺孔優(yōu)化
- MMAT_LVARS:局部變量還原
但是Ghidra不同,Ghidra將所有的優(yōu)化代碼全部塞到了coreation.cc一個文件里面。并且使用了類似于“不動點算法”的工作原理,不斷的優(yōu)化直到中間語言無法優(yōu)化下去位置(不再改變)
我按照個人的經(jīng)驗,將Ghidra的反編譯器中端從里到外、從前到后整理,依次為:
- action-base:通過流敏感技術(shù),將機器碼翻譯成中間語言,識別函數(shù),處理間接跳轉(zhuǎn)等等。
- action-stackstall:抽象操作棧信息,恢復(fù)棧變量
- Varnode:還原高層變量信息
- action-rule:窺孔優(yōu)化,通過預(yù)置的規(guī)則對語義進(jìn)行等價還原
- typerecovery:還原類型信息
- struct: 還原控制流信息
Ghidra這種不斷循環(huán)的算法,存在很大的性能問題,對于超大的函數(shù)往往會反編譯超時。并且這種將優(yōu)化代碼全部塞到coreation.cc的做法。。。使得代碼層次十分混亂。
這一篇文章就到這吧,后面是反編譯器設(shè)計的內(nèi)容了,再寫下去太長了,留到以后的文章吧。
7、之后的內(nèi)容?
如果有時間填坑?
后面將會詳細(xì)說說Ghidra-decompiler反編譯器F5組件的實現(xiàn)細(xì)節(jié)以及架構(gòu)。
以及當(dāng)反編譯器出現(xiàn)問題的時候(比如花指令等混淆),如何進(jìn)行修復(fù)。
?
?
?