通常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的混淆效果差不多。

Java程序版权保护解决方案的更多相关文章

  1. 谈谈java程序代码保护及license设计

    理论上讲,不存在牢不可破的漏洞,只是时间和成本问题.通常我们认为的不可破解,说的是破解需要难以接受的时间和成本.对于java程序来说,class文件很容易被反编译,所以理论上而言,对java程序做li ...

  2. 如何保护java程序不被反编译

    Java是一种 跨平台的.解释型语言 Java 源代码编译中间“字节码”存储于class文件中.Class文件是一种字节码形式的中间代码,该字节码中包括了很多源代码的信息,例如变量名.方法名 等.因此 ...

  3. 在网页程序或Java程序中调用接口实现短信猫收发短信的解决方案

    方案特点: 在网页程序或Java程序中调用接口实现短信猫收发短信的解决方案,简化软件开发流程,减少各应用系统相同模块的重复开发工作,提高系统稳定性和可靠性. 基于HTTP协议的开发接口 使用特点在网页 ...

  4. 如何有效的保护 JAVA 程序

    从头到尾保护 JAVA 目前关于 JAVA 程序的加密方式不外乎 JAVA 模糊处理(Obfuscator)和运用 ClassLoader 方法进行加密处理这两种方式(其他的方式亦有,但大多是这两种的 ...

  5. java程序向hdfs中追加数据,异常以及解决方案

    今天在学习hdfs时,遇到问题,就是在向hdfs中追加数据总是报错,在经过好几个小时的努力之下终于将他搞定 解决方案如下:在hadoop的hdfs-sit.xml中添加一下三项 <propert ...

  6. [C#防止反编译].NET 产品版权保护方案 (.NET源码加密保护)

    [C#防止反编译].NET 产品版权保护方案 (.NET源码加密保护) 标签: .net加密产品c#dll工具 2011-03-24 21:06 27009人阅读 评论(13) 收藏 举报 分类: C ...

  7. Java程序员岗位

    Java程序员岗位面试题有哪些?   1.面向对象的特征有哪些方面(1)抽象:抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面.抽象并不打算了解全部问题,而只是选择 ...

  8. Java起源、发展历程、环境变量、第一个Java程序等【1】

    若有不正之处,请多多谅解并欢迎批评指正,不甚感激. 请尊重作者劳动成果,转载请标明原文链接: 本文原创作者:pipi-changing 本文原创出处:http://www.cnblogs.com/pi ...

  9. Terrocotta - 基于JVM的Java应用集群解决方案

    前言 越来越多的企业关键应用都必须采用集群技术,实现负载均衡(Load Balancing).容错(Fault Tolerance)和灾难恢复(Failover).以达到系统可用性(High Avai ...

随机推荐

  1. 【BZOJ 1491】 [NOI2007]社交网络

    Description Input Output 输出文件包括n 行,每行一个实数,精确到小数点后3 位.第i 行的实数表 示结点i 在社交网络中的重要程度. Sample Input 4 4 1 2 ...

  2. C# send mail with outlook and word mailmerge

    http://msdn.microsoft.com/en-us/library/microsoft.office.interop.word.document_members(v=office.15). ...

  3. hive-site.xml 参数设置

    <?xml version="1.0"?> <?xml-stylesheet type="text/xsl" href="confi ...

  4. ExtJs 4.2.1 点击按钮弹出表单的窗口

    初学ExtJs,做项目的时候想做一个这样的效果:点击按钮弹出对话框,之前一直是使用EasyUi来做的, EasyUi里有Dialog,用起来很方便,但是现在转移到ExtJs上后,发现没有Dialog这 ...

  5. 解决VS如何同时打开两个工程(xp和win7)

    http://www.360doc.com/content/11/1020/00/7891073_157586269.shtml

  6. PAT-乙级-1004. 成绩排名 (20)

    1004. 成绩排名 (20) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 8000 B 判题程序 Standard 作者 CHEN, Yue 读入n名学生的姓名.学号.成绩,分 ...

  7. BZOJ 2806 cheat

    首先这个题目显然是要二分转换成判断可行性的 之后我们考虑DP 设f(i)表示 1->i 熟悉的子串的长度的最大值 那么对于i这个点,要么不在熟悉的子串中,要么在熟悉的子串中 所以得到 f(i)= ...

  8. codeforces #305 B Mike and Feet

    跟之前做过的51Nod的移数博弈是一样的QAQ 我们考虑每个数的贡献 定义其左边第一个比他小的数的位置为L 定义其右边第一个比他小的数的位置为R 这个可以用排序+链表 或者 单调队列 搞定 那么对于区 ...

  9. module_init宏解析 linux驱动的入口函数module_init的加载和释放

    linux驱动的入口函数module_init的加载和释放 http://blog.csdn.net/zhandoushi1982/article/details/4927579 void free_ ...

  10. 你不知道的pogo pin连接器

    pogo pin连接器是一种带弹簧的探针式连接器,pogo pin连接器结构看起来非常简单,但其制造工艺要求极其的精细与复杂,从车床加工,电镀,组装等每道工序,如果没有一个有良好品质控制和完善的制造水 ...