由於平時業餘興趣和工作需要,研究過並使用過時下流行的各種開源的x86/64彙編和反彙編引擎。如果要對彙編指令進行分析和操作,要麼自己研究Intel指令集寫一個,要麼就用現成的開源引擎。自己寫太浪費時間,又是苦力活,還容易出錯,所以還是使用現成的好一點。
這裡對我曾使用過的比較流行的反彙編引擎做個比較,我使用過的反彙編引擎有:
1. Ollydbg的ODDisassm
Ollydbg的ODDisassm,這是我最早使用的一個開源的反彙編引擎,07年在看雪學院的《加密解密》(三)中我寫的一個很簡單的虛擬機就是使用的這個庫,因為那個時候還沒有那麼多可選擇。不過多虧有這樣一個基礎庫,整個虛擬機從設計到開發完成只用了兩個星期便開發完成(當時對反彙編庫的要求不高,只要求能用字符串文本做中間表示進行編碼/解碼)。
這個反彙編庫的優點是含有彙編接口(即文本解析,將文本字符串解析並編碼成二進位),就拿這個特性來說在當時也算是獨樹一幟的了,到目前為止開源界在做這個工作的人也很少,
不過近年出現的調試器新秀x64dbg,也附帶開發了開源的彙編庫XEDParse,功能與OD的文本解析功能相似,並且支持的指令集更加完整,BUG更少,同時還支持X64,維護一直很強勁。
但是ODDisassm的缺點也很多,比如:
1. 指令集支持不全,由於Ollydbg年久失修,現在甚至連對MMX指令集都不全,而現在的INTEL/AMD的擴展指令集標準又更新了多個版本,什麼SSE5/AVX/AES/XOP就更別提了,完全無法解析。
2. 解碼出來的結構不詳細,比如指令前綴支持不夠友好,這點從Ollydbg的反彙編窗口可以看出,除了movs/cmps等指令以外,repcc與其他指令組合時都是單獨分開的;
再比如寄存器無法表示ah\bh\ch\dh這種高8位寄存器。
3. 作者一次性開源後便不再維護開源版本,對於反彙編上的BUG很難即時修復。
不過這些也可以理解,因為在當時作者的開發目的是進行文本彙編\反彙編,所以沒有為解碼出的信息建立結構體以及接口。總的來說,如今再使用這個反彙編引擎,已經落後於時代了。
2. BeaEngine
BeaEngine是我用的第二個庫,當時使用OD庫已經不能滿足我的需求了。在做反編譯器的時候,需要一個能夠解碼信息越多越好的庫,於是我找到了BeaEngine,這個庫我記得以前的版本不支持高8位寄存器識別,現在的版本也支持了。
在使用過程中基本上沒有發現什麼明顯的缺點,不常用的新的擴展指令集也實現了不少。
目前實現的擴展指令集有:
引用:
FPU, MMX, SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2, VMX, CLMUL, AES, MPX 同時它也給不同種類的指令進行了分類,這在判斷不同指令時很方便。它還有一個特點是可以解碼出每一條指令所使用到和影響到的寄存器,包括標誌位寄存器,甚至精確到標誌位寄存器的每一個位置。
這個功能用來做優化器與混淆器再好不過了。
但是個人認為BeaEngine的編碼風格實在是不咋地,各種變量強制轉換,各種命名風格,給人一種亂亂的感覺。對我這種對編碼有潔癖的人來說,實在是無法忍受,所以後來又換了其他的庫。如果你不在意這些,BeaEngine的性能還是比較不錯的。
還有一點,BeaEngine經常性的爆出一些BUG,所以使用它時要有心理準備。
3. udis86
udis86應該是我最喜愛的反彙編引擎了。udis86支持的X86擴展指令集有:
引用:
MMX, FPU (x87), AMD 3DNow, SSE, SSE2, SSE3, SSSE3, SSE4.1, SSE4.2, AES, AMD-V, INTEL-VMX, SMX udis86的代碼風格十分簡潔,功能函數短小,變量命名清楚又簡潔,接口乾淨意思明白,操作靈活,如果你自己有需求維護一個自己的分支,花不了幾十分鐘的時間就能熟悉整個代碼構架。
說了這麼多溢美之詞,它到底有什麼優點呢?
udis86的優點就是接口很靈活,你可以選擇對一條指令使用ud_decode只進行解碼,再對解碼後的結構使用ud_translate_intel轉換成文本格式,
或者你也可以直接使用ud_disassemble一次性完成整個操作。而且這些接口都只需要一行就能搞定。
由於udis86的這種組合模式設計理念,他可以適應各種場景,如果你要開發一個IDA那樣的反彙編器,它能做;你要開發一個指令模擬器、分析器、優化器、混淆器,它也能做。
這種理念直接使udis86在擁有了強大的適應能力的同時還兼顧了性能,我做過性能測試,
udis86是我用過的解碼細節能力相近的情況下,解碼速度最快的引擎了。
至於缺點的話,目前還沒發現,不過,udis86不支持BeaEngine那種的寄存器分析,算是點小遺憾。
4. Capstone
capstone可以說是所有反彙編引擎中集大成者,對於它我要多費點口水,因為我對他是又愛又恨。capstone是基於LLVM框架中的MC組件部分移植過來,所以LLVM支持的CPU構架,capstone也都支持。
它支持的CPU構架有:
引用:
Arm, Arm64 (Armv8), M68K, Mips, PowerPC, Sparc, SystemZ, XCore & X86 (include X86_64) 而且Capstone對X86構架的指令集支持是最全的,這一點是其他引擎都比不上的,其支持的X86擴展指令集有:
引用:
3dnow, 3dnowa, x86_64, adx, aes, atom, avx, avx2, avx512cd, avx512er, avx512f, avx512pf, bmi, bmi2, fma, fma4, fsgsbase, lzcnt, mmx, sha, slm, sse, sse2, sse3, sse4.1, sse4.2, sse4a, ssse3, tbm, xop. 很強吧?
在目前移動端如此火熱的背景下,支持ARM的反彙編庫還是非常少的,如果要同時進行X86與ARM下的編譯器方面的開發,能使用一個統一的接口那自然是更好。
另外capstone的next分支中也支持BeaEngine那種解碼時分析指令使用和影響到的寄存器這種炫酷的特技(master分支沒有這個接口),有這樣的基礎庫存在真的可以偷不少的懶。
僅從X86/64平臺來看,無論是從解碼能力還是指令集支持,Capstone可以稱得上是一個完全超越了BeaEngine的存在。
老套路,說完了好話,又該說缺點了。
由於capstone是從LLVM移植過來,capstone是C語言的項目,而LLVM是C++項目,所以在移植過程中做了很多適配工作,顯得很臃腫。
舉個慄子,LLVM中的MCInst是一個單條底層機制指令的描述類,由於capstone是C項目,移植時將這些類變成了結構,把成員函數變成了獨立的C函數,比如MCInst_Init,MCInst_setOpcode等等。並且由於LLVM框架的複雜性和高度兼容性,裡面的所有的概念都做了高度抽象,並且Capstone又做了適配接口將其轉換到自己的構架中,這會造成解碼時中間層過多,性能下降。一條指令的解碼過程用到的重要的中間層結構順序是這樣的:
引用:
MCInst => InternalInstruction => cs_insn 最基礎的解碼工作依靠LLVM的構架解碼到Capstone的InternalInstruction,它是一個包含了解碼過程中的所有細節的內部結構,解碼完成後再調用update_pub_insn將認為需要公開暴露出來的內容複製到cs_insn中。而其他反彙編引擎都是一次性解碼到目標結構中的。
Capstone的解碼過程如此複雜,自然會對性能造成影響,我做過一個不太嚴格的性能測試,Capstone的性能消耗時間大概是udis86的5、6倍(順便吹一下,這裡我給Capstone提交了一個小小的Pull Request,分別在這裡和這裡,PR中附帶了一個benchmark,經過測試性能提高了將近有20%),如果換種方式測試,udis86隻使用ud_decode進行解碼,而Capstone沒有獨立的解碼接口,
需要做一些Hack,也讓它不生成彙編文本,那麼Capstone的消耗時間大概是udis86的2倍多,由此可見Capstone的文本操作又比udis86慢更多。
其次,Capstone的內存消耗很大,解碼一條指令時你傳入的指令結構cs_insn必須由動態分配函數來分配,並且還要分配兩次,一次是cs_insn,一次是cs_detail。這會造成巨量的內存碎片,另外,每一條指令的結構體都很大,有多大我不記得了,sizeof(cs_insn)+sizeof(cs_detail)好像至少在2K以上。
必須使用動態內存這一點是Capstone與其他反彙編引擎不一樣的地方。
如果要使用Capstone做大量指令分析的話,那麼得給它配一個固定對象內存分配器才行,那樣能稍微緩解一下內存碎片情況,也能提高一點性能。
可能是基於以上理由,x64dbg社區本來最開始是使用BeaEngine作為支撐基礎的,但是BeaEngine總是爆出不少BUG,所以後來選擇由Capstone替換,但是也僅用Capstone來做GUI的文本反彙編,因為它解碼速度雖然不行,但是BUG很少(因為LLVM有蘋果那麼大個公司做支撐),而流圖和指令分析(還不完善)目前仍然使用的BeaEngine,這也是沒辦法的事,畢竟性能也很重要。
還有一個問題,如果你需要的是解碼能力強的反彙編引擎,那麼建議你在選擇前先對比一下各引擎的解碼結構,有沒有你需要或者必須有的欄位。
因為Capstone有一個坑爹的地方,雖然其本身的解碼能力其實很強的,但是Capstone把中間層封裝了一遍,只暴露其認為需要暴露的欄位,並且其主要維護者有點固執(也可以說嚴謹),他堅持認為不太常用的欄位沒必要暴露出來,而接口是越簡潔越好。
比如說指令中立即數Immediate所在的偏移,內存操作數中Displacement所在的偏移,在內部結構InternalInstruction中本來是有的,但是複製到公開結構cs_insn結構中就丟棄了。
還有REP與REPE前綴,雖然都是同一個常量表示,但是配上不同指令功能還是不一樣的,對於這個,capstone內部有一個valid_repe函數可以區別,但是也沒有暴露到公開結構中,都當做REP來識別。雖然這些都很冷門,但是做指令分析和變形,這些還是很有用處的。
所以我個人認為capstone的接口用起來實在讓人不爽,但是它的功能又實在太強大,如果研究下其源碼的內部構造,會發現很多接口沒提供,但是內部卻有的好東西,所以我自己維護了一個分支,痛並快樂著的使用著。
其他
其實還有XDE,不過我沒使用過,就不做評論了。
另外,blackbone中含有一個長度反彙編引擎也值得一提,名字叫ldasm,其實它也算不上一個引擎,因為它只有一個函數,作用只是計算一條指令的長度,在hook的時候重定位跳轉指令時很有用處。代碼傳送門
總結
這幾款反彙編引擎各有長處(OD除外。。),但是又各自有那麼一丁點兒的缺陷,這世上沒有完美的事情,人家都開源了,有的用就不錯了,自己總要做些事情不是?
挑一款好用的庫,使用過程中發現BUG,給社區提交個Issue,或者做個解決方案再發個Pull Request,也算付了使用費了。
下面對這3款反彙編引擎以個人經驗做個比較:
性能 :udis86 > BeaEngine > capstone
解碼能力 :capstone > BeaEngine > udis86 (udis86不支持寄存器分析,其餘解碼能力是相近的)
平臺支持 :capstone > (udis86 = BeaEngine)
X86擴展指令集:capstone > (udis86 ≈BeaEngine)
如果你需要的是一個X86/64下的性能又好同時解碼能力又強的反彙編引擎,並且不需要寄存器分析這種特技的話,那麼udis86合適你;
如果你還需要帶寄存器分析功能的話,那麼BeaEngine與capstone合適你;如果你還需要ARM構架支持的話,capstone應該會更適合你。
每一款引擎各有優劣,使用起來仁者見仁智者見智,鞋子合不合適只有自己的腳才知道了。 如要查看我的其他相關技術文章,歡迎訪問bughoho.me。
看雪眾測:http://ce.kanxue.com
看雪論壇:http://bbs.pediy.com/
微信ID:ikanxue
看雪學院,致力於安全研究16年!