Android應(yīng)用程序通用自動(dòng)脫殼方法
本文為烏云峰會(huì)上《Android應(yīng)用程序通用自動(dòng)脫殼方法研究》的擴(kuò)展延伸版。
0x00 背景及意義
Android應(yīng)用程序相比傳統(tǒng)PC應(yīng)用程序更容易被逆向,因?yàn)楸荒嫦蚝竽軌蛲暾倪€原出Java代碼或者smali中間語言,兩者都具有很豐富的高層語義信息,理解起來更為容易,讓程序邏輯輕易暴露給技術(shù)能力甚至并不需要很高門檻的攻擊者面前。因此Android應(yīng)用程序加固保護(hù)服務(wù)隨之應(yīng)運(yùn)而生。從一開始只有甲方公司提供服務(wù)到現(xiàn)在大型互聯(lián)網(wǎng)公司都有自己的加固保護(hù)服務(wù),同時(shí)與金錢相關(guān)的Android應(yīng)用程序例如銀行等也越來越多開始使用加固保護(hù)自己,這個(gè)市場在不斷的擴(kuò)大。
一個(gè)典型的加固保護(hù)服務(wù)通常能夠提供如下保護(hù):防逆向,防篡改,反調(diào)試,反竊取等功能。加固服務(wù)雖然不能夠避免和防止應(yīng)用程序自身的安全問題和漏洞,但能夠有效的保護(hù)程序真實(shí)邏輯,保護(hù)應(yīng)用程序完整性。但是這些特點(diǎn)同時(shí)也容易被惡意程序利用,有數(shù)據(jù)表明隨著加固保護(hù)的流行,加殼惡意程序的比例也在不斷上升。一方面惡意程序分析需要先脫殼,另一方面正常的應(yīng)用程序如果被輕易脫殼后分析,其面臨的風(fēng)險(xiǎn)也會(huì)上升。
0x01 研究對象
通常加固服務(wù)提供DEX的整體加固方案和定制化的加固。定制化的加固通常需要與開發(fā)更為緊密的結(jié)合,可能涉及更深層次加固(如native代碼加固等),而DEX整體加固只需要用戶提供編譯好的Android應(yīng)用程序APK即可。前者目前缺乏樣本并需要與加固廠商深度合作,而后者被大多數(shù)加固服務(wù)廠商作為最基本的免費(fèi)服務(wù)提供,因而后者被使用的更為廣泛。本文主要研究對象是針對后者的Android應(yīng)用程序可執(zhí)行文件DEX的保護(hù),即DEX文件加密,旨在研究通用的DEX文件恢復(fù)方法。而定制化的加固服務(wù)或針對native代碼的混淆保護(hù)等不在本文研究范圍內(nèi)。
0x02 加固服務(wù)特點(diǎn)
我們通過一個(gè)靜態(tài)逆向加固方法的例子來詳細(xì)描述加固服務(wù)通常具有的特點(diǎn)。該例子是幾個(gè)月前某加固廠商使用的方案,由于加固服務(wù)經(jīng)常變換解密算法和方案,因此實(shí)現(xiàn)細(xì)節(jié)并不適用于現(xiàn)在的產(chǎn)品,或其他加固服務(wù),但整體的加固思想和方法和使用的保護(hù)手段基本上大同小異。
通常當(dāng)我們用靜態(tài)工具分析一個(gè)加固后的APP時(shí),AndroidManifest.xml文件里會(huì)在保留原始的所有信息,包括定義的組件、權(quán)限等等的基礎(chǔ)上,新增一個(gè)入口點(diǎn)類,通常是application。
而DEX的代碼是這樣的。

DEX代碼只包含很少的類和代碼,其主要是做些檢測工作或者準(zhǔn)備工作,然后通過載入一個(gè)native庫去動(dòng)態(tài)加載原始的DEX文件。由于使用了動(dòng)態(tài)加載機(jī)制,因此加固過的DEX文件中不會(huì)涉及原始DEX的真正代碼(也有一些加固并沒有采取完整DEX的動(dòng)態(tài)加載)。
接著使用IDA去逆向入口點(diǎn)加載運(yùn)行的native代碼,通常so庫也是被混淆加殼的。手段包括破壞ELF頭部信息讓IDA解析失敗,如下圖:


