Java程序版權保護解決方案


      通常C、C++等編程語言開發的程序都被編譯成目標代碼,這些目標代碼都是本機器的二進制可執行代碼。通常所有的源文件被編譯、鏈接成一個可執行文件。在這些可執行文件中,編譯器刪除了程序中的變量名稱、方法名稱等信息,這些信息往往是由內存地址表示,例如如果需要使用一個變量,往往是通過這個變量的地址來訪問的。因此,反編譯這些本地的目標代碼就是非常困難的。  


  Java語言的出現,使得反編譯變得非常容易而有效。原因如下:1.由於跨平台的需求,Java的指令集比較簡單而通用,較容易得出程序的語義信息;2.Java編譯器將每一個類編譯成一個單獨的文件,這也簡化了反編譯的工作;3.Java 的Class文件中,仍然保留所有的方法名稱、變量名稱,並且通過這些名稱來訪問變量和方法,這些符號往往帶有許多語義信息。由於Java程序自身的特點,對於不經過處理的Java程序反編譯的效果非常好。

  目前,市場上有許多Java的反編譯工具,有免費的,也有商業使用的,還有的是開放源代碼的。這些工具的反編譯速度和效果都非常不錯。好的反編譯軟件,能夠反編譯出非常接近源代碼的程序。因此,通過反編譯器,黑客能夠對這些程序進行更改,或者復用其中的程序。因此,如何保護Java程序不被反編譯,是非常重要的一個問題。

  一、常用的保護技術

  由於Java字節碼的抽象級別較高,因此它們較容易被反編譯。本節介紹了幾種常用的方法,用於保護Java字節碼不被反編譯。通常,這些方法不能夠絕對防止程序被反編譯,而是加大反編譯的難度而已,因為這些方法都有自己的使用環境和弱點。

  1、隔離Java程序

  最簡單的方法就是讓用戶不能夠訪問到Java Class程序,這種方法是最根本的方法,具體實現有多種方式。例如,開發人員可以將關鍵的Java Class放在服務器端,客戶端通過訪問服務器的相關接口來獲得服務,而不是直接訪問Class文件。這樣黑客就沒有辦法反編譯Class文件。目前,通過接口提供服務的標准和協議也越來越多,例如 HTTP、Web Service、RPC等。但是有很多應用都不適合這種保護方式,例如對於單機運行的程序就無法隔離Java程序。這種保護方式見圖1所示。



  圖1隔離Java程序示意圖

   

    我們的應用程序HRMS,實際上就是這樣的一種B/S結構的應用程序,最終的用戶只能通過瀏覽器瀏覽JSP頁面,在JSP頁面中完成所有的業務功能,並且我們沒有使用Applet 這種在客戶斷運行的程序,最終的用戶更本沒有機會接觸到處理JSP頁面請求的后台servlet的 class文件,也就保護了我們的程序不被反編譯。


  2、對Class文件進行加密

  為了防止Class文件被直接反編譯,許多開發人員將一些關鍵的Class文件進行加密,例如對注冊碼、序列號管理相關的類等。在使用這些被加密的類之前,程序首先需要對這些類進行解密,而后再將這些類裝載到JVM當中。這些類的解密可以由硬件完成,也可以使用軟件完成。

  在實現時,開發人員往往通過自定義ClassLoader類來完成加密類的裝載(注意由於安全性的原因,Applet不能夠支持自定義的ClassLoader)。自定義的ClassLoader首先找到加密的類,而后進行解密,最后將解密后的類裝載到JVM當中。在這種保護方式中,自定義的ClassLoader是非常關鍵的類。由於它本身不是被加密的,因此它可能成為黑客最先攻擊的目標。如果相關的解密密鑰和算法被攻克,那么被加密的類也很容易被解密。這種保護方式示意圖見圖2。



  圖2 對Class文件進行加密示意圖

 

