文章出處

從頭到尾保護 JAVA

目前關于 JAVA 程序的加密方式不外乎 JAVA 模糊處理(Obfuscator)和運用 ClassLoader 方法進行加密處理這兩種方式(其他的方式亦有,但大多是這兩種的延伸和變異)。這兩種方式不管給 JAVA 反編譯器造成多少困難, 畢竟還是有跡可尋,有機可乘的。本文介紹的方法是對 ClassLoader 方式加密處理的一種改進,使之達到傳統二進制程序代碼安全。

胡憲利 (samenhu@yahoo.com.cn)中興通訊SoftSwitch產品部

2002 年 6 月 30 日

  • +內容

第一章 流行的加密方式簡介

關于 JAVA 程序的加密方式,一直以來都是以 JAVA 模糊處理(Obfuscator)為主。這方面的研究結果也頗多,既有模糊器(如現在大名鼎鼎的 JODE),也有針對反編譯器的"炸彈"(如針對反編譯工具 Mocha 的 "炸彈" Crema 和 HoseMocha)。模糊器,從其字面上,我們就可以知道它是通過模糊處理 JAVA 代碼,具體的說,就是更換變量名,函數名,甚至類名等方法使其反編譯出來的代碼變得不可理解。舉個例子來說吧。

先將將下面源代碼編譯成 class 文件。

      public class  test 
		{
				 int sortway; 
				 void sort(Vector a) 
				 { 
					……
				 } 
				 void setSortWay(int way) 
				 { 
					……
				 } 
				 void sort(Vector a, int way) 
				 { 
					……
				 } 
}

后通過 JODE 進行模糊處理后,反編譯過來后, 可能變成下列代碼。

     public class  OoOoooOo0Oo0O 
     {
                 int OoOo0oOo0Oo0O; 
                 void OoO0ooOo0Oo0O (Vector OoOoo0Oo0OoOO) 
                 { 
                    ……
                 } 
                 void OoOo00oOoOo0O (int Oo0oooOo0Oo0O) 
                 { 
                    ……
                 } 
                 void OoO0ooOo0Oo0O (Vector OoOoo0Oo0OoOO, int Oo0oooOo0Oo0O)
                 { 
                     OoOo00oOoOo0O (Oo0oooOo0Oo0O); 
                     OoO0ooOo0Oo0O (OoOoo0Oo0OoOO); 
                 } 
     }

其實這只是做到了視覺上的處理,其業務邏輯卻依然不變,加以耐心,仍是可以攻破的,如果用在用戶身份驗證等目的上,完全可以找到身份驗證算法而加以突破限制。

而所謂的"炸彈"是針對反編譯工具本身的缺陷,這種方法對于特定的反編譯工具是非常有效的,然而到目前為止,還沒有一個全能型的,對每一種反編譯工具皆有效,其局限性是明顯的!

另一種方法是采用 ClassLoader 加密。JAVA 虛擬機通過一個稱為 ClassLoader 的對象裝來載類文件的字節碼,而 ClassLoader 是可以由 JAVA 程序自己來定制的。ClassLoader 是如何裝載類的呢? ClassLoader 根據類名在 jar 包中找到該類的文件,讀取文件,并把它轉換成一個 Class 對象。該方法的原理就是,對需加密的類文件我們先行采用一定的方法(可以是 PGP, RSA, MD5 等方法)進行加密處理,我們可以在讀取文件之后,進行解密后,再轉換成一個 Class 對象。

關于 ClassLoader 工作方式的詳細介紹就不在此一一述說了,前面已有文章專題討論了。

有沒有發現,該方法并未解決 ClassLoader 本身的安全性 ? 顯然,只要反編譯了該 ClassLoader 類,就可以順藤摸瓜找到其它的類了。可見 ClassLoader 本身"明碼"方式仍然造成一定的不安全性,然而,如果該方法解決了 ClassLoader 本身的安全性,其不失為一個比較好安全方案。

 

第二章 ClassLoader 加密方式改進

JAVA 程序是通過 java.exe/javaw.exe 來啟動的,要對 ClassLoader 進行解密處理,只能從 java.exe/javaw.exe 身上著手。

我們先來考察一下 JDK 的發布路徑, 發現 JDK 的每一個版本都提供了 src.jar,用 winzip 打開看看, 可以看到一個 launcher 的路徑,里面包含的就是 java.exe/javaw.exe 的程序代碼。哈哈, 這下我們可以隨心所欲了。:-)打開 java.c 看看,里面有一段, 如下:

	 jstring mainClassName = GetMainClassName(env, jarfile); 
	 if ((*env)->ExceptionOccurred(env)) { 
	    (*env)->ExceptionDescribe(env); 
	    goto leave; 
	 } 
	 if (mainClassName == NULL) { 
	    fprintf(stderr, "Failed to load Main-Class manifest attribute "
		    "from\n%s\n", jarfile); 
	    goto leave; 
	 } 
	 classname = (char *)(*env)->GetStringUTFChars(env, mainClassName, 0); 
	 if (classname == NULL) { 
	    (*env)->ExceptionDescribe(env); 
	    goto leave; 
	 } 
	 mainClass = LoadClass(env, classname); 
	 (*env)->ReleaseStringUTFChars(env, mainClassName, classname); 
    } else { 
	 mainClass = LoadClass(env, classname); 
    } 
    if (mainClass == NULL) { 
        (*env)->ExceptionDescribe(env); 
        status = 4; 
 goto leave; 
    }

