1. 程式人生 > >反編譯系列教程

反編譯系列教程

轉自: https://blog.csdn.net/u011001084/article/details/50914663

 

0x00 簡介

《編譯原理》課程在大學本科階段就難道了很多計算機專業的同學。而反編譯技術更需要從事者具有深厚的編譯技術基礎,因此一直是很多業內人士希望能夠深入瞭解和掌握的一門技術。

從現在開始,我們討論反向編譯的一些內容。顧名思義,反編譯可以認為是編譯的逆過程,這一點從編譯和反編譯(Compile and De-compile,英文中也有用De-compilation來表示反編譯的)的中英文描述都可看出。但這看似只有一字之差的名稱,在實際應用中並不是簡單的“逆流而上”。因為在多數時候“創造”遠比“恢復”來得簡單,就好像您不小心打碎了家裡的花瓶,想要通過修復和粘接把碎片恢復成原樣,幾乎是不可能辦到的。即便能夠基本復原,但粘接的花瓶與它原來的樣子也不會完全一樣。與碎片到花瓶的變化類似,反編譯過程遠比編譯過程複雜和繁瑣,而且反編譯的結果也遠遠不如您原來編寫的程式碼那麼“美妙”。但是反向編譯卻能在程式功能分析、惡意程式碼發現、二進位制翻譯等特殊工作中發揮重要作用,這也是筆者仍然致力於研究這一併不十分完善的技術的重要原因。

0x01 反編譯的概念和基本過程


1.什麼是反編譯

在這一節裡我們將對反編譯做出一個基本的說明,給出反編譯的概念,使讀者們可以對反編譯有一個初步的理解。我們給出的概念不是學術研究中嚴謹的定義,只是作者基於相關文獻和多年來的研究心得所給出的一段說明。此外,我們還會揭示一個事實,儘管編譯與反編譯看起來是正反兩項技術,但有趣的是:反編譯器的編寫方法卻恰恰依賴於編譯器的編寫技術。

反編譯概念

反編譯技術是通過對低階語言程式碼(二進位制程式碼或者彙編程式碼等)進行分析轉化,得到等價的高階語言(不限制語言型別,本書的描述主要以C語言為例)程式碼的過程。它涉及指令系統,可執行檔案格式,反彙編技術,資料型別分析技術,控制流分析技術和高階程式碼生成技術等。

編譯與反編譯

反編譯的本質是編譯的逆過程。從二十世紀五十年代第一個編譯器出現開始,將機器碼轉換成為高階語言的期望就引發了人們廣泛的興趣。圖1揭示了編譯器同反編譯器之間的關係。

可以看到編譯器同反編譯器都將程式從一種形式轉換到另外一種形式,而且在轉換的步驟中,都使用了類似的中間表示。差別只是在於編譯器的總體方向是從源程式到機器碼,而反編譯器的總體方向則是從機器碼到源程式。儘管在整體方向上兩者是相反的,但編譯器和反編譯器往往在分析階段使用類似的技術,例如資料流分析。

編譯器通過對原始碼進行解析得到中間表示(IR,intermediaterepresentation),反編譯器通過對指令進行解碼得到中間表示。類似的,編譯器的低階程式碼生成同反編譯器的高階程式碼生成恰好對應。

1-1

圖1 編譯器同反編譯器在結構上的對應性

編譯的過程為源程式打上了機器屬性,比如CPU及暫存器的使用、機器指令的使用、以及記憶體地址的分配等;而反編譯過程則需要剝離機器相關的細節,儘可能地區分指令和資料,通過逆向分析,重建高階資料結構和程式結構。

反編譯器

反編譯器是利用反編譯技術實現的具體軟體系統。它讀入一個機器語言的程式(被編譯器編譯生成的二進位制編碼,即源語言),並把它翻譯為一個等價的高階語言程式(即目標語言)。反編譯器或反向編譯器,嘗試逆向一個編譯器的過程,把一個二進位制程式或可執行程式翻譯成一個高階語言程式。它應用基本的反編譯器技術,把多種多樣的機器語言二進位制程式反編譯成某種高階語言。反編譯器的結構是基於編譯器的結構,而且應用類似的原理和技術進行程式分析。

2.反編譯的基本過程

