高 琦 劉克勝 常 超 邱世萬
基于自修改字節碼的Android軟件保護技術研究
高 琦 劉克勝 常 超 邱世萬
(合肥電子工程學院 安徽 合肥 230037)
隨著侵權現象的不斷發生,加強Android軟件的保護已成為一個研究熱點。對軟件逆向工程及其對抗措施進行分析和評價,指出重點的研究領域;針對Android軟件逆向工程,在研究APK安裝原理及dex文件格式的基礎上,采取軟件運行時自修改dalvik字節碼的方案有效改變了代碼的執行流程,增加了代碼的迷惑性。通過實驗實現對軟件關鍵模塊的隱藏,使分析者無法得到正確的代碼流程,證明了該方法在軟件保護領域具有可行性,為軟件保護提供了新的思路。
Android 軟件保護 字節碼 dex文件 代碼自修改
Android軟件市場發展十分迅猛,根據360公司2014年的統計數據[1],僅國內37個大型市場中,軟件數達50萬以上的就有6個,其中3個市場的軟件數超百萬。但市場繁榮的背后隱藏著嚴重的版權保護問題,如游戲廠商“Dead Trigge”因軟件盜版而被迫免費出售軟件的案例曾引起廣泛熱議[2],給開發商帶來巨大損失。更為嚴重的問題是,軟件破解的方法被大量用于惡意代碼的編寫,如偽裝成合法應用竊取用戶敏感信息的惡意軟件[3],不但侵犯了開發者的版權,更損害了用戶的利益和個人隱私。文獻[4]詳細說明了這種惡意軟件從合法逆向、代碼分析、惡意篡改到重打包生成APK并發布的整個過程。
程序自修改(SMC)是一種允許代碼在運行時對自身進行修改的技術手段,最早由日本研究者于2003年提出[5],現在已比較成熟[6],相關文獻[7,8]將此方法進行改進以應對動態分析技術的發展,取得了理想效果。2013年,Bluebox Scurity網站的一篇文章[9]為自修改技術在Android移動端的應用提供了啟示。本文在分析Android軟件靜態分析技術的基礎上,重點研究了APK在系統中的安裝及APK的可執行文件格式,掌握了dalvik字節碼在系統中的存放形式,并將SMC技術原理與Android系統架構和軟件格式相結合,提出了一種基于自修改dalvik字節碼的Android軟件保護方案,并編寫實例進行了測試。一方面,將SMC技術用于Android系統,擴展了其應用范圍,另一方面,本文實現的方法可以有效地提高Android平臺軟件對抗逆向工程的能力。
1.1 Android軟件逆向工程
逆向工程,通常也稱之為Android靜態分析,指的是采用詞法分析、語法分析等手段生成軟件的反匯編代碼,分析員通過閱讀代碼掌握程序功能的一類技術。在實際操作中,分析員通常會采用動靜結合的方法開展分析,但靜態分析仍在其中起主要作用。靜態分析的基本方法是代碼的反匯編和語法分析,通過分析代碼獲取目標軟件各功能模塊及其實現方法。
Android平臺軟件通常基于Java語言編寫,經.class文件轉化成.dex文件,并被dalvik虛擬機所解釋。反匯編程序基于dex文件格式編寫,將Java代碼反匯編生成一種smali格式的語言文件,該文件分別為程序的每一個抽象類、普通類、內部類或接口生成一個對應的smali文件,按原代碼的組織形式呈現在用戶面前。常用的反匯編工具是baksmali、apktool和dex2jar。
1.2 對抗逆向工程的Android軟件保護技術
如何有效保護軟件版權一直是開發商關注的重點,圍繞該問題業界提出了大量解決方案。目前,對抗逆向分析的方法大致可以歸為以下幾類:
(1) 核心模塊隱藏:修改dex文件特殊字段的值可以實現關鍵方法的隱藏,但字段值的改變很容易被有經驗的分析者發現;動態加載函數也允許將部分代碼以資源形式隱藏在本地或云端,但與云端的交互受網絡條件和用戶資費承受能力的制約;利用Java語言的反射機制[10]實現函數功能也能隱藏代碼意圖,但通常仍不可避免地需要將相關參數存儲于云端。
(2) 軟件完整性校驗:對dex文件或整個APK計算hash值,結果存儲于云端或資源文件中,每次運行時都計算該hash值,與存儲的值進行比較。但是這種方法可以通過修改代碼邏輯使之失效。
(3) 破壞代碼可讀性: “append”函數允許逐個字符地產生字符串數組,以增大分析者提取關鍵字段的難度;也可以通過使用goto指令實現代碼的亂序;dexguard或proguard等工具對軟件進行的處理,隱藏了類和方法的名稱,使分析者無法直接判斷代碼作用。
(4) 使用NDK:原生代碼的逆向分析難度遠遠大于Java層代碼,因此軟件的核心功能通過C代碼實現可以增強保護。
(5) 加殼:對關鍵代碼進行加密處理。
(6) 逆向工具對抗:對逆向工具進行分析,尋找其缺陷,有針對性地破壞其功能。Manifest欺騙通過在AndroidManifest文件的
綜合對上述六種方法的分析,目前逆向工程的對抗技術主要存在兩點不足:
一是代碼閱讀的難度雖然增大,但執行流程沒有改變,有經驗的分析者完全有能力克服閱讀上的障礙,因此,需要在提高代碼不可讀性、改變代碼執行流程方面加以研究;
二是針對逆向工具對抗的方法具有時效性,隨著分析技術的不斷發展,對抗方法也需要不斷改進,因此,軟件保護的重點應當是對代碼本身的保護。
基于此分析,本文主要研究通過軟件運行時修改代碼執行流程的方法保護代碼。
APK是Android平臺的軟件安裝包,DEX文件是可執行的Android程序。本節結合Android4.0版本源代碼研究了系統對APK安裝包進行的處理過程,并分析了dex文件格式,為自修改dalvik字節碼提供了理論基礎。
2.1 APK的安裝過程
分析APK文件的安裝過程,重點研究APK安裝后代碼及數據的存放,梳理軟件在系統中的運行模式,為正確定位dalvik字節碼的內存地址提供理論支撐。
系統軟件包packageinstaller.apk接收到系統的Intent后開始軟件的安裝。分析源碼發現這一過程的最終落腳點是frameworksasecmdinstalldcommands.c中的方法install(),該方法執行完畢后通過socket向系統上層報告軟件安裝信息。
APK文件本質是一個壓縮包,包含META-INF(簽名信息)、res(資源文件)、AndroidManifest.xml(配置文件)、classes.dex(Dalvik字節碼)、resources.arsc(二進制資源文件)、lib(原生庫)等內容。APK安裝時,系統首先將APK復制于/data/app目錄下,然后分別將APK的數據部分和dex可執行程序文件部分存于/data/data和/data/ dalvik-cache目錄,其中數據部分包括databases數據庫目錄、cache緩存數據目錄、file應用程序控制的文件目錄、lib庫目錄。Android程序首次啟動時,Dalvik虛擬機通過依存關系樹優化程序代碼,包括將APK中dex格式優化為odex格式,并刪除原dex文件。odex文件是Android平臺的可運行文件,比dex文件體積小。這種優化可以加快軟件啟動速度,減少對RAM的占用。
2.2 dex文件與odex文件的格式
Dex文件是軟件的可執行程序在內存中的存放形式,修改dalvik字節碼的本質是對dex文件的修改,研究該文件格式,重點是研究函數的字節碼在文件中的存放位置和存放格式,為自修改dalvik字節碼的實現提供框架支撐。
Android4.1以前版本的源碼的文檔dex-format.html記錄了dex的文件格式,本文主要結合Android4.0版本源碼對相關內容進行分析和梳理。
Odex文件被視為dex文件的超集,其文件結構如圖1所示。

