無論是修改許多網上源碼庫中的代碼,還是調用常見的操作系統例行程序,您免不了要花一些時間去琢磨您沒有編寫過的代碼,而且您還 可能沒有這些代碼的源文件。在開始調試代碼時,您需要有一個好的 Java 反編譯器,并了解正確使用它的技術。同時,您還要知道如何保護您自己的代碼不被窺視。為此,您還需了解有關代碼模糊處理的問題。在這篇有關打開和封鎖 Java 代碼的初學者指南中, Greg Travis 使用 Mocha、HoseMocha、jmangle 和 JODE 等流行工具中的范例,來循序漸進地教你有關反匯編、反編譯和 Java 代碼模糊處理的基礎知識。
沒有比發現一個錯誤,卻沒有源代碼就不能修改更令人沮喪的了。正是這個原因導致了 Java 反編譯器的出現,它可以把編譯后的字節碼完全轉回成源代碼。盡管代碼反編譯器不只是針對 Java 語言,但它從來沒有象在 Java 開發人員中那樣被公開地或廣泛地使用。
與 反編譯針鋒相對的是模糊處理。假設反編譯人員能很容易從編譯后的代碼中設法得到源代碼,那么要保護您的代碼和有價值的技術秘密就不是那么簡單了。隨著 Java 反編譯器的普遍使用, Java 模糊處理器也同樣被普及,它的作用就好像放一塊煙幕在您的代碼前面。反編譯和模糊處理在商業開發領域中引起了一場爭論 -- 爭論中的大部分都集中在了 Java 語言上。
在本文中,我將讓您了解代碼反編譯和模糊處理的具體過程,討論在這兩種技術之后的理論問題,同時簡要地談到它們在商業編程領域中所引起的爭論。我還將介紹一些比較有名的反編譯器和模糊處理器(有商業的,也有開放源代碼的),并隨著文章的深入使用它們來創建一些實例。
反 編譯是一個將目標代碼轉換成源代碼的過程。這應該很清楚了,因為編譯是一個將源代碼轉換成目標代碼的過程。但什么是目標代碼呢?大體上的定義是:目標代碼 是一種用語言表示的代碼,這種語言能通過實機或虛擬機直接執行。對于象 C 這樣的語言,目標代碼通常運行在硬件 CPU 上,而 Java 目標代碼通常運行在虛擬機上。
正 如以上所描述的,反編譯聽上去比較簡單,但它實際上是非常困難的 -- 從本質上說,它所包含的是根據小規模、低層次的行為來推斷大規模、高層次的行為。為了對此有個直觀的理解,我們把一個計算機程序看作是一個復雜的公司組織 結構。高層管理人員向他們的下屬下達類似“最大程度地提高技術生產能力”的命令,下屬們再把這些命令轉變成更具體的行動,例如安裝新的 XML 數據庫。
作為該公司的新雇員,您可能會問下屬他或她在做些什么,并得到回答,“我在安裝新的 XML 數據庫。”從這句話中,您不可能推斷出其最終目的是最大程度地提高技術生產能力。畢竟,最終目標不盡相同,例如可能是分離供應鏈或累積消費者的數據。
然而,如果屬于好奇心特強的那類人,您可能會再多問幾個問題,并讓公司中不同級別的下屬回答您的問題。最后,當把所有的答案匯總后,您可能會猜到企業更大的目標是最大程度地提高技術生產能力。
如果您把計算機程序的工作方式看作類似一個公司的組織結構,那么對于為什么反編譯代碼不是無關緊要的,以上的這個比方就會給你一個直接的感受。從比較理論化的角度來看,這兒要引用在該領域的杰出研究員 Cristina Cifuentes 對反編譯過程的描述:
任 何一個二進制改造工程都需要對存儲在二進制文件中的代碼進行反匯編。從理論上說,分離 von Neumann 上的數據和代碼就好象停機問題,因此完全的靜態翻譯是不可能的。然而,實際上可以使用不同技術來提高可被靜態翻譯的代碼的所占比例,或者采取可在運行中被 使用的動態翻譯技術。 --"Binary Reengineering of Distributed Object Technology"(請參閱 參考資料)
把目標代碼轉換成源代碼并不是反編譯時碰到的唯一問題。一個 Java 類文件潛在包含了一些不同類型的信息。知道類文件中可能包含了哪類信息對于了解您如何利用該信息以及對于信息作何種處理都是很重要的。這其實就是 Java 反匯編器所要做的。
Java 類文件的真正二進制格式不是很重要。重要的是知道在那些字節中包含了哪些不同種類的信息。到了這一步,我們將利用多數 JDK 都帶有的一個工具 -- javap。 javap 是一個 Java 代碼反匯編器,它和反編譯器是不同的。反匯編器把機器可讀格式的目標代碼(如清單 1 所示)轉換成人們可讀的代碼(如清單 2 所示)。
0000000 feca beba 0300 2d00 4200 0008 081f 3400 |
Local variables for method void priv(int) |
請注意,清單 2 所示的并不是源代碼。該清單的第一部分列出了方法的局部變量;第二部分是匯編代碼,它也是人們可讀的目標代碼。
javap 被用來反匯編或解包一個類文件。這里簡要列出了可以通過使用 javap 進行反匯編的 Java 類文件所包含的信息:
- 成員變量。每個類文件中包含了對應于該類每個數據成員的所有名稱信息和類型信息。
- 經過反匯編后的方法。類的每一個方法都是由一串虛擬機指令來表示的,并附帶它的類型簽名。
- 行號。每個方法中的每個節被映射到源代碼行,在可能的情況下,源代碼行來生成節。這使得實時系統和調試器能夠為在運行狀態的程序提供堆棧跟蹤。
-
局部變量名一旦方法被編譯了,這個方法的局部變量就不太需要名稱了,但是能通過對 javac 編譯器使用
-g選項來包含它們。這也使得實時系統和調試器能幫助您。
既然對 Java 類文件的內部情況已有所了解,讓我們看一下如何能轉換這些信息來達到我們的目的。
從概念上講,反編譯器使用起來非常簡單。他就是把編譯器逆過來用:你給它 .class 文件,它還給你一個源代碼文件。
一些比較新的反編譯器有精致的圖形界面。但在一開始所舉的例子中,我們將使用的是 Mocha,它是第一個公開的可利用的反編譯器。在本文的最后,我會討論一下在 GPL 下一個較新的反編譯器。(請參閱 參考資料,下載 Mocha 并獲取 Java 反編譯器的清單。)
讓我們假設在目錄中有一個名為 Foo.class 的類文件。用 Mocha 對它進行反編譯非常簡單,只要鍵入以下命令:
$ java mocha.Decompiler Foo.class |
這會生成一個新的名為 Foo.mocha 的文件(Mocha 使用 Foo.mocha 這個名字以避免覆蓋原文件的源代碼)。這個新文件就是 Java 的源文件,并且假設一切順利的話,您現在就能正常地編譯它。只需把它重命名為 Foo.java 就可以開始了。
但是這兒有個問題:如果在一些您已經有所改動的代碼上運行 Mocha,您會注意到它生成的代碼和源代碼不是完全一樣的。我舉個例子,這樣您能明白我的意思。清單 3 所示的原始源代碼是來自一個名為 Foo.java 的測試程序。
private int member = 10; |
以下是 Mocha 生成的代碼
private int member; |
這兩個代碼片段的成員變量
member
被初始化為 10
的位置不同。在原始源代碼中,它在與聲明的同一行中被表示為一個初始值,而在被反編譯后的源代碼中,它在一個構造符中被表示為一條賦值語句。反編譯后的代
碼告訴我們一些有關源代碼被編譯的方法;即它的初始值是作為在構造符中的賦值來被編譯的。通過觀察其反編譯后的輸出結果,您能了解到不少 Java
編譯器的工作方法。
雖然 Mocha
的確可以反匯編您的目標代碼,但它不會總是成功的。由于困難重重,沒有一個反編譯器能夠準確無誤地翻譯出源代碼,而且每個反編譯器處理它們在翻譯過程中的
漏洞的方式也不同。舉例來說,Mocha 有時在輸出準確的循環構造的結構方面有一些問題。如果真的這樣,它會在最終輸出中使用偽 goto 語句,如清單 5 所示。
if (i1 == i3) goto 214 else 138; |
撇開 Mocha 的問題不談,反編譯器在通常情況下還是能比較準確地翻譯出源代碼。一旦知道了某一反編譯器的弱點,您可以手工分析和轉換反編譯后的代碼,以使它們能較準確 地符合原始源代碼。隨著反編譯器正變得越來越出色,我們又碰到了另外一個問題:如果您不想讓任何人能反編譯您的代碼,那該怎么辦呢?
雖然,大部分的代碼反編譯是完全正大光明的,但事實是一個優秀的反匯編器是軟件侵權的必需工具之一。正因如此,尤其對于在商業和不開放源代碼領域中的開發人員來說,便宜的(或免費的) Java 代碼反匯編工具的存在是一個嚴重的問題。
就 語言本身而言, 由于其相對簡單的 Java 虛擬機(與真實的微處理器相比)和其寫得很規范的字節碼格式, Java 代碼非常容易反匯編。而這隨著 Java 語言在 Web 開發平臺上的日益普及,已經在商業開發領域引起了很多爭議。自從 Mocha 于 1996 年首次發布以來,一些在保護它們的源代碼方面有過投資的公司和個人一直在為 Java 反編譯器大吵大鬧。
實際上,當 Mocha 第一次發布時,它的作者 Hanpeter van Vliet 曾被一些公司的訴訟威脅過(請參閱 參考資料)。起初,他把反編譯器從他的網站上移去,但是他后來以 Crema 的形式提供了一個更好的解決方案。Crema 是一個 Java 模糊處理器,它完全對立于 Mocha。
自 Crema 發布以來,許多 Java 模糊處理器開始出現,其中一些是商業的,也有一些是開放源代碼的。正如您看到的那樣,一個好的 Java 模糊處理器可以在很大程度上保護您的 Java 代碼。
代碼模糊處理字面上的意思就是模糊處理您代碼的行為。Java 模糊處理器用不易察覺的方法改變程序,以致于它的 運行對 JVM 來說是一模一樣的,但它使得試圖理解程序的人更加迷惑了。
讓 我們看一下當反匯編器遇到經過模糊處理后的代碼會發生什么情況。清單 6 顯示了 Mocha 在嘗試反匯編被一種名為 jmangle 的工具模糊處理的 Java 代碼后的結果。請注意以下的一小段程序和我們在前面清單中使用的是相同的,盡管乍一看,您肯定不會這么認為。
public Foo() |
象 jmangle 這樣的模糊處理器把許多變量名和方法名(有時甚至是類名和包的名稱)轉換成沒有意義的字符串。這樣就使得人們難以閱讀程序,但對于 JVM 來說,其在本質上和原來的程序是一樣的。
所有的模糊處理器都要使標記變得沒有意義,但他們所做的不僅僅是這些。Crema 之所以臭名昭著是因為它用了許多卑鄙的手段來阻止反匯編,并且有許多在已經出現的模糊處理器中,紛紛仿效它。
一種常用的模糊處理代碼的方法是用一個非法的字符串來替代類文件中的標記,這比使用沒有意義的字符串更進了一步。替代的有可能是一個關鍵字,例如
private ,或者甚至是象
*** 這樣沒有意義的標記。一些虛擬機 -- 尤其在瀏覽器中 -- 對這些古怪的用法不會作出合法的反應。從技術上說,一個象
= 這樣的變量與 Java 的規范是相反的;一些虛擬機可以忽略它,而另一些不可以這樣。
按字面意思,Crema 使用的另一個計策就是炸彈。Crema 具有完全關閉 Mocha 的能力。它在編譯后的代碼中添加一個小“炸彈”,導致 Mocha 在試圖反編譯代碼時崩潰。
可惜,Crema 已經沒有了,但有一種名為 HoseMocha 的工具是專門為關閉 Mocha 而設計的。為了了解 HoseMocha 是如何工作的,我們將使用 javap,這個值得信賴的反匯編器。清單 7 所示的是 HoseMocha 放置炸彈前的代碼。
Method void main(java.lang.String[]) |
以下是 HoseMocha 處理后的代碼。
Method void main(java.lang.String[]) |
您看到那顆炸彈嗎?請注意現在這個程序在返回后面有 一條 pop 語句。等一下 -- 一個函數在返回之后還能做什么嗎?很顯然,它不能,而這就是關鍵所在。在返回語句后放一條指令確保了它不會被執行。您這兒所見的是根本不可能被反匯編的。 因為它沒有對應任何可能的 Java 源代碼,所以也就沒有任何意義。
但為什么這一個小小的障礙就能導致 Mocha 崩潰呢? Mocha 可以只是簡單地忽略它,或發一條警告信息并繼續下去。盡管 Mocha 對于此類炸彈的脆弱性可以被認為是一個程序錯誤,但更有可能的是 van Vliet 為了回應對 Mocha 的攻擊而故意設置的。
到此為止,我們已經了解了較老的反匯編工具和模糊處理工具 -- 雖然有點過時,但還是比較出色的。但是,類似工具在這幾年已經變得更加成熟,尤其在圖形界面方面更是如此。在本文的最后,我們看一下一個較新的反匯編器,僅僅讓您有個大致的概念。
在過去的五年中,不僅僅是反匯編和模糊處理的技術越來越復雜,而且這些工具的界面也更加華麗。在最近出現的反匯編器中,有幾個能讓您瀏覽 .class 文件的目錄并且只要單擊一下,就能對它們進行反匯編。
JODE (Java 優化和反編譯環境)就是這樣一個程序。在命令行中鍵入 .jar 文件的名稱, JODE 就會允許您圖形化地瀏覽它的類,并自動反匯編每個類以讓您查看。這特別有助于通過 Java SDK 提供的庫來查找源代碼。簡單地鍵入以下命令:
$ java jode.swingui.Main --classpath [path to your Java SDK]/jre/lib/rt.jar |
您就會得到如圖 1 所示的對文件的完整翻譯。
請參閱 參考資料,獲取更有用的工具的清單。
無論選擇使用象 Mocha 或 HoseMocha 這樣的經典工具,還是樂于親自研究一下更新的工具,您都應把這篇文章作為您學習 Java 反匯編和模糊處理的起點。在此,請瀏覽一下在 參考資料中所提供的許多鏈接,試著使用其中的一些工具,并準備以后不斷磨練自己的技術。盡管有許多爭議,反匯編和模糊處理的技術如今依然存在,并且在今后的幾年中只會變得更加成熟和完善。
- 您可以參閱本文在 developerWorks 全球站點上的 英文原文.
- 雖然 Mocha已經過時,但使用起來要比較有趣,而且偶爾會有一些用處。
- 正如在它之前的 Crema, HoseMocha制造一個阻止 Mocha 工作的炸彈。
- Borland 的 JBuilder據說是基于原來 Crema 的代碼。
- 請查看 jmangle。
- SourceForge 如今擁有 JODE,它可以在 GPL 下獲得。
- WingDis是另一個流行的商業反編譯器。
- Blackdown 列出了許多在 Linux 上的 Java 開發工具,包括 JAD、它被認為是“最快的 Java 反匯編器”。
- Zelix KlassMaster是一個作為模糊處理器工作的商業類文件查看工具。
- Marc Meurrens 的 Java 代碼工程是一個出色的網站,涵蓋了匯編器、反匯編器、模糊處理器及相關信息。
- 反匯編和反匯編器是另一個包羅萬象的頁面,有許多軟件和研究論文的有用鏈接。
-
Cristina Cifuentes維護著
反匯編頁面,有許多有關反匯編的理論和實際工作的信息,包括一個名為
dcc的 C 反匯編器。 - 已故的 Maurice Halstead 被許多人認為是反匯編之父。請閱讀有關他從 1960 年至 1976 年領導的 反匯編項目的信息。
- 請參閱“ Java 反編譯器比較”(JavaWorld,1997 年 7 月),這里有對 Java 反匯編器的大范圍回顧。
- 請閱讀 Hanpeter van Vliet 的 原始反匯編器宣言(Web Techniques,1997 年 9 月)。
- IBM 蘇黎世研究實驗室在安全性和 Java 加密技術上投入了大量資源。
- 請閱讀 IBM 是如何研究 Java 安全性和分布式目標系統。
- 在 “ 讓您的軟件運行:模糊安全性” (developerWorks,2000 年 10 月)中,作者 Gary McGraw 和 John Viega 討論了試圖在運行軟件時實現保密。
- 請不要錯過 developerWorks 在這重要領域的 安全性專題。
- McGraw 和 Felten 的經典著作 Securing Java, 2nd Edition (John Wiley & Sons, 1999)的第 6 章有關于 Java 反匯編器的更多信息。
|
Greg Travis 是一名居住在紐約的自由程序員。他對計算機的興趣可以追溯到 "The Bionic Woman" 中的這樣一段情節,Jamie 試圖逃離一幢其燈光和門都被邪惡的人工智能所控制的大樓,而且人工智能還通過擴音器嘲弄她。Greg 堅定地認為當計算機程序工作時,它是完全一致的。可通過 mito@panix.com聯系 Greg。 |
||

浙公網安備 33010602011771號