一、引言
“安全性”一直是Java语言所强调的重点之一。基于安全性的考虑,Java程序所受到的限制比一般由C语言或汇编语言编写的原生程序(NativeProgram)严格了许多。举例来说,原生程序可以直接读取内存中的资料,包括将要执行的程序代码和程序中的变量数据。但是Java语言却无法执行如此底层的动作,Java程序所能取得的资料只局限于Java虚拟机(JVM)以上。基于这些限制,使得传统的利用程序代码结构来判别的防篡改技术无法应用到Java软件上。除此之外,所有的Java程序在执行之前都必须先通过验证。检查类文件的“结构”、“环境”、与“内容”符合一定的格式。因此以加解密为基础的防篡改技术就派不上用场了,因为如果事先将Java程序中的其中几个类文件加密,则该类文件会因为格式不符合而无法通过虚拟机的验证。基于种种的限制,使得目前市面上尚未出现适用于Java软件的防篡改器。本文将提出一种Java程序防篡改器设计方案。
二、适用于Java软件的哈希运算
在Java中,所有的指令都是以堆栈为基础的(Stack-Based)。每个指令所需要的操作数(operand)都必须先放入堆栈之中,再进行相对应的运算。因此,在Java中,堆栈是信息处理的核心。本方案主要的思路是利用堆栈的内容作为判别的特征。因为指令的变化大多会影响堆栈的内容,因此,只要事先记录堆栈的改变状态,并在执行时动态地追踪堆栈的内容,即可判别程序代码是否曾经被改动过。假设某一程序片断中包含了指令序列I={I1,I2,...,In},每个指令执行后所产生的堆栈序列为S={S1,S2,...,Sn}。我们主要的想法是预先抓取指令与堆栈的执行轨迹T,并根据T计算出哈希值H。因为执行轨迹T反映出实际的执行情况,即执行轨迹T是随着指令序列I与堆栈序列S而变化。而哈希值H是根据轨迹T计算而成。因此I、S、T、与H之间的相依关系如下:HASH(I,S)→HASH(T)→h由于任何轻微的指令变化都会影响堆栈的内容,因此只要T包含足够的执行阶段数据,无论对程序代码还是数据而言,任何轻微的变化都会造成哈希值H的改变。因此哈希值H即可作为判别执行状态的主要特征。最理想的轨迹T应该包含每个指令本身以及当时的堆栈内容。举例来说,在执行之前,必须事先分析出对照表,并在执行时逐一比对堆栈的内容,监控堆栈是否依照对照表中的顺序变化。但如果要在执行期间监控堆栈的内容,则必须修改Java虚拟机,甚至需要特殊硬件的协助,才能在执行期间监控指令与堆栈的运行情况。这样一来,只有具有此功能的Java虚拟机,才具有保护的功效。这就违背了Java程序最重要的“跨平台”特性了。因此我们必须考虑如何利用纯软件的方式,来实现动态堆栈追踪法。
三、纯软件方法实现方案
其中一种可行的方法是在Java程序中,加入额外的哈希指令(HashingInstruc⁃tion),其负责在执行期间动态监控堆栈的内容,确保堆栈的内容是否如先前所预期的。哈希指令负责取得上一个指令执行后,所产生的堆栈内容,转换成相对应的哈希值,并储存在局部变量中。由于哈希指令将伴随着被保护的程序区块一起运算,因此哈希指令必须越精简越好,以免影响整体的系统效率。在所有的字节码(Bytecode)指令中,没有一个指令能够一次取出堆栈中所有元素。因此如果要取得堆栈中所有的内容,则必须逐一记录堆栈中的每一个元素。但是,如果堆栈高度太高,则必须利用多个复制指令(dup、dup2...等)与储存指令(istore、lstore...等)才能完成。如此大处理量的哈希指令,不但会使程序性能变差,也可能会对外界暴露哈希指令的行踪,造成安全隐患。为了减少哈希指令的动作,我们并不详细地记录堆栈中所有的元素,而只记录其中具有显著特征的元素。在堆栈中变化量最大的元素就是“栈顶元素”(TopofStack),亦即堆栈中最顶端的元素。全部的字节码指令在获取参数或回传结果时,都是从栈顶元素逐一存取。另外,栈顶元素也是最容易取得的元素,只要二至三个指令即可完成,很容易跟原有的程序指令混合在一点,不容易被恶意程序跟踪。通过以上方法,可以实现在不需要修改底层操作系统或Java虚拟机的前提下,利用动态堆栈追踪法来保护Java软件不被任意篡改。
四、总结
本文提出使用动态堆栈追踪法改良非显性的哈希运算,并将此方法应用于Java软件的防篡改技术。
作者:李玮 单位:河北工业职业技术学院