在這一小節,我們按照三種不同的分類,從多個角度闡述反編譯的基本過程。

  1. 如果按照反編譯技術實施的順序劃分,則可以分為7個階段,它們是:句法分析、語義分析、中間程式碼生成、控制流圖生成、控制流分析、程式碼生成。如圖所示。

    1-2

    圖2 按照反編譯技術實施的順序劃分

  2. 如果按照實踐中的具體操作劃分,一般也可以分為7個不同的步驟,分別是:檔案裝載,指令解碼,語義對映,相關圖構造,過程分析,型別分析和結果輸出等。如圖 3。

    1-3

    圖3 反編譯操作

    以逆向分析為目的,反編譯的各個階段並不是一個嚴格的一遍順序,而是存在著一些並行的模組,並且也需要通過迴圈執行分析過程來針對某些特殊問題(例如非N分支程式碼產生的間接跳轉指令)進行分析和恢復。

  3. 反編譯的處理過程,如果按功能區分,可以分為:前端、中端和後端三個部分。其實這種劃分方式是將上述兩種過程的階段進行合併,也就是將幾個反編譯器階段組合在一起。這樣劃分的好處是:通過設計不同的前端、中端和後端以實現針對多種源和目標的反編譯器。

0x02 反編譯的前世今生


1.建立——20世紀60年代

隨著第一臺計算機的誕生,硬體與軟體就成了構成計算機系統的兩個重要組成部分,它們相伴而生、相互促進,它們分工迥異、各司其職。為了使用和驅動不斷演化的硬體系統,通過計算機語言來編寫系統和應用程式的方式逐漸確立了主流的地位。伴隨著高階程式語言的出現,第一代編譯器也在20世紀50年代產生了,它實現了高階語言到機器程式碼的等價轉換,完成了人類認知到機器“認知”的轉換。而幾乎與此同時,將機器碼還原成為高階語言的逆向思考也引發了研究人員的求知慾望。

在第一代編譯器出現十年之後的20世紀60年代,以小規模積體電路為特徵的第三代計算機開始出現。由於其與第二代電晶體計算機的差異較大,使得執行在第二代計算機上的軟體幾乎面臨著即將被全部淘汰的危險。然而基於當時的軟體開發技術和開發成本,淘汰軟體意味著巨大的損失。為了挽救這些價值不菲的軟體,同時也為了加速開發第三代機器的軟體,與軟體移植技術相關的研究逐漸興起,例如:程式轉換器、交叉彙編器、翻譯器、反編譯器等。其中反編譯器成了最初的研究熱點。

1) D-Neliac——反編譯器的開山祖師

一些美國公司和研究機構開始著手進行軟體移植工具的研究,並且由美國海軍電子實驗室的J.K.Donally和H.Englander在1960年實現了第一代反編譯器D-Neliacdecompiler。由此我們的反編譯器鼻祖正式“粉墨登場”了,比它的兄弟“編譯器”整整小了10歲。Donnelly-Neliac(D-Neliac)可以將機器程式碼反編譯成Neliac(類似於Algol的一種程式語言,是NEL在1955年開發的一個Algol-Algorithmiclanguage型別語言)程式程式碼。

2) Sassaman的Fortran反編譯器——不是一個人的江湖

無獨有偶,1966年Sassaman在TRW公司開發了一個反編譯器,該反編譯器以IBM 7000序列的符號化彙編程式做為輸入併產生Fortran程式,是第一個使用匯程式設計序而非純二進位制程式碼作為輸入的反編譯器。它是有據可查的第二個正式的反編譯器,但是由於它以包含大量有用資訊的符號化彙編程式作為輸入,而省卻了反彙編的過程,也因此降低了反編譯的難度。這是第一個使用匯程式設計序而非純二進位制程式碼作為輸入的反編譯器。彙編程式包含名字、巨集、資料和指令形式的有用資訊,在二進位制程式或者可執行程式中是沒有這些資訊的,因此避免了在反編譯器的語法分析階段區分資料和指令的問題。

又因為該反編譯器的輸出是1960年代的標準程式語言Fortran程式(第二代、第三代計算機上都在使用Fortran),所以使得它看起來更具有實用意義。該反編譯器面向的是涉及代數運算的工程應用程式,使用者需要掌握一定的相關知識,並能夠為子程式的識別自定義規則。在熟練的程式設計師干預下,該反編譯器的正確率可以達到90%,因此它是屬於少數人的“陽春白雪”。

3)Lockheed Neliac——開山祖師的傳人

1967年,洛克希德導彈與空間公司在海軍電子實驗室開發的Neliac編譯器上做了一些增強,我們稱它為Lockheed Neliac。

反編譯器LockheedNeliac與D-Neliac類似,它們都可將機器程式碼程式轉換成Neliac程式程式碼,特別是它們可以將非Neliac語言生成的機器程式碼轉換為Neliac程式程式碼。這一時期的反編譯器採用模式匹配的方法進行反編譯,把許多複雜的問題留給程式設計師手工解決。

