Java程序的保护
java是⼀种跨平台的、解释型语⾔。java 源代码编译中间“字节码”存储于class⽂件中。class⽂件是⼀种字节码形式的中间代码,该字节码中包括了很多源代码的信息,例如变量名、⽅法名等。因此,java中间代码的反编译就变得⾮常容易。⽬前市场上有许多免费的、商⽤的反编译软件,都能够⽣成⾼质量的反编译后的源代码。所以,对开发⼈员来说,如何保护java程序就变成了⼀个⾮常重要的挑战。本⽂⾸先讨论了保护java程序的基本⽅法,然后对代码混淆问题进⾏深⼊研究,最后结合⼀个实际的应⽤程序,分析如何在实践中保护java程序。   
反编译成为保护java程序的最⼤挑战
  通常c、c++等编程语⾔开发的程序都被编译成⽬标代码,这些⽬标代码都是本机器的⼆进制可执⾏代码。通常所有的源⽂件被编译、链接成⼀个可执⾏⽂件。在这些可执⾏⽂件中,编译器删除了程序中的变量名称、⽅法名称等信息,这些信息往往是由内存地址表⽰,例如如果需要使⽤⼀个变量,往往是通过这个变量的地址来访问的。因此,反编译这些本地的⽬标代码就是⾮常困难的。
  java语⾔的出现,使得反编译变得⾮常容易⽽有效。原因如下:1.由于跨平台的需求,java的指令集⽐较简单⽽通⽤,较容易得出程序的语义信息;2.java编译器将每⼀个类编译成⼀个单独的⽂件,这也简化了反编译的⼯作;3.java 的class⽂件中,仍然保留所有的⽅法名称、变量名称,并且通过这些名称来
访问变量和⽅法,这些符号往往带有许多语义信息。由于java程序⾃⾝的特点,对于不经过处理的java 程序反编译的效果⾮常好。
  ⽬前,市场上有许多java的反编译⼯具,有免费的,也有商业使⽤的,还有的是开放源代码的。这些⼯具的反编译速度和效果都⾮常不错。好的反编译软件,能够反编译出⾮常接近源代码的程序。因此,通过反编译器,⿊客能够对这些程序进⾏更改,或者复⽤其中的程序。因此,如何保护java程序不被反编译,是⾮常重要的⼀个问题。
常⽤的保护技术
  由于java字节码的抽象级别较⾼,因此它们较容易被反编译。本节介绍了⼏种常⽤的⽅法,⽤于保护java字节码不被反编译。通常,这些⽅法不能够绝对防⽌程序被反编译,⽽是加⼤反编译的难度⽽已,因为这些⽅法都有⾃⼰的使⽤环境和弱点。
 隔离java程序
  最简单的⽅法就是让⽤户不能够访问到java class程序,这种⽅法是最根本的⽅法,具体实现有多种⽅式。例如,开发⼈员可以将关键的java class放在服务器端,客户端通过访问服务器的相关接⼝来获得服务,⽽不是直接访问class⽂件。这样⿊客就没有办法反编译class ⽂件。⽬前,通过接⼝提供服
务的标准和协议也越来越多,例如 http、web service、rpc等。但是有很多应⽤都不适合这种保护⽅式,例如对于单机运⾏的程序就⽆法隔离java程序。这种保护⽅式见图1所⽰。
图1隔离java程序⽰意图
 对class⽂件进⾏加密
  为了防⽌class⽂件被直接反编译,许多开发⼈员将⼀些关键的class⽂件进⾏加密,例如对注册码、序列号管理相关的类等。在使⽤这些被加密的类之前,程序⾸先需要对这些类进⾏解密,⽽后再将这些类装载到jvm当中。这些类的解密可以由硬件完成,也可以使⽤软件完成。
  在实现时,开发⼈员往往通过⾃定义classloader类来完成加密类的装载(注意由于安全性的原因,applet不能够⽀持⾃定义的classloader)。⾃定义的classloader⾸先到加密的类,⽽后进⾏解密,最后将解密后的类装载到jvm当中。在这种保护⽅式中,⾃定义的classloader是⾮常关键的类。由于它本⾝不是被加密的,因此它可能成为⿊客最先攻击的⽬标。如果相关的解密密钥和算法被攻克,那么被加密的类也很容易被解密。这种保护⽅式⽰意图见图2。
图2 对class⽂件进⾏加密⽰意图
 转换成本地代码
  将程序转换成本地代码也是⼀种防⽌反编译的有效⽅法。因为本地代码往往难以被反编译。开发⼈员可以选择将整个应⽤程序转换成本地代码,也可以选择关键模块转换。如果仅仅转换关键部分模块,java程序在使⽤这些模块时,需要使⽤jni技术进⾏调⽤。
  当然,在使⽤这种技术保护java程序的同时,也牺牲了java的跨平台特性。对于不同的平台,我们需要维护不同版本的本地代码,这将加重软件⽀持和维护的⼯作。不过对于⼀些关键的模块,有时这种⽅案往往是必要的。
  为了保证这些本地代码不被修改和替代,通常需要对这些代码进⾏数字签名。在使⽤这些本地代码之前,往往需要对这些本地代码进⾏认证,确保这些代码没有被⿊客更改。如果签名检查通过,则调⽤相关jni⽅法。这种保护⽅式⽰意图见图3。
 图3 转换成本地代码⽰意图
