陈 冰,魏 江
(西北工业大学 电子信息学院,陕西 西安 710072)
Java语言以其“一次编写,到处运行”的思想和较为安全的内存管理访问机制,实现了热点代码检测和运行时编译及优化,使得Java应用随着运行时间的增加而获得更高的性能。Java有一套完善的应用程序接口,还有大量来自商业机构和开源商业社区的第三方类库来帮助实现各种各样的功能[1]。Java的这些优势大大提升了程序的开发效率,同时也给开发者带来了一个烦恼,Java的跨平台特性使得Java源代码被编译为一种中间代码——字节码,字节码可以较容易地被反编译为可读性很高的源代码。无论采用何种形式的代码混淆和自定义Classloader,只要依据Java虚拟机规范[2]对加密后的字节码文件进行文件格式比对和逻辑分析很容易获取软件源码,使软件的安全和知识产权难以保护。采用加密狗将部分软件核心算法分离至硬件的方式能有效的提高程序的安全性,但会增加硬件成本并占用本地接口资源且不便于远程部署。本文提出一种解决方案,基于开源Hotspot虚拟机,通过定制虚拟机、加密字节码文件和修改类的生命周期3种方式来提高对Java源码的保护,将传统对Java软件加密的思路由字节码文件拓展到虚拟机内部,使逆向工程难度由反编译字节码提高至对二进制文件的破解,显著提升了软件的安全性。
Java源码编译后生成的字节码文件具有非常好的跨平台特性,该特性正是基于字节码文件格式遵循Java虚拟机规范,可以由任意平台下的虚拟机解释运行。
根据Java虚拟机规范[2]规定,字节码文件结构体如表1所示, 其中 u2为 unsigned short,u4为 unsigned long,cp_info是常量池的指针数组,指针数组个数为onstant_pool_count,结构体cp_info为:
struct cp_info
{
u1 tag;//常数表数据类型
u1*info;//常数表数据
}[3]
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载7个阶段。其中验证、准备和解析三个部分统称为连接,这7个阶段的发生顺序如图1所示[1]。
字节码文件被加载到虚拟机后通过一个类的全限定名来获取定义此类的二进制字节流,然后将该字节流所代表的静态存储结构转化为方法区的运行时数据结构,最后在Java堆中生成一个代表这个类的java.lang.class对象,作为方法区访问这些数据的入口[4]。
在验证阶段,虚拟机主要校验字节码文件的文件格式、元数据、字节码和符号引用。在文件格式验证中,首先验证是否以魔数0xCAFEBABE开头,主次版本号是否在当前虚拟机的处理范围内,常量池的常量中是否存在不被支持的常量类型(检查常量的Tag标志)。指向常量的各种索引值中是否指向不存在的常量或不符合UTF8编码的数据,字节码文件中各个部分及文件本身是否有被删除的或附加的其他信息等。在经过了文件格式验证之后,字节流才会被存储于内存的方法区中[5]。其余三个验证阶段全部是基于方法区的存储结构进行的。元数据验证主要验证这个类是否存在父类、抽象类、接口等;字节码验证主要验证类的方法体是否堆栈超界;符号引用验证主要对自身以外的信息进行匹配性校验,如:全限定名是否存在,类、字段和方法的访问性等信息[6]。
表1 字节码文件数据结构Tab.1 The structure and format of bytecode file
图1 类的生命周期Fig.1 The lifetime of class
传统的软件保护方法主要有:本地化技术、远程接口访问技术、软件数字水印技术以及混淆技术等方式进行加密[7]。
本地化技术可以有效的将软件由中间代码转换为二进制代码,提高了对软件的保护强度,但是失去了跨平台性且无法应用于B/S模式。
远程接口访问技术由于实现了代码的隔离起到了保护的作用,这种模式只适用于B/S模式,对单机应用软件不适用。
静态软件数字水印技术健壮性较差,动态软件数字水印算法具有较好的健壮性,但是它只能保护整个应用程序,而不能保护某一部分特定的代码,同时,动态软件数字水印的检测方式令它的某些应用受到限制。目前的水印算法在提供可靠的版权证明方面或多或少都有一些尚不完善的地方[8]。
混淆技术可以很大程度上破坏反编译代码的可读性,增加逆向工程的难度,而且该难度只是增加了逆向工程的时间量而非技术难度。
编译生成的或通过不同方式加密后的字节码文件必须严格遵循Java虚拟机规范[2],否则程序在官方虚拟机下无法正确识别并运行。逆向工程正是依照该规范将传统软件保护方式保护后的字节码文件成功反编译。通过定制Java虚拟机和修改虚拟机规范中的数据结构,由Java源码编译的字节码文件遵循定制虚拟机规范和数据结构,逆向工程由于无法获悉Class遵循的虚拟机规范和数据结构无法对文件反编译,有效的保护软件。
定制Java虚拟机主要通过在官方虚拟机Hotspot的内部添加文件解密算法、硬件授权检测算法及对规范内的常量池Tag值进行重新排序和赋值。
虚拟机加载字节码文件前,首先检验程序运行环境是否经过授权。若经过授权则正式开始进入类的生命周期,否则退出程序并提示未经授权信息。
对运行环境的授权通过读取本地指定目录下的授权文件和本地硬件机器码,将机器码与自定义字符串组合后的值经MD5算法加密并与授权文件的授权码比对 (如图2所示)。由于MD5算法不可逆且与硬件码组合的字符串随机,使得授权码具有唯一性,保证软件只能在授权的电脑上正常运行[9]。
每次加载字节码文件的硬件授权验证会降低虚拟机的运行效率,通过在虚拟机中设置独立的全局变量,标识该运行环境是否经过授权,同时开启后台线程,不定时检测本机硬件是否授权,从而在保证硬件授权验证的同时,兼顾虚拟机的运行效率。
图2 授权验证流程Fig.2 Authorization verification process
Java虚拟机规范[2]中的常量池中常量的数据结构如表2所示。
表2 常量池内常量的数据结构Tab.2 Structure of the constant
逆向工程根据常量池内常量的Tag值确认常量的数据结构,字节码文件其余数据结构基于不同常量值,依照虚拟机规范进而推测出Java源码[10]。本方法通过对常量池内所有Tag值重新排列,达到逆向工程无法按照官方Java虚拟机规范中的Tag列表正确识别常量所代表的数据结构,完成对常量池的加密。由于无法获取常量池内所有的常量值和索引信息,对字节码文件的剩余数据便无法正确识别。
对常量池中Tag值的重新设置,不会影响虚拟机的运行效率。对软件的分发采用定制专用虚拟机的方式,即每套软件只能在授权方提供的指定且唯一的虚拟机下运行。对逆向工程而言同一套软件的不同分发版中同一类型的常量Tag值并不相同,无法获取随机排序的规律性,有效的保护了软件源码。
对常量池中常量的Tag值替换可以避免对常量的识别,但对字节码文件中其余的数据机构却无法保护。因此对字节码文件中的不同数据结构采用整体加密方式,将字节码文件的数据经过算法变换后全部不再符合虚拟机规范。这样可以避免对字节码文件中其余数据结构的反编译。由于在虚拟机内部对加密后的数据解密需要耗费一定的时间,加密算法采用运算量最低的数字替换法。将字节码文件中十六进制字节数据重新映射为新的十六进制数据,虚拟机加载字节码文件识别到加密标识后,按照加密的映射关系进行解密。
原始字节码文件格式如图3所示[11],加密后的字节码文件格式如图4所示,加密后的数据结构如表3所示。
图3 加密前的字节码文件结构Fig.3 Structure of bytecode file before encryption
图4 加密后的字节码文件结构Fig.4 Structure of bytecode file after encryption
该Tag值区别于常规字节码文件的Tag值,Length表示加密后的数据长度,Data为加密后的字节流数据,如表3所示。
表3 加密后的数据结构Tab.3 The encrypted data structures
以上3种虚拟机的定制方法,全部使用可增强对软件的保护效果,但会带来软件运行效率的降低,单独或组合使用可在保证软件运行效率的前提下增强对软件的保护效果。
不同定制方法的使用及优缺点如表4所示。
表4 不同保护方式优缺点Tab.4 Different advantages and disadvantages of protection
定制虚拟机可以保证在不同平台下运行B/S和C/S模式的Java程序,从根源上解决代码混淆易被反编译的问题。定制虚拟机与传统保护方法特性对比如表5所示。
文中提出基于Hotspot虚拟机的Java软件加密方案,通过在定制虚拟机完成对运行环境硬件授权验证、修改常量池内常量Tag值混淆字节码数据格式和加密字节码文件等方式来保护Java程序。加密后的程序只有在软件授权后和定制虚拟机下才可以正确运行。对字节码的解密过程在虚拟机内部,只有破解了虚拟机内部的解密算法才可以对软件进行反编译,大大增加了软件破解的难度。本方案的不足之处在于,虚拟机内部大量对字节码的解密过程会延长系统启动和运行时间,增加系统开销,如何优化方案来降低虚拟机系统开销需要在以后的工作中进一步研究。
表5 特性对比Tab.5 Comparison of characteristics
[1]周志明.深入理解Java虚拟机[M].北京:机械工业出版社.2011.
[2]Venners B.Inside the Java 2 Virtual Machine[M].(Second Edition).McGraw-Hill Companies,2000.
[3]Lindholm T,Yellin n,Bracha G,et al.The Java Virtual Machine Specification[M].(Second Edition).Addison-Wesley Professional,1999.
[4]James G,Guy S,Gilad B.The Java Language Specification[M].(Third Edition).ADDISON-WESLEY,2000.
[5]左天军,朱智林,韩俊刚,等.Java动态类加载分析[J].计算机科学,2005:194-196.ZUO Tian-jun,ZHU Zhi-lin,HAN Jun-gang,et al.The analysis of Java dynamic class loading[J].Computer science,2005:194-196.
[6]Hirt M,Lagergren M.Orocal JRockit The Definitive[M].Guide.Packt Publishing,2010.
[7]看雪.加密与解密——软件保护技术及完全解决方案[M].北京:电子工业出版社,2001.
[8]吴强.加密与解密[M].北京:企业管理出版社,2009.
[9]DouglasR.Stinson.密码学原理与实践[M].北京:电子工业出版社,2003.
[10]Muchnick S S.Advanced Compiler Design and Implemen tation[M].Elserier Science,2003.
[11]Biham E,Shamir A.Differential cryptanalysis of the data encryption standard[M].Berlin:Springer-Verlag,1993.