一、前言
1.1 什么是Jocky?
我們知道,Java是一種跨平臺(tái)的編程語言,其源碼(.java文件)被編譯成與平臺(tái)無關(guān)的字節(jié)碼(.class文件),然后在運(yùn)行期動(dòng)態(tài)鏈接。這樣,編譯后的類文件中將包含有符號(hào)表,從而使得Java程序很容易被反編譯。相信每一個(gè)Java開發(fā)人員,都曾經(jīng)用過諸如Jad之類的反編譯器,對(duì)Java的class 文件進(jìn)行反編譯,從而觀察程序的結(jié)構(gòu)與實(shí)現(xiàn)細(xì)節(jié)。如此一來,對(duì)于那些需要嚴(yán)格進(jìn)行知識(shí)產(chǎn)權(quán)保護(hù)的Java應(yīng)用,如何有效的保護(hù)客戶的商業(yè)投資,是開發(fā)人員經(jīng)常需要面對(duì)的問題。于是就出現(xiàn)了Java混淆編譯器,它的作用是打亂class文件中的符號(hào)信息,從而使反向工程變得非常困難。
Jocky就是這樣一款優(yōu)秀的Java混淆編譯器。
1.2 為什么需要Jocky?
目前業(yè)界有不少商業(yè)的甚或是開源的混淆編譯器,但它們普遍存在一些這樣或者那樣的問題。一般而言,現(xiàn)有的混淆器都是對(duì)編譯好的 class文件進(jìn)行混淆,這樣就需要編譯和混淆兩個(gè)步驟。而事實(shí)上,并不是所有的符號(hào)都需要混淆。如果你開發(fā)的是一個(gè)類庫,或者某些類需要?jiǎng)討B(tài)裝載,那些公共API(或者說:那些被publish出來的API)就必須保留符號(hào)不變,只有這樣,別人才能使用你的類庫。現(xiàn)有的混淆器提供了GUI或腳本的方式來對(duì)那些需要保留的符號(hào)名稱進(jìn)行配置,但如果程序較大時(shí),配置工作將變得很復(fù)雜,而程序一旦修改,配置工作又要重新進(jìn)行。某些混淆器能夠調(diào)整字節(jié)碼的順序,使反編譯更加困難,但筆者經(jīng)歷過混淆之后的程序運(yùn)行出錯(cuò)的情況。而Jocky與其它混淆編譯器最大的不同之處在于:它是直接從源碼上做文章,也就是說編譯過程本身就是一個(gè)混淆過程。
1.3 Jocky是如何工作的?
Jocky混淆編譯器是在Sun JDK中提供的Java編譯器(javac)的基礎(chǔ)上完成的,修改了其中的代碼生成過程,對(duì)編譯器生成的中間代碼進(jìn)行混淆,最后再生成class文件,這樣編譯和混淆只需要一個(gè)步驟就可以完成。另外可以在源程序中插入 符號(hào)保留指令 來控制哪些符號(hào)需要保留,將混淆過程與開發(fā)過程融合在一起,不需要單獨(dú)的配置。1.4 Jocky的作用
1.4.1代碼混淆
如前文所述,混淆編譯是Jocky的首要用途。我們舉一個(gè)最簡單的例子,下面的SimpleBean是未經(jīng)混淆的class文件通過Jad反編譯以后獲得的源文件:
1
![]() ![]() ![]() 2 ![]() 3 ![]() 4 ![]() 5 ![]() 6 ![]() 7 ![]() ![]() ![]() 8 ![]() 9 ![]() 10 ![]() 11 ![]() ![]() ![]() 12 ![]() 13 ![]() 14 ![]() 15 ![]() ![]() ![]() 16 ![]() 17 ![]() 18 ![]() 19 ![]() ![]() ![]() 20 ![]() 21 ![]() 22 ![]() 23 ![]() |
<未混淆的類文件反編譯后的效果> |
下面是經(jīng)Jocky混淆過的類文件,通過Jad反編譯后產(chǎn)生的源文件:
1
![]() ![]() ![]() 2 ![]() 3 ![]() 4 ![]() 5 ![]() 6 ![]() 7 ![]() ![]() ![]() 8 ![]() 9 ![]() 10 ![]() 11 ![]() 12 ![]() 13 ![]() 14 ![]() 15 ![]() 16 ![]() 17 ![]() ![]() ![]() 18 ![]() 19 ![]() 20 ![]() 21 ![]() ![]() ![]() 22 ![]() 23 ![]() 24 ![]() ![]() ![]() 25 ![]() ![]() ![]() 26 ![]() 27 ![]() |
<Jocky混淆過的類文件反編譯的效果> |
1.4.2 支持將JDK 5.0的語法編譯成能夠在JDK 1.4上運(yùn)行的類文件
JDK 5.0在語法層面上有許多新增特色,能夠?yàn)楹喕瘧?yīng)用的開發(fā)帶來一些便利。譬如Generics、Enhanced for Loop以及 Autoboxing/Unboxing等。但另人遺憾的是,倘若利用這些新的語法開發(fā)應(yīng)用,就意味著不能夠在JDK 1.4上運(yùn)行,而JDK 1.4畢竟是目前最為普及的VM版本。幸運(yùn)是,Jocky的另一個(gè)特色就是:通過參數(shù)配置,能夠把用JDK 5.0語法編寫的應(yīng)用編譯成JDK 1.4上的類文件版本。我們可以把經(jīng)過 Jocky編譯的類文件以UltraEdit打開,可以發(fā)現(xiàn)在第8個(gè)字節(jié)上(類文件的major version)的數(shù)值是0x30,即十進(jìn)制的48,這是JDK 1.4所能夠理解的類文件版本(JDK 5.0默認(rèn)編譯的類文件版本是49)。前提是:應(yīng)用中不能夠使用JDK 1.4中所沒有的一些API。二、Jocky的用法
2.1 常規(guī)用法
使用Jocky非常簡單,獲得jocky.jar以后,只需要運(yùn)行java -jar jocky.jar就可以啟動(dòng)Jocky混淆編譯器,jocky的命令行參數(shù)和javac完全相同,但增加了一個(gè)新的參數(shù)-scramble,它的用法如下:-scramble 混淆所有package private或private符號(hào)
-scrambleall 混淆所有符號(hào)
-scramble:<level> 混淆相應(yīng)級(jí)別的符號(hào)
其中<level>指定混淆級(jí)別,可以是以下幾種級(jí)別:
-scramble:none 不進(jìn)行混淆
-scramble:private 對(duì)所有private訪問級(jí)別的元素進(jìn)行混淆
-scramble:package 對(duì)所有private或package private元素進(jìn)行混淆
-scramble:protected 對(duì)所有private, package private, protected元素進(jìn)行混淆
-scramble:public 對(duì)所有的元素都進(jìn)行混淆
-scramble:all 相當(dāng)于-scramble:public
如果使用-scramble不帶級(jí)別參數(shù),則相當(dāng)于-scramble:package
2.2 Jocky for Ant
近年來,Ant已經(jīng)成為Java應(yīng)用開發(fā)中打包工具的事實(shí)上的標(biāo)準(zhǔn)。在應(yīng)用的開發(fā)過程中,我們往往都會(huì)有一個(gè)Ant腳本,通過該腳本,能夠?qū)?yīng)用進(jìn)行編譯、打包、發(fā)布等一系列過程。因此,Jocky的最佳切入點(diǎn)便是對(duì)Ant的支持。在Ant中使用Jocky非常簡單:
1. 將lib\jocky-ant.jar copy至ANT_HOME\lib目錄下。
2. 在ant腳本中加入這樣一行代碼,以引入Jocky Task