通過readelf可以明顯看到ELF頭部的幾個(gè)字段是有問題的。

修復(fù)之后,IDA可以正常反匯編so文件了。接著我們從入口點(diǎn)開始分析,會(huì)發(fā)現(xiàn)F5反編譯成C代碼會(huì)有問題,多個(gè)函數(shù)內(nèi)容都不能反編譯成正常的C代碼。直接看匯編代碼看到如下的花指令:

這是我們總結(jié)的該產(chǎn)品的花指令模式。它會(huì)通過壓棧跳轉(zhuǎn)出棧的方式讓反編譯的函數(shù)辨識(shí)出現(xiàn)問題,因?yàn)榉淳幾g通常會(huì)認(rèn)為一個(gè)壓棧操作為函數(shù)調(diào)用,而其實(shí)他通過壓棧,計(jì)算寄存器值,跳轉(zhuǎn)再出棧讓反編譯失效后并平衡棧后,再執(zhí)行一條真正有用的指令。因此上述例子中只有兩條真正有用的指令。
通過寫腳本甚至是人工的方式可以把真正的匯編指令提取出來。提取后再逆向代碼,其功能是去解密JNI_OnLoad函數(shù)。JNI_OnLoad會(huì)從一段數(shù)據(jù)中再解密出另一個(gè)ELF文件,而此時(shí)這個(gè)新的ELF文件還不能正確反匯編,后面的代碼會(huì)接著對該ELF進(jìn)行數(shù)據(jù)的修正。先解壓新ELF文件中的text端,從text端中提取一個(gè)key再去解密rotext,最后才解密出一個(gè)真正的對DEX的殼程序,形如:

