上一篇我們講了apk防止反編譯技術(shù)中的加殼技術(shù),如果有不明白的可以查看我的上一篇博客http://my.oschina.net/u/2323218/blog/393372。接下來我們將介紹另一種防止apk反編譯的技術(shù)-運行時修改字節(jié)碼。這種方法是在工作中在實現(xiàn)app wrapping時佑吝,看到國外的一篇關(guān)于android安全的介紹實現(xiàn)的并且獨創(chuàng)损谦。下面我們來介紹一下這種方法。
我們知道apk生成后所有的java生成的class文件都被dx命令整合成了一個classes.dex文件球及,當(dāng)apk運行時dalvik虛擬機加載classes.dex文件并且用dexopt命令進(jìn)行進(jìn)一步的優(yōu)化成odex文件。我們的方法就是在這個過程中修改dalvik指令來達(dá)到我們的目的呻疹。
一吃引、dex文件格式
dex的文件格式通常有7個主要部分和數(shù)據(jù)區(qū)組成,格式如下:
header部分記錄了主要的信息其他的部分只是索引刽锤,索引的內(nèi)容存在data區(qū)域镊尺。
Header部分結(jié)構(gòu)如下:
字段名稱
偏移值
長度
描述
magic
0x0
8
'Magic'值,即魔數(shù)字段并思,格式如”dex/n035/0”庐氮,其中的035表示結(jié)構(gòu)的版本。
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
標(biāo)識字節(jié)順序的常量,根據(jù)這個常量可以判斷文件是否交換了字節(jié)順序,缺省情況下=0x78563412。
link_size
0x2C
4
連接段的大小莱坎,如果為0就表示是靜態(tài)連接衣式。
link_off
0x30
4
連接段的開始位置,從本文件頭開始算起檐什。如果連接段的大小為0碴卧,這里也是0。
map_off
0x34
4
map數(shù)據(jù)基地址乃正。
string_ids_size
0x38
4
字符串列表的字符串個數(shù)住册。
string_ids_off
0x3C
4
字符串列表表基地址。
type_ids_size
0x40
4
類型列表里類型個數(shù)烫葬。
type_ids_off
0x44
4
類型列表基地址界弧。
proto_ids_size
0x48
4
原型列表里原型個數(shù)凡蜻。
proto_ids_off
0x4C
4
原型列表基地址。
field_ids_size
0x50
4
字段列表里字段個數(shù)垢箕。
field_ids_off
0x54
4
字段列表基地址划栓。
method_ids_size
0x58
4
方法列表里方法個數(shù)。
method_ids_off
0x5C
4
方法列表基地址条获。
class_defs_size
0x60
4
類定義類表中類的個數(shù)忠荞。
class_defs_off
0x64
4
類定義列表基地址。
data_size
0x68
4
數(shù)據(jù)段的大小帅掘,必須以4字節(jié)對齊癣朗。
data_off
0x6C
4
數(shù)據(jù)段基地址
dex與class文件相比的一個優(yōu)勢副砍,就是將所有的常量字符串集統(tǒng)一管理起來了披粟,這樣就可以減少冗余祝钢,最終的dex文件size也能變小一些。詳細(xì)的dex文件介紹就不說了吱窝,有興趣的可以查看android源碼dalvik/docs目錄下的dex-format.html文件有詳細(xì)介紹讥邻。不過我記得在android4.0版本后就沒有了這個文件。
根據(jù)上面的dex文件的格式結(jié)構(gòu)院峡,dalvik虛擬機運行dex文件執(zhí)行的字節(jié)碼就存在method_ids區(qū)域里面兴使。我們查看dalvik虛擬機源碼會有一個
struct DexCode {
u2registersSize;
u2insSize;
u2outsSize;
u2triesSize;
u4debugInfoOff;/* file offset to debug info stream */
u4insnsSize;/* size of the insns array, in u2 units */
u2insns[1];
/* followed by optional u2 padding */
/* followed by try_item[triesSize] */
/* followed by uleb128 handlersSize */
/* followed by catch_handler_item[handlersSize] */
};
這樣一個結(jié)構(gòu),這里的insns數(shù)組存放的就是dalvik的字節(jié)碼照激。我們只要定位到相關(guān)類方法的DexCode數(shù)據(jù)段发魄,即可通過修改insns數(shù)組,從而實現(xiàn)我們的目的俩垃。
二励幼、odex文件格式
apk安裝或啟動時,會通過dexopt來將dex生成優(yōu)化的odex文件吆寨。過程是將apk中的classes.dex解壓后赏淌,用dexopt處理并保存為/data/dalvik-cache/data@app@-X.apk@classes.dex文件。
odex文件結(jié)構(gòu)如下:
從上圖中我們發(fā)現(xiàn)dex文件作為優(yōu)化后的odex的一部分啄清,我們只需要從odex中找出dex的部分即可以了。
三俺孙、方法實現(xiàn)
要實現(xiàn)修改字節(jié)碼辣卒,就需要先定位到想要修改得代碼的位置,這就需要先解析dex文件睛榄。dex文件的解析在dalvik源碼的dexDump.cpp給出了我們具體的實現(xiàn)荣茫,根據(jù)它的實現(xiàn)我們可以查找我們需要的類及方法。具體實現(xiàn)步驟如下:
(1)找到我們apk生成的odex文件场靴,獲得odex文件在內(nèi)存中的映射地址和大小啡莉。實現(xiàn)代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23void*base?=?NULL;
intmodule_size?=?0;
charfilename[512];
//?simple?test?code??here!
for(inti=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文件在內(nèi)存中的映射地址
if(base?!=?NULL){
break;
}
}
module_size?=?get_module_size(-1,?filename);//獲得odex文件大小
(2)知道dex文件在odex中的偏移港准,以便解析dex文件。代碼如下:
1
2
3
4
5
6
7
8
9
10
11//?search?dex?from?odex
void*dexBase?=?searchDexStart(base);
if(checkDexMagic(dexBase)?==false){
ALOGE("Error!?invalid?dex?format?at:?%p",?dexBase);
return;
}
(3)找到dex偏移以后就可以解析dex文件咧欣,從而查找我們要進(jìn)行替換的方法所在的類浅缸,然后在該類中找到該方法并返回該方法對應(yīng)的DexCode結(jié)構(gòu)體。函數(shù)實現(xiàn)如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19staticconstDexCode?*dexFindClassMethod(DexFile?*dexFile,constchar*clazz,constchar*method)
{
DexClassData*?classData?=?dexFindClassData(dexFile,?clazz);
if(classData?==?NULL)returnNULL;
constDexCode*?code?=?dexFindMethodInsns(dexFile,?classData,?method);
if(code?!=?NULL)?{
dumpDexCode(code);
}
returncode;
}
(4)找到DexCode后就可以進(jìn)行指令替換了魄咕。實現(xiàn)如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27constDexCode??*code?=
dexFindClassMethod(&gDexFile,"Lcom/android/dex/myclass;","setflagHidden");
constDexCode*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;?kinsnsSize;?k++){
pCode->insns[k]?=?code->insns[k];
}
mprotect(base,?module_size,?PROT_READ?|?PROT_EXEC);
}
注意:由于是在運行時修改的dalvik指令衩椒,這是進(jìn)程的內(nèi)存映射為只讀的,所以需要調(diào)用mprotect函數(shù)將只讀改為讀寫才能進(jìn)行指令的修改