3. 設(shè)置Jocky的一些基本屬性,包括: jocky.jar包的位置,以及混淆級(jí)別,如下所示:

4. 當(dāng)設(shè)置jocky的enable屬性為true時(shí),此時(shí),Ant腳本中的javac編譯命令,便會(huì)被自動(dòng)替換成Jocky編譯器;當(dāng)設(shè)置enable屬性為false時(shí),javac編譯命令將恢復(fù)成正常設(shè)置,示例腳本如下:
1
![]() 2 ![]() 3 ![]() 4 ![]() 5 ![]() 6 ![]() 7 ![]() 8 ![]() 9 ![]() 10 ![]() 11 ![]() 12 ![]() 13 ![]() 14 ![]() 15 ![]() 16 ![]() 17 ![]() 18 ![]() |
<Jocky的Ant腳本示例> |
注意: Jocky for Ant在Ant 1.6.5上開發(fā),推薦使用該版本。
2.3 Jocky for Eclipse
Jocky提供了Eclipse的插件,從而能夠直接在Eclipse中使用Jocky。1. Jocky插件的安裝:
將Jocky插件安裝至Eclipse中非常簡單,只需要將eclipse/plugins/org.apusic.jocky_1.0.0目錄 copy 至 Eclipse的 plugins目錄即可。或者在Eclipse/links文件夾中,通過link方式指定Jocky的插件目錄。
2. 在Eclipse中使用Jocky:
在Eclipse中使用Jocky也非常簡單,任何一個(gè)Java工程,選中工程通過右鍵菜單,都可以出現(xiàn)Jocky的快捷菜單:
![]() |
<Jocky在Eclipse中的右鍵菜單> |
![]() |
<Jocky在Eclipse中的屬性設(shè)置> |
事實(shí)上,在Eclipse中使用Jocky時(shí),Jocky也是首先針對(duì)所選工程生成Ant的Build文件(默認(rèn)名稱jocky_build.xml),然后再通過Ant完成混淆編譯。
以下是Jocky在Eclipse中自動(dòng)生成的Ant Build 文件示例:
1
![]() 2 ![]() 3 ![]() 4 ![]() 5 ![]() 6 ![]() 7 ![]() 8 ![]() 9 ![]() 10 ![]() 11 ![]() 12 ![]() 13 ![]() 14 ![]() 15 ![]() 16 ![]() 17 ![]() 18 ![]() 19 ![]() 20 ![]() 21 ![]() 22 ![]() 23 ![]() 24 ![]() 25 ![]() 26 ![]() |
<Jocky在Eclipse中自動(dòng)生成的Ant腳本示例> |
注1:只支持Eclipse 3.1.1及以上版本。
注2:如果在Eclipse中找不到Jocky插件,請(qǐng)刪除 Eclipse安裝目錄/configuration/org.eclipse.update 文件夾 (Maybe an eclipse bug?)。
2.4 如何使用符號(hào)保留指令
除了在命令行用 -scramble 參數(shù)控制符號(hào)混淆級(jí)別外,還可以在源代碼中使用符號(hào)保留指令來控制那些符號(hào)需要保留。符號(hào)保留指令是一個(gè)Java文檔注釋指令,可以插入在類和類成員的文檔注釋中,例如:
1
![]() 2 ![]() 3 ![]() 4 ![]() 5 ![]() 6 ![]() 7 ![]() 8 ![]() 9 ![]() 10 ![]() 11 ![]() 12 ![]() 13 ![]() 14 ![]() 15 ![]() 16 ![]() 17 ![]() 18 ![]() 19 ![]() 20 ![]() 21 ![]() 22 ![]() 23 ![]() 24 ![]() 25 ![]() 26 ![]() 27 ![]() |
<使用preserved指令的示例> |
如果沒有@preserve指令,則根據(jù)混淆級(jí)別及成員的訪問級(jí)別來確定符號(hào)是否保留。
對(duì)于類的符號(hào)保留指令可以附帶一個(gè)保留級(jí)別參數(shù),來控制類成員的符號(hào)保留,包括:
事實(shí)上,即便不加@preserve指令,Jocky對(duì)Java語言特有的一些private級(jí)別的方法不進(jìn)行混淆,譬如,在序列化時(shí)有特殊作用的writeObject及readObject方法等。但筆者強(qiáng)烈建議: 針對(duì)這些有特殊含義不能夠被混淆的 private級(jí)別的方法或者字段,請(qǐng)以@preserve指令予以保護(hù)。@preserve 僅對(duì)類名進(jìn)行保留,類成員的保留根據(jù)-scramble命令行參數(shù)決定
@preserve public 保留所有public成員
@preserve protected 保留所有public和protected成員
@preserve package 保留所有public, protected, package private成員
@preserve private 保留所有成員
@preserve all 相當(dāng)于@preserve private
注1:建議通過IDE的JavaDoc設(shè)置,來輔助@preserve指令的書寫。