樂(lè)德廣,趙 杰,龔聲蓉
1(常熟理工學(xué)院 計(jì)算機(jī)科學(xué)與工程學(xué)院,江蘇 常熟 215500)2(蘇州同程網(wǎng)絡(luò)科技股份有限公司,江蘇 蘇州 215123)
隨著安卓移動(dòng)終端的普及,各種安卓應(yīng)用層出不窮.在安卓應(yīng)用給用戶(hù)帶來(lái)豐富體驗(yàn)的同時(shí),其安全問(wèn)題也日益突出[1].一方面,由于安卓系統(tǒng)的開(kāi)放性使得攻擊者可以在系統(tǒng)層面分析安卓應(yīng)用的運(yùn)行行為,并動(dòng)態(tài)獲取應(yīng)用中的數(shù)據(jù),造成用戶(hù)隱私數(shù)據(jù)泄露.另一方面,安卓應(yīng)用中大部分的邏輯由Java代碼實(shí)現(xiàn),并編譯成DEX格式的可執(zhí)行文件.該文件直接打包在APK文件包中,攻擊者通過(guò)逆向工程對(duì)其進(jìn)行逆向編譯獲取Smali或Java源碼,并進(jìn)行分析修改后重打包,使安卓應(yīng)用面臨木馬、廣告等惡意代碼注入攻擊.文獻(xiàn)[2]分析指出,在所考查的1,260個(gè)惡意應(yīng)用樣本中,有86%屬于重打包型惡意應(yīng)用.文獻(xiàn)[3]分析了來(lái)自應(yīng)用市場(chǎng)的84,767個(gè)安卓應(yīng)用,發(fā)現(xiàn)谷歌官方應(yīng)用市場(chǎng)中1.0%的應(yīng)用是盜版或山寨應(yīng)用,在第3方應(yīng)用市場(chǎng)中有0.97%至2.7%不等的應(yīng)用出現(xiàn)隱私數(shù)據(jù)竊取等問(wèn)題.因此,如何抵御對(duì)安卓應(yīng)用的逆向工程分析與利用,保護(hù)安卓應(yīng)用成為軟件安全的研究熱點(diǎn).
文獻(xiàn)[4]在安卓應(yīng)用開(kāi)發(fā)中利用反射調(diào)用機(jī)制隱藏對(duì)敏感API的調(diào)用和對(duì)敏感數(shù)據(jù)的存取.但是這種方式可通過(guò)查看是否利用Java反射調(diào)用進(jìn)行識(shí)別.文獻(xiàn)[5]基于加密和動(dòng)態(tài)加載技術(shù)提出反DEX文件靜態(tài)逆向工程的加殼保護(hù)方案,該方案是先把DEX文件作加密隱藏處理,然后將其封裝入殼程序中.在運(yùn)行時(shí),殼程序?qū)用芎蟮腄EX解密,得到可用的原DEX,然后用DEXClassLoader加載器將原DEX動(dòng)態(tài)加載運(yùn)行.這種加殼保護(hù)方案存在DEX文件被加載到內(nèi)存后其內(nèi)容是連續(xù)且解密的問(wèn)題,攻擊者可以通過(guò)脫殼技術(shù)獲取原DEX[6].文獻(xiàn)[7]針對(duì)DEX文件的脫殼破解問(wèn)題提出了一種通過(guò)JNI(Java Native Interface)機(jī)制的Native層SO(Shared Object)加固技術(shù),該技術(shù)將DEX文件內(nèi)的Java方法用C/C++實(shí)現(xiàn),并封裝到Native層的SO中.安卓應(yīng)用運(yùn)行時(shí),通過(guò)JNI機(jī)制對(duì)SO中的Java方法進(jìn)行動(dòng)態(tài)加載.這種加固技術(shù)可以結(jié)合IDA Pro等二進(jìn)制反編譯工具進(jìn)行逆向工程分析[8].文獻(xiàn)[9]針對(duì)抵御安卓應(yīng)用逆向工程的代碼混淆技術(shù)進(jìn)行分析,并分為名字混淆、控制流混淆以及指令編碼混淆.其中,名字混淆主要是將代碼中包、類(lèi)或者方法的名字變換為無(wú)意義的字符串.但是由于DEX字節(jié)碼在執(zhí)行時(shí)總是以明文形式存在內(nèi)存中,攻擊者針對(duì)名字混淆可以通過(guò)動(dòng)態(tài)分析,獲取DEX字節(jié)碼加載的實(shí)現(xiàn)邏輯[10].文獻(xiàn)[11]針對(duì)逆向工程在反編譯階段采用的線(xiàn)性?huà)呙韬瓦f歸遍歷算法,提出了分支翻轉(zhuǎn)花指令線(xiàn)性?huà)呙璺烙夹g(shù),以及通過(guò)調(diào)用轉(zhuǎn)換和跳表偽造等多種控制流混淆技術(shù)防止對(duì)程序的逆向遞歸遍歷.但是這些控制流混淆方法由于在安卓應(yīng)用運(yùn)行之前所有混淆代碼都可以靜態(tài)加載,難以抵御動(dòng)態(tài)污點(diǎn)追蹤技術(shù)的調(diào)試和分析.文獻(xiàn)[12]提出一種基于Huffman編碼和LZW編碼的移動(dòng)應(yīng)用混淆技術(shù).該技術(shù)使用LZW編碼對(duì)指令片段中的指令序列進(jìn)行重新排列,并使用Huffman編碼對(duì)每一種類(lèi)型的指令參數(shù)進(jìn)行重新排列,提高混淆技術(shù)的隱蔽性.但是該技術(shù)在混淆過(guò)程中,一段代碼片段會(huì)根據(jù)不同的指令生成一個(gè)LZW編碼表,并根據(jù)相同指令的不同參數(shù)生成多個(gè)Huffman編碼表.這兩類(lèi)編碼表需要通過(guò)動(dòng)態(tài)方式加載,且編碼表獨(dú)立于整個(gè)代碼片段存在.因此,需要通過(guò)加密方式對(duì)編碼表進(jìn)行保護(hù),這樣其混淆技術(shù)的安全強(qiáng)度主要取決于對(duì)編碼表的加密強(qiáng)度.目前經(jīng)典對(duì)稱(chēng)密碼和公鑰密碼分別存在密鑰和證書(shū)的保存及使用等問(wèn)題.
針對(duì)上述問(wèn)題,本文提出了一種抵御逆向工程的安卓應(yīng)用抽離映射混淆技術(shù).該技術(shù)首先對(duì)安卓應(yīng)用中Java方法的code_item代碼進(jìn)行抽離處理,其次根據(jù)操作碼映射表對(duì)抽離出來(lái)的code_item代碼進(jìn)行映射混淆,并將映射后的code_item混淆代碼生成code_item索引表后封裝到SO中.然后,利用JNI機(jī)制對(duì)SO中的code_item混淆代碼進(jìn)行注冊(cè).接著,根據(jù)操作碼映射表及Dalvik字節(jié)碼標(biāo)準(zhǔn)實(shí)現(xiàn)映射解釋器.最后,把映射解釋器封裝到SO后和code_item索引表一起生成映射解釋執(zhí)行環(huán)境.本技術(shù)不僅將安卓應(yīng)用進(jìn)行有效混淆,抵御逆向工程分析與利用,實(shí)現(xiàn)安卓應(yīng)用的安全保護(hù).而且混淆后的安卓應(yīng)用運(yùn)行邏輯保持正確,運(yùn)行的安卓系統(tǒng)環(huán)境保持不變.
安卓應(yīng)用的逆向工程主要是把APK(Android Application PacKage)程序通過(guò)逆向編譯等手段還原為可讀邏輯代碼,并進(jìn)行分析與利用的過(guò)程.安卓應(yīng)用APK程序包含Dalvik字節(jié)碼、資源和配置信息等,其內(nèi)部結(jié)構(gòu)由assets目錄、META-INF目錄、res目錄、AndroidManifest.xml文件、classes.dex文件和resources.arsc文件組成.其中,classes.dex是Java源碼編譯后生成的可執(zhí)行文件[13],通過(guò)逆向工程可將其反編譯成Smali或Java代碼,如圖1所示.