圖1 Odex文件結構
綜合代碼中對odex文件的敘述,可以用如下的結構對其進行定義,其中header是文件頭,dexfile是dex文件。
struct ODEXFile{
DexOptHeader header;
//文件頭
DEXFile dexfile ;
//dex文件
Dependences dependlib ;
//庫
ChunkDexClassLookup classlook;
//輔助數據
ChunkRegisterMapPool Registerpool;
//輔助數據
ChundEnd end;
//文件結束
}
源碼中的dalvik/libdex/DexFile.h文件對該結構體的各個要素在dalvik虛擬機中的映射作了定義。文件頭結構DexOptHeader中,dexOffset代表dex文件頭偏移,dexLength代表dex文件總長度。
struct DexOptHeader {
//odex文件頭
u1 magic[8];
/* includes version number */
u4 dexOffset;
/* file offset of DEX header */
u4 dexLength;
u4 depsOffset;
/* offset of optimized DEX dependency table */
u4 depsLength;
u4 optOffset;
/* file offset of optimized data tables */
u4 optLength;
u4 flags;
/* some info flags */
u4 checksum;
/* adler32 checksum covering deps/opt */
};
結構體的dexOffset字段記錄了dex的基地址偏移。
Android系統對dex文件結構的定義如圖2所示。

