快精灵印艺坊 您身边的文印专家
广州名片 深圳名片 会员卡 贵宾卡 印刷 设计教程
产品展示 在线订购 会员中心 产品模板 设计指南 在线编辑
 首页 名片设计   CorelDRAW   Illustrator   AuotoCAD   Painter   其他软件   Photoshop   Fireworks   Flash  

 » 彩色名片
 » PVC卡
 » 彩色磁性卡
 » 彩页/画册
 » 个性印务
 » 彩色不干胶
 » 明信片
   » 明信片
   » 彩色书签
   » 门挂
 » 其他产品与服务
   » 创业锦囊
   » 办公用品
     » 信封、信纸
     » 便签纸、斜面纸砖
     » 无碳复印纸
   » 海报
   » 大篇幅印刷
     » KT板
     » 海报
     » 横幅

如何有效的保护JAVA程序

第一章 流行的加密方法简介

关于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最好可配置!





返回类别: 教程
上一教程: 使用Java Server Faces技术自定义组件
下一教程: Google让你突破下载

您可以阅读与"如何有效的保护JAVA程序"相关的教程:
· 如何在 Java 应用程序中读取 8 位和 24 位 Microsoft Windows 位图(转)
· 如何优化JAVA程序设计和编码,提高JAVA性能
· 如何将做好的JAVA程序生成可执行文件
· 一个JAVA的初学者如何准确编译HELLOWORLD程序
· java程序如何穿透带有密码验证的代理
    微笑服务 优质保证 索取样品