圖1 APK逆向工程Fig.1 APK reverse engineering
在圖1中,由于APK是一個(gè)ZIP壓縮格式的程序包,首先通過(guò)unzip將APK解壓,并提取出DEX文件.接著,通過(guò)jeb將DEX逆向編譯為Smali代碼.Smali采用Jasmin語(yǔ)法格式,類(lèi)似于匯編語(yǔ)言,具有良好的可讀性,可以看到變量、方法調(diào)用、系統(tǒng)API調(diào)用和返回類(lèi)型等信息.通過(guò)分析這些信息得出Smali代碼的執(zhí)行過(guò)程,并根據(jù)執(zhí)行過(guò)程在相應(yīng)代碼中找到關(guān)鍵點(diǎn)后修改或直接注入Smali代碼.最后,通過(guò)apktool重新編譯Smali代碼生成新的APK程序,稱(chēng)為重打包攻擊.文獻(xiàn)[14]將DEX反編譯生成Smali代碼,然后將惡意代碼拷貝到存放目標(biāo)應(yīng)用Smali文件的目錄中,同時(shí)將惡意代碼所有組件和其需要的應(yīng)用權(quán)限聲明添加到目標(biāo)應(yīng)用的AndroidManifest.xml文件中.最后,重新對(duì)被修改過(guò)的目標(biāo)應(yīng)用進(jìn)行簽名打包,生成新的APK程序,不僅破壞目標(biāo)應(yīng)用的完整性,而且擾亂程序功能.
此外,從圖1可以看出,DEX文件還可以先被dex2jar反編譯成Jar文件,該文件包含了大量編寫(xiě)代碼時(shí)的信息,如常量、變量、類(lèi)名和方法名等,都具有很強(qiáng)的可讀性.然后,通過(guò)jd-gui反編譯或直接通過(guò)jadx反編譯得到Java源代碼.由于Java源代碼的類(lèi)名和方法通常都有一定的命名規(guī)范,逆向分析人員很容易根據(jù)這些信息直接分析代碼功能和實(shí)現(xiàn)方法.文獻(xiàn)[15]提出將DEX反編譯為Java代碼,然后利用Java代碼切片及靜態(tài)分析工具分析安卓應(yīng)用的體系結(jié)構(gòu)、數(shù)據(jù)結(jié)構(gòu)和程序設(shè)計(jì)思路.文獻(xiàn)[16]將DEX文件反編譯成Java文件,再對(duì)安卓應(yīng)用進(jìn)行控制流、數(shù)據(jù)流、數(shù)據(jù)結(jié)構(gòu)和語(yǔ)義分析,發(fā)現(xiàn)安卓應(yīng)用的業(yè)務(wù)邏輯和應(yīng)用申請(qǐng)的權(quán)限信息,并對(duì)安卓應(yīng)用進(jìn)行山寨.文獻(xiàn)[17]使用dex2jar工具將DEX文件轉(zhuǎn)換為Jar文件,然后通過(guò)FindBugs工具統(tǒng)計(jì)安卓應(yīng)用中Intent的依賴(lài)關(guān)系和API調(diào)用情況.因此,通過(guò)對(duì)安卓應(yīng)用逆向工程分析與利用,還引起安卓應(yīng)用的盜版、隱私數(shù)據(jù)泄露、核心算法竊取等侵害他人知識(shí)產(chǎn)權(quán)的安全問(wèn)題.
本節(jié)重點(diǎn)研究APK程序中DEX文件的code_item代碼混淆技術(shù).首先,通過(guò)Java方法抽離映射混淆APK程序中DEX文件的code_item代碼.然后,結(jié)合Java方法注冊(cè)和映射解釋執(zhí)行確保混淆APK程序在安卓系統(tǒng)的正常運(yùn)行.
在安卓應(yīng)用中,其Java方法編譯在DEX文件的code_item代碼中.因此,首先在APK的DEX文件中找到Java方法對(duì)應(yīng)的code_item代碼,并將它從DEX文件中抽離出來(lái).例如一個(gè)算術(shù)運(yùn)算的Java方法,其源代碼如下所示.