圖2 Dex文件結構
Dalvik虛擬機在源碼中將文件映射為DexFile結構體:
struct DexFile {
/* directly-mapped ″opt″ header */
const DexOptHeader* pOptHeader;
/* pointers to directly-mapped structs and arrays in base DEX */
const DexHeader* pHeader;
const DexStringId* pStringIds;
const DexTypeId* pTypeIds;
const DexFieldId* pFieldIds;
const DexMethodId* pMethodIds;
const DexProtoId* pProtoIds;
const DexClassDef* pClassDefs;
const DexLink* pLinkData;
……
}
其中DexHeader是文件頭,它的methodIdsSize、mehtodIdsOff、classDefsSize、classDefsOff指針分別代表DexMethodId(方法結構體)及DexClassDef(類結構體)字段的大小和偏移。
struct DexHeader {
//dex文件頭
u1 magic[8];
/* includes version number */
u4 checksum;
/* adler32 checksum */
u1 signature[kSHA1DigestLen];
/* SHA-1 hash */
u4 fileSize;
/* length of entire file */
u4 headerSize;
/* offset to start of next section */
u4 endianTag;
u4 linkSize;
u4 linkOff;
u4 mapOff;
u4 stringIdsSize;
u4 stringIdsOff;
u4 typeIdsSize;
u4 typeIdsOff;
u4 protoIdsSize;
u4 protoIdsOff;
u4 fieldIdsSize;
u4 fieldIdsOff;
u4 methodIdsSize;
u4 methodIdsOff;
u4 classDefsSize;
u4 classDefsOff;
u4 dataSize;
u4 dataOff;
};
DexClassDef有一個classDataOff字段,指向一個DexClassData結構體,該結構體的directMethods字段又指向一個描述程序中調用的方法的DexMethod結構體,聲明如下:
struct DexMethod{
//方法信息
u4 methodIdx;
u4 accessFlags;
u4 codeOff;
}
codeOff字段指向DexCode結構體,它的定義為:
struct DexCode {
//方法的實現代碼
u2 registersSize;
u2 insSize;
u2 outsSize;
u2 triesSize;
u4 debugInfoOff;
/* file offset to debug info stream */
u4 insnsSize;
/* size of the insns array, in u2 units */
u2 insns[1];
}
其中insns字段內存儲了該方法的dalvik字節碼,進行字節碼修改,主要就是尋找這個字段,并修改該字段的內容。
2.3 Android原生程序及其調用
原生代碼運行在系統庫層,對dalvik字節碼的修改只能通過原生代碼實現, 通過Java語言的JNI調用與原生庫進行交互,利用NDK編程生成動態鏈接文件實現對字節碼的修改。
NDK(native development kit)是Android平臺對C和C++代碼的支持,Android自誕生起就表達了對C代碼的支持,NDK將這種支持加以具體實現。它包含一系列動態庫開發工具,集成交叉編譯,通過mk文件對CPU、平臺等硬件條件進行透明化處理,生成.so文件并與Java層生成的文件隨應用一起打包入APK壓縮包,向市場發布。但Andoid明確表示NDK只支持部分功能,無法實現全C代碼的Android軟件。NDK編程的優點在于代碼重用和代碼保護,可以增加代碼跨硬件的可移植性,或增大分析者逆向獲取源代碼結構的難度;它的缺點在于代碼復雜度的大幅增加,可能給開發者帶來不便和風險。因此,編程人員往往只對關鍵代碼或對系統性能有較高要求的部分才使用C代碼實現,如涉及顯示器的操作[11,12]。
Java本地調用JNI是Java語言平臺的組成部分,它提供了對C代碼的支持,使得原生庫像普通Java方法一樣為主程序所調用,同時JNI也提供了原生代碼對java代碼的調用方案。它有一套與Java語言相對的數據類型、數據結構及函數標記。在JNI平臺的支持下,Android應用可以通過靈活地調用C代碼實現更多功能。如圖3所示。

圖3 JNI調用示意圖
JNI的書寫步驟包括:
(1) 編寫帶native聲明方法的Java類;
(2) 使用Javac編譯Java類;
(3) 使用Javah+Java生成頭文件;
(4) 跟據頭文件,實現本地代碼;
(5) 編譯生成動態連接庫。
本節設計了一段示例代碼,該示例軟件的作用是對數據進行簡單加密,即把用戶輸入的數據按1.8倍輸出,用以加密數據。同時,在逆向分析時,攻擊者通過靜態分析得出的結論卻是用戶輸入的數據被直接輸出,未進行乘法處理,從而無法分析出軟件的真實流程。圖4是示例軟件的設計思路。

