Android系统自诞生以来的短短几年时间,便迅速占领了智能手机市场。在这个过程中,Android系统不断地进行优化和改进,底层虚拟机也从Dalvik换成了ART(Android Runtime)[1]。在ART虚拟机(Virtual Machine, VM)出现之前,Android系统使用Dalvik作为解释执行Android应用的虚拟机,虽然Dalvik使用了JIT(Just In Time)技术[2],但是在效率上还是远不及iOS(iPhone Operating System)和Windows phone系统。这是因为Dalvik在运行时需要动态地将DEX字节码翻译成本地机器码执行,而对于运行在iOS和Windows phone上的应用程序来说,其本身就保存了可直接运行的机器码,运行时不再需要任何编译,这样在执行效率上就比Dalvik高很多。为了提高运行效率,Google公司提出ART虚拟机来改进Dalvik虚拟机的不足。ART虚拟机使用的是AOT(Ahead-Of-Time)技术[3],每次运行时不再需要编译任何代码,这样就提高了应用的执行效率,系统也更加流畅。由此,很多应用加固服务也开始将自身升级为针对ART环境的加固。加固服务的初衷是保护正常应用不被恶意反编译、篡改和重打包,但是一些恶意应用也常常使用加固技术[4]对自己进行加固,以躲避安全软件的查杀。虽然每个加固服务厂商都会在加固之前对应用进行安全检测,但还是能找到大量的被这些加固服务加固的恶意应用样本。迄今为止,移动平台的安全检测软件都是基于源码的检测[5],因此无法检测出被加固的恶意应用[6]。所以在强调加固[3, 7]保护研究的同时,也需要对脱壳技术进行研究,这样才能在保护正常应用程序的同时也能够防止恶意程序利用加固技术逃避检测。
本文提出了一个基于ART虚拟机的通用脱壳方案,该方案不需要理解加固服务使用的加固算法和策略,也不需要寻找加固后的APK中存在的特征文件,便可实现DEX的自动化脱壳。
1 ART虚拟机和Dalvik虚拟机和Dalvik虚拟机一致,ART虚拟机也是由Zygote进程创建的,区别是Dalvik虚拟机在libdvm.so库中实现,而ART虚拟机在libart.so库中实现。Android系统以系统属性persist.sys.dalvik.vm.lib的值来决定运行应用时使用的虚拟机。如果值等于libdvm.so,则在Dalvik虚拟机中运行应用;如果值等于libart.so,则在ART虚拟机中运行应用。
Dalvik为了减少应用的启动时间,在应用安装时会使用Installer类的成员函数dexopt对APK(Android Package)里面的Dalvik字节码进行优化,在/data/dalvik-cache目录中生成一个odex[8]文件,这样就可以避免在每次启动应用时都要从应用的安装包里解压出DEX文件。虽然Dalvik使用了JIT技术,将使用频率最高的字节码编译成本地指令执行,但是应用每次运行时还是需要编译一部分字节码。而ART虚拟机就不会存在这些问题,其使用AOT技术将编译过程置于应用运行之前。ART模式并不要求开发者将自己的应用直接编译成目标机器码,而是在应用安装时,使用dex2oat工具将Dalvik字节码编译成本地机器码,保存为一个OAT文件(一个自定义的ELF文件),这样就不会影响原有成熟的Android应用开发方式,同时程序运行时也不需要再对字节码进行编译,提高了执行效率。ART机制存在的问题是,安装应用时耗时较多,同时由于编译生成的OAT文件比原有的DEX文件大很多,所以也会额外消耗许多存储空间。
2 加固与脱壳技术简介加固技术经历了从最初的整包加密,到字节码变形,再到VMProtect的演变过程[9];脱壳技术也从最初的人工分析,逐渐发展到内存dump[10],进一步再到现在的基于Android源码插桩的方式[11],源码插桩是迄今为止最高效、最通用的脱壳方式。本文对每种加固与脱壳技术的优缺点进行了总结,如表 1、表 2所示。
整包加密是将整个DEX文件加密保存在其他目录,运行时通过加固者自己编写的动态链接库(*.so)将原始的DEX文件解密出来,重新映射到内存中,通过动态加载和反射调用的方式将应用真正的代码运行起来。这样能从一定程度上保护APP,同时将解密代码写到动态链接库中,增加了人工分析的难度。但是在Android HOOK[12]技术出现后,这种方法就不再适用了,破解者可以通过直接dump应用程序内存的方式得到内存中解密出的DEX文件。于是字节码变形的加固方式便应运而生[13],该方法主要是对classes.dex文件的所有method部分的数据进行加密,以随机的名称存储在隐蔽的文件夹中,应用运行时将这部分数据解密出来,映射到内存中,通过修改codeoff指针重新指向这些method,同时还采用各种复杂的Anti-debugging技术[14]来阻碍人工分析和调试,通过只解密恢复当前执行代码的策略来防止内存dump。VMProtect是目前已知的加固效果最好的方式,但这种方式还停留在研究阶段,是未来Android应用加固的发展方向。
脱壳技术的发展是随着加固技术的发展而发展起来的,其中最通用也最耗时的脱壳方式是人工分析,这种方法在技术发展的早期很有效,其明显缺点是需要分析人员有大量的分析经验。基于HOOK技术的脱壳方法随后出现,通过HOOK技术,脱壳程序可以附加到应用程序的进程中,通过在内存中搜索关键词如dex035、dey036等,定位内存中解密后的DEX文件或者odex文件,直接dump这段内存即可得到应用真正的DEX文件。但是随着加固技术的发展,加固者会将解密出的DEX文件的魔数(magic)值抹除,并且采用只解密恢复正在执行的method的策略[15],在这种情况下直接dump内存的方法便不再通用。最近出现的源码插桩方法可以解决内存dump方法无法解决的问题,该方法通过修改Android源码,定制一个具有脱壳功能的ROM来实现脱壳。这种方式不受任何Anti-debugging的影响,也不会因为在内存中匹配不到关键字而无法脱壳。本文就是在源码插桩技术的基础上结合模拟运行技术,提出了一种通用的自动脱壳方法。
3 脱壳模型构建和实现由第2章的分析可以看出,目前实现自动化脱壳面临的主要问题有3个方面:
1) 通用脱壳点的定位。不同的加固服务会有不同的特征文件和特征函数,如何准确找到一个通用的脱壳位置是一个需要解决的问题,在该脱壳点上能够对几乎所有类型的加固服务进行脱壳,该脱壳点不会随加固服务的变化而变化。
2) 绕过Anti-debugging技术。许多Anti-debugging技术会检测脱壳行为,在检测到脱壳行为后会终止程序的运行,从而导致脱壳失败。
3) 解密还未运行的代码。运行时解密策略会导致传统的脱壳技术不能还原出还未运行的代码,从而无法还原出完整的原始DEX文件。
本文围绕以上的3个问题,提出了一种通用的基于ART虚拟机的自动化脱壳方案,通过分析ART虚拟机中类的加载过程和方法的执行过程找到通用的脱壳点,通过源码插桩技术隐藏脱壳行为,通过模拟运行技术还原出所有被加固的代码。整个脱壳过程如图 2所示。将加固的APK安装在修改了系统源码(将开发的脱壳系统的代码加入系统源码)的Android手机中,然后点击运行,运行时脱壳系统会先对通用脱壳点进行监测,如果监测到,就开启模拟运行,得到应用的DEX文件数据并收集这些数据,最后再根据收集的这些数据进行DEX重组,完成脱壳,如果没有,则不进行脱壳操作。
通用脱壳点的选择在脱壳过程中是非常关键的,目前为止,只有DexHunter[16]研究了基于ART虚拟机的脱壳,主要思路是通过对所有加固方式进行详细的分析,为每个加固服务找出一个特征字符串来定位脱壳点,选择的关键函数是类的三种加载方式(ClassLoader.loadClass、artAllocobjectFromCode和Class.forName)共同调用的函数DefineClass,然后在这个函数里插入DexHunter的脱壳代码在应用运行时进行脱壳。
本文对ART虚拟机的类加载过程以及类包含的method的执行过程进行了详细研究。由于ART虚拟机在应用安装时就将应用的Dalvik字节码编译成了本地机器码,程序运行时直接执行应用编译好的本地机器指令,所以ART虚拟机不像Dalvik虚拟机那样一直需要解释器来执行应用的代码,只有在应用的字节码没有对应的本地代码时才会使用解释器执行应用的字节码。如图 3所示,安装时系统会首先根据ART_USE_PORTABLE_COMPILER的值来选择编译方式,如果该值为true,那么系统使用Portable后端将Dalvik字节码编译成本地机器指令,如果该值为false,则系统使用Quick后端进行编译。使用哪种后端进行编译只会影响应用的运行速度,不会影响到执行程序时是否使用解释器。编译好之后在ClassLinker里的NeedsInterpreter函数会判断执行过程中是否使用解释器执行应用代码,如果虚拟机不是运行在解释模式,而且执行的method不是本地method,也不是代理method,就直接执行编译好的DEX本地机器码。如果ART虚拟机启动时使用了-Xint参数,运行在解释模式,那么所有的method就都会使用解释器执行,解释执行的函数为Execute。
对于加固技术来说,不管是Dalvik还是ART,都选择使用动态加载和反射调用的方式启动应用的DEX文件。这是因为由于Android系统存在碎片化问题,目前还无法做到在保持良好兼容性的同时对本地机器码进行动态修改[3]。也就是说目前的加壳服务对于应用的加固还是选择修改Dalvik字节码,这样加固程序运行时就必然会使用ART的解释器了,因为修改后的字节码要经过解释后才能运行,这就必然会经过Execute函数。综上所述,该系统选用Execute函数作为关键函数,插入脱壳代码。
找到关键函数后,本文不使用像DexHunter那样的特征字符串来进行脱壳点定位,而是使用应用的MainActivity来作为脱壳定位点[15]。因为无论应用使用哪种加固服务,执行流程始终会转到原始程序的MainActivity来实现程序的正常功能, 所以并不用寻找加固后的应用中与加固服务相关的特征文件或者执行函数,这些函数的功能只是在运行时将加密的DEX文件进行还原。只需确定应用原始的MainActivity执行开始执行,就可以判断应用的DEX文件已经被还原到内存中了,因此MainActivity可以作为一个通用的脱壳标识。
3.2 模拟运行模拟运行主要是应对加固服务的运行时解密策略,即只解密恢复正在执行的method的代码。通过对Android源码的分析与类加载和类method执行过程的研究,找到可以实现模拟运行的方法。根据3.1节中找到的通用脱壳点,将模拟运行的实现放在Execute函数中。和Dalvik中一样,在ART中,应用的所有method都存储在class_defs结构中,class_defs结构中又存放着每一个class的指针,指向每一个class,而每个class都有一个class_data_item结构,method信息就保存在这个结构中。在每个method执行时都需要将整个class初始化,保证脱壳时,类的变量的初始化完成,特别是静态method中的变量初始化,从而保证提取出的DEX文件的正确性。同时类加载时可能没有执行Clinit函数,这个函数需要先于其他所有函数执行。
通过分析ART虚拟机中method的执行过程,本文提出了一种创新的方法,可以模拟执行所有的method,从而应对运行时解密策略。在每个method的执行过程中,ClassLinker里会调用LinkCode函数定位到method的代码去执行,但是在执行前会使用NeedsInterpreter函数判断是使用解释器执行还是直接执行本地机器码,如果该method没有对应的机器码,就使用解释器执行。使用解释器执行时会调用GetCompiledCodeToInterpreterBridge函数,该函数就是从执行本地机器码的方式转为使用解释器执行。而在使用解释器执行前,加密服务需要保证传入解释器的字节码是应用被加壳前原始的字节码,也即在调用该函数时,正确的字节码必然已经被恢复在内存中。所以可以通过遍历每个method,然后调用该函数实现对每个method的模拟运行,得到所有method的正确字节码。整个过程如图 4所示。
这里使用FindClass来查找DEX文件中所有的class,使用这个函数可以避免重复查找class、重复初始化相同的类。该函数会返回一个class对象,然后通过这个class对象调用IsInitialized函数,判断该类是否初始化,如果初始化好了,就遍历该类中每一个method,如果没有初始化,就使用EnsureInitialized函数进行初始化,初始化完成之后得到一个kclass对象,然后在kclass中遍历所有method,并调用GetCompiledCodeToInterpreterBridge函数实现每个method的执行,执行完成后使用UpdateMethodsCode函数更新method代码,然后就可以将初始化后的类的数据从内存中dump下来。最后将这些收集好的class数据进行重组即可恢复出原始的DEX文件。
需要注意的是,在整个过程中,所有method的执行并不是应用主动进行的,而是在程序加载到内存中真正运行前,脱壳程序通过主动调用GetCompiledCodeToInterpreterBridge方法实现每个method的加载,因此称之为模拟运行。
3.3 DEX重组所有的数据收集完成之后,需要将它们根据标准DEX文件的格式进行重组,以恢复出加固前的原始DEX文件。重组时先将整个DEX文件dump出来,然后将所有收集好的数据放在这个文件的末尾。现有的加固服务几乎都是针对字节码的,少部分加固服务为了防止内存dump,对DEX文件的magic值进行了修改,但是使用在ART虚拟机中插入脱壳代码的方式,可以以结构体指针准确地dump整个DEX文件,而不会受此影响。对于不直接将类的数据分别还原到DEX文件里的原因是,class_data_item的成员是使用uleb128编码的,修改这些值会导致许多偏移地址的改变,这样修改起来就十分复杂。所以选择将收集好的class数据放在末尾,这样每个class数据都是固定大小的,每增加一个class数据,只需要将原有结构体中指向class数据的指针偏移这个class数据的大小就可以了,而不需要去修改其他指针的值。
整个重组方法如图 5所示,将模拟运行收集的class数据放在文件末尾,每个class数据又是由很多method的数据组成,统一放在一个class数据中,如图中的code_item。重组时还需要注意修改class_data_item中的codeoff,通过实际脱壳分析,ART虚拟机脱壳时不需要修改method_id和field_id,这与Dalvik虚拟机在解释器脱壳时的情况不一样;并且ART虚拟机可直接恢复出DEX文件,没有odex文件,不需要进行相应的转换。
修改Android4.4.4版本的源码中ART虚拟机的部分代码,加入基于本文提出的方法所开发的脱壳系统,然后将源码重编译后刷入搭载Qualcomm Snapdragon 800 2.3 GHz CPU和2 GB内存的nexus 5智能手机上,作为实验环境。
4.1 启动延时测试对于没有进行任何加固的应用来说,使用ART虚拟机运行时启动延时非常小,因为其在安装时已经将Dalvik字节码编译成了本地机器码。而对于加固的应用来说,在运行前需要先将加密的代码进行还原,所以加固行为会造成一定的启动延时。本文实现的脱壳系统需要通过模拟运行的方式得到原始的未经加密的方法,在模拟运行完成后程序才会正常执行,因此脱壳系统也会引入一定的启动延时。本节围绕该脱壳系统启动延时的问题,进行实验测试。
测试方式一 将不同大小的应用上传到同一种加固服务中进行加固,加固完成后分别安装到两台部署了该脱壳系统和没有部署该脱壳系统的nexus 5手机中运行,每个样本分别运行20次,启动方式使用am命令:
am start-n包名/.MainActivity
启动延时Ti为从命令执行开始到应用界面启动完成结束,分别记录下两个手机中加固应用的启动时间,最后计算脱壳的时间延时T,为了减小误差,脱壳延时定为20次的平均值,计算方法如下。
T=
加固服务为阿里加固,记录结果如图 6所示。
表 3列出了使用的10个测试应用,分别记录下APK的大小(单位为MB)和该APK中包含的DEX文件的大小(单位为MB)。从图 6中可以清楚地看到,在APK大小为5.21 MB时,脱壳延时有小幅度的回落,根据表 3中显示该APK的DEX文件大小为2.4 MB,小于大小为4.56 MB和7.1 MB的APK的DEX文件。所以,根据测试结果可以看出,启动延时和APK的大小无关,而和APK中DEX文件大小有关,DEX文件越大,其包含的代码量也就越大,因此脱壳延时就越大。
测试方式二 与DexHunter作脱壳延时对比。迄今为止只有DexHunter实现了ART中DEX文件的自动脱壳,因此本文将该系统的脱壳延时与DexHunter作一个对比。
为了便于计时,这里选择延时较明显的测试应用,APK大小分别为20.78 MB、15.4 MB、10 MB,分别将该系统和DexHunter部署到两台nexus 5手机上,另外再取一台没有部署任何脱壳系统的nexus 5手机,安装选择的测试APK,使用测试一中的脱壳延时计算方法,得到测试结果如表 4所示。
从表 4中可以看出,DexHunter的脱壳延时普遍比本文脱壳系统小,原因是本文脱壳系统引入了模拟运行方法,这样会使得脱壳延时增加。
4.2 对比分析DexHunter是一个非常著名的DEX脱壳系统,本节从脱壳方法、通用型等方面将本文提出的脱壳方法与DexHunter进行对比。
DexHunter脱壳过程中所使用的location_和fileName是其技术人员通过人工分析得到的,通过对每个加固服务的加固方式进行详细的分析和研究,选择每个加固服务的特征字符串作为脱壳开始的标志。采用这样的方法有一个限制,如果加固者改变了原来的加固方法,修改了加固后的特征文件,那么DexHunter就不能再准确地定位出脱壳点。而本文提出的脱壳方法不存在这样的缺点,该方法是在ART虚拟机的解释器中进行插桩,脱壳点是以APP运行时启动MainActivity作为定位,适合于所有的加固服务。本文方法通过解析APK中的配置来获取应用的MainActivity,任何加固服务都不能改变该配置文件,因为程序的运行必须要以这个配置文件为基础去注册相应的权限和组件,如果加固服务修改了其中的一个权限和组件信息,都会使加固后的应用无法运行。同时本文还提出了模拟运行的方法,这样就克服了只能恢复出部分正在执行的代码的缺陷。就通用性和脱壳性能来说,本文方法都优于DexHunter。
测试时使用最新的阿里加固将APP加固之后放在DexHunter里进行脱壳,发现DexHunter并不能成功脱壳。这是因为DexHunter使用的是/data/data/com.example.seventyfour.tencenttest/files/libmobisecy1.zip作为脱壳开始的定位标志,而最新的阿里加固已不再采用原来的加固方式,导致DexHunter不能定位到脱壳点。DexHunter的开发人员必须对阿里加固进行持续地跟踪分析,找出新的脱壳开始的标志,才能始终保证DexHunter兼容最新版的加固方式。而本文提出的方法不会受到这样的影响,该脱壳系统是以应用的MainActivity作为脱壳开始的标志,只需要通过解析AndroidMenifest.xml文件获取应用的MainActivity就可以了,不需要人工进行分析。
5 结语本文系统是植入到ART虚拟机中的,运行在Android系统的RunTime层,所以可以绕过所有的Anti-debugging进行有效脱壳。系统直接在真机上运行,有很好的兼容性和通用性。随着Android系统的发展,ART虚拟机必然完全替代Dalvik虚拟机,本文提出的脱壳方案可以实现ART虚拟机中的动态脱壳。但是所有的动态脱壳系统都有一个通病,在实现上会有某些特征,加固者可以通过这些特征加以对抗。加壳与脱壳技术是在不断的博弈中持续进步的,此方面的研究存在一定的周期性,新型加壳方法的出现会导致新一轮的脱壳技术研究热潮,如何针对最新的加壳方法进行通用性强的自动化脱壳是一个需要持续进行研究的内容。
[1] | 李霞. Android虚拟机运行时技术的分析与评测[D]. 南京: 东南大学, 2015. (LI X. Analysis and evaluation of runtime technology of Android virtual machine[D]. Nanjing:Southeast University, 2015.) http://cdmd.cnki.com.cn/Article/CDMD-10286-1016050449.htm |
[2] | ABSAR J, SHEKHAR D. Eliminating partially-redundant array-bounds check in the Android Dalvik JIT compiler[C]//Proceedings of the 9th International Conference on Principles and Practice of Programming in Java. New York:ACM, 2011:121-128. |
[3] | 张洪睿, 张亚腾. 一种ART模式下应用加固方案[J]. 软件, 2015, 36(12): 176-179. (ZHANG H R, ZHANG Y T. Application of packing scheme in ART mode[J]. Software, 2015, 36(12): 176-179. DOI:10.3969/j.issn.1003-6970.2015.12.040) |
[4] | LIAO Y, LI J, LI B, et al. Automated detection and classification for packed Android applications[C]//Proceedings of the 2016 IEEE International Conference on Mobile Services. Piscataway, NJ:IEEE, 2016:200-203. |
[5] | FEREIDOONI H, CONTI M, YAO D, et al. ANASTASIA:ANdroid mAlware detection using STatic analySIs of Applications[C]//Proceedings of the 20168th IFIP International Conference on New Technologies, Mobility and Security. Piscataway, NJ:IEEE, 2016:1-5. |
[6] | RASTOGI V, CHEN Y, JIANG X. DroidChameleon:evaluating Android anti-malware against transformation attacks[C]//Proceedings of the 8th ACM SIGSAC Symposium on Information, computer and Communications Security. New York:ACM, 2013:329-334. |
[7] | 朱洪军, 陈耀光, 华保健, 等. 一种Android应用加固方案[J]. 计算机应用与软件, 2016, 33(11): 297-300. (ZHU H J, CHEN Y G, HUA B J, et al. An Android application packing scheme[J]. Journal of Computer Applications and Software, 2016, 33(11): 297-300. DOI:10.3969/j.issn.1000-386x.2016.11.067) |
[8] | 邱寅峰, 泮晓波. APK文件的快速加载方法: CN201510657289. 9[P]. 2015-10-12. (QIU Y F, PAN X B. Fast loading method for APK files:CN201510657289.9[P]. 2015-10-12.) |
[9] | YU R. Android packers:facing the challenges, building solutions[EB/OL].[2016-11-20]. https://www.virusbulletin.com/uploads/pdf/conference/vb2014/VB2014-Yu.pdf. |
[10] | KANG M G, POOSANKAM P, YIN H. Renovo:a hidden code extractor for packed executables[C]//Proceedings of the 2007 ACM Workshop on Recurring Malcode. New York:ACM, 2007:46-53. |
[11] | LI J, ZHANG Y, YANG W, et al. DIAS:Automated online analysis for Android applications[C]//Proceedings of the 2014 IEEE International Conference on Computer and Information Technology. Washington, DC:IEEE Computer Society, 2014:307-314. |
[12] | STRAZZERE T. Android-unpacker[EB/OL].[2016-11-20].https://github.com/strazzere/androidunpacker. |
[13] | 高琦, 刘克胜, 常超, 等. 基于自修改字节码的Android软件保护技术研究[J]. 计算机应用与软件, 2016, 33(4): 230-234. (GAO Q, LIU K S, CHANG C, et al. Research on Android software protection technology based on self-modified bytecode[J]. Journal of Computer Applications and Software, 2016, 33(4): 230-234.) |
[14] | CHO H, LIM J, KIM H, et al. Anti-debugging scheme for protecting mobile apps on Android platform[J]. Journal of Supercomputing, 2016, 72(1): 1-15. DOI:10.1007/s11227-015-1595-5 |
[15] | YANG W, ZHANG Y, LI J, et al. AppSpear:bytecode decrypting and DEX reassembling for packed Android malware[C]//Proceedings of the 18th International Symposium Research in Attacks, Intrusions, and Defenses. Berlin:Springer, 2015:359-381. |
[16] | ZHANG Y, LUO X, YIN H. DexHunter:toward extracting hidden code from packed Android applications[C]//Proceedings of the 20th European Symposium on Research in Computer Security. Berlin:Springer, 2015:293-311. |