以上步驟其實(shí)是一個(gè)ELF文件的殼。新的被解密修正后的ELF文件才是真正對DEX殼的解密程序。這個(gè)程序并沒有混淆或者加殼,通過逆向后發(fā)現(xiàn),他會(huì)取原始DEX后的一段padding數(shù)據(jù),獲取一些解密和解壓需要的參數(shù),對整段padding數(shù)據(jù)解密解壓,就能得到真正原始的DEX文件了。當(dāng)然ELF中還包括一些反調(diào)試反分析的代碼,由于我們這是靜態(tài)分析,不需要顧及這部分代碼,如果是使用調(diào)試器去附加進(jìn)程使用dump等動(dòng)態(tài)分析時(shí)就需要考慮怎么優(yōu)雅的bypass這些反調(diào)試技巧了。
以上例子是一個(gè)動(dòng)態(tài)加載DEX的例子,雖然不同的加固服務(wù)在很多技術(shù)細(xì)節(jié)包括解密算法、花指令模式、ELF殼等等上天差地別,但基本上能夠代表絕大多數(shù)使用動(dòng)態(tài)加載DEX方式的加固服務(wù)的整體解密釋放運(yùn)行和靜態(tài)逆向和破解它的思想方法。我們也是以這個(gè)例子來管中窺豹。因?yàn)轭l繁的變換解密算法和加固方式也是加固服務(wù)的第一大特點(diǎn)。
同時(shí)事實(shí)上還存在一些加固并沒有使用完整DEX文件的動(dòng)態(tài)加載機(jī)制,而是使用運(yùn)行時(shí)動(dòng)態(tài)自修改,這種機(jī)制下加固后的DEX文件中將存在原始DEX中的部分準(zhǔn)確信息,但受保護(hù)的部分代碼還是會(huì)選擇其他方式隱藏。另外還有兩者相結(jié)合的方式。后面的案例分析中我們將有所涉及。
總結(jié)一下,一個(gè)加固過的Android應(yīng)用程序?qū)嶋H上主要是隱藏真正的DEX文件,其自身也會(huì)加入諸多保護(hù)措施來防止被輕易逆向??梢钥吹饺绻冹o態(tài)逆向分析其脫殼算法會(huì)非常耗時(shí)耗力,另外不同的加固服務(wù)采取不一樣的算法,而每個(gè)本身又會(huì)頻繁變換算法和加固技術(shù)讓純靜態(tài)的逆向脫殼方法短時(shí)間內(nèi)就失效。同時(shí)加固服務(wù)還會(huì)采取除DEX動(dòng)態(tài)加載以外的諸多安卓應(yīng)用程序保護(hù)措施,我們這里稍作總結(jié),并不展開,因?yàn)檫@部分內(nèi)容甚至可以單獨(dú)寫文章詳細(xì)說。
第一大類是完整性檢驗(yàn)。包含了在運(yùn)行時(shí)對自身的完整性校驗(yàn),如檢查內(nèi)存中DEX文件的檢驗(yàn)值和檢查應(yīng)用程序證書來檢測是否被重打包及插入代碼。以及對自身環(huán)境的檢測,如通過檢查特定設(shè)備文件等方式檢測模擬器,通過ptrace或者進(jìn)程狀態(tài)等方式檢測是否被調(diào)試,hook特定的函數(shù)防止代碼內(nèi)存被讀取或dump等。
第二大類是代碼混淆。通常混淆需要基于源碼或字節(jié)碼上修改,其目的是為了讓分析者更難以理解程序的語義。最常見的包括修改變量名,方法名,類名等,加密常量字符串,使用Java反射機(jī)制調(diào)用方法,插入垃圾指令或無效代碼打亂程序控制流,使用更為復(fù)雜的操作替換原始的基本指令,使用JNI方法打斷控制流等。
第三大類我們定義為防分析或代碼隱藏技術(shù),其目的是為了用各種方法防止程序代碼被直接暴露,輕易分析。最常見的就是上述的DEX整體加密保護(hù),以及運(yùn)行時(shí)動(dòng)態(tài)自修改。運(yùn)行時(shí)動(dòng)態(tài)自修改主要是在程序運(yùn)行時(shí)當(dāng)執(zhí)行到特定的類或方法時(shí)才將代碼解密并執(zhí)行,同時(shí)還可能動(dòng)態(tài)之后才修正或修改部分dalvik數(shù)據(jù)結(jié)構(gòu)讓分析變得困難。另外一些防分析技術(shù)需要利用一些小的技巧。例如利用靜態(tài)分析工具的bug,或解析時(shí)的特性來做對抗,包括曾經(jīng)出現(xiàn)的manifest cheating,APK偽加密,dex文件中的方法隱藏,插入非法指令或不存在的類讓靜態(tài)分析工具崩潰等等。#p#
0x03 脫殼方法思想
面對加固程序,當(dāng)前比較流行和常用的脫殼方法主要是兩種方法。一種是靜態(tài)的逆向分析,其缺點(diǎn)也很明顯,難度大而且無法對抗變換算法。另一種主要是基于內(nèi)存dump技術(shù)的脫殼。缺點(diǎn)在于需要考慮先bypass各種反調(diào)試的方法,同時(shí)還需要面對日益發(fā)展和新的層出不窮的反內(nèi)存dump的各種技巧。例如篡改dex文件頭防窮搜,動(dòng)態(tài)篡改dalvik數(shù)據(jù)結(jié)構(gòu)破壞內(nèi)存中的DEX文件等等,這些對抗技術(shù)讓即使dump出DEX文件后,還需要做大量的通過觀察加固特性后的人工修復(fù)工作。
所以我們提出一種通用的自動(dòng)化脫殼方法,我們的方法基于動(dòng)態(tài)分析,無需關(guān)心各個(gè)不同的加固保護(hù)具體實(shí)現(xiàn),也可以統(tǒng)一繞過各種反調(diào)試的手段,同時(shí)也不需要做后期大量的修復(fù)工作。
首先我們脫殼的對象是Android應(yīng)用程序中的DEX文件,因此我們選擇直接修改Android系統(tǒng)中Dalvik虛擬機(jī)的源代碼進(jìn)行插樁。因?yàn)镈EX文件中的代碼都需要在Dalvik虛擬機(jī)上解釋執(zhí)行,所有的真實(shí)行為都能在Dalvik虛擬機(jī)上暴露。Dalvik有多個(gè)解釋模式,其中有portable模式是基于C++實(shí)現(xiàn)的,而其他模式由于優(yōu)化的緣故使用平臺(tái)相關(guān)的匯編語言開發(fā),為了方便實(shí)現(xiàn)我們的插樁的代碼,一旦發(fā)現(xiàn)開始解釋執(zhí)行需要被脫殼的APP時(shí),我們先(源碼目錄dalvik/vm/interp/Interp.cpp)將解釋模式改為portable。這么做的一個(gè)好處在于直接修改執(zhí)行環(huán)境可以讓加殼程序更加難以檢測脫殼行為的存在,相比于調(diào)試器附加等方法,該方法更為透明。在解釋器上做的另一個(gè)好處在于不需要去關(guān)心加固程序在哪個(gè)階段進(jìn)行類的加載和初始化以及解密代碼等,直接在運(yùn)行時(shí)就能得到最真實(shí)的數(shù)據(jù)和行為。插樁代碼實(shí)現(xiàn)在Dalvik解釋執(zhí)行的每條指令切換處(dalvik/vm/mterp/out/InterpC-portable.cpp),這樣可以在執(zhí)行過程中的任意指令處進(jìn)行脫殼的操作,一邊應(yīng)對邊運(yùn)行邊解密的加固程序。最后基于源碼的修改能夠?qū)嵤┱鏅C(jī)部署,Android原生源碼可以完美支持所有的Nexus系列手機(jī),也不需要去應(yīng)對加固程序的檢測模擬器手段。
脫殼的本質(zhì)是去獲取程序真實(shí)的行為,因此插樁代碼其實(shí)就是去得到內(nèi)存中的Dalvik數(shù)據(jù)結(jié)構(gòu),來反映被執(zhí)行的真實(shí)代碼。在指令執(zhí)行時(shí)可以直接得到該條指令屬于的方法,Method這個(gè)結(jié)構(gòu)。而每個(gè)被執(zhí)行的方法中都有該方法屬于的類對象clazz,而clazz(源碼目錄dalvik/vm/oo/Object.h)中又有pDvmDex(dalvik/vm/DvmDex.h)對象,其中有pDexFile(dalvik/libdex/DexFile.h)結(jié)構(gòu)體代表了DEX文件,也就是說,執(zhí)行過程中獲取當(dāng)前方法后,用curMethod->clazz->pDvmDex->pDexFile就能夠得到這個(gè)方法屬于的DEX文件結(jié)構(gòu)。該結(jié)構(gòu)體中包含了所有DEX文件在解釋其中被執(zhí)行時(shí)的內(nèi)存信息,通過解析這個(gè)DexFile結(jié)構(gòu)體就能恢復(fù)出最真實(shí)的DEX。
0x04 簡單脫殼實(shí)現(xiàn)
至此,我們的第一個(gè)反應(yīng)是有沒有現(xiàn)成的程序,可以去翻譯Dalvik字節(jié)碼的,但是以讀入內(nèi)存中的DexFile結(jié)構(gòu)體為輸入,同時(shí)可以直接基于源碼實(shí)現(xiàn),也就是用C/C++實(shí)現(xiàn)的,而不是像更多的靜態(tài)逆向工具直接以讀入一個(gè)靜態(tài)DEX文件為輸入。找了下發(fā)現(xiàn)Android系統(tǒng)源碼里本身就提供了DexDump(dalvik/dexdump/DexDump.cpp)這個(gè)工具,直接能滿足這個(gè)要求。我們對DexDump代碼稍作修改,插入到解釋器中,如下圖:

讓他去讀取DexFile,默認(rèn)就直接在一個(gè)APP的主Activity處執(zhí)行這個(gè)代碼,主Activity可以通過AndroidManifest.xml文件獲取,因?yàn)樵撐募械娜肟邳c(diǎn)類都不會(huì)被隱藏。 我們發(fā)現(xiàn)這樣幾乎就能夠應(yīng)對大多數(shù)加固程序了,能夠得到加固程序被隱藏的DEX文件中的真實(shí)代碼,輸出如下圖:

但這個(gè)方法的缺點(diǎn)也很明顯,就是輸出是dalvik字節(jié)碼的文本形式,一方面無法反匯編成Java,另一方面文本形式非常不適合后續(xù)的復(fù)雜程序的分析,我們的最佳目的是得到一個(gè)完整的DEX文件。
0x05 完善脫殼實(shí)現(xiàn)
通常到上一步,許多其他的脫殼工具為了恢復(fù)出完整的DEX文件,會(huì)選擇直接讀取pDexFile->baseAddr或者pDvmDex->memMap為起始地址,直接將整個(gè)文件大小的內(nèi)存dump出來。然而我們發(fā)現(xiàn)對某些加固軟件,這樣dump出來的代碼里依然不包含真實(shí)的代碼,這是由于DEX文件中部分真實(shí)信息在運(yùn)行時(shí)被修改和映射到了文件連續(xù)內(nèi)存以外的部分,如下圖,一個(gè)DEX文件被載入內(nèi)存后,理應(yīng)是在一個(gè)連續(xù)的內(nèi)存空間中,然后被解析賦值為各個(gè)動(dòng)態(tài)執(zhí)行時(shí)Dalvik所需的結(jié)構(gòu)體,而部分索性性質(zhì)的結(jié)構(gòu)體應(yīng)該指向連續(xù)的data數(shù)據(jù)塊。但加固程序可能會(huì)做些修改,例如將header的部分?jǐn)?shù)據(jù)篡改,以及重新分配不連續(xù)的內(nèi)存來存放data數(shù)據(jù),并讓那些索引數(shù)據(jù)塊指向的新分配的data塊。這樣如果直接用dump的方法,則無法得到完整的DEX文件。