圖4 軟件設計
為了更加清晰地展示實驗內容,該軟件只實現了關鍵代碼,修飾性的代碼全部去除。
3.1 執行流程
(1) Java層主函數代碼設計
創建包com.tim.parsetest,涉及的具體操作的實現代碼在類文件SetDataMethod中實現;
設計一個方法setdata(),供主函數調用;
設計一個方法setdatahidden(),作為調用setdata ()時,軟件實際運行的代碼;
在主界面MainActivity中調用方法setdata();
本實驗中,為了使實驗內容更加直觀,為輸入數據賦值“1.0”,直接在界面輸出結果值。
(2) 啟動C層代碼
Android系統通過System.loadlibrary()函數調用動態鏈接庫.so文件,并首先執行JNI_Onload()函數。該函數主要目的是進行JNI版本的聲明和代碼初始化,相當于一個初始化函數。
本實驗在JNI_Onload()函數內調用動態鏈接庫parsingdex. so,該動態鏈接庫的作用是在C層完成字節碼的修改和替換。確保在軟件調用動態鏈接庫時,setdatahidden()對setdata()的替換可以在C層代碼初始化期間完成,不需要顯性地在Java層調用該方法,從而確保了setdatahidden()函數在代碼執行路徑中不出現。
(3) 尋找對應方法的DexCode結構體
由上文2.2節的分析,DexCode結構體中存儲了方法對應的字節碼,因此代碼執行替換操作之前必須正確尋找和定位setdata()與setdatahidden()對應的的DexCode結構體。
第一步,定位odex文件,獲取odex文件的基地址odexbase。先是跟據odex文件在系統中的存諸路徑及格式構造odex文件名:
odexname=/data/dalvik-cache/data@app@com.tim.parsetest-%d.apk@classes.d
然后調用getmodulebase()方法搜索本進程的proc/self/ maps文件獲取odex的基地址。
第二步,獲取odex內存塊大小odexsize。通過getmodulesize()方法獲取系統maps文件內odex所占內存的起始值和結束值,它們的差值就是odex的大小。
第三步,獲取dex文件位置dexbase。調用getdexstart()方法,在odex文件頭讀取dex文件偏移字段dexoffset的值,采用odexbase+dexoffset的方法得到dex內存塊基址。讀取maps文件的偽代碼可如下所示:
fp=fopen(proc/self/maps);
while(fgets(line,fp)=1){
if(strstr(line,odex文件名)=1){
讀取該行地址信息;
}
}
fclose;
第四步,解析dex文件頭。對DexFile結構體的關鍵字段賦值,方便下一步操作。
第五步,調用getmethod()方法,尋找DexCode結構體。在系統中搜索該結構體的流程如圖5所示。