图3 转换成本地代码⽰意图
 代码混淆
代码混淆是对class⽂件进⾏重新组织和处理,使得处理后的代码与处理前代码完成相同的功能(语义)。但是混淆后的代码很难被反编译,即反编译后得出的代码是⾮常难懂、晦涩的,因此反编译⼈员很难得出程序的真正语义。从理论上来说,⿊客如果有⾜够的时间,被混淆的代码仍然可能被破解,甚⾄⽬前有些⼈正在研制反混淆的⼯具。但是从实际情况来看,由于混淆技术的多元化发展,混淆理论的成熟,经过混淆的java代码还是能够很好地防⽌反编译。下⾯我们会详细介绍混淆技术,因为混淆是⼀种保护java程序的重要技术。图4是代码混淆的⽰意图。
图4 代码混淆⽰意图
  ⼏种技术的总结
  以上⼏种技术都有不同的应⽤环境,各⾃都有⾃⼰的弱点,表1是相关特点的⽐较。
表1 不同保护技术⽐较表
混淆技术介绍
  到⽬前为⽌,对于java程序的保护,混淆技术还是最基本的保护⽅法。java混淆⼯具也⾮常多,包括商业的、免费的、开放源代码的。sun公司也提供了⾃⼰的混淆⼯具。它们⼤多都是对class⽂件进⾏混淆处理,也有少量⼯具⾸先对源代码进⾏处理,然后再对class进⾏处理,这样加⼤了混淆处理的⼒度。⽬前,商业上⽐较成功的混淆⼯具包括jproof公司的1stbarrier系列、eastridge公司的jshrink和
4thpass的sourceguard等。主要的混淆技术按照混淆⽬标可以进⾏如下分类,它们分别为符号混淆(lexical obfuscation)、数据混淆(data obfuscation)、控制混淆(control obfuscation)、预防性混淆(prevent transformation)。 
  符号混淆 
  在class中存在许多与程序执⾏本⾝⽆关的信息,例如⽅法名称、变量名称,这些符号的名称往往带有⼀定的含义。例如某个⽅法名为 
java源代码加密getkeylength(),那么这个⽅法很可能就是⽤来返回key的长度。符号混淆就是将这些信息打乱,把这些信息变成⽆任何意义的表⽰,例如将所有的变量从vairant_001开始编号;对于所有的⽅法从method
_001开始编号。这将对反编译带来⼀定的困难。对于私有函数、局部变量,通常可以改变它们的符号,⽽不影响程序的运⾏。但是对于⼀些接⼝名称、公有函数、成员变量,如果有其它外部模块需要引⽤这些符号,我们往往需要保留这些名称,否则外部模块不到这些名称的⽅法和变量。因此,多数的混淆⼯具对于符号混淆,都提供了丰富的选项,让⽤户选择是否、如何进⾏符号混淆。 
  数据混淆 数据混淆是对程序使⽤的数据进⾏混淆。混淆的⽅法也有多种,主要可以分为改变数据存储及编码(store and encode transform)、改变数据访问(access transform)。
  改变数据存储和编码可以打乱程序使⽤的数据存储⽅式。例如将⼀个有10个成员的数组,拆开为10个变量,并且打乱这些变量的名字;将⼀个两维数组转化为⼀个⼀维数组等。对于⼀些复杂的数据结构,我们将打乱它的数据结构,例如⽤多个类代替⼀个复杂的类等。   
  另外⼀种⽅式是改变数据访问。例如访问数组的下标时,我们可以进⾏⼀定的计算,图5就是⼀个例⼦。
  在实践混淆处理中,这两种⽅法通常是综合使⽤的,在打乱数据存储的同时,也打乱数据访问的⽅式。经过对数据混淆,程序的语义变得复杂了,这样增⼤了反编译的难度。
 控制混淆
  控制混淆就是对程序的控制流进⾏混淆,使得程序的控制流更加难以反编译,通常控制流的改变需要增加⼀些额外的计算和控制流,因此在性能上会给程序带来⼀定的负⾯影响。有时,需要在程序的性能和混淆程度之间进⾏权衡。控制混淆的技术最为复杂,技巧也最多。这些技术可以分为如下⼏类:
  增加混淆控制 通过增加额外的、复杂的控制流,可以将程序原来的语义隐藏起来。例如,对于按次序执⾏的两个语句a、b,我们可以增加⼀个控制条件,以决定b的执⾏。通过这种⽅式加⼤反汇编的难度。但是所有的⼲扰控制都不应该影响b的执⾏。图6就给出三种⽅式,为这个例⼦增加混淆控制。   
图6 增加混淆控制的三种⽅式

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。