乐德广,赵 杰,龚声蓉
1.常熟理工学院 计算机科学与工程学院,江苏 常熟215500
2.苏州同程网络科技股份有限公司,江苏 苏州215123
ARM架构的处理器以其高速度、低功耗等许多优异的特性而在智能手机中得到非常广泛的应用。目前大部分的安卓智能手机都采用ARM处理器。与此同时,ARM应用程序的数量每年呈现指数式的增长。2019年,全球手机APP下载量高达2040亿次[1],在休闲、娱乐、社交、办公、投资、购物、生活和医疗等方面都有手机APP的身影,它们不仅影响着人们的日常生活,而且推动着经济发展。
在各类ARM应用呈井喷式增长,为用户带来便利和促进经济增长的同时,针对ARM架构的程序攻击日益突出,如ARM指令形式化变换逆向工程、静态分析和动态调试等[2],带来了巨大的隐私信息泄露的隐患,也造成各种恶意软件和盗版应用泛滥。因此,如何保护基于ARM处理器的应用程序,成为软件安全研究的重点。
代码混淆是一种保留语义的程序变换技术,其目的是使程序逻辑变得难以理解,增加逆向工程的难度,从而有效保护软件[3]。文献[4]在分析安卓软件遭受恶意攻击的基础上,提出一种基于Java的安卓应用代码混淆技术,旨在提高安卓应用代码的隐蔽性,进而使安卓应用得到更有效的保护。但是该技术工作在Java层,很容易被逆向反编译。文献[5]通过研究市场上流行的软件混淆技术,开发了一种主要针对Smali代码的混淆算法,研究其控制流混淆,变量混淆和虚假代码的注入,通过分析反编译工具的弱点,其混淆之后的代码可以免受反编译工具的静态分析。文献[6]通过对原DEX文件进行重构和加密,将其关键Java函数属性改为Native(C/C++属性),并通过Hook技术和反射机制隐式恢复并执行原Java函数,可以有效抵御静态分析攻击,获取高强度的保护效果。
以上ARM程序混淆方法都是从高级语言(如Java或C/C++)层面,或者中间语言(如Smali)层面对程序进行混淆,没有从底层汇编指令层面进行考虑,并充分利用ARM指令集的工作模式及寄存器间接寻址等特性对程序进行混淆。而目前针对底层汇编ARM指令的混淆,尚未有适当理论方法来指导生成多模式切换的指令混淆以准确测试ARM应用安全充分性、达到更全面的代码指令安全。
为此,本文研究如何从混淆底层ARM汇编指令方式的角度,提高ARM程序安全性,并提出一种基于模式切换的ARM汇编代码混淆算法。该算法通过ARM处理器的多指令集工作模式建立新的模式切换模型,描述ARM指令切换行为的全貌和逻辑规律,提高指令混淆中考虑模式切换的全面性,建立指令混淆的新型指令模式切换混淆方式,并提出对应的指令切换混淆算法。此外,在对ARM指令集中数据处理和切换指令进行分析的同时,结合ARM体系结构寄存器寻址特征,提出基于寄存器间接寻址跳转的ARM寄存器混淆和虚假指令混淆的优化方法,使得ARM应用中关键代码的安全性在ARM平台上进一步的提升。
ARM是一种低功耗、高性能的RISC处理器架构。ARM体系结构采用定长指令,指令格式和寻址方式也相对简单,大大降低了体系结构的复杂性。为兼容数据总线宽度为16位的应用系统,ARM体系结构除了支持执行效率很高的32位ARM指令集以外,同时支持16位的Thumb指令集[7]。与ARM指令集相比较,Thumb指令集中的数据处理指令的操作数和指令地址仍然是32位,但Thumb指令集为实现16位的指令长度,并舍弃了ARM指令集的一些特性,如ARM指令大多数都是有条件执行的,而Thumb指令则是无条件执行。此外,大多数的Thumb数据处理指令的目的寄存器与其中一个源寄存器相同。因此,与等价的32位指令相比,Thumb指令集在保留32指令优势的同时,大大地节省了系统的存储空间。
在ARM应用程序的编写过程中,只要遵循一定调用的规则,Thumb子程序和ARM子程序可以互相调用[8]。当处理器在执行ARM程序段时,称ARM处理器处于ARM工作模式,当处理器在执行Thumb程序段时,称ARM处理器处于Thumb工作模式。和X86程序一样,ARM程序面临逆向攻击。在程序逆向分析中,一般以反汇编技术为基础通过分析二进制文件,利用线性扫描或递归遍历算法反汇编整个可执行程序,将机器码翻译成汇编代码[9],并通过分析和处理汇编指令发现其内部的行为和特征。文献[10]提出一种结合具体路径执行和递归遍历算法的反汇编技术,用于更好地针对自修改和重叠指令的逆向工程分析。文献[11]根据所有可能的基本块可以通过推测恢复的原理,提出一种二进制代码的推测性反汇编技术。该技术结合重叠冲突分析和控制流图冲突分析提炼基本块来确定汇编指令。文献[12]利用演绎验证和有界模型检测,提出一种基于反向有界动态符号执行的反汇编方法实现对混淆代码的动态精确反汇编分析。文献[13]通过增加成功的反汇编样本,并基于SVM进行对它们进行训练和分类,然后结合线性扫描算法建立一种轻量反汇编方法,有效反汇编出代码混淆后的指令操作码。文献[14]通过对程序运行时的能耗侧信道泄露监测和结合机器学习算法确定其汇编指令,并提出一种侧信道反汇编器用于固件逆向工程。文献[15]通过模拟二进制程序分析中的不确定性提出一种概率反汇编技术。该技术基于融合一组能到达地址的不确定特征计算代码空间中每个地址的概率,用于指示该地址表示真正指令的可能性,并进行相应的反汇编,从而实现对反汇编代码的求精,即在避免漏报的同时,具有很低的误报率。
因此,通过反汇编对ARM程序进行逆向分析与利用,引起ARM程序的破解和盗版、隐私数据泄露和知识产权窃取等安全问题。
在ARM反汇编中,因为ARM和Thumb指令集切换的不明晰而导致切换信息丢失并且造成反汇编出现错误。因此,在下面提出的ARM汇编代码混淆算法中,结合Thumb模式与ARM模式,首先通过指令等价变换将Thumb模式中的指令序列变换成ARM模式下的指令序列。其次,利用切换指令在Thumb模式和ARM模式之间进行切换混淆,使程序的控制流复杂化,并利用寄存器间接寻址混淆进一步隐藏切换信息,导致在反汇编中漏解析本来可达的分支流程或者错解析本来不可达的分支流程。此外,通过虚假指令对反汇编工具的混淆作用,在程序中构造虚假分支,来迷惑反汇编器,达到代码保护的目的。算法总体架构如图1所示。
图1 算法总体架构Fig.1 Architecture of algorithm
在图1中,首先预处理待保护的Thumb指令,定位关键指令序列ST,然后对关键指令序列进行等价变形SA指令序列。接着,在原始Thumb模式中添加新ARM模式,并将变形处理后新生成的SA指令序列保存在新的ARM模式中。然后,利用切换指令对程序指令进行模式切换,当程序执行到关键指令序列时,会切换至ARM模式中执行变形处理后的指令序列,并利用MT寄存器混淆保护模式切换地址。同时,在模式切换过程中加入QT虚假指令序列填充ST指令序列空间。根据图1算法架构,基于模式切换的ARM汇编代码混淆算法描述如下:
步骤1在Thumb指令序列中选定一个分割位置P,设定分割位置后的待移动指令序列为ST,ST的结束位置为E。
步骤2在分割位置P处,随机选定可用的寄存器作为间接跳转寻址的寄存器。
步骤3将Thumb指令序列ST转换为等价的ARM指令序列SA:AR M(SA)=Thumb(ST)。
步骤4在ARM指令序列中选取一个新位置P1,将SA放在位置P1处。
步骤5在Thumb指令集中构造寄存器混淆指令序列MT,使得步骤2中选定的寄存器的计算结果为新的跳转位置P1。
步骤6在Thumb指令集中构造切换指令,使MT之后的控制流跳转到P1处,并将指令集切换至ARM模式。
步骤7如果len(ST)-len(MT+1)>0,那么在切换指令之后构造虚假指令序列QT,且len(QT)=len(ST)-len(MT+1)。
步骤8在SA的最后构造指令并使用切换指令跳转至位置E,并将指令集切换至Thumb模式。
算法中的模式切换使用ARM汇编指令集中的模式切换指令来进行构造。其中,根据变形模板函数ARM()和变形参数等对指令序列ST进行变形,生成变形指令序列SA。而SA处的地址是寄存器间接选址,且寄存器值是通过间接计算指令序列MT混淆产生。在进行反汇编时,反汇编器无法直接判定寄存器值,所以不能确定模式切换路径SA的执行入口信息,造成递归扫描反汇编结果出错。另外,通过在切换指令后插入不可执行虚假指令序列QT构建另一条分支路径。因为添加的切换指令是合法的,且不可执行虚假指令序列QT处的地址也是合法的目的地址,所以QT处的虚假指令序列一定会被反汇编器反汇编,从而将虚假指令QT与其后的原指令结合在一起,引起线性扫描反汇编出错。
根据上述算法描述,ARM汇编代码混淆部分主要包含三方面研究内容:(1)模式切换混淆,主要通过模式切换指令和设计指令变形的模板函数ARM(),利用模板函数对原始指令进行等价变形和模式切换。(2)寄存器混淆,通过寄存器随机分配及间接选址计算,隐藏模式切换指令的真实地址。(3)虚假指令混淆,提供了在算法中利用虚假指令序列来实现对于线性扫描反汇编的重同步的延迟。下面重点介绍这些关键技术。
在ARM程序中有两种方法可以实现程序控制流程的跳转,一种是直接向PC寄存器赋值实现跳转,另一种是使用跳转指令直接跳转。其中,BX(Branch eXchange)是带模式切换的跳转指令,跳转到指定的目标地址执行程序。ARM架构支持在一个上下文中运行ARM和Thumb两种指令集,它们可通过BX指令进行切换。BX指令格式如下所示:
其中,BX需要一个目标地址寄存器作为第一操作数:BX寄存器(Rx)。如果目标地址寄存器的[0]位为1,则跳转时将当前程序状态寄存器(Current Program Status Register,CPSR)中的标志T置位,即把目标地址的代码解释为Thumb指令。如果目标地址寄存器的[0]位为0,则跳转时将CPSR中的标志T复位,即把目标地址的代码解释为ARM指令。因此,切换依据如下所示:
在以上代码中,如果需要跳转的地址模2余1则表示跳转到Thumb模式,否则就是ARM模式。如果原始程序是Thumb模式,那么就可以利用这个特性来混淆ARM汇编指令。例如,混淆前关键代码如下所示:
在以上混淆代码中,同时存在Thumb和ARM两种模式的指令序列时,反汇编工具在进行反汇编时需要准确地识别出不同位置对应的指令序列。但是在多模式指令混淆中,通过2次BX实现了跨指令集的来回切换,再配合其他等价变形指令[0x233C~0x2360],所以反汇编工具在执行自动分析时往往容易出错,包括无法识别和识别成错误模式。
为了进一步加强复杂度,需要对Rx寄存器混淆,从静态和动态两方面增加逆向分析的难度。寄存器混淆的核心思想是将寄存器随机分配和间接化计算,同时保持程序指令及其行为相同,即把立即数赋值变换成间接赋值,并把一个指令的输出值重新定位到一个任意的内部寄存器,导致代码中每次出现的指令版本都是不相同,大大增加动静态分析难度。例如:
在代码片段[0x2236~0x2242]中,通过“ADD R6,R6,R5”计算最终的跳转地址,它的结果依赖寄存器R5的值,而R5又是从内存地址R4中读取的,即需要再进一步向前分析向该内存地址存储写数据的位置。这样就实现了R5的一个间接赋值,从而使得其更难看出0x2242处BX R6实际的跳转位置。
由于虚假指令对反汇编工具的混淆作用,在本算法中利用虚假指令序列来实现对线性扫描反汇编的重同步的延迟。虚假指令本身对程序执行并没有影响,因此虚假指令序列的主体部分还需要无效指令和随机数据的填充。为有效利用这部分填充的随机数据,在生成虚假指令序列时,就需要对这部分数据进行有目的的构造,在随机的数据中引用原程序中的元变量和对象(如寄存器和内存地址等),通过写内存和加减法计算,然后根据指令长度选取指定数量指令形成虚假指令序列,进一步提升虚假分支的迷惑性,提高混淆强度。此外,为了使元数据更有效与虚假指令配合,元数据会经过变形,误导反汇编器的判断,使得即使发现此处的指令,也无法确定指令的执行逻辑。例如:
其中,地址空间0x2242到0x2244是在添加了模式切换BX R6指令后,反汇编引擎对二进制数据进行的重新解读所得出的反汇编虚假指令序列。这里,利用了寄存器R1和内存地址[SP,#0x1C+var_10]作为元数据构造虚假指令。尽管动态执行无法执行其原本的功能,但是仍然会进行静态线性扫描反汇编,因为原本的二进制数据并未发生改变,因此更难看出[0x2242~0x2244]处的指令是虚假指令,使得逆向分析者无法进行正确的静态分析。
本文实验平台选取Google Pixel XL硬件和Android 7.1.1操作系统。选用的测试程序来自arm mbedtls密码库,分别是MD5和SHA1哈希算法,及DES和AES加密算法程序[16],并参考Collberg提出的对代码混淆的评价指标[17],分别从强度、弹性和开销三方面进行测试与分析。
经过本文算法混淆后汇编代码块的指令数量和跳转指令的数量显著增加,同时程序的控制流变得更加复杂。因此,本文以控制流循环复杂度作为强度测试指标,分析混淆前后控制流循环复杂度的变化。控制流循环复杂度记为V(G)[18],计算公式如公式(1)所示:
其中,e表示控制流图中边的数量,n表示控制流图中节点的数量。
由于混淆前,程序的控制流仅涉及Thumb模式逻辑,而混淆后的程序控制流包含Thumb模式的原始逻辑和ARM模式的新增混淆两种执行逻辑。为降低分析的复杂度,首先把混淆前的ST执行逻辑看作一个整体,其控制流循环复杂度表示为x。混淆后,SA的执行逻辑部分的控制流循环复杂度为执行ARM模式等价指令变换混淆部分的控制流循环复杂度,如3.1节所示,仍然把它看作一个整体,表示为y1。其次,SA的模式切换入口混淆指令变换部分的执行代码逻辑主要由MT和QT构成,如3.2节和3.3节所示,其控制流循环复杂度分别表示为y2和y3。其中,y2为寄存器混淆的控制流循环复杂度,y3为虚假指令混淆的控制流循环复杂度。最后,通过模式切换指令在整个混淆后的汇编代码的控制流中新增2条边,即e=2。综上,混淆前后程序的控制流循环复杂度比为VT/V=(y1+y2+y3+2)/x。根据ARM原始程序Thumb模式逻辑,在4个测试用例中分别选取MD5的mbedtls_md5_update_ret、SHA1的mbedtls_sha1_update_ret、AES的mbedtls_internal_aes_encrypt、DES的mbedtls_des_crypt_ecb作为测试函数,计算其混淆前后的x和y1,y2,y3值,其测试结果如表1所示。
表1 程序混淆前后控制流循环复杂度比较Table1 Comparison of cyclomatic complexity between two programs before and after obfuscation
由表1可知,混淆后ARM应用程序控制流循环复杂度远大于混淆前,复杂度的增加主要是由于本文算法的寄存器混淆和虚假花指令混淆所产生,这部分复杂度远大于原始ST的复杂度,所以会引起较大的增长率,说明本文混淆方法对ARM应用程序的混淆强度大大提高。此外,本混淆算法的核心在于对原始ST控制流的破坏,且混淆后的yi,i=1,2,3是一个不确定量,使得像IDA Pro等常见的控制流分析工具难以准确地分析其控制流循环复杂度。所以从这个角度看,它同样提高了混淆的强度。
弹性的度量用于衡量本文算法能够抵抗ARM反汇编逆向分析的能力。本文利用IDA Pro自动化分析逆向工具对指令模式切换混淆前后的程序进行比较,分析指令模式切换混淆抗逆向分析的效果。其中,在对MD5哈希算法中的mbedtls_md5_update_ret反汇编后,其混淆前后的局部控制流分别如图2和图3所示。
图2 mbedtls_md5_update_ret混淆前局部控制流图Fig.2 Partial control flow graph of mbedtls_md5_update_ret before obfuscation
图3 mbedtls_md5_update_ret混淆后局部控制流图Fig.3 Partial control flow graph of mbedtls_md5_update_ret after obfuscation
在图2中,混淆前的mbedtls_md5_update_ret局部控制流图的基本块“loc_12C8C”原本在结束位置会通过跳转指令跳转至基本块“loc_12CA2”,该局部控制流图被正确分析。从图3混淆后的mbedtls_md5_update_ret局部控制流发现,原本正确的控制流被截断,基本块“loc_12C8C”被分析成以“BX R4”结尾,因此在ARM反汇编分析时,不能直观地从控制流图中得出其跳转的信息,从而影响了其进一步准确地分析完整的控制流。
采用IDA Pro,Objdump和Radare等ARM逆向工具进一步对MD5、SHA1、DES和AES测试用例中的mbedtls_md5_update_ret、mbedtls_sha1_update_ret、mbedtls_internal_aes_encrypt和mbedtls_des_crypt_ecb关键函数进行逆向工程测试。表2显示了所有测试用例的测试结果。
表2 逆向工程测试结果Table 2 Test results of reverse engineering
从表2的测试结果可以看出,ARM静态逆向工程无法分析出被本文模式切换混淆过的关键函数。这是由于mbedtls_md5_update_ret、mbedtls_sha1_update_ret、mbedtls_internal_aes_encrypt和mbedtls_des_crypt_ecb函数的ST指令在Thumb模式中被切换混淆,而这些逆向编译工具都是基于Thumb模式中对这部分指令进行分析的,所以这些工具都不能正确地逆向编译出被模式切换混淆过的ARM汇编代码。
下面对混淆前后,程序的开销进行测试,测试指标包含体积开销和时间开销。首先,对MD5、SHA1、DES和AES四个测试用例程序进行体积开销测试,并与OLLVM混淆进行横向比较。这里定义GRS为程序文件体积的增长率(%),S0为混淆前程序的文件大小,S1为混淆后程序的文件大小。这样得到计算GRS的公式如公式(2)所示:
ARM汇编指令为定长指令,为准确衡量混淆前后程序大小的增加幅度,以汇编指令条数I量化体积大小,则对测试程序的体积性能开销的测试数据对比,在静态和动态情况下的结果分别如表3和表4所示。
表3 静态指令程序混淆前后体积开销比较Table 3 Comparison of size cost between two static instruction programs before and after obfuscation
从表3可以看出,本文方法混淆的程序静态指令增长的绝对值波动较小,集中在40条左右,这主要与选择的基本块指令数量有关,增长率则呈现出原始总质量越多,增长率越低的情况。而OLLVM混淆后的程序静态指令增长的绝对值波动较大,且其平均增长率为111.76%,是本文方法的14倍。动态指令是指在正确输入的情况下,混淆前后在执行时被执行到的指令,从表4可以看出,本文方法混淆后的程序动态指令增长的规律与静态指令一致,且比OLLVM混淆后的程序动态指令增长的绝对值和增长率都更低,因此本文方法的程序体积开销更小。
表4 动态指令程序混淆前后体积开销比较Table 4 Comparison of size cost between two dynamic instruction programs before and after obfuscation
时间开销是衡量软件保护技术的另一重要指标,定义程序运行时间增长率GRT:
其中,T1表示本文混淆算法处理后程序的运行时间,T0表示程序原始运行时间。分别对16、32、64和128 Byte的随机数进行MD5和SHA1哈希运算,以及DES和AES加密运算,其中DES和AES分别采用mbedtls中的测试密钥。表5显示了本文方法和OLLVM混淆前后的运算时间(ms)及其增长率(%)比较。
从表5可以看出,本文方法混淆前后程序运行时间增长率在[1%,21%]之间波动,呈现出与体积增长一致的特征,且原始运行时间越长,增长率就越低,对原始程序的影响就越小。此外,OLLVM混淆前后程序运行时间的增长率在[7%,44%]区间,不论是增长的大小还是波动范围都比本文方法大。因此,本文方法的时间开销对于程序本身运行影响不大。
表5 程序混淆前后时间开销比较Table 5 Comparison of time cost between two programs before and after obfuscation
针对ARM反汇编和逆向分析引起的ARM二进制程序破解和盗版、隐私数据泄露和知识产权窃取等安全问题,提出了一种基于模式切换的ARM汇编代码混淆算法,该算法对底层的汇编指令进行代码混淆,使反汇编时得到的错误汇编指令在之后的逐级逆向分析中,会由于逐步积累而很难获取有价值的结果。此外,由于模式切换和变形指令在变换混淆后,不论是在静态执行文件中,还是在内存里动态调试中,都呈现不同的控制流,因此给传统的静态和动态反汇编分析方法带来了极大挑战。同时,还对本文提出的代码混淆算法的强度、弹性和开销进行了测试评价。实验测试结果表明,该方法能有效地提升ARM程序的混淆强度和弹性,并且对于性能开销的消耗比较少。在下一步工作中,将结合ARM指令集和Thumb指令集的切换特性,继续扩展模式切换指令,进一步提高保护效果。在寄存器混淆中插入不透明谓词。针对虚假指令混淆,研究隐藏有效数据和进行二次分支跳转的条件,使虚假指令被更有效的利用。