我們旨在以統(tǒng)一的方法恢復(fù)出原始的DEX文件,不希望還需要針對的不同的殼來做后續(xù)的修復(fù),因?yàn)檫@樣又將進(jìn)入到和靜態(tài)逆向加固算法一樣的困境。因此我們基于上述簡單實(shí)現(xiàn),有了個(gè)更加完善的實(shí)現(xiàn)方案,稱之為DEX文件重組。過程非常簡單,就是在程序執(zhí)行過程中先獲取所有解釋器所需的Dalvik數(shù)據(jù)結(jié)構(gòu),這里都是內(nèi)存中真實(shí)的被解釋執(zhí)行的數(shù)據(jù)結(jié)構(gòu),然后再將這些數(shù)據(jù)結(jié)構(gòu)組合重新寫回成一個(gè)新的DEX文件。如上圖所示,即使內(nèi)存不連續(xù),我們也無需關(guān)心他對原始映射內(nèi)存的操作,可以直接獲取每塊不連續(xù)的數(shù)據(jù),按照一定的規(guī)范去把這些數(shù)據(jù)重組成一個(gè)新的DEX文件。 第一步是去準(zhǔn)確獲取每個(gè)Dalvik數(shù)據(jù)機(jī)構(gòu),為了保證獲取的準(zhǔn)確性,我們采取的方式是和運(yùn)行中解釋器中去執(zhí)行程序時(shí)的獲取方式一致(參考DexFile.h 文件中的dexGetXXXX方法),因?yàn)橐粋€(gè)DEX文件,同一塊數(shù)據(jù)可能有很多種方式去獲取的,打個(gè)比方,常量字符串可以去讀文件頭里的偏移去獲取,也可以通過stringId列表去獲取,等等。正常情況下這些方式都應(yīng)該是正確的,但是加固程序會(huì)去做一些破壞。但它不能去破壞運(yùn)行時(shí)這些數(shù)據(jù)被獲取時(shí)用的數(shù)據(jù),因?yàn)檫@個(gè)一旦破壞,程序就無法正常運(yùn)行了。具體的獲取方式如下圖所示:

我們需要遍歷每個(gè)數(shù)組(如pStringIds,pProtoIds,…,pClassDefs)里的某些指針和偏移,每項(xiàng)中都逐一獲取,將其內(nèi)容再合并成一個(gè)大類(如stringData,typeList,…,ClassData,Code)。接著獲取完重寫的時(shí)候,需要注意幾個(gè)問題。首先是對獲取這些數(shù)據(jù)塊的排列問題,我們參考了dalvik/libdex/DexFile.h里的map item type codes枚舉的順序進(jìn)行排列。排列好需要調(diào)整每個(gè)數(shù)據(jù)項(xiàng)里的偏移值為新的偏移,如stringDataOff, parametersOff, interfacesOff, classDataOff, codeOff等,接著對于DexHeader, MapList這兩個(gè)結(jié)構(gòu)體中的值,我們需要重新計(jì)算后填寫,而不是直接取原來的值,對于一些固定的值例如Header里面的文件頭等,我們根據(jù)已有知識(shí)直接填寫。最后需要考慮到內(nèi)存中的數(shù)據(jù)表達(dá)和DEX文件中的某些數(shù)據(jù)格式的差異,例如有些數(shù)據(jù)項(xiàng)在文件中是ULEB128編碼的,而在內(nèi)存中就直接是int類型,另外還需要注意4字節(jié)的對齊,以及encoded_method_format里是field_idx_diff,method_idx_diff而不是簡單的index等。具體細(xì)節(jié)可參考官方的DEX文件格式文檔
https://source.android.com/devices/tech/dalvik/dex-format.html
我們在重組的時(shí)候忽略了一些數(shù)據(jù)塊,例如所有和annotation相關(guān)的數(shù)據(jù)結(jié)構(gòu),因?yàn)檫@部分真實(shí)程序使用不多,而結(jié)構(gòu)又特別復(fù)雜,忽略以后對分析程序真實(shí)行為影響不大。#p#
0x06 實(shí)驗(yàn)與發(fā)現(xiàn)
改完代碼后,我們重新編譯了libdvm模塊并將新生成libdvm.so寫入系統(tǒng)目錄/system/lib/下覆蓋掉原始的庫文件,我們實(shí)驗(yàn)的對象是Galaxy Nexus手機(jī)對應(yīng)Android 4.3版本和Nexus 4手機(jī)對應(yīng)的Android 4.4.2版本。然后我們提交了一個(gè)簡單的應(yīng)用程序,送到各個(gè)在線加固服務(wù)上獲取加固后的應(yīng)用程序版本再實(shí)施脫殼。實(shí)驗(yàn)發(fā)現(xiàn)幾乎能夠針對所有的加固程序恢復(fù)出原始的DEX文件。以下是一些針對加固程序的發(fā)現(xiàn)。主要集中在不同的加固所使用的自我保護(hù)的手段,這里有些結(jié)果是DexDump的文本,因?yàn)橛行┍Wo(hù)措施用這個(gè)方式更好的展示出細(xì)節(jié),當(dāng)然全部都能直接恢復(fù)成DEX文件。


以上兩個(gè)例子表明,有些加固程序會(huì)將magic number抹去,來隱藏內(nèi)存中的DEX文件,讓窮搜DEX文件的方式失效,另外還會(huì)篡改header的大小,以及將header中的各種字段偏移值抹去,由于我們用的方法是對header重新計(jì)算,因此重組后的DEX不受其影響。


另外有些加固程序會(huì)額外插入一些類來破壞正常的反編譯效果,例如這個(gè)類就有個(gè)方法是能夠讓dex2jar失效。

還有殼將codeOff改成了負(fù)的值,這樣代碼就會(huì)被映射到文件內(nèi)存范圍之外。我們的方法可以直接將代碼獲取后重新寫回到正常的位置。

另外還有殼是重寫了某些方法,將代碼放入一個(gè)新的方法中,并在執(zhí)行前去解密,執(zhí)行后再重新抹去。對于這種情況,由于我們脫殼代碼插樁于每個(gè)方法調(diào)用處,因此我們只需要調(diào)整脫殼點(diǎn)到該方法執(zhí)行處去實(shí)施脫殼就能恢復(fù)出代碼了。