4)小結——De-compiler的拓荒者們

整個60年代,反編譯方面的研究主要集中在研製專門用途的反編譯器上,並希望其可以作為軟體移植的工具來使用。但是受限於當時計算機發展水平,以及體系結構的影響,這一時期的反編譯器主要採用模式匹配的方法進行反編譯,需要較多的人工干預;並且反編譯的結果通常不經過優化處理,所以輸出的結果程式碼效率較低、可讀性較差。

從前邊的敘述中可以看出,儘管從編譯器被髮明以來,人們就對編譯的逆過程倍感興趣,但真正邁出這關鍵的一步卻是由於進行“軟體移植”的訴求。那個年代的計算機程式停留在大型商業公司和高等院校的實驗室裡,只有很少的人能接觸和掌握高階語言的程式設計,程式的複雜程度也較為有限,也沒有大量出現病毒,因此利用反編譯進行程式分析並沒有那麼重要。

2.發展——20世紀70年代

反編譯技術建立初期,並沒有很快成為通用的商業軟體流行起來。大學校園的理論研究,成為推動這項技術發展的主要動力。下面我們按照時間的脈絡,走進歷史程序中的“象牙塔”,回顧一下反編譯技術的發展過程。反編譯理論方面的研究成為這個時期的熱門,隨著美國多位博士以反編譯作為博士研究課題,一些對逆向編譯和程式移植有一定影響的理論和技術被相繼提出。在這個研究領域,豁然呈現出百花齊放的景象。

1)花開一朵——博士們的研究

1973年,DoctorHollander首次針對反編譯方式生成的程式碼,使用控制流與資料流分析相結合的技術手段來進行優化。此外,他還基於元語言描述定義了一個反編譯過程的五級模型,並且實現了從IBM System360彙編子集到類ALGOL語言的反編譯。該反編譯器雖然引入了一些新的技術,即綜合利用資料流和控制流分析技術來改善反編譯器的輸出程式碼,並使用形式化方法來分析程式碼,但其核心依然是構築在模式匹配技術基礎之上。

幾乎在同時,DoctorHousel的博士論文對反編譯進行了較系統的論述,文中描述了一種可行的反編譯方法,並通過實驗驗證了部分理論。Housel的反編譯方法借用編譯器、圖、和優化理論的概念,並據此設計了一種包括部分彙編、分析器、程式碼生成等三個主要步驟的反編譯器。然而受限於當時實驗平臺和目標程式,Housel僅僅測試了6個程式。實驗中,有88%的指令可以通過反編譯器自動生成,剩餘的指令則需要程式設計師進行手工干預。這個反編譯器證明,通過使用已知的編譯器和圖的方法,可以實現生成良好高階程式碼的反編譯器。中間表示法的使用使得分析完全不依賴機器。這個方法學的主要缺陷在於源語言的選擇,MIX組合語言,在這些程式中不僅帶有大量可用資訊,而且它是一個簡單化的、非現實的組合語言。

1974年,DoctorFriedman在他的博士學位論文中描述了一個反編譯器,用於在相同體系結構等級內的小型計算機作業系統的遷移。該編譯器包含四個主要部分:前期處理器、反編譯器、程式碼生成器、和編譯器,它是Housel反編譯器的一個改寫版。Friedman在反編譯作業系統程式碼的方向上邁出了第一步,而且它例證了在反編譯機器依賴的程式碼時反編譯器面對的困難。不足的是,該遷移系統對輸入程式有所要求,即需要對輸入程式做大量格式化工作;同時,該系統最後產生的程式程式碼具有較大的空間膨脹率,而程式碼膨脹率高又帶來了執行時間和效率的低下。

1978年,DoctorHopwood所做的工作實現了從組合語言程式到MOL620語言程式的翻譯功能。他引入了控制流圖的概念,即指定一條指令作為控制流圖的一個節點。與現在廣為採用的以基本塊為節點的方式相比,Hopwood的方案對記憶體的要求會更高一些。Hopwood的博士學位論文描述了有關其設計的一個包含七個步驟的反編譯器的內容,他的研究的主要缺點是控制流向圖的粒度和在最後的目標程式中暫存器的使用。其中控制流向圖的粒度的使用,導致該反編譯器處理大規模程式的時候開銷太大;而在其所生成的目標高階語言程式碼中使用了暫存器,導致反編譯結果並非純正的高階程式碼。

2)花開二朵——反編譯技術在工程上的應用

