本發(fā)明涉及信息安全技術領域,尤其涉及一種基于dex與so文件動態(tài)執(zhí)行的Android應用加固方法。
背景技術:
Android是google公司于2007年推出的開源手機操作系統(tǒng),由于其強大的功能和靈活的定制能力,使其在短短幾年內(nèi)便躍居智能手機操作系統(tǒng)市場份額的首位,據(jù)著名市場研究機構(gòu)IDC的最新數(shù)據(jù)表明,2015年第三季度,Android系統(tǒng)智能手機的市場份額高達84.7%,遠超其他系統(tǒng)。
雖然Android系統(tǒng)在設計之初便充分考慮到了安全問題,但隨著其廣泛應用,諸多潛在的安全問題還是逐漸地暴露出來,對其安全性的研究開始受到人們的廣泛關注。由于Android平臺軟件使用的編程語言是Java,而Java源代碼編譯后的二進制代碼極易被反編譯,導致其破解難度遠小于其他使用編譯性語言編寫的程序。雖然Android 2.3以后加入了代碼混淆機制,但通過逆向工程,其API級別上的代碼仍是難以隱藏,對攻擊者來說,破解的可能性仍是很大。
現(xiàn)有的一些Android應用程序加固方法,大多采用靜態(tài)處理的方法,即對dex文件進行一些修改來增加攻擊的難度。這種加固方法只是增加了代碼閱讀難度?;蛘呋贘NI調(diào)用機制,通過加密原so文件,使用殼程序加載并解密原so文件,間接調(diào)用原so文件的函數(shù)。然而,由于基于JNI調(diào)用機制該方案,不可避免地會在硬盤短暫的留下解密后的明文so文件,攻擊者使用Hook系統(tǒng)API、自動化腳本可以輕易地破解此方案。因此攻擊者依然能夠得到程序的代碼,無法保證應用程序的安全性。
技術實現(xiàn)要素:
為了克服上述現(xiàn)有技術的不足,本發(fā)明詳細分析了Android的系統(tǒng)架構(gòu)、程序結(jié)構(gòu)、程序執(zhí)行機制以及Android平臺軟件面臨的逆向威脅,基于classes.dex文件動態(tài)加載、利用so庫解密dex文件加固方案,提出和實現(xiàn)了一種能使so庫脫離JNI調(diào)用機制依賴,從而在內(nèi)存中完成so文件的加解密,保證沒有臨時so文件的生成、保證不能通過內(nèi)存中映射表截斷方法獲得的so核心代碼的具有更高安全性的加固方案。
本發(fā)明提供的技術方案是:
一種基于dex與so文件動態(tài)執(zhí)行的Android應用加固方法,通過二進制流加密方法對Android應用程序中的關鍵代碼進行加固,使得Android應用程序代碼得到保護;所述加固方法包括加密過程和解密過程,具體包括如下步驟:
A.通過軟件加固模塊實現(xiàn)加密過程:
軟件加固模塊即為針對Android應用程序的加固模塊,通過二進制流加密方法對Android
應用程序中的關鍵代碼進行加固;
A1.解壓軟件主體部分apk文件,提取其classes.dex文件;
一個Android應用程序(軟件)會打包成一個APK文件,對于APK文件進行解壓可以得到dex文件、assets文件、arsc文件等;其中classes.dex包含是Android平臺上可執(zhí)行文件的類型;
A2.將核心功能部分代碼編譯成獨立的apk文件并對其進行加密;
核心功能部分代碼根據(jù)提取classes.dex文件之后對代碼進行分析得到,一般地,核心代碼是自己編寫的程序,而不是引用的系統(tǒng)或者外部庫的代碼;
A3.把加密后的apk文件寫入到軟件主體部分的classes.dex文件的末尾,并在文件尾部添加加密數(shù)據(jù)的大小,用以解密時找到核心功能部分代碼的起始位置;
應用程序編譯成獨立的apk文件之后可以得到此文件的大小的數(shù)值,即為加密數(shù)據(jù)的大??;
A4.重新計算classes.dex文件的checksum、signature和file_size字段的值。分別計算這幾個字段變化之后的值,替換原位置的checksum、signature和file_size字段的值,替換原位置的內(nèi)容即可;
A5.將修改后的classes.dex文件放回軟件主體部分apk包中,使用Android SDK中提供的簽名工具對程序進行簽名;
A6.讀取待加固的so庫文件(假設為a.so),解析文件信息,得到待加密函數(shù)的偏移地址與大小,并向待加密函數(shù)末尾處寫入decryDEX()解密函數(shù),用于解密加密后的dex文件,重新計算待加密函數(shù)的大小并填寫;具體地,修改解密函數(shù)中的數(shù)值,也就是將原來待加密函數(shù)中的數(shù)值修改為改變之后的數(shù)值,此數(shù)值為待加密函數(shù)的大??;
A7.對待加密函數(shù)進行異或加密;
A8.解析so庫文件的文件頭,修改文件頭部部分字段值,包括so文件大小、so文件中函數(shù)的偏移地址;
A9.計算so庫文件的MD5值,并將其寫入文件末尾;
A10.在so文件中的.init_array節(jié)的代碼段中添加運行環(huán)境檢測、對抗動態(tài)調(diào)試、MD5完整性校驗,以及對so文件進行解密的代碼。
B.開始動態(tài)執(zhí)行Android應用程序進行解密時,執(zhí)行如下操作:
B1.軟件主體部分從自身的apk文件中讀取classes.dex文件,在classes.dex文件尾部得到加密數(shù)據(jù)的長度,根據(jù)加密數(shù)據(jù)長度計算出加密數(shù)據(jù)的起始位置,從而讀取得到加密數(shù)據(jù);
B2.調(diào)用loadLibrary系統(tǒng)函數(shù)加載a.so,這一階段loadLibrary會加載共享庫(即實施例中的so文件a.so)、控制權(quán)傳遞給動態(tài)連接器Linker,Linker會檢查文件頭部合法性、根據(jù)頭部的數(shù)據(jù)分別讀入對應的各種數(shù)據(jù)結(jié)構(gòu),并將所有PT_LOAD屬性的段加載至合適的地址空間,然后為該庫在共享庫鏈表中分配一個soinfo節(jié)點并填充其數(shù)據(jù)結(jié)構(gòu),執(zhí)行標記為.init,.init_array的節(jié)的代碼進行代碼初始化工作,若存在JNI_OnLoad函數(shù),執(zhí)行該代碼;(.init_array過程會解密加密函數(shù));
B3.調(diào)用decryDEX()解密得到核心功能部分的apk;
B4.通過Android API提供的DexClassLoader類,對核心功能部分碼(核心功能部分的apk)進行反射調(diào)用,從而實現(xiàn)核心功能部分代碼的動態(tài)加載;
B5.調(diào)用完成后,刪除核心功能部分apk文件,從而避免核心功能部分代碼暴露在系統(tǒng)內(nèi)部存儲之中,被攻擊者得到。
與現(xiàn)有技術相比,本發(fā)明的有益效果是:
現(xiàn)有的一些Android應用程序加固方法,大多采用靜態(tài)處理的方法,即對dex文件進行一些修改來增加攻擊的難度,或者基于JNI調(diào)用機制,通過加密原so文件,使用殼程序加載并解密原so文件,間接調(diào)用原so文件的函數(shù)。然而,由于基于JNI調(diào)用機制該方案,不可避免地會在硬盤短暫的留下解密后的明文so文件,攻擊者使用Hook系統(tǒng)API、自動化腳本可以輕易地破解此方案。本發(fā)明提供的加固方法對核心dex進行加密保護,對核心dex的解密函數(shù)用C++語言編寫,增加了其反編譯的難度,以動態(tài)鏈接庫的形式存在并且也進行了加密。相當于對核心dex雙重加密,對動態(tài)鏈接庫的關鍵代碼也進行了加密。并且解密過程擺脫了JNI調(diào)用機制,使得解密過程不會在硬盤留下解密后的明文so文件,全部在內(nèi)存中進行,避免被攻擊者得到。本發(fā)明方法能夠保證沒有臨時so文件的生成、保證不能通過內(nèi)存中映射表截斷方法獲得的so核心代碼,是具有更高安全性的加固方案。
附圖說明
圖1是本發(fā)明提供方法的流程框圖;
其中,A為提取dex文件核心功能部分并編譯成一個獨立的apk文件,并將它寫入文件classes.dex文件尾部;B為將該獨立的apk文件解密函數(shù)寫入待加固的so文件中的待加密部分并加密該部分代碼;C為在dex的軟件主體寫入相關加載so文件的代碼,并計算classes.dex文件的checksum、signature和file_size字段的值并替換原位置的內(nèi)容;D為讀取classes.dex文件尾部,執(zhí)行dex主體中的so文件,解密classes核心部分正常運行。
具體實施方式
下面結(jié)合附圖,通過實施例進一步描述本發(fā)明,但不以任何方式限制本發(fā)明的范圍。
本發(fā)明提供一種基于dex與so文件動態(tài)執(zhí)行的Android應用加固方法,圖1是本發(fā)明提供方法的流程框圖,包括如下步驟:
A.軟件加固模塊:
A1.解壓軟件主體部分apk文件,提取其classes.dex文件;
A2.將核心功能部分代碼編譯成獨立的apk文件并對其進行加密;
A3.把加密后的apk文件寫入到軟件主體部分的classes.dex文件的末尾,并在文件尾部添加加密數(shù)據(jù)的大小,用以解密時找到核心功能部分代碼的起始位置;
A4.重新計算classes.dex文件的checksum、signature和file_size字段的值。分別計算這幾個字段變化之后的值,替換原位置的內(nèi)容即;
A5.將修改后的classes.dex文件放回軟件主體部分apk包中,使用AndroidSDK中提供的簽名工具對程序進行簽名;
A6.讀取待加固的so庫文件(假設為a.so),解析文件信息,得到待加密函數(shù)的偏移地址與大小并向待價密函數(shù)末尾處寫入decryDEX()解密函數(shù)用于解密加密后的dex文件,重新計算待加密函數(shù)的大小并填寫;
A7.對待加密函數(shù)進行異或加密;
A8.解析文件頭,修改文件頭部部分字段值;
A9.計算文件的MD5值,并將其寫入文件末尾;
A10.在.init_array節(jié)的代碼段中添加運行環(huán)境檢測、對抗動態(tài)調(diào)試、MD5完整性校驗,對so文件相應加密函數(shù)解密的解密代碼。
B.開始動態(tài)執(zhí)行Android應用程序時,執(zhí)行如下操作:
B1.軟件主體部分從自身的apk文件中讀取classes.dex文件,在classes.dex文件尾部得到加密數(shù)據(jù)的長度,根據(jù)加密數(shù)據(jù)長度計算出加密數(shù)據(jù)的起始位置,從而讀取得到加密數(shù)據(jù);
B2.調(diào)用loadLibrary加載a.so,這一階段loadLibrary會加載的共享庫、控制權(quán)傳遞給動態(tài)連接器Linker,Linker會檢查文件頭部合法性、根據(jù)頭部的數(shù)據(jù)分別讀入對應的各種數(shù)據(jù)結(jié)構(gòu),并將所有PT_LOAD屬性的段加載至合適的地址空間,然后為該庫在共享庫鏈表中分配一個soinfo節(jié)點并填充其數(shù)據(jù)結(jié)構(gòu),執(zhí)行標記為.init,.init_array的節(jié)的代碼進行代碼初始化工作,若存在JNI_OnLoad函數(shù),執(zhí)行該代碼。(.init_array過程會解密加密函數(shù))
B3.調(diào)用decryDEX()解密得到核心功能部分apk;
B4.通過Android API提供的DexClassLoader類,對核心功能部分碼進行反射調(diào)用,從而實現(xiàn)核心功能部分代碼的動態(tài)加載;
B5.調(diào)用完成后,刪除核心功能部分apk文件,從而避免核心功能部分代碼暴露在系統(tǒng)內(nèi)部存儲之中,被攻擊者得到。
實施例
本實施例是對于一個Android應用進行加固操作,包括部分反調(diào)試步驟。此Android應用可以命名為b.apk,對于b.apk對于可以進行加固,經(jīng)過以上的加固步驟,可以對于b.apk進行加固,防止此APK被調(diào)試破解,查看軟件的源碼。對于此APK的實施實例步驟如下所示,其中A、B步驟作為常規(guī)的防調(diào)試步驟加入,C、D、E步驟作為以上發(fā)明的核心步驟進行。具體步驟如下所示:
A.檢測模擬器:
A1.檢測“/dev/socket/qemud”,“/dev/qemu_pipe”這兩個通道。//判斷兩個通道是否存在,存在則為模擬器access(“/dev/socket/qemud”,0)access(“/dev/qemu_pipe”,0)
A2.檢測props。包括:ro.product.model:該值在模擬器中為sdk,通常在正常手機中為手機的型號;ro.build.tags:該值在模擬器中為test-keys,通常在正常手機中為release-keys;ro.kernel.qemu:該值在模擬器中為1,通常在正常手機中沒有該屬性。
B.防動態(tài)調(diào)試:
B1.多進程使用ptrace,//阻止被調(diào)試器附加ptrace(PTRACE_TRACEME,0,0,0);
B2.對proc/xxx/task和proc/xxx/status進行檢測;默認情況下status中TracerPid值為0,若不為0,則程序正處于被調(diào)試狀態(tài)。
C.自定義so庫文件ELF頭文件信息
ELF頭部的各個字段如下:typedef struct{unsigned char e_ident[16];Elf32_Half e_type;Elf32_Half e_machine;Elf32_Word e_version;Elf32_Addr e_entry;Elf32_Off e_phoff;Elf32_Off e_shoff;Elf32_Word e_flags;Elf32_Half e_ehsize;Elf32_Half e_phentsize;Elf32_Half e_phnum;Elf32_Half e_shentsize;Elf32_Half e_shnum;Elf32_Half e_shstrndx;}Elf32_Ehdr;
通過對Android平臺下動態(tài)庫的加載過程的分析,發(fā)現(xiàn)許多字段并沒有使用。修改這些字段值也不會影響動態(tài)庫的正常使用。然而,在使用readelf、IDA等靜態(tài)分析工具的時候,若這些字段值錯誤,會導致靜態(tài)分析失敗。本發(fā)明技術方案利用這一特性,修改部分字段,達到阻止程序被靜態(tài)分析的目的。具體包括:1)修改e_ident字段后9個字節(jié)。2)修改e_type,e_machine,e_version,e_flag字段。3)修改e_shoff,e_shentsize,e_shnum,e_shstrndx字段。
D.so特定函數(shù)加密
D1.讀取文件頭,獲取e_phoff、e_phentsize和e_phnum信息。
D2.通過Elf32_Phdr中的p_type字段,找到DYNAMIC。其實DYNAMIC就是.dynamic section,這是so文件中的一個段。從p_offset和p_filesz字段得到文件中的起始位置和長度。
D3.遍歷.dynamic,找到.dynsym、.dynstr、.hash section文件中的偏移和.dynstr的大小。在我的測試環(huán)境下,F(xiàn)edora 14和Windows 7Cygwin x64中elf.h定義.hash的d_tag標示是:DT_GNU_HASH;而Android源碼中的是:DT_HASH。
D4.根據(jù)函數(shù)名稱,計算hash值,進一步得到待加密函數(shù)的偏移地址與大小。
D5.根據(jù)hash值,找到下標hash%nbuckets的bucket;根據(jù)bucket中的值,讀取.dynsym中的對應索引的Elf32_Sym符號;從符號的st_name所以找到在.dynstr中對應的字符串與函數(shù)名進行比較。若不等,則根據(jù)chain[hash%nbuckets]找下一個Elf32_Sym符號,直到找到或者chain終止為止,代碼如下:
for(i=bucket[funHash%nbucket];i?。?;i=chain[i]){if(strcmp(dynstr+(funSym+i)->st_name,funcName)==0){flag=0;break;}}
D6.找到函數(shù)對應的Elf32_Sym符號后,即可根據(jù)st_value和st_size字段找到函數(shù)的位置和大小。
D7.將需要加密的區(qū)域進行加密,采用異或加密方法,即取反操作:*content=~(*content)。
E.解密流程為加密逆過程,大體相同,只有一些細微的區(qū)別,具體如下:
E1.找到so文件在內(nèi)存中的起始地址。
E2.通過so文件頭找到Phdr;從Phdr找到PT_DYNAMIC后,需取p_vaddr和p_filesz字段。
后續(xù)步驟與D7步驟中的加密(取反操作)相同。由此完成對應用的加固。
需要注意的是,公布實施例的目的在于幫助進一步理解本發(fā)明,但是本領域的技術人員可以理解:在不脫離本發(fā)明及所附權(quán)利要求的精神和范圍內(nèi),各種替換和修改都是可能的。因此,本發(fā)明不應局限于實施例所公開的內(nèi)容,本發(fā)明要求保護的范圍以權(quán)利要求書界定的范圍為準。