其中,函數 LoadClass 見下:

 static jclass 
 LoadClass(JNIEnv *env, char *name) 
 { 
    char *buf = MemAlloc(strlen(name) + 1); 
    char *s = buf, *t = name, c; 
    jclass cls; 
    jlong start, end; 
    if (debug) 
	 start = CounterGet(); 
    do { 
        c = *t++; 
	 *s++ = (c == '.') ? '/' : c; 
    } while (c != '\0'); 
    cls = (*env)->FindClass(env, buf); 
    free(buf); 
    if (debug) { 
	 end   = CounterGet(); 
	 printf("%ld micro seconds to load main class\n", 
	       (jint)Counter2Micros(end-start)); 
	 printf("----_JAVA_LAUNCHER_DEBUG----\n"); 
    } 
    return cls; 
 }

分析上面的程序,我們可以看到 env 中的函數 FindClass 根據類名直接得到 mainClass 對象的。如果我們要裝載已加密過的 JAVA 程序, 顯然直接調用 FindClass 函數是不行的,那么,我們有沒有辦法自己讀取文件,然后將之轉換成一個 mainClass 對象呢?

我們來看看 JNIEnv 里面還有什么?打開 JDK 路徑 \include\jni.h, 在里面我們查到下列定義:

 #ifdef __cplusplus 
 typedef JNIEnv_ JNIEnv; 
 #else 
 typedef const struct JNINativeInterface_ *JNIEnv; 
 #endif

而在 JNINativeInterface_ 的定義中:

 struct JNINativeInterface_ { 
    ……
    jclass (JNICALL *DefineClass) 
      (JNIEnv *env, const char *name, jobject loader, const jbyte *buf, 
       jsize len); 
	……
}

對了,DefineClass 就是我們要找的,它可以將一個緩沖區(class 字節碼)轉換成一個類實例!下面就是一個實現如何裝載加密 Class:

	 static jclass 
 LoadClass(JNIEnv *env, char *name) 
 { 
		 FILE *in; 
    long length, i; 
		 char *cc; 
	 int  x; 
    	 char javaloader [MAXPATHLEN], javapath[MAXPATHLEN]; 
    char *buf = MemAlloc(strlen(name) + 1); 
    	 char *s = buf, *t = name, c; 
    jclass cls; 
    	 jlong start, end; 
    if (debug) 
		 start = CounterGet(); 
    do { 
    	    c = *t++; 
	 *s++ = (c == '.') ? '/' : c; 
    	 } while (c != '\0'); 
		 /* 如果裝載的類是 MyLoader*/ 
	 if(strcmp(buf,"MyLoader")==0) { 
		 if (GetApplicationHome(javapath, sizeof(javapath))) 
		 { 
			 sprintf(javaloader, "%s\\MyLoader.class", javapath); 
		 } 
		 if ((in = fopen(javaloader, "rb")) == NULL) 
		 { 
			 fprintf(stderr, "Cannot open input file.\n"); 
			 return (jclass)0x0f; 
		 } 
		 /* 讀出加密的 class 文件 */ 
		 fseek(in, 0L, SEEK_END); 
		 length = ftell(in); 
		 fseek(in, 0, SEEK_SET); 
		
		 cc = MemAlloc(length); 
		 fread((void*)cc,length,1,in); 
		 fclose(in); 
		 /* 解密算法 */ 
		……
		 /* 將解密后的 class 字節碼轉換成 class*/ 
		 cls = (*env)->DefineClass(env, buf, 0, cc, length-1); 
	    free(cc); 
	 } 
		 else 
			 cls = (*env)->FindClass(env, buf); 
    
	 free(buf); 
    	 if (debug) { 
	 end   = CounterGet(); 
		 printf("%ld micro seconds to load main class\n", 
		       (jint)Counter2Micros(end-start)); 
	 printf("----_JAVA_LAUNCHER_DEBUG----\n"); 
    } 
 	   return cls; 
 }
 

第三章 應用范例

在實際應用中,建議新的啟動程序繼續采用 java.exe 的參數調用格式, 即 java [-options] class [args...],這樣的話,一方面程序在開發版本(非加密)和發布版本(加密)時的調用方式就保持一致了,便于別人的理解,另一方面啟動程序的制作也簡單多了,只需改動 java.c 中的 LoadClass 方法了。

下面是一般應用的示意圖:

如果調用的方式是這樣的:class1 調用 class2,而由 class2 調用 class3,其中 class2 有自己定制的 ClassLoader(非 class3 所用的 ClassLoader),則這時應該在 class2 和 class3 之間加一層 interface,由 interface 調用 class3 相應的 ClassLoader 來裝載 class3, 而 interface 本身則不能加密。這種形式的典型應用是 Tomcat 上的 web 應用,Tomcat 裝載 servlet 類時,是采用自己的 ClassLoader 來裝載的, 如果對 servlet 加密,Tomcat 則在裝載 servlet 時不會裝載成功,必須采用 interface 的方式!下面則是其應用示意圖:

 

第四章 應用范圍

由于解密需要一定的時間,如果不加區分的全部進行加密處理,勢必會影響到程序的速度和響應。所以應該在需要加密的地方才加密,比方說,用戶密碼驗證,專利算法,或者是數據庫密碼等等,這樣的才不會導致系統的性能下降。

要達到以上目的, ClassLoader 必須對 class 加以判斷,非加密的 class 調用 JVM 系統 ClassLoader 的 LoadClass 函數, 而對加密的才加以解密處理。建議:ClassLoader 最好可配置!

 

原文:http://www.ibm.com/developerworks/cn/java/l-protectjava/

相關文章:

如何利用DES加密的算法保護Java源代碼

http://security.ctocio.com.cn/tips/42/7728542.shtml


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()