圖5 DexCode結構體的確定過程
通過枚舉類結構DexClassDef,確定對應SetDataMethod類的DexClassDef結構體,同理,枚舉DexMethod確定setdata()與setdatahidden()方法的DexMethod結構體。
(4) 修改讀寫權限
要想隱性地修改setdata()方法的dalvik字節碼,必須調用mprotect()函數為相應內存區間增加PROT_WRITE聲明,賦予軟件將字節碼寫入setdata()的權限。
(5) 實現字節碼的寫入
以字符串數組的形式逐個替換setdata()方法的insns[]字段。可用如下偽代碼表示:
mprotect(dex,PROT_READ|PROT_WRITE|PROT|EXEC);
for(i=0;i insns_setdata[i] = insns_setdatahidden[i]; } mprotect(dex,PROT_READ|PROT_WRITE); //恢復權限的目的是防止軟件在運行時出現不可預知的錯誤 3.2 實驗結果 對代碼進行編譯和組建,生成dalvikmodifytest.apk文件。 利用工具ApkIDE對文件進行逆向,分析其軟件功能。為了直觀地展現結果,APK文件在組建時未做任何混淆。 (1) 靜態分析 對軟件進行逆向,發現主程序調用了setdata()方法,參數1.0,逆向代碼如下: invoke-virtual {v0, v2, v3}, Lcom/tim/parsetest/SetDataMethod;-> setdata(D)V Setdata()方法中,輸入的參數1.0被直接存入data域,此時,data=1.0,逆向代碼為: iput-object v0, p0, Lcom/tim/parsetest/SetDataMethod;->data:Ljava/jang/ Double; 隨后getdata()方法將data值直接輸出: iget-object v0, p0, Lcom/tim/parsetest/SetDataMethod;->data:Ljava/jang/ Double; return v0; 從逆向分析判斷,軟件將1.0存入data域后,通過getdata()直接輸出,其結果應與輸入參數一致,為1.0。從分析過程來看,setdatahidden()方法并未被調用。 (2) 實際運行 在Android 4.0版本系統中運行該APK,界面中輸出了getdata()函數的運行結果如圖6所示。 圖6 真實的實驗結果與逆向分析結果不符 按照逆向工程的步驟得出的結果是1.0,但實際運行的卻是輸入數據1.8倍的結果1.8,說明該軟件并未執行setdata()方法,而是在調用setdata()方法時,執行了setdatahidden()方法的字節碼。因此,軟件的關鍵加密模塊setdatahidden()被隱藏,受到了保護。 本實驗成功地迷惑了逆向分析者,實現了對dalvik字節碼的自修改,更改了代碼的執行流程,增加了代碼的迷惑性,證實了該方法在軟件保護領域對抗逆向工程的可行性。 本文分析了軟件逆向工程及其對抗,將研究點關注于增加代碼迷惑性和改變代碼執行流程,在研究了APK安裝過程、dex文件結構及JNI調用的基礎上,編寫了一個簡單的示例代碼實現了自修改dalvik字節碼,并證實了該方案在軟件保護上的可用性和廣闊前景。 下一步主要有兩個方面的工作,一是采用更加成熟的方法實現字節碼寫入,以防止可能出現的內存塊溢出;二是進一步研究該方法在對抗軟件動態分析方面的可行性。 [1] 360互聯網中心.2013年中國手機安全狀況報告[EB/OL].(2014-02-11).[2014-09-11].http://awhzfien7r.l5.yunpan.cn/lkl QPERX j6bcPGgA. [2] Android Police.Just How Bad Is App Piracy On Android Anyway? Hint: We’re Asking The Wrong Question[EB/OL].(2012-07-31).[2014-09-11].http://www.androidpolice.com/2012/07/31/editorial-just-how-bad-is-app-piracy-on-android-anyways-hint-were-asking-the-wrong-question/. [3] 360互聯網中心.2014年第一期中國移動支付安全報告[EB/OL].(2014 -03-11).[2014-9-11].http://aqvs9knlja.l5.yanpan.cn/lk/Q4VgpLKMLV9Xq. [4] 伍景珠.基于Android平臺的軟件保護方案的研究與實現[D].北京:北京郵電大學,2013. [5] Kanzaki Y,Monden A.Computer Software and Applications Conference[C].Dallas,USA:COMPSAC,2003. [6] 徐江凌.基于反跟蹤和自修改代碼技術的軟件保護系統設計[D].成都:電子科技大學,2010. [7] 王祥根,司端鋒,馮登國.一種基于自修改代碼技術的軟件保護方法[J].中國科學院研究生院學報,2009,26(5):688-694. [8] 高兵,林果園,王瑩.基于代碼自修改的軟件反跟蹤技術結構[J].信息網絡安全,2014(5):46-51. [9] Patrick Schulz.Android Security Analysis Challenge Tampering Dalvik Bytecode During Runtime[EB/OL].(2013-03-25).[2014-09-11].http://blog.bluebox.com/2013/03/25/android-security-analysis-challenge-tampering-dalvik-bytecode-during-runtime/. [10] 李興華.Java開發實戰經典[M].北京:清華大學出版社,2009:576-606. [11] 馬建設,趙雪紅,蘇萍,等.基于Android系統的視頻播放器開發[J].計算機應用與軟件,2013,30(11):136-137,175. [12] 楊倩,楊明趙.Android顯示服務器——SurfaceFlinger研究[J].計算機應用與軟件,2014,31(6):324-326. RESEARCH ON ANDROID SOFTWARE PROTECTION BASED ON SELF-MODIFYING BYTE CODE Gao Qi Liu Kesheng Chang Chao Qiu Shiwan (HefeiElectronicEngineeringInstitution,Hefei230037,Anhui,China) Along with the copyright infringement occurs continuously, to strengthen the protection of Android software has become a hot research topic. In this paper we carry out analysis and comment on software reverse engineering as well as its countermeasures, and point out the research focus. Aiming at reverse engineering of Android software, after studying APK installation principle and the format of DEX file, we employ the scheme of self-modifying Dalvik byte code during the operation of software to have effectively changed code execution process, and increased the perplexity of code. Through the experiment we realise the concealment of key software modules, and lead to the analyser failed in finding the correct code flow, this proves that the method has the feasibility in software protection field, and provides a new idea for software protection. Android Software protection Byte code Dex file Code self-modifying 2014-10-21。高琦,碩士生,主研領域:信息安全。劉克勝,教授。常超,博士生。邱世萬,碩士生。 TP311.54 A 10.3969/j.issn.1000-386x.2016.04.054
4 結 語