上一篇咱們講了apk防止反編譯技術中的加殼技術,若是有不明白的能夠查看個人上一篇博客http://my.oschina.net/u/2323218/blog/393372。接下來咱們將介紹另外一種防止apk反編譯的技術-運行時修改字節碼。這種方法是在工做中在實現app wrapping時,看到國外的一篇關於android 安全的介紹實現的並不是首創。下面咱們來介紹一下這種方法。html
咱們知道apk生成後全部的java生成的class文件都被dx命令整合成了一個classes.dex文件,當apk運行時dalvik虛擬機加載classes.dex文件而且用dexopt命令進行進一步的優化成odex文件。咱們的方法就是在這個過程當中修改dalvik指令來達到咱們的目的。java
1、dex文件格式android
dex的文件格式一般有7個主要部分和數據區組成,格式以下:程序員
header部分記錄了主要的信息其餘的部分只是索引,索引的內容存在data區域。數組
Header部分結構以下:安全
字段名稱微信 |
偏移值app |
長度微信公衆平臺 |
描述函數 |
magic |
0x0 |
8 |
'Magic'值,即魔數字段,格式如」dex/n035/0」,其中的035表示結構的版本。 |
checksum |
0x8 |
4 |
校驗碼。 |
signature |
0xC |
20 |
SHA-1簽名。 |
file_size |
0x20 |
4 |
Dex文件的總長度。 |
header_size |
0x24 |
4 |
文件頭長度,009版本=0x5C,035版本=0x70。 |
endian_tag |
0x28 |
4 |
標識字節順序的常量,根據這個常量能夠判斷文件是否交換了字節順序,缺省狀況下=0x78563412。 |
link_size |
0x2C |
4 |
鏈接段的大小,若是爲0就表示是靜態鏈接。 |
link_off |
0x30 |
4 |
鏈接段的開始位置,從本文件頭開始算起。若是鏈接段的大小爲0,這裏也是0。 |
map_off |
0x34 |
4 |
map數據基地址。 |
string_ids_size |
0x38 |
4 |
字符串列表的字符串個數。 |
string_ids_off |
0x3C |
4 |
字符串列表表基地址。 |
type_ids_size |
0x40 |
4 |
類型列表裏類型個數。 |
type_ids_off |
0x44 |
4 |
類型列表基地址。 |
proto_ids_size |
0x48 |
4 |
原型列表裏原型個數。 |
proto_ids_off |
0x4C |
4 |
原型列表基地址。 |
field_ids_size |
0x50 |
4 |
字段列表裏字段個數。 |
field_ids_off |
0x54 |
4 |
字段列表基地址。 |
method_ids_size |
0x58 |
4 |
方法列表裏方法個數。 |
method_ids_off |
0x5C |
4 |
方法列表基地址。 |
class_defs_size |
0x60 |
4 |
類定義類表中類的個數。 |
class_defs_off |
0x64 |
4 |
類定義列表基地址。 |
data_size |
0x68 |
4 |
數據段的大小,必須以4字節對齊。 |
data_off |
0x6C |
4 |
數據段基地址 |
dex與class文件相比的一個優點,就是將全部的常量字符串集統一管理起來了,這樣就能夠減小冗餘,最終的dex文件size也能變小一些。詳細的dex文件介紹就不說了,有興趣的能夠查看android 源碼dalvik/docs目錄下的dex-format.html文件有詳細介紹。不過我記得在android4.0版本後就沒有了這個文件。
根據上面的dex文件的格式結構,dalvik虛擬機運行dex文件執行的字節碼就存在method_ids區域裏面。咱們查看dalvik虛擬機源碼會有一個
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];
/* followed by optional u2 padding */
/* followed by try_item[triesSize] */
/* followed by uleb128 handlersSize */
/* followed by catch_handler_item[handlersSize] */
};
這樣一個結構,這裏的insns數組存放的就是dalvik的字節碼。咱們只要定位到相關類方法的DexCode數據段,便可經過修改insns數組,從而實現咱們的目的。
2、odex文件格式
apk安裝或啓動時,會經過dexopt來將dex生成優化的odex文件。過程是將apk中的classes.dex解壓後,用dexopt處理並保存爲/data/dalvik-cache/data@app @<package-name>-X.apk@classes.dex文件。
odex文件結構以下:
從上圖中咱們發現dex文件做爲優化後的odex的一部分,咱們只須要從odex中找出dex的部分便可以了。
3、方法實現
要實現修改字節碼,就須要先定位到想要修改得代碼的位置,這就須要先解析dex文件。dex文件的解析在dalvik源碼的dexDump.cpp給出了咱們具體的實現,根據它的實現咱們能夠查找咱們須要的類及方法。具體實現步驟以下:
(1) 找到咱們apk生成的odex文件,得到odex文件在內存中的映射地址和大小。實現代碼以下:
void *base = NULL; int module_size = 0; char filename[512]; // simple test code here! for(int i=0; i<2; i++){ sprintf(filename,"/data/dalvik-cache/data@app@%s-%d.apk@classes.dex", "com.android.dex", i+1); base = get_module_base(-1, filename);//得到odex文件在內存中的映射地址 if(base != NULL){ break; } } module_size = get_module_size(-1, filename); //得到odex文件大小
(2) 知道dex文件在odex中的偏移,以便解析dex文件。代碼以下:
// search dex from odex void *dexBase = searchDexStart(base); if(checkDexMagic(dexBase) == false){ ALOGE("Error! invalid dex format at: %p", dexBase); return; }
(3) 找到dex偏移之後就能夠解析dex文件,從而查找咱們要進行替換的方法所在的類,而後在該類中找到該方法並返回該方法對應的DexCode結構體。函數實現以下:
static const DexCode *dexFindClassMethod(DexFile *dexFile, const char *clazz, const char *method) { DexClassData* classData = dexFindClassData(dexFile, clazz); if(classData == NULL) return NULL; const DexCode* code = dexFindMethodInsns(dexFile, classData, method); if(code != NULL) { dumpDexCode(code); } return code; }
(4) 找到DexCode後就能夠進行指令替換了。實現以下:
const DexCode *code = dexFindClassMethod(&gDexFile, "Lcom/android/dex/myclass;", "setflagHidden"); const DexCode*code2 = dexFindClassMethod(&gDexFile, "Lcom/android/dex/myclass;", "setflag"); // remap!!!! if(mprotect(base, module_size, PROT_READ | PROT_WRITE | PROT_EXEC) == 0){ DexCode *pCode = (DexCode *)code2; // Modify! pCode->registersSize = code->registersSize; for(u4 k=0; k<code->insnsSize; k++){ pCode->insns[k] = code->insns[k]; } mprotect(base, module_size, PROT_READ | PROT_EXEC); }
注意:因爲是在運行時修改的dalvik指令,這是進程的內存映射爲只讀的,因此須要調用mprotect函數將只讀改成讀寫才能進行指令的修改。
根據上面的講述相信你們對運行時修改字節碼的技術有了必定的瞭解,下一篇咱們將講解另外一種android apk防止反編譯技術,期待你們的捧場。若是對這篇講的技術有任何疑問及想要得到這篇文章講的技術的工程源碼
歡迎關注我的微信公衆平臺:程序員互動聯盟(coder_online),掃一掃下方二維碼或搜索微信號coder_online便可關注,咱們能夠在線交流。