谢 鑫 向 飞
1(湖南信息学院 湖南 长沙 410151)2(信息工程大学 河南 郑州 450000)
伴随着人工智能、大数据、物联网、区块链等新兴技术的迅速发展,大量基于这些新兴技术的软件得到开发和应用,人们在使用这些页面美观、交互良好和操作便捷的软件之时,却往往忽略了软件的安全性。提升软件安全性是在软件生命周期之中进行,如对软件架构进行安全设计,对代码进行安全性分析,对软件进行安全性测试以及对软件进行保护等。而软件保护一般是在编码中或者开发完成后采用若干安全技术对软件进行保护,防止攻击者的逆向分析和恶意篡改。
现有主流的软件保护技术有代码混淆技术(Code Obfuscation)、软件水印技术(Software Watermarking)、软件防篡改技术(Software Tampering)和软件胎记技术(Software Birthmarking)等。代码混淆技术通过对源代码或二进制代码进行等价变形,使得代码形态、结构和内容对于逆向分析变得难以理解,从而大大增加攻击时间和经济开销,迫使逆向分析者放弃攻击的一种软件保护技术。技术发展之初,学者Collberg将代码混淆技术分为布局混淆、数据混淆、控制混淆和预防混淆,随着近些年对于该技术的研究深入,研究热点主要集中在采用虚拟机框架对软件安全性进行保护。
2002年国外软件安全研究人员公开了虚拟机保护技术的设计框架,并从C/C++编码角度给出了实现原理[1]。随后国内的看雪、吾爱破解等论坛上对该技术的讨论和交流日益增多,紧接着越来越多基于虚拟机保护技术的商业工具被开发出来,并应用于各种软件的安全保护之中,如VMProtect、CodeVirtualizer和Themida等。除此之外,软件安全人员不断对虚拟机框架的安全性进行改进,主要成果有:Fang等[2]设计了一种多层级的虚拟机强度增强方法,该方法首先对软件中需要保护的指令序列进行虚拟机化,生成一系列虚拟指令序列和虚拟指令解释函数,然后再对Handler中的若干指令进行虚拟机化,再次生成新的Handler,反复迭代该过程生成最终用于保护关键代码的虚拟机保护框架。杨明等[3]在单重虚拟机保护框架基础上引入嵌套思想,基于多个不同强度的虚拟机保护框架嵌套保护软件关键代码,若逆向攻击者没有完全分析清楚前一重虚拟机保护机制时,则无法开展对下一重虚拟机保护机制的分析。房鼎益等[4]为提升虚拟机框架安全性,首先在虚拟机保护中引入了基于扭曲变形的加密模块,然后加入了基于时间多样性思想[5]的保护技术,构造了多条等价执行路径,可以使软件在每次执行时路径发生随机改变,方法能有效抵御累积攻击。之后又对Handler模块进行数据流混淆,对谓词进行隐藏,用双进程对虚拟机框架进行保护,增强了框架抵御语义逆向攻击的性能[6]。王怀军等[7]为改进框架的保护强度,首先引入寄存器轮转指令和防调试指令,然后基于路径多样化构造语义等价的变形Handler模块,从而提升Handler模块的整体强度[8-9]。张丽娜等[10]通过对字节码进行加解密实现虚拟机强度改进。吴伟民等[11]在虚拟指令中嵌入虚拟花指令,并运用模糊变换技术提升虚拟指令系统的复杂程度。汤战勇等[12]通过对虚拟机的指令集进行随机化,使得受保护软件的代码呈现多样化的形态。谢鑫等首先提出了将软件中核心代码进行并行化,然后基于多种虚拟机对不同的并行代码片段进行保护[13],逆向攻击者只有分析清楚并行算法和不同虚拟机机理才能获取受保护代码的语义。之后又对Handler模块进行多样化,变长切分和随机乱序,用跳转表对指令片段进行重组[14]。另外,还在虚拟机框架上引入了多种不同加解密算法,实现了基于Handler模块的动态加解密保护方法[15]。
针对虚拟机保护的一般攻击方法为:首先对虚拟机代码入口进行定位[16],分析出虚拟机Dispatcher模块的起始和终止位置,其次根据Dispatcher模块的调度算法逐条读取虚拟指令,分析每条虚拟指令对应Handler模块的功能,最后对所有执行的虚拟指令序列进行优化,还原程序功能[17]。现有对于虚拟机保护的研究主要集中在虚拟指令和Handler模块的改进上,对Dispatcher模块的增强保护研究较少,如Kuang等[18]提出了一种多Dispatcher虚拟机保护框架,赫朝辉[19]提出了一种混合式多层次Dispatcher结构,抗攻击性得到了一定提升,但强度依然不高。针对Dispatcher模块隐蔽性低和抵御静、动态逆向攻击强度不高的问题,提出一种基于增强型调度器的虚拟机软件保护方法。
基于虚拟机的软件保护方法主要采用虚拟化技术对软件中关键算法所对应的指令序列进行虚拟化,然后通过嵌入软件中的解释器模块对这些虚拟机指令进行解释执行,从而完成软件的正常运行。虚拟机框架核心为虚拟指令集和解释器模块,虚拟指令集需要能对本地x86汇编指令集进行完整表示,一条x86汇编指令一般对应一条或多条虚拟指令。解释器模块主要包括:用于保存临时寄存器环境中寄存器值的虚拟机上下文结构(VMcontext),用于对虚拟指令进行解释执行的Handlers模块,用于对Handlers进行循环调用的Dispatcher模块,用于表示虚拟指令序列的驱动数据(VMdata)以及环境初始化(VMinit)和退出模块(VMexit)等。
若要运用虚拟机技术对程序进行保护,首先需定位程序需保护的核心算法,抽取目标指令序列,然后构建完备的虚拟指令集,对目标指令序列进行虚拟化,生成虚拟指令序列,再将虚拟指令序列转化为字节码形式的驱动数据,写到原始程序空间之中,最后将VMcontext,Handlers和Dispatcher等模块嵌入到需要保护的PE程序之中,生成保护后程序,基本原理如图1所示。
图1 基于虚拟机保护的基本原理
基于虚拟机保护后的软件执行过程主要分为如下步骤:
1) 软件开始执行,当代码执行到保护代码时,则转向受保护软件中嵌入的解释器模块执行。
2) 运用解释器模块中的初始化代码对现场环境进行保存,如:将真实寄存器值保存到Vmcontext中,对某些寄存器赋初值等。
3) 执行Dispatcher模块,从VMdata之中读取字节数据,对字节码数据进行解码,得到虚拟指令对应的Handler索引值。
4) 通过索引值计算Handler函数在软件中的地址,并转向Handler函数代码进行执行。
5) 执行完Handler指令序列后转向步骤3,循环执行步骤3-步骤5,直到完成所有VMdata数据的解释执行。
6) 还原虚拟机框架现场,跳出解释器,执行程序后面未保护的代码序列。
逆向攻击者若要理解基于虚拟机保护的程序功能,则需分析清楚解释器及相关模块的工作机理和每一条虚拟指令的功能。现有一些方法对虚拟指令和Handler强度进行了改进,使对于这些模块的逆向分析变得困难,Dispatcher模块成为了整个虚拟机保护框架中的短板,容易受到“动态跟踪,静态分析”的攻击。针对Dispatcher模块安全性不高的问题,提出一种对Dispatcher模块安全性增强的方法,基本思想为:首先采用多样化技术生成多种语义等价但形态各异的Dispatcher模块指令序列,再按照语义将其切分成若干指令序列片段,随机选择指令序列片段进行加密和控制流迭代混淆处理,然后采用随机函数将不同的指令片段进行连接,最后将新生成的指令序列以及加解密分散嵌入到受保护程序之中,Dispatcher模块安全性增强框架如图2所示,具体步骤如下:
1) 将Dispatcher代码分成三部分:Dispatcher入口代码序列I,Dispatcher核心代码序列C和Dispatcher出口代码序列O。
2) 将Dispatcher核心代码序列C随机划分成长度各异的指令片段,记做C=(C1,C2,…,Cn)。
3) 采用等价指令替换和花指令插入等方法[14]对C中的所有指令片段进行多样化,生成m种与C语义等价但形式不同的指令序列,记作S={S1,S2,…,Sm},其中Si=(A1i,A2i,…,Ani),且A1i和C1、A2i和C2、A3i和C3、…、Ani和Cn语义等价。
图2 Dispatcher模块安全性增强框架
4) 在多样化代码片段中随机挑选若干指令序列,如A11、A2m、A22、A33、An1、Anm等,基于插入不透明谓词的方法[20]对其进行k次控制流迭代混淆,具体原理如图3所示。
图3 基于不透明谓词的k次迭代控制流混淆示意
5) 基于步骤4所生成的指令序列集合,再次随机挑选指令序列片段,如A1m、A23、A32、An2、A11、A22等,运用多种不同的密码算法对其进行加密处理。
6) 用随机函数对每一层指令序列片段进行连接,如集合{A(i)1,A(i)2,…,A(i)m}中的每一个指令序列片段可随机选择执行其下一层指令序列集合{A(i+1)1,A(i+1)2,…,A(i+1)m}中任意一个片段,最后生成网状结构的代码路径。
7) 将新生成的Dispatcher代码和所有Handler函数代码进行连接,并将新生成的Dispatcher模块和用于程序动态执行的随机、加密和解密函数分散嵌入到程序空间之中,如图4所示。
图4 Dispatcher模块和加解密函数分散嵌入程序空间
强度分析为基于增强型Dispatcher的虚拟机抵御静动态逆向分析攻击的能力。原始虚拟机Dispatcher模块代码通常以未加密状态存放在程序新增的区块之中,攻击者可以通过内存断点方式找到模块入口,并通过内存转储和反汇编等方法理解代码执行流程和调度方式。而安全性增强的Dispatcher模块代码采用了多样化技术,基于不透明谓词对控制流进行了迭代混淆,而且对部分指令片段进行动态加解密处理。
若设原始虚拟机Dispatcher模块执行路径为1,通过指令多样化技术可以生成m种语义等价且形式不同的指令序列,将所有语义等价的指令序列分为n层,并采用随机函数对不同层指令序列进行连接,形成网状结构。若攻击者采用指令动态追踪方法对Dispatcher模块指令序列进行记录,理论上共可得到mn种不同的程序执行路径,大大增加了攻击者动态跟踪分析的难度。
安全性增强的Dispatcher模块在生成多样化指令序列时,基于不透明谓词对若干指令片段进行控制流迭代混淆。若每进行一次控制流迭代混淆,控制流边数目增加一倍,当迭代k次时,单个指令序列的控制流边数目变成2k个,若随机挑选i个指令序列进行混淆,则控制流边数目将增长为i×2k个,极大增加了通过构建代码控制流图进行静态逆向分析的难度。
另外,方法还对Dispatcher模块中部分指令片段进行了加密处理,只有在程序动态运行时才解密运行。在程序未执行时,这些指令片段为加密状态保存在程序中,直接采用静态逆向分析方法对其解析将出现乱码指令,同样增加了受保护程序的分析难度。
开销分析为混淆前后程序时间和空间对比分析。基于增强型Dispatcher模块的虚拟机保护框架相比原始虚拟机保护框架增加了多层随机函数,控制流迭代混淆中的不透明谓词以及指令序列片段加解密三部分时间开销。
3.2.1 时间开销分析
1) 若将多样化Dispatcher模块划分成n个指令片段,每一次随机函数运行的平均时间为q,则时间开销增长q×(n-1);
2) 若在Dispatcher模块单次执行序列的n个指令片段中随机选择e1个指令片段进行k次控制流迭代,每次迭代插入的不透明谓词运行平均时间为d,则时间开销增长k×d×e1;
3) 若在Dispatcher模块单次执行序列的n个指令片段中随机选择了e2个指令序列进行加密,每个指令片段解密平均时间为u,则时间开销增长e2×u。由此可得,三部分总的时间开销增长理论值为q×(n-1)+k×d×e1+e2×u。
若原始程序运行在某个计算环境下的时间开销为X1。改进后的虚拟机保护框架中未引入加密方法时,则运用该框架对原始程序进行保护后的时间开销为X2,该值一般为X1的1.5~10倍左右[5,12]。基于上述分析,若在现有虚拟机保护框架上再单独对Dispatcher模块进行安全性增强,则时间开销将进一步增长,其增长的时间理论值为Z1=q×(n-1)+k×d×e1+e2×u。若Z值相比X2没有大大增长,由于现有软件和硬件计算能力不断发展,本文增强方案将不会给受保护程序的正常运行造成影响。但如果Z1值相比X2得到大幅度增长,权衡时间开销和安全性来说,Dispatcher模块增强方案是一种性价比不高的选择。因此,在对Dispatcher模块进行安全性增强时,在保证方法具有一定强度的同时,要尽可能选择运行速度快的随机函数、加解密函数和不透明谓词,控制指令片段e1、e2和迭代次数,以此降低整个调度器增强方案对于虚拟机原始保护框架时间开销增长的影响。
3.2.2 空间开销分析
空间开销的增长主要包括随机函数代码空间,基于控制流迭代的不透明谓词和冗余代码空间,以及加解密函数代码空间。设随机函数有f1个,平均代码空间为r1;进行了k次控制流迭代,插入f2个不透明谓词,f3段冗余代码,平均代码空间为r2和r3;引入的f4个加解密函数平均代码空间为r4,总空间开销增长理论值为f1×r1+f2×r2+f3×r3+f4×r4。
若原始程序所占存储空间为Y1。运用改进后的虚拟机保护框架对原始程序进行保护后所占存储空间Y2一般为Y1的1.5~2倍左右[5,12]。基于上述分析,若在现有虚拟机保护框架上再单独对Dispatcher模块进行安全性增强,则空间开销还将增长,其增长的理论值为Z2=f1×r1+f2×r2+f3×r3+f4×r4。若Z2值相比Y2没有大大增长,由于现有内存和硬盘等存储能力的提升,则本文增强方案将不会给受保护程序的存储开销带来影响。但如果Z2值相比Y2大幅度增长,权衡空间开销和安全性来说,Dispatcher模块增强方案仍需进一步优化。因此,在对Dispatcher模块进行安全性增强时,在保证方法具有一定强度的同时,要尽可能选择占用存储空间小的随机函数、加解密函数和不透明谓词,并且要对冗余代码数目,迭代次数进行控制,以此降低空间开销增长的影响。
针对基于增强型Dispatcher模块的虚拟机保护框架的反混淆攻击,一般首先对增强型Dispatcher模块进行代码定位,然后采用反混淆技术对Dispatcher模块代码进行处理。
原始虚拟机保护框架由于Dispatcher模块集中存储在受保护程序中的特定空间,逆向攻击者基于断点跟踪较为容易对其进行定位,并进行分析和理解。而在改进的虚拟机保护框架中将单一Dispatcher模块转化成多种语义等价且形式不同的Dispatcher模块,并将代码分散嵌入在整个程序空间的内部,这种方式增加了Dispatcher模块的隐蔽性。而对于攻击者而已,若要成功理解Dispatcher模块,首先需对分散在整个程序空间的代码进行定位,然后进行代码语义整合,最后对多种不同形式的Dispatcher进行语义分析,极大增加了分析难度。
若要准确定位出多样化后的Dispatcher代码,则需采用反混淆技术对代码进行处理。现有反混淆方法主要基于优化技术,典型方法有:等价指令整合、常量传播和折叠、循环压缩、流程优化等[21]。这些技术能在一定程度上能对指令进行约简,对冗余流程进行去除,对代码结构进行优化。但在安全增强型Dispatcher模块中,不仅对代码进行了多样化处理,还挑选了一些代码片段进行控制流膨胀,并采用加解密技术对部分指令片段进行了处理。若要对其进行反混淆,先要对加密指令片段进行解密,然后对控制流膨胀后的代码进行定位、分析和约简,最后对多样化Dispatcher指令序列进行语义归一化处理,而代码解密,膨胀控制流结构约简和代码语义归一都将大大增加攻击者分析代价。
验证实验基本环境如下:
1) Intel(R) Core(TM) i3 CPU M380 2.53 Hz双核处理器。
2) Windows 7 SP1 32位旗舰版操作系统,内存容量2 GB。
3) 运用Visual Studio2008对开源虚拟机框架(原始VM)[22]进行编译,并基于该框架进行Dispatcher模块安全性增强。
4) 基于C语言和标准插入排序(InsertionSort)、希尔排序(ShellSort)、快速排序(QuickSort)和冒泡排序(BubbleSort)算法实现了四款测试程序,以及4组(每组500个)随机测试数据。
5) 在操作系统目录C:WindowsSystem32下,挑选网络功能控制台应用程序ping.exe, route.exe和图形化应用程序notepad.exe, regedt32.exe和mspaint.exe作为典型应用场景下的实际应用测试程序。
生成四款排序测试程序,并在不同参数条件下,运用Dispatcher模块安全性增强虚拟机框架对测试程序进行保护,运用生成的基准测试程序和受保护程序对测试数据进行排序,对比保护前后测试程序的时间和空间开销。
4.1.1 Dispatcher指令序列多样化
基于原始VM虚拟机框架,运用等价指令替换和花指令随机嵌入等方法对Dispatcher指令序列进行多样化,添加Dispatcher入口代码,Dispatcher其他代码不进行增强变换,将多样化后语义等价Dispatcher数目记为M。当M=1为原始VM,不同M下测试程序时间和空间开销如表1和表2所示。由表1和表2可知,原始VM框架对测试程序进行保护后,会给程序引入一定的时间和空间开销。由于多样化后不同路径的Dispatcher指令序列规模基本一致,当Dispatcher路径数目增多时,保护后程序空间开销将会线性增长,而无论有多少种Dispatcher路径,受保护程序每次执行时,都只选Dispatcher模块中一条路径执行,因此当M的值为1~5之间时,受保护程序的时间开销基本稳定。
表1 不同Dispatcher数目下测试程序时间开销
表2 不同Dispatcher数目下测试程序空间开销
4.1.2 Dispatcher指令序列分片
基于Dispatcher参数M=4时的虚拟机保护框架,对指令序列进行分片,在每一层分片指令序列末尾采用随机函数对分片代码进行连接,而Dispatcher其他部分不进行安全性增强变换。将单个Dispatcher模块执行序列的分片数目记为N,在不同分片数目下测试程序的时间和空间开销,实验数据如表3和表4所示。由表可知,向多样化Dispatcher模块中插入路径随机选择函数时,将给受保护程序引入一定的时间和空间开销。增长的时间开销主要为连接不同指令片段间随机选择函数的执行时间,由于Dispatcher模块在选择调度执行Handler时,会被程序反复调度,因此Dispatcher模块中指令分片数目越多,受保护程序所执行的时间开销就越大。而增长的空间开销是由于不同指令分片间所插入的随机选择函数占用空间决定的。当参数M=4、N=2时,表示在Dispatcher模块中插入4个随机选择函数,当M=4、N=3时,插入4×2=8个随机选择函数,以此类推。
表3 不同分片数目下测试程序时间开销
表4 不同分片数目下测试程序空间开销
4.1.3 Dispatcher指令片段控制流迭代
基于Dispatcher参数M=4、N=4时的虚拟机保护框架,随机选择6个指令片段进行控制流迭代混淆,根据3.2中的理论分析,实验设计和选择运行速度快,所占存储空间小,安全性高的基于二维超混沌映射方程组的不透明谓词来对控制流进行迭代混淆[20],Dispatcher模块中其他部分不进行安全性增强变换。记单个指令片段迭代次数为K,K=0时为虚拟机保护框架未进行控制流混淆的初始状态,不同迭代次数下的测试程序时间和空间开销如表5和表6所示。随着迭代次数的增加,基于不透明谓词的迭代控制流混淆方法将使得受保护程序的控制流循环复杂度[23]不断增加,提升了虚拟机整体语义混淆能力。
表5 不同迭代次数下测试程序时间开销
表6 不同迭代次数下测试程序空间开销
4.1.4 Dispatcher指令片段加密
基于Dispatcher参数M=4、N=4和基于6个指令片段K=4时的虚拟机保护框架,随机选择Dispatcher模块中的4个指令碎片进行加密,记为D=4,根据软件保护有效性评估指标[23]和3.2中的理论分析,从算法简单、实现便捷、加解密速度快、占用存储空间小以及具有一定安全性的角度出发,实验在虚拟机保护框架中嵌入了TEA、RC4和Xor三种不同形式的加解密函数模块,记为X=3,运用该参数下的虚拟机框架(MNKDX_VM)对测试程序进行保护,并对比测试程序在商业虚拟CV和VMP保护下的时间和空间开销。
一般虚拟机保护框架中,当Handler函数要执行时都要通过Dispatcher模块对其进行调度执行。由于MNKDX_VM的Dispatcher模块中引入了多种加密函数对指令片段进行加密处理,当Dispatcher模块需执行时,首先要对该模块中部分加密指令片段进行解密,然后再解密指令片段运行结束后,再次进行加密。该种方式将使程序运行时间大度增加,尤其在反复需要对Dispatcher模块进行调度的排序程序之中,具体实验数据如表7和表8。由此可得,在运用本文提出的Dispatcher安全性增强方法时,需要权衡保护强度和性能开销,合理调整M、N、K、D和X五个参数,通常只对软件中关键算法或模块进行保护。
表7 不同虚拟机下测试程序时间开销
表8 不同虚拟机下测试程序空间开销
对比不同虚拟机框架下受保护程序,相关信息如表9所示。商业级虚拟机框架CV和VMP中Handler数目比原始VM和MNKDX_VM要多,由于MNKDX_VM是基于原始VM框架的Dispatcher模块强度升级,因此两者Handler数目和指令序列状态一致,MNKDX_VM中Dispatcher模块中的指令状态根据D数目部分或全部为密文。MNKDX_VM相比原始VM、CV和VMP在Dispatcher模块强度上进行了改进,增加的加解密、随机选择和不透明谓词等函数大幅度增加了逆向攻击者的分析难度。
表9 不同虚拟机下测试程序信息比较
为了测试本文方法在实际场景下针对典型应用程序的真实效果,在Win7操作系统下选取实验材料5)中五款程序,分别采用CV、VMP和MNKDX_VM对其进行保护,相关实验测试结果如表10和表11所示。由数据可得,未受到保护时的原始程序都可以正常运行,而且启动时间都小于0.5秒,当采用VMP虚拟机保护软件对其进行保护时,notepad和regedt32两款图形化程序依然可以正常运行,启动时间小于1秒,而其他三款程序都不能正常启动。当采用CV虚拟机保护软件对应用程序进行保护时,本文分别选择了其中速度最快的虚拟机引擎tiger32_red和复杂程度最高的虚拟机引擎eagle32_black;当采用CV对实际应用程序进行保护时,除了regedt32依旧能够在较大的时间开销(tiger32_red为3秒左右,eagle32_black为8秒左右)下正常运行,其他应用程序都不能够正常启动。而在设定M=4、N=4、K=4、D=4和X=3,采用本文虚拟机框架对这些应用程序进行保护时,五款程序都能够正常运行,而且增长的时间开销控制在一个较为合理的范围。另外从实际场景下的应用程序所占系统空间来看,本文方法保护后的程序所占系统空间相比CV和VMP有较大幅度的降低。
表10 不同虚拟机下应用程序运行状态
续表10
表11 不同虚拟机下应用程序空间开销
本文针对现有虚拟机保护框架中Dispatcher模块抗逆向攻击安全性不高的问题,提出一种Dispatcher模块安全性的增强方法,提升了虚拟机保护框架中Dispatcher模块代码对静态反汇编、内存断点和优化等逆向分析难度,提升了虚拟机保护框架的整体安全性。该方法同样适用于现有其他虚拟机Dispatcher模块的强度升级。但在强度提升的同时,将给受保护程序引入一些额外的函数及运行控制信息,会带来一定的时间和空间开销。在实际使用本文方法时,需要合理对不同参数进行设置,以平衡保护强度和性能开销。下一步将对控制流迭代混淆和加解密方案进行优化,并在大量具有不同结构的程序基础上,对本文相关参数的最优选择问题进行深入研究。