2. 宁夏大学 物理电气信息学院, 银川 750021
目前针对Android平台的软件保护大多集中在如何保护Dalvik字节码程序, 对于本地代码程序的安全性还没有引起人们足够的重视.为了阻止攻击者对本地代码的破坏, 设计实现了一种Android平台本地代码保护方法.在原始代码中插入多个冗余数据和完整性校验代码, 并且对编译后的程序进行加密, 使本地代码具备了抵抗攻击者静态分析和动态篡改的能力.实验结果表明, 该方法可以在攻击者对本地代码进行篡改时及时地发现篡改行为, 从而有效地保护Android平台本地代码, 增强整个应用软件的安全性.
2. School of Physics Electrical Engineering, Ningxia University, Yinchuan 750021, China
Most research of Android software protection focuses on how to protect the Dalvik bytecode program. However, the security of native program has not been paid enough attention. In order to prevent possible attacks for Android native program, a protecting method was proposed. By inserting multiple redundant data and integrity check code into the source code, the native program will have abilities to resist dynamic tampering. It also can resist disassembling attack by combining with code encryption technology. Experiment shows that this method can protect the Android native program effectively when attacker tries to crack it by dynamically altering program code or disassembling the program.
Dalvik字节码程序结构简单,很容易将其反编译成Java源代码,因此互联网上出现了大量的Android盗版软件,严重损害了开发者的权益.
Aleksandrina[1]提出一种利用代码混淆来保护Dalvik字节码程序的方法,这种方法能提供较好的静态保护,但对攻击者的动态篡改基本无效. J Patrick Schulz[2]提出一种利用Android本地代码来产生“自修改”Dalvik字节码的方法,使得Dalvik字节码可以在运行时产生动态的变化.这种方法可以增加攻击者破解软件的难度,但如果攻击者直接对本地代码进行破解,那么Dalvik字节码的安全仍然无法得到保障.
如果能对文献[2]中的方法进行改进,对本地代码增加保护,使其在保护Dalvik字节码的同时也能提高自身的安全性,对于软件整体的安全性无疑是大有裨益的.基于这种考虑,笔者提出一种增强Android平台本地代码安全性的方法,利用代码校验和代码加密来保护本地代码,使其能有效地抵御攻击者的静态反编译和动态篡改攻击.
1 代码完整性校验和代码加密1.1 代码完整性校验代码的完整性校验是利用计算某段代码的Hash值来判断该段代码的完整性.由于需要在程序中存储原始Hash值并将其与计算出的Hash值进行比较,容易被攻击者发现,所以Bill Horne等提出一种代码完整性校验方法[3],原理如下.
1) 在程序中随机插入n个形如“if(Hash(start,end))RESPOND()”的保护代码.
2) 插入至少n个修正数据块c1, c2,…, cn.
3) 产生n个重叠的区间I1, I2,…, In,并使每个Ii中都有一个修正数据块ci.
4) 将各保护代码与区间Ii关联起来,并计算ci的值,使得Ii的Hash值总是为0.
该算法避免了出现Hash值总与一个特定值比较的语句,因此可以比较自然地将Hash校验函数融入原始代码中.而修正数据值的计算方程为
其中:xk为修正数据块的值;Cn-k+1为xk的系数;z为不含xk的区间的Hash值,
代码加密也称为“代码加壳”、“动态加载”,是一种在PC平台上比较常见的软件保护方法,其原理如图 1所示.
在代码加密的过程中,原始程序被当作数据加密存储在“加壳”程序的数据段中,形成一个新的程序.当这个新生成的程序开始运行时,首先执行的是“外壳”代码,由它将被保护的原始程序代码从数据段中解密,并释放到内存或文件系统中;然后“外壳”代码将运行权还给原始程序,继续运行原始程序,直至终止.如果攻击者对“加壳”后的代码进行逆向分析,看到的只是“外壳”代码,无法了解原始程序的任何信息.
2 Android本地代码保护方法为了增强Android本地代码的安全性,将代码加密方法与代码完整性校验方法结合起来,提出一种保护Android平台本地代码的方法,结构如图 2所示.分为2个处理阶段:一个是代码校验处理阶段,在此阶段为原始程序和解密加载程序加入代码校验功能;另一个是代码加密阶段,在此阶段加密前一段产生的原始程序,然后与解密加载程序一同打包.
代码校验处理阶段利用Bill Horne的代码完整性校验算法来对原始本地代码的源程序和解密加载程序进行处理,为其增加代码校验功能.文献[3]中只给出了算法,并没有具体的实现过程,因此在Android平台上实现其算法需要解决在什么位置插入Hash值计算函数和响应函数以及如何实现修正数据块等问题.代码校验处理的过程如下.
1) 以原始代码中的函数为单位,人工划定N个代码保护区间,每个区间包含1个或多个函数.
2) 选定保护区间中的1个或多个函数,将计算hash值和进行响应的代码插入选定的函数中.重复加入计算Hash值和响应的代码而不将其设计成函数是为了增加攻击的难度,避免这些函数一旦遭到篡改而使整个保护机制受到影响.
3) 在保护区间的任意一个函数中加入作为修正数据块的冗余数据.修正数据块的作用是保证整个保护区间的Hash值在正常情况下总为0,它绝不能参与或干扰原始程序的任何操作或计算,因此利用汇编语言在程序中插入一个用“.long”声明的冗余数据作为修正数据,然后使用“volatile”关键字来避免编译器将其当作无用数据删除.为了方便后续的计算,冗余数据的初始值设置为0.
4) 编译、调试并计算出正确的冗余数据值.
5) 将计算出的正确冗余数据填入原来的位置,调试程序以确保程序在正常的运行状态下,Hash代码的计算结果总是0.
6) 最后,编译修改完毕的程序,生成temp.so文件,进行整体测试.
除了对原始本地代码加入校验功能外,考虑到攻击者也可能对解密加载程序进行篡改,因此对解密加载程序(De_Load.so)也采取同样的代码校验处理,为其增加保护.
2.2 代码加密阶段代码加密阶段主要需要解决2个问题:一是加密算法的选择和密钥的生成、保护,二是密文的提取和加载.
1) 加密算法的选择和密钥的生成、保护
由于所设计的保护方法是应用在Android平台,需要考虑加密算法的复杂度,所以选择高级加密标准(AES, advanced encryption standard)作为代码加密的算法.密钥产生方法为
密钥Key =MD5(Time & Address)
其中:MD5表示消息摘要算法5(MD5, message digest algorithm 5),Time表示主机的系统时间,Address表示主机的IP地址,“&”表示将2个数据连接在一起.
为了保护密钥Key的安全,利用Shamir的(t, n)门限分存方案[4],将密钥隐藏在密文中,通过每次提取不同的因子来恢复密钥Key.密钥嵌入和提取的算法流程如图 3所示.
由于密钥Key的恢复过程是从m×n份密钥因子中随机地提取t×m份因子进行计算恢复的,所以使攻击者不容易发现密钥Key的恢复过程,从而保护密钥的安全.
2) 密文的提取和加载
对于密文的提取和加载,为了避免实现过于复杂,没有采用将密文嵌入解密程序中的做法,而是根据前述的“代码加密”的原理,设计实现了一种“间接”的提取加载方式,如图 4所示.
首先,解密加载程序De_Load.so从En_File中提取密钥Key,用密钥Key将En_File中的temp.so文件解密提取出来,存储在当前目录下;然后,De_Load.so加载提取出的temp.so文件,得到加载后的指针并从当前目录下删除temp.so文件;最后,De_Load.so利用上一步得到的指针,继续执行temp.so程序中的代码.
虽然在解密提取temp.so文件时,也可以将其存储在内存中,等De_Load.so程序结束后跳转到temp.so在内存中的地址继续执行,但这一过程需要依靠CPU提供的跳转指令来完成.对于移动平台来说,它并不具备类似PC平台x86那样统一的指令体系,即使是比较常见的ARM处理器,也具有多个版本,如v4、v5、v6、v7等,不同版本的处理器有不同的实现跳转的方法.因此,如果选择将temp.so文件释放到内存中再跳转执行,那么这种方式只能在特定版本的处理器上实现,不具备较好的通用性.所以在综合考虑之后,选择了将其释放到文件系统再进行加载的方式,从而避免了不同平台处理器之间的差异.另外,由于temp.so文件出现和消失的时间间隔很短,因此攻击者不容易发现temp.so文件的存在,从而“间接”地实现了“代码加密”方法.
3 实验为了对所设计的Android本地代码保护方法进行验证,设计了以下验证性实验.实验的硬件环境为普通的PC(操作系统为ubuntu13.10) 和一台ALCATEL 918D手机(操作系统版本为Android 2.3.5),开发环境为eclipse和Android NDK(版本为r9d).
首先,编写一个用于测试的本地代码文件temp.c,其中包括3个函数,调用关系如图 5左侧所示,在函数2中加入修正数据块,在函数3中加入校验响应代码,由函数3对函数2的完整性进行校验,根据校验的结果输出相应信息.修改后的3个函数形成如图 5右侧所示的结构.修改完成后,将temp.c编译为temp.so文件.
其次,取本机系统时间和IP地址,生成密钥Key,用AES加密算法加密temp.so文件,生成加密文件En_File,使用密钥嵌入算法将密钥Key添加到En_File中.
再次,根据密文的提取和加载算法,编写解密加载程序De_Load.c并加入与temp.so同样的校验保护,编译生成De_Load.so文件.将De_Load.so文件和加密文件En_File放在同一目录下.
最后,编写Dalvik字节码程序调用De_Load.so,将En_File中的temp.so恢复出来,加载temp.so文件并调用temp.so中的函数1.
现在,尝试对程序进行“破解”.首先,使用反编译工具IDA Pro对En_File进行反编译,由于En_File是加密状态,因此IDA Pro无法对其进行反编译;然后,尝试对temp.so中的函数2进行动态篡改,用gdb调试器在De_Load.so中设置断点,在中断程序执行之后查看程序内存,可以从中找到函数2在内存中的数据,如图 6所示;最后,对函数2的数据进行修改,如将第5行的“0x59”改成“0x00”,如图 7所示.
修改完成后,让程序继续运行,当程序执行到函数3时成功地检测到了篡改行为,并根据预设进行了响应.
4 结束语目前Android平台的软件保护大多集中在保护Dalvik字节码程序,对于本地代码程序的保护方法还较少.为了对Android平台本地代码程序进行保护,设计了一种结合代码校验和代码加密的Android本地代码保护方法.通过代码校验方法,可以阻止攻击者对本地代码程序的动态篡改攻击;而使用代码加密方法,可以阻止攻击者对本地代码程序的反编译攻击.通过实验证明了这种方法的有效性.通过使用所设计的Android本地代码保护方法,可以增强本地代码的安全性,提高Android平台应用程序的安全性,维护开发者的权益.
[1] | Aleksandrina Kovacheva. Efficient code obfuscation for Android[J].Communications in Computer and Information Science, 2013, 409: 104–119. doi: 10.1007/978-3-319-03783-7 |
[2] | Patrick Schulz. Android security analysis challenge: tampering Dalvik bytecode during runtime[EB/OL]. BlueboxSec, 2013. https://bluebox.com/technical/android-security-analysis-challenge-tampering-dalvik-bytecode-dur-ing-runtime. |
[3] | Bill Horne, Lesley Matheson, Casey Sheehan, et al. Dynamic self-checking techniques for improved tamper resistance[J].Security and Privacy in Digital Rights Management, 2002, 2320: 141–159. doi: 10.1007/3-540-47870-1 |
[4] | Shamir A. How to share a secret[J].Communications of the ACM, 1979, 24(11): 612–613. |