但是這種方法需要首先需要對編寫代碼對類文件進行加密,然后自己編寫類加載器來,在往JAVA虛擬機導入class文件的同時進行class文件的解密。而且這種方適合有一個入口的簡單應用程序,即Main[]函數,在這個入口的地方依次找到所有加載的類文件,以及這個類所有依賴的類,依次加載;我們的應用程序功能是由很多個servlet共同完成,可能不止一個入口,這樣也會給編寫類加載器帶來很大的困難;



  3、轉換成本地代碼

  將程序轉換成本地代碼也是一種防止反編譯的有效方法。因為本地代碼往往難以被反編譯。開發人員可以選擇將整個應用程序轉換成本地代碼,也可以選擇關鍵模塊轉換。如果僅僅轉換關鍵部分模塊,Java程序在使用這些模塊時,需要使用JNI技術進行調用。

  當然,在使用這種技術保護Java程序的同時,也犧牲了Java的跨平台特性。對於不同的平台,我們需要維護不同版本的本地代碼,這將加重軟件支持和維護的工作。不過對於一些關鍵的模塊,有時這種方案往往是必要的。

  為了保證這些本地代碼不被修改和替代,通常需要對這些代碼進行數字簽名。在使用這些本地代碼之前,往往需要對這些本地代碼進行認證,確保這些代碼沒有被黑客更改。如果簽名檢查通過,則調用相關JNI方法。這種保護方式示意圖見圖3。

 

 

  圖3 轉換成本地代碼示意圖

  4、代碼混淆

  代碼混淆是對Class文件進行重新組織和處理,使得處理后的代碼與處理前代碼完成相同的功能(語義)。但是混淆后的代碼很難被反編譯,即反編譯后得出的代碼是非常難懂、晦澀的,因此反編譯人員很難得出程序的真正語義。從理論上來說,黑客如果有足夠的時間,被混淆的代碼仍然可能被破解,甚至目前有些人正在研制反混淆的工具。但是從實際情況來看,由於混淆技術的多元化發展,混淆理論的成熟,經過混淆的Java代碼還是能夠很好地防止反編譯。下面我們會詳細介紹混淆技術,因為混淆是一種保護Java程序的重要技術。圖4是代碼混淆的示意圖。


  圖4 代碼混淆示意圖

  幾種技術的總結

  以上幾種技術都有不同的應用環境,各自都有自己的弱點,表1是相關特點的比較。

 

  5、混淆技術介紹

到目前為止,對於Java程序的保護,混淆技術還是最基本的保護方法。Java混淆工具也非常多,包括商業的、免費的、開放源代碼的。Sun公司也提供了自己的混淆工具。它們大多都是對Class文件進行混淆處理,也有少量工具首先對源代碼進行處理,然后再對Class進行處理,這樣加大了混淆處理的力度。目前,商業上比較成功的混淆工具包括JProof公司的1stBarrier系列、Eastridge公司的JShrink和4thpass.com的SourceGuard等。主要的混淆技術按照混淆目標可以進行如下分類,它們分別為符號混淆(Lexical Obfuscation)、數據混淆(Data Obfuscation)、控制混淆(Control Obfuscation)、預防性混淆(Prevent Transformation)。

  符號混淆

  在Class中存在許多與程序執行本身無關的信息,例如方法名稱、變量名稱,這些符號的名稱往往帶有一定的含義。例如某個方法名為getKeyLength(),那么這個方法很可能就是用來返回Key的長度。符號混淆就是將這些信息打亂,把這些信息變成無任何意義的表示,例如將所有的變量從vairant_001開始編號;對於所有的方法從method_001開始編號。這將對反編譯帶來一定的困難。對於私有函數、局部變量,通常可以改變它們的符號,而不影響程序的運行。但是對於一些接口名稱、公有函數、成員變量,如果有其它外部模塊需要引用這些符號,我們往往需要保留這些名稱,否則外部模塊找不到這些名稱的方法和變量。因此,多數的混淆工具對於符號混淆,都提供了豐富的選項,讓用戶選擇是否、如何進行符號混淆。

  數據混淆

  圖5 改變數據訪問

  數據混淆是對程序使用的數據進行混淆。混淆的方法也有多種,主要可以分為改變數據存儲及編碼(Store and Encode Transform)、改變數據訪問(Access Transform)。

  改變數據存儲和編碼可以打亂程序使用的數據存儲方式。例如將一個有10個成員的數組,拆開為10個變量,並且打亂這些變量的名字;將一個兩維數組轉化為一個一維數組等。對於一些復雜的數據結構,我們將打亂它的數據結構,例如用多個類代替一個復雜的類等。

  另外一種方式是改變數據訪問。例如訪問數組的下標時,我們可以進行一定的計算,圖5就是一個例子。

  在實踐混淆處理中,這兩種方法通常是綜合使用的,在打亂數據存儲的同時,也打亂數據訪問的方式。經過對數據混淆,程序的語義變得復雜了,這樣增大了反編譯的難度。

  控制混淆

  控制混淆就是對程序的控制流進行混淆,使得程序的控制流更加難以反編譯,通常控制流的改變需要增加一些額外的計算和控制流,因此在性能上會給程序帶來一定的負面影響。有時,需要在程序的性能和混淆程度之間進行權衡。控制混淆的技術最為復雜,技巧也最多。這些技術可以分為如下幾類:

  增加混淆控制 通過增加額外的、復雜的控制流,可以將程序原來的語義隱藏起來。例如,對於按次序執行的兩個語句A、B,我們可以增加一個控制條件,以決定B的執行。通過這種方式加大反匯編的難度。但是所有的干擾控制都不應該影響B的執行。圖6就給出三種方式,為這個例子增加混淆控制。



  圖6 增加混淆控制的三種方式

  控制流重組 重組控制流也是重要的混淆方法。例如,程序調用一個方法,在混淆后,可以將該方法代碼嵌入到調用程序當中。反過來,程序中的一段代碼也可以轉變為一個函數調用。另外,對於一個循環的控制流,為可以拆分多個循環的控制流,或者將循環轉化成一個遞歸過程。這種方法最為復雜,研究的人員也非常多。

  預防性混淆

  這種混淆通常是針對一些專用的反編譯器而設計的,一般來說,這些技術利用反編譯器的弱點或者Bug來設計混淆方案。例如,有些反編譯器對於Return后面的指令不進行反編譯,而有些混淆方案恰恰將代碼放在Return語句后面。這種混淆的有效性對於不同反編譯器的作用也不太相同的。一個好的混淆工具,通常會綜合使用這些混淆技術。

