zoukankan      html  css  js  c++  java
  • 如何有效的保护 JAVA 程序

    从头到尾保护 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
    %s
    ", 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 != ''); 
        cls = (*env)->FindClass(env, buf); 
        free(buf); 
        if (debug) { 
    	 end   = CounterGet(); 
    	 printf("%ld micro seconds to load main class
    ", 
    	       (jint)Counter2Micros(end-start)); 
    	 printf("----_JAVA_LAUNCHER_DEBUG----
    "); 
        } 
        return cls; 
     }

    分析上面的程序,我们可以看到 env 中的函数 FindClass 根据类名直接得到 mainClass 对象的。如果我们要装载已加密过的 JAVA 程序, 显然直接调用 FindClass 函数是不行的,那么,我们有没有办法自己读取文件,然后将之转换成一个 mainClass 对象呢?

    我们来看看 JNIEnv 里面还有什么?打开 JDK 路径 includejni.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 != ''); 
    		 /* 如果装载的类是 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.
    "); 
    			 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
    ", 
    		       (jint)Counter2Micros(end-start)); 
    	 printf("----_JAVA_LAUNCHER_DEBUG----
    "); 
        } 
     	   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

  • 相关阅读:
    团队项目——站立会议DAY14
    团队项目——站立会议DAY13
    团队项目——站立会议DAY12
    团队项目——站立会议 DAY11
    团队项目——站立会议DAY10
    团队项目——站立会议DAY9
    团队项目——站立会议 DAY8
    团队项目——站立会议DAY7
    团队项目——站立会议DAY6
    作业五:团队项目——项目启动及需求分析
  • 原文地址:https://www.cnblogs.com/langtianya/p/3712499.html
Copyright © 2011-2022 走看看