【摘 要】Java 編譯的class文件很容易被反編譯,不利于保護一些核心的代碼。文章分析了class文件的幾種加密方案的優缺點,并提出了安全可靠的class文件加密方案。
【關鍵詞】Java;Classloader;JavaAgent
Java 的class 類文件是Java源代碼文件經過編譯后生成的字節碼文件,類文件有固定的結構,比如前四個字節固定為0xCAFEBABE,接下來四個字節是副版本號和主版本號等等。源代碼中的變量、常量等一切全在結構中,了解的類文件的固定結構,就能輕易地根據class文件生成java源文件。這對項目某個核心的模塊來說,會造成關鍵技術的泄漏。尤其是涉及到版權的license管理,如何對class類進行加密,是公司必須要考慮的問題。針對這個問題,有各種解決方案,有class混淆方案,也有class加密方案。class加密也有不同的加密技巧和策略。
class混淆器就是在不影響代碼執行的條件下,對發布出去的程序進行重新組織和處理,使得處理后的代碼與處理前代碼完成相同的功能。混淆方法主要有外觀混淆、控制混淆、預防混淆、數據混淆。混淆后的代碼很難被反編譯,但不是不能被反編譯,反編譯后的程序雖然可讀性較差,但整理后程序的程序代碼還是可用的,程序邏輯可以修改。如果重寫這個類和方法,就可以直接跳過某個校驗過程以達到某種目的,比如跳過license的驗證或者直接返回許可信息。由于java的ClassLoader可以直接加載這個類,所以修改某個類并直接運行不成問題。安全的方案就是把class直接加密,但加密后的class并不能被ClassLoader直接識別并運行。
由于class文件有固定的結構,ClassLoader將class文件加載到JVM虛擬機,程序就可以運行了。加密后class文件,原來的ClassLoader無法正常解析,要繼承并重寫自己的ClassLoader類和defineClass()方法,defineClass()能將class二進制內容轉換成class對象,類的解密工作也可以在這里進行,也就是在把類字節轉交給JVM運行之前,解密class文件為正常的字節碼文件。當然,這種解密的方法直接暴露在自定義的ClassLoader類中,可以反編譯ClassLoader類來得到解密方法并得到正常的class字節碼,把這些字節碼寫入到文件,再反編譯就能得到Java源文件,所以這種方法并不安全。
對于WEB項目,一般來說WEB容器有自己的加載器,比如TOMCAT的catalina.jar,可以修改兩個文件,org.apache.catalina.loader.WebappClassLoaderBase.class文件中的findResourceInternal方法和 org.apache.catalina.startup.ContextConfig.class文件中的processAnnotationsJar方法、processAnnotationsJndi方法、processAnnotationsFile方法,在調用processAnnotationsStream方法之前解密類文件,把解密后的字節流傳給processAnnotationsStream。這種加密方法比較靈活,可以根據情況只加密解密部分核心的類文件以提高運行效率。當然,這種方法要和具體的TOMCAT版本相結合,如果要使用其它的WEB容器或者不同版本的TOMCAT,都需要修改相應版本的catalina.jar文件,不方便管理。從安全性的角度來看,此種方法和自定義ClassLoader差不多,如果catalina.jar文件和WebappClassLoaderBase.class、ContextConfig.class得不到有效的保護,可以通過修改這兩個文件在得到解密后的class文件后再反編譯得到源文件。
前面兩種方法是用Java來實現解密class文件,本身解密的class能被反編譯,這其實是不安全的,當然,你可以嵌套幾次加密和解密的過程,但這樣操作太過復雜。使用JVMTI編程接口,用C/C++來實現解密過程是個不錯的選擇。JVMTI是JVM Tool Interface,它提供了本地編程接口,利用JDK中JVM的某些類似鉤子機制和事件監聽機制,監聽加載class事件。通過啟動java程序時加參數-agentlib來指定lib庫,例如:java -agentlib:libClassLoader,其中libClassLoader是用C/C++編寫,可以是Windows下的DLL庫,也可以是Linux下的so文件,在libClassLoader中實現對加密class的解密工作。由于C/C++編譯后的庫文件被反編譯基本上是匯編語言,要理解其中的解密邏輯幾乎無望。此外,lib還能被加殼,進一步增加了反編譯的難度。程序只要在JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved)方法中設置ClassFileLoadHook回調方法,并實現自己的ClassFileLoadHook方法即可。實踐證明,這是一個行之有效的運行class解密方案。
此外,加密方法的選擇直接影響了java項目的運行效率。Class加密可以選擇對稱加密,也可以選擇非對稱的加密算法,如RSA,class文件可以完整加密,也可以部分加密。安全其見,建議選擇RSA等非對稱算法,避免密鑰的泄露。由于非對稱加密算法的效率較低,工程中可以結合實際只對類的頭部結構信息加密,可以大大提高運行效率。
作者簡介:王文學(1976-),男,河南人,講師,研究方向:網絡安全。
參考文獻:
[1]《Java虛擬機規范》 Tim Lindholm 機械工業出版社