6.我們所采用的混淆工具retroGuard

RetroGuard v1.1是集成在Jbuilder9中的一個混淆工具,在Tools\Configue Obfuscators中可以到看默認的混淆器設置;

要在工程中使用RetroGuard的步驟:

1) 設置工程屬性:

Project\Project Properties下,屬性頁面如下;

在Build選項卡中,設置混淆,選中Obfuscate復選框;

 

2) 重啟工程;

設置混淆之后,需要重新啟動工程;

 

3) 編譯工程

重啟工程之后,執行編譯,編譯生成的class文件就是已經經過混淆的class 文件了。

 

RetreGuard采用的混淆技術主要是符號混淆,把類文件中的主要方法名和類名混淆,消除代碼的語義;

下面可以對比混淆的效果:

沒有經過混淆的class文件經過反編譯軟件反編譯結果如下(截取的代碼片段):

if(request.getParameter("requestor").equals("QueryResultJsp1_1"))

            {

                if(request.getParameter("url") != null && request.getParameter("url").equals("UP"))

                    viewurl = pageUp(request, response);

                else

                if(request.getParameter("url") != null && request.getParameter("url").equals("DOWN"))

                    viewurl = pageDown(request, response);

                else

                if(request.getParameter("url") != null && request.getParameter("url").equals("FIRST"))

                    viewurl = firstPage(request, response);

                else

                if(request.getParameter("url") != null && request.getParameter("url").equals("LAST"))

                    viewurl = lastPage(request, response);

} else

可以看出,沒有經過混淆的class文件,被反編譯之后,是很容易理解的;

經過混淆之后,同樣的一段代碼片段的反編譯結果如下:

if(request.getParameter("requestor").equals("QueryResultJsp1_1"))

            {

                if(request.getParameter("url") != null && request.getParameter("url").equals("UP"))

                    viewurl = _$4866(request, response);

                else

                if(request.getParameter("url") != null && request.getParameter("url").equals("DOWN"))

                    viewurl = _$4867(request, response);

                else

                if(request.getParameter("url") != null && request.getParameter("url").equals("FIRST"))

                    viewurl = _$4868(request, response);

                else

                if(request.getParameter("url") != null && request.getParameter("url").equals("LAST"))

                    viewurl = _$4869(request, response);

            } else

