如何封鎖您的(或開啟別人的) Java 代碼

來源:互聯網
上載者:User

無論是修改許多網上源碼庫中的代碼,還是調用常見的作業系統例行程式,您免不了要花一些時間去琢磨您沒有編寫過的代碼,而且您還
可能沒有這些代碼的源檔案。在開始調試代碼時,您需要有一個好的 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 所示)。


清單 1. 一個類檔案的原始內容

0000000 feca beba 0300 2d00 4200 0008 081f 3400
0000020 0008 073f 2c00 0007 0735 3600 0007 0737
0000040 3800 0007 0a39 0400 1500 000a 0007 0a15
0000060 0800 1600 000a 0008 0a17 0800 1800 0009
...

清單 2. javap 的輸出結果

Local variables for method void priv(int)
Foo this pc=0, length=35, slot=0
int argument pc=0, length=35, slot=1
Method void main(java.lang.String[])
0 new #4
3 invokespecial #10
6 return

 

請注意,清單 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 的測試程式。


清單 3. Foo.java 的一小部分原始原始碼

     private int member = 10;
public Foo() {
int local = returnInteger();
System.out.println( "foo constructor" );
priv( local );
}

 

以下是 Mocha 產生的程式碼


清單 4. Mocha 產生的 Foo.java 的原始碼

       private int member;
public Foo()
{
member = 10;
int local = returnInteger();
System.out.println("foo constructor");
priv(local);
}

 

這兩個程式碼片段的成員變數
member
被初始化為 10
的位置不同。在原始原始碼中,它在與聲明的同一行中被表示為一個初始值,而在被反編譯後的原始碼中,它在一個構造符中被表示為一條指派陳述式。反編譯後的代
碼告訴我們一些有關原始碼被編譯的方法;即它的初始值是作為在構造符中的賦值來被編譯的。通過觀察其反編譯後的輸出結果,您能瞭解到不少 Java
編譯器的工作方法。

反編譯是困難的:不斷重複

雖然 Mocha
的確可以反組譯碼您的目標代碼,但它不會總是成功的。由於困難重重,沒有一個反編譯器能夠準確無誤地翻譯出原始碼,而且每個反編譯器處理它們在翻譯過程中的
漏洞的方式也不同。舉例來說,Mocha 有時在輸出準確的迴圈構造的結構方面有一些問題。如果真的這樣,它會在最終輸出中使用偽 goto 語句,如清單 5 所示。


清單 5. Mocha 不能準確地反編譯

           if (i1 == i3) goto 214 else 138;
j3 = getSegment(i3).getZOrder();
if (j1 != 1) goto 177 else 154;
if (j3 > k2 && (!k1 || j3 < j2)) goto 203 else 173;
expression 0
if (j3 < k2 && (!k1 || j3 > j2)) goto 203 else 196;
expression 0
if == goto 201
continue;
i2 = i3;

 

撇開 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 代碼後的結果。請注意以下的一小段程式和我們在前面清單中使用的是相同的,儘管乍一看,您肯定不會這麼認為。


清單 6. 經過 jmangle 模糊處理的代碼

       public Foo()
{
jm2 = 10;
int i = jm0();
System.out.println("foo constructor");
jm1(i);
}

 

象 jmangle 這樣的模糊處理器把許多變數名和方法名(有時甚至是類名和包的名稱)轉換成沒有意義的字串。這樣就使得人們難以閱讀程式,但對於 JVM 來說,其在本質上和原來的程式是一樣的。

變得卑鄙

所有的模糊處理器都要使標記變得沒有意義,但他們所做的不僅僅是這些。Crema 之所以臭名昭著是因為它用了許多卑鄙的手段來阻止反組譯碼,並且有許多在已經出現的模糊處理器中,紛紛仿效它。

一種常用的模糊處理代碼的方法是用一個非法的字串來替代類檔案中的標記,這比使用沒有意義的字串更進了一步。替代的有可能是一個關鍵字,例如
private ,或者甚至是象
*** 這樣沒有意義的標記。一些虛擬機器 -- 尤其在瀏覽器中 -- 對這些古怪的用法不會作出合法的反應。從技術上說,一個象
= 這樣的變數與 Java 的規範是相反的;一些虛擬機器可以忽略它,而另一些不可以這樣。

Crema 放置炸彈

按字面意思,Crema 使用的另一個計策就是炸彈。Crema 具有完全關閉 Mocha 的能力。它在編譯後的代碼中添加一個小“炸彈”,導致 Mocha 在試圖反編譯代碼時崩潰。

可惜,Crema 已經沒有了,但有一種名為 HoseMocha 的工具是專門為關閉 Mocha 而設計的。為了瞭解 HoseMocha 是如何工作的,我們將使用 javap,這個值得信賴的反組譯碼器。清單 7 所示的是 HoseMocha 放置炸彈前的代碼。


清單 7. 放置炸彈前的代碼

Method void main(java.lang.String[])
0 new #4
3 invokespecial #10
6 return

 

以下是 HoseMocha 處理後的代碼。


清單 8. 放置炸彈後的代碼

Method void main(java.lang.String[])
0 new #4
3 invokespecial #10
6 return
7 pop

 

您看到那顆炸彈嗎?請注意現在這個程式在返回後面有
一條 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 所示的對檔案的完整翻譯。

圖 1. JODE: 一種反組譯碼器

請參閱
參考資料,擷取更有用的工具的清單。





結論

無論選擇使用象 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。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.