publicclassOne{publicstaticinttest1(inta,intb){ intc=a?a; returnb+c;}}
以上Java方法test1編譯后在DEX文件中的code_item代碼為0x04000200000000000B952900050000009200020290 0103000F01.其中,0x92000202900103000F01為code_item的Dalvik字節(jié)碼,它對(duì)應(yīng)test1方法的c=a*a,b+c運(yùn)算指令和return返回指令.
Java方法映射是將code_item中的Dalvik字節(jié)碼進(jìn)行操作碼映射混淆.根據(jù)DEX格式,code_item的insns字段指向Dalvik字節(jié)碼,首先根據(jù)Dalvik字節(jié)碼語(yǔ)法在insns字段中確定其指令流中的操作碼信息,然后依次把每個(gè)操作碼根據(jù)操作碼映射表映射成新的操作碼.以3.1節(jié)中test1方法的字節(jié)碼為例,其操作碼映射處理過(guò)程如圖2所示.

圖2 Java方法的操作碼映射Fig.2 Operating code mapping of Java method
在圖2中,首先分析該字節(jié)碼包含3條指令,其操作碼分別為0x92、0x90和0x0F.根據(jù)圖2的操作碼映射表,操作碼映射后分別變?yōu)?x17、0x2C和0x76.映射混淆后,code_item的insns字節(jié)碼為0x170002022C0103007601.code_item的操作碼映射保證了即使安卓應(yīng)用的code_item被分析出來(lái)時(shí),也難以逆向出其原始的code_item代碼.將抽離并進(jìn)行操作碼映射混淆后的code_item組成一個(gè)code_item索引表,然后通過(guò)C/C++程序編譯將code_item索引表封裝到SO中.
為確保Java方法被抽離映射后其字節(jié)碼能夠被正確地在Native層中執(zhí)行,需要在Java層中對(duì)抽離映射的code_item混淆代碼進(jìn)行注冊(cè),包括Java方法參數(shù)、code_item索引值和映射解釋執(zhí)行環(huán)境入口方法的配置.在進(jìn)行Java方法注冊(cè)時(shí),首先定義一個(gè)MethodStub類(lèi),它是映射解釋執(zhí)行環(huán)境的Java層入口類(lèi).MethodStub根據(jù)Java方法的不同返回類(lèi)型,定義不同的入口方法,如MethodStub.cInt和MethodStub.cFloat等,這些入口方法的參數(shù)為可變參數(shù),且都是native屬性,說(shuō)明其通過(guò)JNI機(jī)制調(diào)用Native層實(shí)現(xiàn)的映射解釋執(zhí)行環(huán)境.在3.1節(jié)的test1方法中,其參數(shù)為兩個(gè)int型變量a和b,返回類(lèi)型為int.因此,在進(jìn)行Java注冊(cè)時(shí),根據(jù)返回int型定義其入口方法定為cInt方法,即MethodStub.cInt,其注冊(cè)代碼如下所示.

publicclassOne{ publicstaticinttest1(inta,intb){returnMethodStub.cInt(newObject[]{newInteger(a),newInte?ger(b),Integer.valueOf(0)}); }}publicclassMethodStub{ publicstaticnativeintcInt(Object...paramVarArgs); publicstaticnativefloatcFloat(Object...paramVarArgs);}
以上代碼中,調(diào)用MethodStub.cInt方法時(shí),使用對(duì)象數(shù)組Object[]作為參數(shù),其值分別為new Integer(a),new Integer(b),Integer.valueOf(0).其中,前兩個(gè)參數(shù)a和b是test1方法的參數(shù),第3個(gè)參數(shù)0是code_item索引表的索引值.為了便于參數(shù)的傳遞,這些參數(shù)被統(tǒng)一封裝成Java類(lèi)類(lèi)型.
經(jīng)Java方法注冊(cè)后,安卓應(yīng)用中DEX的code_item代碼變?yōu)樽?cè)方法所對(duì)應(yīng)的code_item代碼,即0x12031 2302300 D50A2201090 A7020BB 5441004 D01000312112202090 A7020BB 5452004D02 000112217110 C45403000C024 D02000171104 A5600000A000F00.當(dāng)對(duì)該安卓應(yīng)用進(jìn)行逆向工程分析時(shí),一方面原始的code_item代碼被抽離映射混淆,且原始的code_item代碼在執(zhí)行時(shí)不會(huì)被動(dòng)態(tài)加載到安卓系統(tǒng)的ART(Android Runtime)運(yùn)行環(huán)境中.另一方面安卓應(yīng)用中DEX的code_item代碼不包含原Java方法的任何邏輯,所以很難對(duì)其進(jìn)行有效的逆向工程分析.
經(jīng)抽離映射混淆后的code_item代碼在Native層的SO中由映射解釋器進(jìn)行映射解釋執(zhí)行,它們共同構(gòu)成了映射解釋執(zhí)行環(huán)境.其中,映射解釋器是一個(gè)根據(jù)操作碼映射表和Dalvik操作碼標(biāo)準(zhǔn)實(shí)現(xiàn)的code_item混淆代碼解釋器.當(dāng)沒(méi)有映射解釋執(zhí)行環(huán)境的時(shí)候,安卓應(yīng)用code_item代碼的解釋執(zhí)行通過(guò)安卓系統(tǒng)的ART完成,如圖3(a)所示.當(dāng)安卓應(yīng)用存在映射解釋執(zhí)行環(huán)境時(shí),安卓應(yīng)用code_item混淆代碼的

圖3 映射解釋執(zhí)行環(huán)境Fig.3 Execution environment of mapping interpretation
解釋執(zhí)行通過(guò)映射解釋執(zhí)行環(huán)境完成,如果code_item代碼中存在調(diào)用安卓系統(tǒng)方法等邏輯,將通過(guò)JNIEnv接口與ART進(jìn)行交互,如圖3(b)所示.此外,安卓應(yīng)用中沒(méi)有被混淆的code_item代碼,其解釋執(zhí)行也將通過(guò)ART完成.
當(dāng)3.3節(jié)的注冊(cè)代碼被調(diào)用時(shí),它會(huì)由MethodStub.cInt通過(guò)JNI從Java層調(diào)入Native層的映射解釋執(zhí)行環(huán)境,其實(shí)現(xiàn)代碼如下所示.

JNIEXPORTjintJNICALLnativeInt(JNIEnv?env,jclassclazz,job?jectArrayobject_array){JValueresult;CodeItem?code_item=GetCodeItem(object_array);EnterInterpreter(env,clazz,object_array,code_item,&result);returnresult.GetI();}staticJNINativeMethodnative_methods[]={{"cInt","([Ljava/lang/Object;)I",(void?)nativeInt}};
在以上代碼中,nativeInt()函數(shù)是MethodStub.cInt方法對(duì)應(yīng)Native層的實(shí)現(xiàn),其object_array參數(shù)接收MethodStub.cInt方法的參數(shù)值{a,b,0}.根據(jù)object_array接收的code_item索引值0,nativeInt()函數(shù)通過(guò)調(diào)用GetCodetItem()函數(shù)在code_item索引表中找到Java方法對(duì)應(yīng)的code_item混淆代碼.接著,以object_array接收的Java方法參數(shù)值{a,b}和code_item混淆代碼作為參數(shù)調(diào)用映射解釋器函數(shù)EnterInterpreter().根據(jù)ART的解釋執(zhí)行原理,ART解釋器提供Goto和Switch兩種實(shí)現(xiàn)方式[18].本文的映射解釋器采用Switch方式解釋執(zhí)行映射操作碼,即根據(jù)3.2節(jié)的操作碼映射表設(shè)置case值及其對(duì)應(yīng)的解釋執(zhí)行過(guò)程,其實(shí)現(xiàn)的關(guān)鍵代碼如下所示.

voidEnterInterpreter(JNIEnv?env,jclassclazz,jobjectArrayobject_ar?ray,CodeItem?code_item,Jvalue?value){...switch(inst?>Opcode(inst_data)){ case0x2C:{shadow_frame.SetVReg(inst?>VRegA_23x(inst_data),SafeAdd(shadow_frame.GetVReg(inst?>VRegB_23x()),shadow_frame.GetVReg(inst?>VRegC_23x()))); inst=inst?>Next_2xx(); break;} case0x17:{shadow_frame.SetVReg(inst?>VRegA_23x(inst_data),SafeMul(shadow_frame.GetVReg(inst?>VRegB_23x()),shadow_frame.GetVReg(inst?>VRegC_23x()))); inst=inst?>Next_2xx(); break; inst=inst?>Next_1xx(); break;} case0x76:{ JValueresult; result.SetJ(0);result.SetI(shadow_frame.GetVReg(inst?>VRegA_11x(inst_data))); returnresult;}}
在以上代碼中,根據(jù)操作碼的映射關(guān)系,case值0x2C對(duì)應(yīng)的處理過(guò)程是Dalvik字節(jié)碼中0x90指令類(lèi)型為ADD_INT的處理過(guò)程.根據(jù)操作碼映射關(guān)系實(shí)現(xiàn)的解釋執(zhí)行環(huán)境保證了在Java方法的code_item代碼被抽離映射混淆后,該方法仍然能夠被正確解釋執(zhí)行.
本節(jié)將對(duì)第3節(jié)提出的抽離映射混淆技術(shù)進(jìn)行實(shí)驗(yàn)測(cè)試,包括混淆后安卓應(yīng)用抵御逆向工程分析的有效性驗(yàn)證,以及通過(guò)安卓應(yīng)用混淆前后的大小變化、運(yùn)行時(shí)的內(nèi)存消耗和運(yùn)行時(shí)間進(jìn)行性能測(cè)試.
表1 測(cè)試用例
Table 1 Test cases

測(cè)試用例Java方法邏輯類(lèi)型Clac.a(chǎn)pk算術(shù)運(yùn)算CallMethod.a(chǎn)pk自定義方法調(diào)用CallAndMethod.a(chǎn)pk系統(tǒng)方法調(diào)用CallJNIMethod.a(chǎn)pkJNI方法調(diào)用NativeMethod.a(chǎn)pkNative方法Activity.a(chǎn)pk界面類(lèi)的內(nèi)部方法ThreadMethod.a(chǎn)pk線(xiàn)程方法VarArgsMethod.a(chǎn)pk可變參數(shù)方法
根據(jù)安卓應(yīng)用常見(jiàn)Java方法的內(nèi)部邏輯特點(diǎn)及類(lèi)型,構(gòu)建如表1所示的安卓應(yīng)用作為測(cè)試用例.
表1中包含的方法邏輯特性具有一定代表性,能夠覆蓋大部分Java方法.為了驗(yàn)證本文技術(shù)抵御逆向工程分析的有效性,首先分別對(duì)抽離映射混淆后的APK采用Jeb,jadx和Dex2jar等Java逆向編譯工具進(jìn)行靜態(tài)逆向工程分析,其中jadx對(duì)Clac.apk的逆向編譯結(jié)果如圖4所示.

圖4 Clac.apk靜態(tài)逆向編譯結(jié)果Fig.4 Static reverse compilation of Clac.apk
從圖4中可以發(fā)現(xiàn),jadx逆向編譯出來(lái)的test2方法是本文抽離映射混淆后的Java方法邏輯,無(wú)法得到其原始Java方法.使用這些工具進(jìn)一步對(duì)表1中的測(cè)試用例進(jìn)行逆向工程測(cè)試,得到的結(jié)果如表2所示.
表2 靜態(tài)逆向工程測(cè)試結(jié)果
Table 2 Test results of static reverse engineering

測(cè)試用例JebJadxDex2jarClac.a(chǎn)pk×××CallMethod.a(chǎn)pk×××CallAndMethod.a(chǎn)pk×××CallJNIMethod.a(chǎn)pk×××NativeMethod.a(chǎn)pk×××Activity.a(chǎn)pk×××ThreadMethod.a(chǎn)pk×××VarArgsMethod.a(chǎn)pk×××
從表2的實(shí)驗(yàn)結(jié)果可以看出,Java靜態(tài)逆向工程無(wú)法分析出被本文抽離映射混淆過(guò)的Java方法.由于Java方法的code_item代碼在DEX文件中被抽離映射混淆,而這些逆向編譯工具都是基于DEX文件中的code_item代碼進(jìn)行分析的,所以這些工具都不能正確地逆向編譯出被抽離映射混淆過(guò)的Java方法.其次,使用DexHunter動(dòng)態(tài)逆向工程分析Calc.apk,其實(shí)驗(yàn)結(jié)果如圖5所示.

圖5 Clac.apk動(dòng)態(tài)逆向工程結(jié)果Fig.5 Dynamic reverse engineering of Clac.apk
在圖5中,DexHunter獲得了Calc.apk通過(guò)ART加載運(yùn)行的內(nèi)存數(shù)據(jù),并構(gòu)建DEX文件whole.dex.當(dāng)逆向編譯該文件時(shí),發(fā)現(xiàn)它和圖4一樣是混淆后的DEX文件.這是由于混淆后被保護(hù)的Java方法的原始信息并不會(huì)加載到ART中,所以使用動(dòng)態(tài)逆向工程分析無(wú)法獲取其原始DEX文件.
下面,對(duì)Native層進(jìn)行逆向工程分析,使用IDA Pro對(duì)CallMethod.apk混淆后的映射解釋執(zhí)行環(huán)境進(jìn)行動(dòng)態(tài)調(diào)試.首先,根據(jù)程序的執(zhí)行流逐級(jí)跟蹤到code_item索引表在內(nèi)存中的位置,然后根據(jù)Java方法顯示的索引號(hào)找到該Java方法對(duì)應(yīng)的code_item代碼,如圖6所示.

圖6 CallMethod.apk的code_itemFig.6 code_item of CallMethod.apk
在圖6中,分析從0xF9開(kāi)始的這段字節(jié)碼,標(biāo)記出每個(gè)指令的操作碼(圖6中框內(nèi)數(shù)據(jù)),并直接對(duì)這段字節(jié)碼逆向編譯成smali代碼時(shí)出現(xiàn)錯(cuò)誤,無(wú)法成功逆向.繼續(xù)跟蹤程序運(yùn)行,程序控制流進(jìn)入映射解釋器的執(zhí)行邏輯中,為了找到這些被替換的操作碼與原始操作碼的映射關(guān)系,分析映射解釋器的逆向編譯代碼,如圖7所示.

圖7 映射解釋器逆向編譯結(jié)果Fig.7 Reverse compilation of mapping interpreter
從圖7的逆向編譯結(jié)果可以看出,映射解釋器的逆向代碼并不是友好的switch-case結(jié)構(gòu).其次,由于Davilk操作碼有200多個(gè),因此涉及到200多個(gè)解釋子邏輯的字節(jié)碼指令分析.此外,本文的操作碼映射可在安卓應(yīng)用混淆中動(dòng)態(tài)改變,所以很難有效分析出它們之間的映射關(guān)系.面對(duì)不同逆向工程分析方式的結(jié)果如表3所示,其中*表示難以分析.
表3 不同逆向工程方式的結(jié)果
Table 3 Test results of different reverse engineering

逆向工程分析方式結(jié)果Java層靜態(tài)分析×Java層動(dòng)態(tài)和靜態(tài)結(jié)合分析×Native層動(dòng)態(tài)分析?
從表3可以看出,本文混淆技術(shù)可以有效抵御各種針對(duì)安卓應(yīng)用的逆向工程分析,從而大大提高安卓應(yīng)用的安全性.
本小節(jié)從APK大小、內(nèi)存消耗和執(zhí)行時(shí)間3個(gè)方面進(jìn)行測(cè)試比較,進(jìn)一步分析安卓應(yīng)用混淆前后的性能影響.首先,選取表1中的Clac.apk和CallMethod.apk作為測(cè)試用例.表4列出了兩個(gè)測(cè)試用例混淆前后APK包大小變化的情況.
表4 混淆前后安卓應(yīng)用保大小變化(Byte)
Table 4 Comparison of package size between two applications before and after obfuscation (Byte)

測(cè)試用例混淆前混淆后增加大小增加比例Clac.a(chǎn)pk 1256021146001520399416.2%CallMethod.a(chǎn)pk1307045151107520403015.6%
表4的測(cè)試結(jié)果表明,經(jīng)過(guò)混淆后安卓應(yīng)用增加了200-300KB.混淆后APK變大的主要增量是映射解釋執(zhí)行環(huán)境動(dòng)態(tài)庫(kù)SO文件的大小,另外還有code_item代碼混淆處理和解釋執(zhí)行所增加的代碼.
其次,通過(guò)adb shell的命令dump meminfo來(lái)獲取安卓應(yīng)用運(yùn)行時(shí)的內(nèi)存消耗情況.表5列出了兩個(gè)測(cè)試用例混淆前后內(nèi)存消耗的實(shí)驗(yàn)結(jié)果.
從表5可以看出,混淆后的安卓應(yīng)用運(yùn)行時(shí)的內(nèi)存消耗增加了6600KB左右,這主要由以下2方面引起:
1)映射解釋執(zhí)行環(huán)境動(dòng)態(tài)庫(kù)SO文件加載時(shí)映射的內(nèi)存.
2)映射解釋執(zhí)行環(huán)境初始化時(shí)開(kāi)辟的內(nèi)存空間.內(nèi)存的增加與需要混淆保護(hù)的Java方法數(shù)量成正相關(guān),每增加一個(gè)保護(hù)方法,約增加1KB左右的消耗.目前,現(xiàn)有的移動(dòng)設(shè)備大都提供大于2GB的內(nèi)存,因此6MB左右的內(nèi)存消耗增加并不算特別大的影響.
表5 混淆前后安卓應(yīng)用運(yùn)行時(shí)的內(nèi)存消耗(KB)
Table 5 Comparison of memory use between two applications before and after obfuscation (KB)

測(cè)試用例混淆前混淆后Clac.a(chǎn)pk912515758CallMethod.a(chǎn)pk934615964
最后,把Clac.apk和CallMethod.apk兩個(gè)測(cè)試用例中需要保護(hù)的Java方法test2和test3做運(yùn)行時(shí)間比較,其Java方法源碼分別如下所示.

publicstaticinttest2(inta,intb){intc=a+b;intm=c+2;intn=a+4;returnm+n;}publicstaticinttest3(inta,intb){ returnTwo.test2(a,b);}
另外,選取Google Pixel XL和Cubieboard CC-A80終端設(shè)備,以及最新的Android 7.1.1操作系統(tǒng)作為實(shí)驗(yàn)環(huán)境,執(zhí)行時(shí)間采用輸出日志查看時(shí)間戳的方法進(jìn)行測(cè)試.然后,按不同的執(zhí)行次數(shù)去對(duì)比該方法在混淆前后的運(yùn)行時(shí)間,實(shí)驗(yàn)結(jié)果分別如表6和表7所示.
表6 混淆前后Clac.apk運(yùn)行時(shí)間(ms)
Table 6 Comparison of execution time between Clac.apk before and after obfuscation (ms)

設(shè)備次數(shù)110001000050000100000PixelXL混淆前<1<14?51010?20PixelXL混淆后<118130?140700?7101330?1340CC?A80混淆前<1<1<110?2020CC?A80混淆后<120110?160600?7501100?1600
從表6和表7可以看出,當(dāng)運(yùn)行次數(shù)較少時(shí),混淆前后的時(shí)間差異并不明顯,當(dāng)不斷增加運(yùn)行次數(shù)時(shí),混淆前后的運(yùn)行時(shí)間出現(xiàn)明顯差異,其中CubieBoard CC-A80時(shí)間精度只有10ms,且多次測(cè)試的數(shù)據(jù)波動(dòng)范圍較大.當(dāng)測(cè)試用例的執(zhí)行次數(shù)達(dá)到10000次時(shí),混淆前后的實(shí)際運(yùn)行時(shí)間增加不超過(guò)200ms,所以一般情況下,混淆后對(duì)程序的執(zhí)行效率不會(huì)產(chǎn)生任何影響.但是當(dāng)執(zhí)行次數(shù)超過(guò)50000次時(shí),混淆后的安卓應(yīng)用的執(zhí)行時(shí)間增加明顯.分析其原因主要是由于code_item混淆代碼的映射解釋執(zhí)行需要通過(guò)安卓系統(tǒng)的JNI調(diào)用,且部分操作碼解釋執(zhí)行時(shí)需要通過(guò)JNIEnv接口與ART交互.此外,本文的映射解釋器還無(wú)法實(shí)現(xiàn)和ART一樣的字節(jié)碼提前編譯成二進(jìn)制指令.
表7 混淆前后CallMethod.apk運(yùn)行時(shí)間(ms)
Table 7 Comparison of execution time between CallMethod.apk before and after obfuscation (ms)