20世紀70年代的十年是反編譯技術發展的一個黃金時期,當時的反編譯器並未侷限在對高階程式語言的恢復上,而是引入了“翻譯”的特徵。在這個時期,除了一些以理論研究為主的反編譯器模型被提出以外,還有一些具有代表性的實用系統被開發出來。

1974年,由Barbe開發的Piler系統是第一個實現的X型通用反編譯器架構,它的設計目標是實現從多種機器級程式碼到與其各自對應的高階語言程式的反編譯。但是這種多源到多目標的反編譯實現起來具有極高的難度,直接導致Piler系統最終實現時,僅能支援從通用公司的Honeywell 600機器程式碼到Fortran和COBOL兩種高階語言程式的反編譯。可見,當時的反編譯器如果能夠實現對“翻譯”輸出的高階語言再次編譯,並且編譯後程序的執行結果與反編譯前一致的話,就幾乎相當於一個二進位制翻譯器了。

3)花開三朵——反編譯技術在軍事上的應用

反編譯技術同樣被軍方慧眼識珠,在上世紀70年代多項軍事應用中可以看到它的身影。

1974年,Ultrasystems公司的一個反編譯工程中也用到了反編譯器。這個反編譯器作為三叉戟 (Trident)潛艇射擊控制軟體系統的一個文件編寫工具。它以Trident彙編程式作為輸入,產生這個公司開發的Trident高階語言程式。該編譯器分成四個主要階段:規格化、分析、表示式凝聚和程式碼生成。該系統的輸入為經過“規格化預處理”的彙編程式,其目的是為了解決部分指令和資料的區分問題;與此同時,預處理過程還會生成一種中間表示,並對資料進行分析;然後,在表示式凝聚的過程期間,算術表示式和邏輯表示式將被還原並建立;最後,再通過和THLL(Ultra systems公司的名為Trident的高階語言)匹配控制結構,而生成輸出的高階語言程式。

1978年,D.A.Workman主導了一項有軍方背景的反編譯應用研究,目的是為美國軍方實時訓練裝置系統設計適用的高階語言。該應用在F4型教練機的相關設計中發揮了作用。F4教練機的作業系統是用匯編語言編寫的,因此這個反編譯器的輸入語言是組合語言。由於這項應用的目標是用於設計,因此沒有確定輸出語言,沒有實現程式碼生成,僅僅實現了反編譯器的兩個階段:階段一,把彙編程式對映為一箇中間語言並收集關於源程式的統計資訊;階段二,產生基本塊的控制流向圖,依據它們的可能型別來劃分指令,並且分析控制流以確定高階控制結構。這項成果的貢獻在於,提出了一種在當時看起來十分獨特的反編譯技術應用。即這個反編譯器,並未以輸出高階語言程式碼為最終目的,而是通過把指令分類來進行資料分析。從這一案例,可以看出反編譯技術的先進性在當時得到了美國軍方的認可,並且在實際應用中催生出了程式碼分析的功能,值得後續研究的借鑑。

4)小結

有了1960年代的技術積累,反編譯技術在高等院校得到了理論和實踐的雙重推進。在理論研究的支撐下,越來越多的實用系統中都使用了反編譯技術。以至於被大家公認的高科技技術集散地的美國軍方,也將反編譯技術投入到軍事訓練和實戰中。因此,在1970年代反編譯技術擁有自己完美的發展期,並獲得了長足的進步。

3.瓶頸期——20世紀80年代

之所以將1980年代稱為反編譯發展的瓶頸期,主要是這項技術所能實現的功能的合法性問題。畢竟隨著計算機的普及和發展,軟體智慧財產權逐步被人們所重視。反編譯技術可以逆向獲得程式原始碼的特性,與軟體智慧財產權的保護存在著一定的矛盾。如何界定科學研究和非法獲利,一時沒有定論,但是這個十年中,相關研究也沒有完全停滯,仍然有一些具有代表性的工作。

1) Zebra

1981年,美國軍方的另一個反編譯例項:美國海軍水下系統中心開發的Zebra樣機試圖實現彙編程式的可移植性。Zebra的主要功能是把ULTRA/32組合語言的一個名叫AN/UYK-7的子集作為輸入語言,進而產生PDP-11/70的彙編程式輸出。儘管Zebra並不是傳統意義的反編譯器,更像是單純的程式移植和變換,但是該系統的實現方式與反編譯並沒有什麼不同,即Zebra的實現主要由三大步驟構成。第一階段,詞彙和流分析:對原程式進行解析,並在基本塊內做控制流分析;第二階段,程式被翻譯為中間形式;第三階段,做中間表示的化簡。