除以上例子外,我們還發(fā)現(xiàn)某些加固程序會(huì)hook進(jìn)程空間中的write函數(shù),檢測寫的內(nèi)容如果是特定的數(shù)據(jù)(如dex文件頭),則讓write操作失敗,或者獲取內(nèi)存地址空間在映射的DEX文件區(qū)域內(nèi),也會(huì)讓write失敗等。還有加固程序會(huì)將原始的DEX文件分離出多個(gè)DEX,以及修改特定的數(shù)據(jù)項(xiàng)如debug_info_off為錯(cuò)誤值,運(yùn)行時(shí)再動(dòng)態(tài)改回正確值。還有殼會(huì)在字節(jié)碼基礎(chǔ)上對原始的程序做代碼混淆。
(注:以上例子都并非最新版本,不保證特定的加固程序現(xiàn)有產(chǎn)品與上述例子依然一致)
0x07 討論與思考
首先我們的方法依然有局限性,一來在研究對象里說明了我們只針對DEX文件加密保護(hù),并不做反混淆的工作。其次我們的方法依然是基于動(dòng)態(tài)分析,將面臨動(dòng)態(tài)分析的局限性,如一段加密代碼是運(yùn)行到才解密,但該方法無法被觸發(fā)執(zhí)行,我們的方法也無法解密這個(gè)方法的代碼。最后用該方法雖然難以被加固程序檢測,但用該方法制作的工具在實(shí)現(xiàn)上勢必會(huì)有某些特征,這些特征可能會(huì)被加固程序加以利用和對抗。
最后是我想和大家一起探討的關(guān)于更好的Android平臺(tái)應(yīng)用程序加固的想法。事實(shí)上Android平臺(tái)的加固破解還是相對容易的,然而并不是沒有更難更安全的加固方案,而是在手機(jī)平臺(tái)上商用的加固方案需要考慮到性能損耗和兼容性的問題,這是無法避免的。同時(shí)綜合這幾個(gè)方面,我覺得加固保護(hù)的趨勢和做法發(fā)展主要集中在以下的幾個(gè)點(diǎn)。
一個(gè)是我覺得Android混淆和加殼其實(shí)可以結(jié)合使用。從攻擊者的角度來看,我認(rèn)為強(qiáng)力的混淆可能要比加殼在保護(hù)代碼邏輯方面更加有效。但是好的混淆方案事實(shí)上非常難以設(shè)計(jì)。目前來看國內(nèi)的加固幾乎不會(huì)對原始的代碼做大的變換和混淆,可能是怕修改的代碼在兼容性上會(huì)有問題。我認(rèn)為這是一個(gè)發(fā)展點(diǎn)。我發(fā)現(xiàn)國外比較優(yōu)秀的工具會(huì)在深度混淆這個(gè)點(diǎn)上做文章,比如dexprotector,他既有加殼,也有混淆,即使脫殼成功,還是需要去面對難以理解的混淆后代碼。
另外我覺得部分加固的效果在安全性上可能要強(qiáng)過整體加固。就像之前的一個(gè)例子,一個(gè)方法只有在運(yùn)行時(shí)才解密自己,一旦脫離運(yùn)行則重新加密或抹掉。這個(gè)等于是利用了動(dòng)態(tài)執(zhí)行覆蓋率低的缺陷來進(jìn)一步保護(hù)自己。
第三個(gè)就是為了更好的加固效果,加固過程應(yīng)該盡可能從現(xiàn)在的開發(fā)后加固變成開發(fā)中的加固?,F(xiàn)在有一些加固SDK就是這方面比較好的嘗試。直接在開發(fā)的過程中敏感的操作使用一個(gè)安全庫的接口。這個(gè)無論是在性能上還是效果都可以對現(xiàn)在的整體一刀切式的加固做個(gè)質(zhì)的提高。熟悉業(yè)務(wù)的開發(fā)人員會(huì)很清楚他們需要保護(hù)的代碼是哪一部分,因?yàn)橐粋€(gè)程序事實(shí)上真正需要被保護(hù)的邏輯可能只是很小一部分,加固范圍的縮小可以大大提高性能,同時(shí)單獨(dú)的安全庫文件可以有針對性的保護(hù)措施,效果會(huì)非常好,另外比起整個(gè)APP加固也更容易做的兼容性測試。
加固另一個(gè)思路是盡可能用Native的代碼,特別是關(guān)鍵的程序邏輯,Native代碼逆向本身就比Java困難,加了混淆或者殼后就更難了,同時(shí)Native代碼事實(shí)上還能在性能上有所提高,是一舉兩得的方案。由此又可以延伸出如何對Android應(yīng)用程序中native代碼做深度保護(hù)的問題,如果敏感操作用深度混淆保護(hù)的native代碼做保護(hù),則攻擊成本勢必將極大提升。
最后我覺得加固保護(hù)的一個(gè)趨勢是盡量少的去利用小trick來做防護(hù),比如那些利用靜態(tài)分析工具的BUG或者系統(tǒng)解析APK的BUG來做加固其實(shí)意義不是很大,加固保護(hù)更應(yīng)該從整個(gè)計(jì)算機(jī)系統(tǒng)的體系結(jié)構(gòu)上來考慮和強(qiáng)化,而不應(yīng)該集中于一些小的技巧。