設(shè)備次數(shù)110001000050000100000PixelXL混淆前<1<17?813?1416?18PixelXL混淆后<117170?180800?8201550?1580CC?A80混淆前<1<1<110?2020?30CC?A80混淆后<120180?200800?8301400?1900
抵御逆向工程分析的安卓應(yīng)用保護(hù)已經(jīng)成為了當(dāng)今國(guó)內(nèi)外軟件安全研究的熱點(diǎn),如何提升安卓應(yīng)用的混淆有效性也成為安全研究人員的重點(diǎn)研究課題.本文分析了安卓應(yīng)用軟件所面臨的逆向工程分析與利用,對(duì)APK程序在ART解析DEX的code_item代碼時(shí)容易被逆向編譯和分析問(wèn)題,提出一種針對(duì)DEX的code_item代碼進(jìn)行抽離映射混淆的保護(hù)技術(shù),最后通過(guò)測(cè)試用例進(jìn)行實(shí)驗(yàn)證明該技術(shù)的有效性.在性能測(cè)試中,當(dāng)運(yùn)行次數(shù)達(dá)到一定量時(shí),混淆安卓應(yīng)用的性能還不是十分理想,如何在提高安全性地同時(shí),降低對(duì)性能的影響將是在今后工作中進(jìn)一步研究的內(nèi)容,包括降低映射解釋執(zhí)行環(huán)境在解釋執(zhí)行時(shí)與ART的交互頻率,研究在映射解釋執(zhí)行環(huán)境中使用預(yù)先編譯的可能性.