Zebra利用已知的技術來開發一個彙編程式的反編譯器,整個研發過程並沒有引入新的概念。但是Zebra提出一個觀點值得我們回味,也就是說:從Zebra的研發過程中看出,反編譯應該作為一個工具幫助解決某個問題,而不是完全解決該問題的工具。這個結論,源於科研人員對反編譯的瞭解而提出的假設,即假如反編譯器不可能達到100%正確。雖然這是一個上世紀80年代初就提出的論斷,但我們仍然可以從中一窺反編譯發展至今的主要作用——程式分析(相關的討論,我們放到相應的章節來具體討論)。

2)Forth Decompiler

1982到1984年,Forth Decompiler系統具有一定的代表性。它是一種可以通過遞迴掃描Forth語言編譯字典條目,而把單詞反編譯成原語和地址的一個工具。但是Forth Decompiler並不是一個純正的反編譯器,它更像是一個逆語法分析工具,該工具遞迴地掃描一個字典表並且返回與給定單詞有關的原語或地址。

3)STS(SoftwareTransport System)

1985年,C.W. Yoo介紹了軟體傳輸系統STS (SoftwareTransport System),實現彙編程式碼從一機器到另一機器的自動轉換。STS的轉換思路是:將機器m1的彙編程式碼反編譯成高階語言程式,然後將獲得的程式在機器m2上編譯成新的彙編程式碼。一個實驗型的STS系統是針對Z-80處理器而研發的C語言交叉編譯器,但是由於STS系統缺少資料型別資訊導致此專案擱淺。

4)Decomp

1988年,Reuter編寫了一套反編譯器,並命名為Decomp,它是一種專用於Vax BSD 4.2機器的反編譯器。Decomp需要帶有符號資訊的目標檔案作為反編譯的輸入,而通過反編譯生成類C原始碼程式,部分輸出的C原始碼經過手工編輯後可以被再次編譯。Decomp做了一件今天看起來十分有趣的事情,也就是它為了一款遊戲而生,它在沒有可用原始碼的情況下把帝國遊戲(Empire)移植到VMS環境。這件事情與筆者目前從事的二進位制翻譯相關研究非常類似,即實現了應用程式級別的移植,並以遊戲這種生動的形式加以展示。

Decomp反編譯器在當時的條件下,花費了大概5人月的工作量,並且可以從因特網上免費獲取它。昆士蘭大學的Cristina Cifuentes在她的博士論文中,成功地重現了Decomp的反編譯過程。從實驗中可以看出,Decomp反編譯生成的程式有正確的控制結構、正確的變數資料型別、庫例程和子過程的名字,甚至使用者程式入口點也得到了還原。當然上述功能的取得,都需要滿足一定的先決條件,但僅從功能上來看,Decomp是一個又實用價值的反編譯器。

5)小結

經過20年的技術積累,1980年代本來應該成為反編譯技術的爆發期,但是由於它天生的逆向屬性,導致其與軟體智慧財產權的保護產生了矛盾。在法律監管的空白時間裡,並沒有產生一些讓我們記憶猶新的優秀系統和應用。整個1980年代的研究延續了不溫不火的態勢,但是對反編譯完備性的討論和實用化的驅使,仍然使得這個方向的研究延續下來。這個10年的研究比1970年代更注重實用性,並從專用這個角度加以體現。

4.反編譯的春天——20世紀90年代

1990年代,反編譯技術作為一個主體終於有了可以依據的法律——許多國家針對軟體逆向工程進行立法,以規範該領域的研究工作。從一篇美國研究者Pamela Samuelson的文章“Reverse-engineering SomeoneElse's Software: Is It Legal?”中,可以重現當時美國對“反編譯”這一類技術的法律定義:根據美國聯邦法律,對擁有版權的軟體進行逆向工程操作,例如反彙編、反編譯,若其目的不是通過剖析原軟體,來研製新產品與之竟爭,所進行的逆向操作是合法的。仔細品味這句話可知,只要是非盈利性的軟體分析和原始碼獲取是被允許的。在上述背景下,反編譯技術迎來了發展的春天,一批國家層面的綜合性研究專案代表了反編譯在當時的地位,比如:由11個歐洲工業和學術組織合作研究的REDO計劃,主要是以調查研究軟體的維護、有效性和軟體系統文件化為目標。他們在軟體逆向工程研究主題下對反編譯進行了大量研究,從邏輯語義上給出反編譯的一些實現方法和本質描述,並將其結果應用於英國核工業部。既然春天來了,除了上述枝葉返青的參天大樹,哪能沒有百花齊放呢?我們再把1990年代的反編譯發展脈絡梳理一下。

此外,在這個十年中,反編譯這項技術與另外一項稱作“二進位制翻譯”的技術結合的越來越緊密。反編譯技術作為二進位制翻譯的一種重要實現方式,我們將在後文詳細介紹二者的聯絡和區別,同時也會花一定篇幅,著重揭開一下能夠體現反編譯技術實用性的“二進位制翻譯”的神祕面紗。

1) Exe2c

1990年,由AustinCode Works公司推出的實驗專案Exe2c可以將Intel80286/DOS可執行程式反彙編,並轉換為內部格式程式碼,最終轉換成C程式。從Exe2c的輸出程式碼看出,它並沒有實現資料流分析,而僅僅實現了部分控制流分析。C語言使用的一些控制結構被恢復,如:if-then和迴圈。它生成的C語言程式的大小是彙編程式的3倍。這個反編譯器的積極意義在於,它是在過去的若干年中第一個嘗試反編譯可執行檔案的反編譯器。其成果表明,為了產生更好的C程式碼需要引入一個較為完善的資料流分析和啟發式功能。而且,建立一個忽略所有由編譯器引進的外來程式碼和發現庫子程式的機制會很有幫助。這一點可以大大降低反編譯的工作量,同時提升反編譯輸出的可用性。

2) PLM-80 Decompiler

1991年研發的PLM-80Decompiler反編譯器也是一款具有國家支援和軍事應用背景的軟體,它是澳大利亞國防部的資訊科技司研究的一個反編譯的國防應用,也是在“反編譯的春天”裡值得一提的一項研究。PLM-80Decompiler研發的主要目的是針對廢棄程式碼的維護、具有科技情報產品的分析、以及針對資訊系統的安全和保密風險的評估,其中除了第一點以外,其它的目的都與反編譯在當今時代的需求所契合,可見該專案在當時是具有前瞻意義的一項研究。

雖然研發目標具有重要意義,但最終由於技術實現的難度,PLM-80Decompiler只實現了一個樣機。該樣機是用Prolog語言編寫的,針對特定機器PLM-80編譯器編譯的Intel 8085彙編程式,可以產生一種稱作“Small-C語言”的目標程式(Small-C代表所生成的程式碼是標準C語言的子集)。從公開可以查閱的文獻中,可以瞭解到PLM-80Decompiler可以從輸入的彙編程式中識別出部分if..then和while()等簡單結構,可以還原一些int和char等簡單的資料型別,以及一些全域性和區域性變數。除此之外,PLM-80Decompiler還設計了一個圖形化使用者介面用於顯示彙編程式和偽C程式,並且使用者還可以通過圖形介面編輯變數的名字、增加註解,以及支援手工確定主程式的入口點等功能。

總的來說,PLM-80Decompiler所做的分析侷限於控制結構和簡單資料型別的識別,並沒有引入針對暫存器使用的分析。同時,該反編譯器也不支援分析編譯器生成的優化程式碼。但值得稱道的是,PLM-80Decompiler對圖形介面的引入,是對使用者體驗的一次提升,也是對反編譯這種需要人工反饋參與工作的一種新的支援手段。

3) 8086 C Decompiling System

1991年推出的8086C Decompiling System是一個將Intel 8086/DOS可執行程式翻譯成C程式的反編譯器。它實現了庫函式的識別以減少生成程式碼的量,同時它基於規則識別出了陣列和結構體的指標等資料型別。不出意外的是,這個反編譯器同樣對輸入檔案有特殊的要求——輸入檔案必須是由Microsoft C V5.0版本小儲存器模型編譯所生成的。這一點再一次印證了:一個反編譯器的可用性受限於它對源編譯器的依賴。多數反編譯器只能針對特定的編譯器型別(或者編譯器版本)完成正確的反編譯動作。

8086 C Decompiling System描述了五個階段:庫函式的識別、符號執行、資料型別識別、程式轉換和C語言程式碼生成。

該系統在庫函式識別階段所做的工作具有一定代表性,很多反編譯器和後來的二進位制翻譯器都採用類似的方法。有必要單獨說明一下:8086 C DecompilingSystem主要實現了對Microsoft C庫函式的識別,用來區分哪些是系統呼叫的庫函式,哪些是使用者自己編寫的函式。這麼做的目的是為了在反編譯時只處理使用者函式,生成相應的C程式碼,而所有庫函式則不必被反編譯。識別庫函式是通過模板匹配的形式完成,即預先構造一個所有C語言的庫函式表,包含一些特殊資訊,這部分工作是反編譯器作者手工實施的。

除了庫函式識別階段以外,該反編譯器其餘階段的實施中規中矩。它的符號執行是將機器指令完全用特有符號來表示,形成一套中間指令;而對資料型別的識別則是通過兩套規則配合來實現的,即首先通過一組規則對於不同資料型別進行資訊收集,然後再根據所收集的資訊和另外一組分析規則共同確定資料型別;程式轉換則把儲存計算轉變成各種地址表示式,例如陣列定址;最後,C語言程式碼生成器通過識別控制結構,繼而轉換成相應的程式結構,並且生成C語言程式碼。

8086 C Decompiling System是由我國合肥工業微機所科研人員開發的一套較早的反編譯系統,代表了當時我國的反編譯研究的方向和水平。

4) Alpha AXP Migration Tools

數字裝備公司(Digital Equipment Corporation)在設計Alpha AXP體系結構的時候,需要能夠在“新”的Alpha AXP計算機上執行“現有”的VAX和MIPS程式碼。正是由於這個動議,使得Alpha AXP MigrationTools作為一套可以完成上述功能的二進位制翻譯工具被開發出來。Alpha AXPMigration Tools可以實現舊體系結構的指令序列轉換成新體系結構的指令序列,即實現不同體系結構間二進位制級程式碼的無縫移植。這個移植過程中被開發者定義成兩個部分,分別是:二進位制翻譯過程和執行時環境。為了保證二進位制翻譯過程的全自動執行,同時能夠實現執行期間建立或修改程式碼的功能,該移植工具在二進位制翻譯部分使用了反編譯技術。

Alpha AXP Migration Tools中反編譯技術的應用,主要體現在機器指令的潛在含義理解和分析上。例如:原體系結構上的條件碼分析,以便後續翻譯過程中可以轉化為Alpha體系結構上的相應操作;通過程式碼分析,確定函式的返回值或者發現程式碼中的錯誤;MIPS中標準庫例程的發現和定位,以減少程式碼翻譯的工作量,因為庫例程絕大多數情況下是可以在新體系架構中找到類似庫函式實現其功能的;發現程式碼中的“成語”(可以理解為在特定體系結構上,完成特定功能的一組指令序列),並使用目標體系結構中功能等價的指令組合來實現該“成語”的功能。通過上述一系列的分析工作,以及其他翻譯工作,最終以AXP操作碼的形式組成翻譯後的二進位制編碼,並由執行時環境執行翻譯的程式碼。

這個工程舉例說明在一個現代翻譯系統中反編譯技術的使用。證明對於眾多類別的二進位制程式來說它是成功的。一些無法翻譯的程式是在技術上不可翻譯的程式,比如使用特權操作碼的程式或者以超級使用者特權執行的程式。

5) 其它反編譯器產品和研究

在這一階段還有眾多公司紛紛推出自己的反編譯器產品,具體情況如表所示。

表1 20世紀90年代部分反編譯器產品

產品名稱 年份 用途 廠商
Valkyrie 1993 用於 Clipper Summer '87 的視覺化反編譯器 CodeWorks
OutFox 1993 用於加密的FoxBASE+程式的反編譯器 不詳
ReFox 1993 用於反編譯加密的FoxPro檔案 Xitech
DOC 1993 用於 AS/400 和 System/38 的COBOL反編譯器 Harman Resources
Uniclip 1993 用於 Clipper Summer '87 EXE 檔案的反編譯器 Stro Ware
Clipback 1993 用於Summer '87 可執行檔案的反編譯器 Intelligent Information Systems
Brillig 1993 用於 Clipper 5.X .exe檔案和.obj檔案的反編譯器 APTware

同時也有一批大學的實驗室,發表自己的試驗系統,其中在這一領域Cifuentes博士的研究成果,成為以後反編譯研究的主要參考方向。Cifuentes在自己的博士論文中指出,可以通過資料流分析識別引數和返回值,通過控制流分析將程式碼恢復成具有結構的C程式。研究型反編譯器dcc展現了她的工作,但它只能處理很小的Intel 80286/DOS可執行程式。

在dcc的基礎上,反編譯器REC(Reverse Engineering Compiler)基於Cifuentes的工作在幾個方面進行了擴充套件,但是它生成的類C程式比較難讀,因為包含了暫存器符號。它可以處理多個處理器(如:Intel 386、Motorola 68K)上執行的多種格式的可執行檔案(如:ELF和Windows PE等)。像陣列等複雜的資料型別保留為訪問記憶體的表示式。

5.持續的研究——進入21世紀

時間荏苒,當歷史的腳步進入新世紀,反編譯的相關研究也隨之更進一步,本節我們將用簡單的筆觸介紹2000年後的相關研究和商用產品。

2001年,Guilfanov介紹了IDA Pro反彙編器使用的型別傳播系統,並著重講解了庫函式的識別工作。IDA Pro利用庫函式的簽名信息來恢復庫函式呼叫語句使用的引數的資料型別,然後使用型別傳播技術處理賦值語句來進行資料型別恢復。上述事實說明,沒有一種資料型別恢復不是根據庫函式型別資訊進行的。

2002年,Morisada釋出了處理32位Windows可執行程式的反編譯器Anatomizer。對某些Windows可執行程式它表現的很出色,可以恢復引數和返回值,條件語句和switch語句也得到了很好的處理。當使用Anatomizer處理Cygwin程式時,庫函式printf無法被恢復。另外,Anatomizer無法處理浮點指令和間接呼叫,陣列仍然被保留為訪問記憶體的表示式。當暫存器在某些過程體中未被定值而先使用時,它無法將其識別成引數。反編譯器Anatomizer經常會異常終止。

2002年,Tröger和Cifuentes給出一個分析間接呼叫指令的方法。如果由虛擬函式產生的間接呼叫被成功識別,那麼關於此間接呼叫的諸多資訊都會獲得。然而,此方法受限於一個基本塊的範圍,導致無法處理所有的情況。

2004年,RaimarFalke基於Mycroft的理論並進行擴充套件開發了反編譯器Yadec,用於恢復陣列資料型別。但需要一個相當於使用者抉擇的檔案來處理衝突。

2004年,由AndreyShulga編寫的反編譯器Andromeda一直沒有公開發行,但從網站可以獲得一個GUI程式對應反編譯生成程式。生成程式給人的印象非常深刻,但不能僅憑一個反編譯生成程式來斷定反編譯器的優劣,可能它恢復出的資料型別是經過手工編輯的。

2007年,IlfakGuilfanov釋出一個整合反彙編器IDA Pro的反編譯器Hex-Rays,用來處理32位Windows可執行程式。反編譯器Hex-Rays可以在視窗中展示反編譯生成的類C程式碼,通過點選函式名跳到函式體視窗。所有函式的引數和返回值都得到了恢復,但作者宣告反編譯生成的程式僅用於閱讀,不能對其進行編譯。

遺憾的是,2008年後國外與反編譯緊密的相關的重要研究很難再被檢索到。筆者分析,其主要原因是反編譯本身的複雜性,不完備性,以及在逆向分析實踐中不如反彙編實用的現實所造成的。此外,虛擬化技術的日漸成熟,也分化了相當一部分使用反編譯形式進行二進位制翻譯的研究資源。

6.身邊的反編譯——我國對反編譯的研究

國內從事反編譯方面研究的團隊較少,能夠在公開報道中查證的有:從1984年開始,合肥工業大學微機所開展了一系列與反編譯相關的研究工作:在國家自然科學基金的資助下,以Dual-68000為硬體平臺,研製T 68000 C反編譯系統;用手工的方法反編譯了UNIX作業系統部分元件;七•五期間,他們還進行了8086 C語言反編譯系統的研究;隨後,他們還發布了商業化的反編譯系統DECLER V1.0和V1.1。1991年發表的文獻中提及,武漢大學從1986年起就開始研製VAX機上的C語言反編譯系統。同年發表的另外一篇文獻中提及,北京控制工程研究所在PC機上開發TC語言反編譯系統。1992年,上海交通大學的科研人員也發表了關於VAX機器上C語言反編譯系統研究與實現的論文。1994年哈爾濱工業大學的研發團隊開發了一款基於Turbo C的小模式反編譯系統。2001年,解放軍資訊大學開始研發ITA二進位制翻譯系統(歷時五年),該系統可以實現將IA64/Lunix架構下由C語言編制的可執行程式反編譯到SW/Lunix下執行。2006年,北京大學電腦科學研究所研發了彙編級別的分析工具BESTAR。該工具實現了Linux平臺上的輕量級彙編程式碼結構化表示功能,它能夠實現利用控制流和資料流分析技術識別通用控制結構。該工具還可以分析程式執行流,重構表示式和函式,發現數據依賴關係,將彙編程式碼轉換成一個結構化、易理解的中間語言程式。

當然前文還提到了8086 CDecompiling System也是國內非常重要的研究和工作,尤其8086 C Decompiling System還在Cifuentes的論文中被提及。