可以看出,代碼中所調用的方法名稱都經過了混淆處理,反編譯之后,依然看不出代碼的語義。

但是,如果有人專門進行反編譯工作,只要有足夠的時間,經過如此混淆處理的class文件,還是可以被完全解讀的。

7.另外一種混淆工具joc

joc是網絡上流行的一種JAVA代碼混淆工具,該工具主要是一個jar包。

這個工具不能集成到JBUILDER中,使用比較麻煩;

使用這個工具的步驟:

1)下載joc.jar包;

2)配置系統的環境變量:

在控制面板\系統\高級\環境變量下,設置變量classPath,增加所有所編譯代碼所需要的類文件的路徑;

D:\IBM\WebSphere MQ\Java\lib\providerutil.jar;D:\IBM\WebSphere MQ\Java\lib\com.ibm.mqjms.jar;D:\IBM\WebSphere MQ\Java\lib\ldap.jar;D:\IBM\WebSphere MQ\Java\lib\jta.jar;D:\IBM\WebSphere MQ\Java\lib\jndi.jar;D:\IBM\WebSphere MQ\Java\lib\jms.jar;D:\IBM\WebSphere MQ\Java\lib\connector.jar;D:\IBM\WebSphere MQ\Java\lib\fscontext.jar;D:\IBM\WebSphere MQ\Java\lib\com.ibm.mq.jar;d:\jbuilder9\jdk1.4\lib\tools.jar;d:\jbuilder9\jdk1.4\lib\dt.jar;D:\work2004\lib\dbaccess.jar;D:\work2004\lib\hrexception.jar;D:\work2004\lib\jspmart.jar;D:\work2004\lib\jxl.jar;D:\work2004\lib\log4j-1.2.8.jar;D:\work2004\lib\orgtree.jar;D:\work2004\lib\pagetailor.jar;D:\work2004\lib\salary.jar;D:\work2004\lib\taglib.jar;D:\work2004\lib\util.jar;D:\WebSphere\AppServer\lib\ecutils.jar;D:\WebSphere\AppServer\lib\j2ee.jar;D:\WebSphere\AppServer\lib\naming.jar;D:\WebSphere\AppServer\lib\namingclient.jar;D:\WebSphere\AppServer\lib\properties;D:\JBuilder9\lib\mail.jar;

3)使用命令行方式,編譯所要編譯的類;

    運行java -jar joc.jar就可以啟動Java混淆編譯器,joc的命令行參數和javac完全相同,但增加了一個新的參數-Xobfuscate,它的用法如下:

       -Xobfuscate:<level>

其中<level>指定混淆級別,可以是以下幾種級別:

-Xobfuscate:none        不進行混淆

-Xobfuscate:private     對所有private訪問級別的元素進行混淆

-Xobfuscate:package     對所有private或package private元素進行混淆

-Xobfuscate:protected   對所有private, package private, protected元素進行混淆

-Xobfuscate:public      對所有的元素都進行混淆

-Xobfuscate:all         相當於-Xobfuscate:public

如果使用-Xobfuscate不帶級別參數,則相當於-Xobfuscate:package;

 

除了在命令行用-Xobfuscate參數控制符號混淆級別外,還可以在源代碼中使用符號保留指令來控制那些符號需要保留,符號保留指令是一個Java文檔注釋指令,可以插入在類和類成員的文檔注釋中,例如:

        /**
        * This class should preserve.
        * @preserve
        */
       public class Foo {
           /**
            * You can specify which field should be preserved.
            * @preserve
            */
           private int x;
       }

如果沒有@preserve指令,則根據混淆級別及成員的訪問級別來確定符號是否保留。

 

對於類的符號保留指令可以附帶一個保留級別參數,來控制類成員的符號保留,包括:

       @preserve            僅對類名進行保留,類成員的保留根據-Xobfuscate命令行參數決定

       @preserve public     保留所有public成員

       @preserve protected  保留所有public和protected成員

       @preserve package    保留所有public, protected, package private成員

       @preserve private    保留所有成員

@preserve all        相當於@preserve private

 

經過這個混淆器混淆之后的類文件的混淆效果和RetroGuard的混淆效果差不多。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM