各種開源彙編、反彙編引擎的非專業比較

2021-02-25 看雪學院

  由於平時業餘興趣和工作需要,研究過並使用過時下流行的各種開源的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年!

相關焦點

  • 智能合約安全系列文章反彙編·下篇
    前言上篇我們詳細分析了智能合約反彙編後的代碼內容,包括多個反彙編指令的含義,數據在棧中的存儲方式,並通過上下文關聯關係梳理代碼邏輯。本篇我們將繼續分析上篇遺留的反彙編代碼,通過上篇學習我們已對反彙編指令在棧和內存存儲的有了一定了解,該篇我們將重點來分析反彙編指令表示的代碼邏輯。
  • 反彙編代碼還原之加減乘
    加法的高級代碼與反彙編而加法也特別簡單,配合上一篇講解的優化方式,可以很好的還原。加法對應的 彙編 **add** 指令.單獨拿出這一段是想告訴大家,第一篇是基礎但也是你學習反彙編的前提。如果以後有除法、乘法等,他們都有單獨的優化。再配合流水線優化,往往就讓你發覺很難,導致無法入門。
  • 通過反彙編來理解restrict關鍵字
    這個代碼很簡單,不過重點不是看它,而是它的反彙編代碼,下面我們通過objdump工具執行objdump -j .text -l -C -S test來生成彙編代碼。 首先來看下main函數的彙編代碼。如下圖所示
  • Unity 在 GitHub 發布 Unity 引擎和編輯器的 C# 原始碼
    3月23日我們在GitHub上發布了Unity引擎和編輯器的C#原始碼,僅供Unity學習參考使用。
  • 混合使用C、C++和彙編語之:內聯彙編和嵌入型彙編的使用
    例如,在下面幾種情況中必須使用內聯彙編或嵌入型彙編。·程序中使用飽和算術運算(Saturatingarithmetic),如SSAT16和USAT16指令。·程序中需要對協處理器進行操作。·在C或C++程序中完成對程序狀態寄存器的操作。使用內聯彙編編寫的程序代碼效率也比較高。
  • 反彙編代碼還原之除數為非2的冪
    本文為看雪論壇精華文章看雪論壇作者ID:TkBinaryhttps://bbs.pediy.com/thread-224499.htmhttps://bbs.pediy.com/thread-224500.htm反彙編代碼還原之優化方式
  • 彙編語言入門
    雖然現在市面上關於彙編語言的書籍資料無窮多,卻無從下手?
  • 零時科技 | 智能合約安全系列文章反彙編·上篇
    ,我們對智能合於opcode的反編譯有了基礎的學習,對於初學者來說,要想熟練運用還得多加練習。本篇我們來一塊學習智能合約反彙編,同樣使用的是Online Solidity Decompiler在線網站,智能合約反彙編對於初學者來說,雖然較難理解,但只要能讀懂智能合約反彙編,就可以非常清晰的了解到合約的代碼邏輯,對審計合約和CTF智能合約都有非常大的幫助。
  • 阿里正式開源輕量級深度學習端側推理引擎「MNN」
    阿里近日正式開源了輕量級深度學習端側推理引擎「MNN」。與 Tensorflow、Caffe2 等同時覆蓋訓練和推理的通用框架相比,MNN 更注重在推理時的加速和優化,在大規模機器學習應用中具有優勢。本文詳細闡述了MNN背後的技術框架和規劃。 近日,阿里正式開源輕量級深度學習端側推理引擎「MNN」。
  • 學習逆向工程(外掛)基礎:彙編指令總結
    逆向工程的過程也就是把軟體逆向分析成代碼的過程,代碼可以實彙編代碼也可能是原始碼。下面介紹兩種方法:反彙編,即使用反彙編器,把程序的原始機器碼,翻譯成較便於閱讀理解的彙編代碼。這適用於任何的電腦程式,對不熟悉機器碼的人特別有用。流行的相關工具有OllyDebug和IDA。
  • 有關arm彙編中的align
    經常會看到arm-linux彙編中有如下的指令:.align n它的含義就是使得下面的代碼按一定規則對齊,.align n 指令的對齊值有兩種方案,n 或 2^n ,各種平臺最初的彙編器一般都不是gas,採取方案1或2的都很多,gas的目標是取代原來的彙編器,必然要保持和原來彙編器的兼容,因此在gas中如何解釋 .align指令會顯得有些混亂,原因在於保持兼容。
  • ARM彙編特殊符號 彙編符號引用
    280位元組內存並初始化[|] ----IF ELSE ENDIF----條件編譯,有選擇的確定需要編譯的代碼IF,ELSE,ENDIF,可以嵌套使用----IF 邏輯表達式指令或偽指令ELSE指令或偽指令ENDIF另外還有符號: $如果在串變量前有一個$,則在彙編
  • MacOS常用軟體彙編之工具篇(建議收藏)
    因Windows系統用戶的使用習慣,很多同類軟體在MacOS系統下很難找到,為大家便於參照,現彙編《MacOS常用軟體彙編之工具篇》,供需要的朋友參考:KeePassX - 一個免費的,開源的,體積小的密碼管理器。MacPass - 開源的密碼管理器。
  • ARM彙編指令:.align理解和用法
    偽指令的作用是:告訴彙編程序,本偽指令下面的內存變量必須從下一個能被Num整除的地址開始分配。如果下一個地址正好能被Num整除,那麼,該偽指令不起作用,否則,彙編程序將空出若干個字節,直到下一個地址能被Num整除為止。
  • 彙編技術內幕(2)
    但如果反彙編Solaris/Linux的二進位文件,就會發現,都用EAX保存函數返回值。這不是偶然現象,是作業系統的ABI(Application Binary Interface)來決定的。Solaris/Linux作業系統的ABI就是Sytem V ABI。
  • 解密入門教學(二)--彙編語言
    禁止製作和提供該軟體的註冊機及破解程序;禁止對本軟體進行反向工程,如反彙編、反編譯等")  作者這樣做,心情我們是可以理解的,畢竟人家花了那麼多心思在自己的軟體上,所以,我不希望你僅僅是因為交不起註冊費的原因來學習破解。
  • C語言與彙編語言的區別
    如果你問一個程式設計師這樣的問題,他也許會這麼回答你:「C語言可讀性好,代碼便於維護,便於開發;彙編語言編寫的程序不容易看懂,可維護性不好,但是執行效率高。」這樣回答是沒有錯的,但只是一個概括,不夠深入。比方說,彙編語言為什麼執行效率比C語言高呢?C語言的可讀性又好在哪裡呢?彙編語言不同樣可以用註解來提高可讀性嗎?等等這些的問題。
  • 彙編語言的基本知識
    一、彙編語言的語句格式     由彙編語言編寫的源程序是由許多語句(也可稱為彙編指令)組成的。
  • @程式設計師,快來速取硬核的彙編語言知識大全!
    用彙編語言編寫的原始碼和本地代碼是一一對應的。因而,本地代碼也可以反過來轉換成彙編語言編寫的代碼。把本地代碼轉換為彙編代碼的這一過程稱為反彙編,執行反彙編的程序稱為反彙編程序。哪怕是 C 語言編寫的原始碼,編譯後也會轉換成特定 CPU 用的本地代碼。而將其反彙編的話,就可以得到彙編語言的原始碼,並對其內容進行調查。
  • 彙編語言知識總結
    彙編語言是門很底層的語言(真的不能再底層了,不然只能用0101的機器語言編碼了╮(╯▽╰)╭),現在很少有人會用彙編語言編程的了(彙編中提供可直接調用的東西太少了,只有一些需要查表的int中斷或者win32彙編的API函數,用彙編寫代碼會很麻煩),那麼是否還有必要學習它呢?答案當然是肯定的。