2020-10-27(dex文件解析)
一張圖搞懂dex
大圖這里
當(dāng)然也可以通過(guò)下面的圖12 DexFile的文件格式,了解更清楚。
DEX文件詳解
什么是dex文件?
如何生成一個(gè)dex文件
dex文件的作用
dex文件格式詳解
什么是dex文件?
dex文件是Android系統(tǒng)中的一種文件,是一種特殊的數(shù)據(jù)格式,和APK、jar 等格式文件類(lèi)似。
能夠被DVM識(shí)別,加載并執(zhí)行的文件格式。
簡(jiǎn)單說(shuō)就是優(yōu)化后的android版.exe。每個(gè)apk安裝包里都有。包含應(yīng)用程序的全部操作指令以及運(yùn)行時(shí)數(shù)據(jù)。
相對(duì)于PC上的java虛擬機(jī)能運(yùn)行.class;android上的Davlik虛擬機(jī)能運(yùn)行.dex。
當(dāng)java程序編譯成class后,還需要使用dx工具將所有的class文件整合到一個(gè)dex文件,目的是其中各個(gè)類(lèi)能夠共享數(shù)據(jù),在一定程度上降低了冗余,同時(shí)也是文件結(jié)構(gòu)更加經(jīng)湊,實(shí)驗(yàn)表明,dex文件是傳統(tǒng)jar文件大小的50%左右
圖2 apk中的dex文件
為何要研究dex格式?因?yàn)閐ex里面包含了所有app代碼,利用反編譯工具可以獲取java源碼。理解并修改dex文件,就能更好的apk破解和防破解。
使用dex文件的最大目的是實(shí)現(xiàn)安全管理,但在追求安全的前提下,一定要注意對(duì)dex文件實(shí)現(xiàn)優(yōu)化處理。
注意:并不是只有Java才可以生成dex文件,C和C++也可以生成dex文件
如何生成一個(gè)dex文件?
通過(guò)IDE自動(dòng)幫我們build生成
手動(dòng)通過(guò)dx命令去生成dex文件
在待測(cè)試的class文件目錄下(我將TestMain.class放到了F盤(pán)根目錄下),執(zhí)行命令dx --dex --output TestMain.dex TestMain.class,就會(huì)生成TestMain.dex文件。
手動(dòng)運(yùn)行dex文件在手機(jī)
在待測(cè)試的dex文件目錄下(我將TestMain.class放到了F盤(pán)根目錄下),通過(guò)adb push TestMain.dex /storage/emulated/0命令,然后通過(guò)adb shell命令進(jìn)入手機(jī),后執(zhí)行dalvikvm -cp /sdcard/TestMain.dex TestMain,就會(huì)打印出
Hello World!
如下圖3所示(使用AS的終端,沒(méi)有用Windows的cmd命令)
圖3 手動(dòng)運(yùn)行dex文件
注意:
環(huán)境變量的配置,dex在SDK目錄下的build-tools目錄下有很多版本,這里可以選擇最新版本目錄下dx.bat配置到環(huán)境變量path路徑下;同樣,adb命令也同樣配置。
運(yùn)行完dex文件,可以通過(guò)exit退出手機(jī),電腦本地盤(pán)符
查看dex文件
大圖這里
圖4 dex文件概貌
通過(guò)010Editor工具(圖片來(lái)自網(wǎng)絡(luò))
大圖這里
圖5 注:圖片來(lái)自網(wǎng)絡(luò)
下圖6是TestMain.dex通過(guò)010Editor工具得到的
大圖這里
圖6 010Editor 檢測(cè)TestMain.dex結(jié)果
圖7是通過(guò)010Editor工具檢測(cè)TestMain.dex得到的 Template Result結(jié)果
大圖這里
圖7 010Editor檢測(cè)TemplateResult結(jié)果
通過(guò)dexdump 命令查看(注意)
利用build-tools 下的dexdump 命令查看,dexdump -d -l plain TestMain.dex,得到下面的結(jié)果
registers:Dalvik 最初目標(biāo)是運(yùn)行在以ARM 做CPU 的機(jī)器上的,ARM 芯片的一個(gè)主要特點(diǎn)是寄存器多。寄存器多的話(huà)有好處,就是可以把操作數(shù)放在寄存器里,而不是像傳統(tǒng)VM 一樣放在棧中。自然,操作寄存器是比操作內(nèi)存(棧嘛,其實(shí)就是一塊內(nèi)存區(qū)域)快。registers 變量表示該方法運(yùn)行過(guò)程中會(huì)使用多少個(gè)寄存器。
ins:輸入?yún)?shù)對(duì)應(yīng)的個(gè)數(shù)
outs:此函數(shù)內(nèi)部調(diào)用其他函數(shù),需要的參數(shù)個(gè)數(shù)。
insns size:以4 字節(jié)為單位,代表該函數(shù)字節(jié)碼的長(zhǎng)度(類(lèi)似Class 文件的code[]數(shù)組)
更多內(nèi)容參考:官網(wǎng)介紹----->Dalvik 可執(zhí)行文件格式
dex文件的作用
記錄整個(gè)工程中所有類(lèi)的信息,記住的整個(gè)工程所有類(lèi)的信息
dex文件格式詳解
一種8位字節(jié)的二進(jìn)制流文件
各個(gè)數(shù)據(jù)按順序緊密的排列,無(wú)間隙
整個(gè)應(yīng)用中所有的Java源文件都放在一個(gè)dex中
大圖這里
圖8 dex文件結(jié)構(gòu)
上圖中的文件頭部分,記錄了dex文件的信息,所有字段大致的一個(gè)分部;索引區(qū)部分,主要包含字符串、類(lèi)型、方法原型、域、方法的索引;索引區(qū)最終又被存儲(chǔ)在數(shù)據(jù)區(qū),其中鏈接數(shù)據(jù)區(qū),主要存儲(chǔ)動(dòng)態(tài)鏈接庫(kù),so庫(kù)的信息。
源碼:/dalvik/libdex/DexFile.h: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; };具體可查看Android源碼官網(wǎng)的關(guān)于dex文件結(jié)構(gòu)的詳解,如下圖9(大圖這里)
圖9 dex文件結(jié)構(gòu)詳解
總結(jié):
數(shù)據(jù)名稱(chēng) 解釋
header dex 文件頭部,記錄整個(gè)dex文件的相關(guān)屬性
string_ids 字符串?dāng)?shù)據(jù)索引,記錄了每個(gè)字符串在數(shù)據(jù)區(qū)的偏移量
type_ids
| header dex | 文件頭部,記錄整個(gè)dex文件的相關(guān)屬性 |
| string_ids | 字符串?dāng)?shù)據(jù)索引,記錄了每個(gè)字符串在數(shù)據(jù)區(qū)的偏移量 |
| type_ids | 類(lèi)似數(shù)據(jù)索引,記錄了每個(gè)類(lèi)型的字符串索引 |
| proto_ids | 原型數(shù)據(jù)索引,記錄了方法聲明的字符串,返回類(lèi)型字符串,參數(shù)列表 |
| field_ids | 字段數(shù)據(jù)索引,記錄了所屬類(lèi),類(lèi)型以及方法名 |
| method_ids | 類(lèi)方法索引,記錄方法所屬類(lèi)名,方法聲明以及方法名等信息 |
| class_defs | 類(lèi)定義數(shù)據(jù)索引,記錄指定類(lèi)各類(lèi)信息,包括接口,超類(lèi),類(lèi)數(shù)據(jù)偏移量 |
| data | 數(shù)據(jù)區(qū),保存了各個(gè)類(lèi)的真是數(shù)據(jù) |
| link_data | 連接數(shù)據(jù)區(qū) |
DEX 文件中會(huì)出現(xiàn)的數(shù)據(jù)類(lèi)型
類(lèi)型 含義
u1 等同于uint8_t,表示 1 字節(jié)的無(wú)符號(hào) 數(shù)
u2 等同于 uint16_t,表示 2 字節(jié)的無(wú)符號(hào)數(shù)
u4 等同于 uint32_t,表示 4 字節(jié)的無(wú)符號(hào)數(shù)
u8 等同于 uint64_t,表示 8 字節(jié)的無(wú)符號(hào)數(shù)
sleb128 有符號(hào) LEB128,可變長(zhǎng)度 1~5 字節(jié)
uleb128 無(wú)符號(hào) LEB128,可變長(zhǎng)度 1~5 字節(jié)
uleb128p1 無(wú)符號(hào) LEB128 值加1,可變長(zhǎng) 1~5 字節(jié)
/dalvik/libdex/DexFile.h中定義如下
typedef uint8_t u1;
typedef uint16_t u2;
typedef uint32_t u4;
typedef uint64_t u8;
typedef int8_t s1;
typedef int16_t s2;
typedef int32_t s4;
typedef int64_t s8;
LEB128
LEB128(“Little-Endian Base 128”)表示任意有符號(hào)或無(wú)符號(hào)整數(shù)的可變長(zhǎng)度編碼。該格式借鑒了 DWARF3 規(guī)范。在 .dex 文件中,LEB128 僅用于對(duì) 32 位數(shù)字進(jìn)行編碼。
每個(gè) LEB128 編碼值均由 1-5 個(gè)字節(jié)組成,共同表示一個(gè) 32 位的值。每個(gè)字節(jié)均已設(shè)置其最高有效位(序列中的最后一個(gè)字節(jié)除外,其最高有效位已清除)。每個(gè)字節(jié)的剩余 7 位均為有效負(fù)荷,即第一個(gè)字節(jié)中有 7 個(gè)最低有效位,第二個(gè)字節(jié)中也是 7 個(gè),依此類(lèi)推。對(duì)于有符號(hào) LEB128 (sleb128),序列中最后一個(gè)字節(jié)的最高有效負(fù)荷位會(huì)進(jìn)行符號(hào)擴(kuò)展,以生成最終值。在無(wú)符號(hào)情況 (uleb128) 下,任何未明確表示的位都會(huì)被解譯為 0。
大圖這里
圖10 雙字節(jié) LEB128 值的按位圖
變量 uleb128p1 用于表示一個(gè)有符號(hào)值,其表示法是編碼為 uleb128 的值加 1。這使得編碼 -1(或被視為無(wú)符號(hào)值 0xffffffff)成為一個(gè)單字節(jié)(但沒(méi)有任何其他負(fù)數(shù)),并且該編碼在下面這些明確說(shuō)明的情況下非常實(shí)用:所表示的數(shù)值必須為非負(fù)數(shù)或 -1(或 0xffffffff);不允許任何其他負(fù)值(或不太可能需要使用較大的無(wú)符號(hào)值)。
以下是這類(lèi)格式的一些示例:
編碼序列 As sleb128 As uleb128 As uleb128p1
00 0 0 -1
01 1 1 0
7f -1 127 126
80 7f -128 16256
dex文件頭
Dex文件頭主要包括校驗(yàn)和以及其他結(jié)構(gòu)的偏移地址和長(zhǎng)度信息。
源碼位于 /dalvik/libdex/DexFile.h:DexHeader
具體詳解如下圖5所示
大圖這里
圖11 dex文件頭信息
各個(gè)字段詳解摘要
mapOff 字段
指定 DexMapList 結(jié)構(gòu)距離 Dex 頭的偏移
DexMapList 結(jié)構(gòu)體:
struct DexMapList
{
u4 size; // DexMapItem 的個(gè)數(shù)
DexMapItem list[1]; // DexMapItem 結(jié)構(gòu)
};
size:表示接下來(lái)有多少個(gè) DexMapItem
list:是一個(gè) DexMapItem 結(jié)構(gòu)體數(shù)組
DexMapItem 結(jié)構(gòu)體:
struct DexMapItem
{
u2 type; // kDexType 開(kāi)頭的類(lèi)型
u2 unused; // 未使用,用于對(duì)齊
u4 size; // 指定類(lèi)型的個(gè)數(shù)
u4 offset; // 指定類(lèi)型數(shù)據(jù)的文件偏移
};
type:一個(gè)枚舉常量
enum
{
kDexTypeHeaderItem = 0x0000, // 對(duì)應(yīng) DexHeader
kDexTypeStringIdItem = 0x0001, // 對(duì)應(yīng) stringIdsSize 與 stringIdsOff 字段
kDexTypeTypeIdItem = 0x0002, // 對(duì)應(yīng) typeIdsSize 與 typeIdsOff 字段
kDexTypeProtoIdItem = 0x0003, // 對(duì)應(yīng) protoIdsSize 與 protoIdsOff 字段
kDexTypeFieldIdItem = 0x0004, // 對(duì)應(yīng) fieldIdsSize 與 fieldIdsOff 字段
kDexTypeMethodIdItem = 0x0005, // 對(duì)應(yīng) methodIdsSize 與 methodIdsOff 字段
kDexTypeClassDefItem = 0x0006, // 對(duì)應(yīng) classDefsSize 與 classDefsOff 字段
kDexTypeMapList = 0x1000,
kDexTypeTypeList = 0x1001,
kDexTypeAnnotationSetRefList = 0x1002,
kDexTypeAnnotationSetItem = 0x1003,
kDexTypeClassDataItem = 0x2000,
kDexTypeCodeItem = 0x2001,
kDexTypeStringDataItem = 0x2002,
kDexTypeDebugInfoItem = 0x2003,
kDexTypeAnnotationItem = 0x2004,
kDexTypeEncodeArrayItem = 0x2005,
kDexTypeAnnotationsDirectoryItem = 0x2006
};
size:指定類(lèi)型的個(gè)數(shù)
offset:指定類(lèi)型數(shù)據(jù)的偏移
DexStringId 結(jié)構(gòu)體(stringIdsSize 與 stringIdsOff 字段)
typedef struct _DexStringId
{
u4 stringDataOff; // 指向 MUTF-8 字符串的偏移
}DexStringId, *PDexStringId;
MUTF-8 編碼:
使用 1~3 字節(jié)編碼長(zhǎng)度
大于 16 位的 Unicode 編碼 U+10000~U+10FFFF 使用 3 字節(jié)來(lái)編碼
U+0000 采用 2 字節(jié)編碼
采用空字符 null 作為結(jié)尾
第一個(gè)字節(jié)存放字節(jié)個(gè)數(shù)(不包含自已)
DexTypeId 結(jié)構(gòu)體(typeIdsSize 與 typeIdsOff 字段)
是一個(gè)類(lèi)型結(jié)構(gòu)體
typedef struct _DexTypeId
{
u4 descriptorIdx; // 指向 DexStringId 列表的索引
}DexTypeId, *PDexTypeId;
descriptorIdx:指向 DexStringId 列表的索引,它對(duì)應(yīng)的字符串代表了具體類(lèi)的類(lèi)型
DexProtoId 結(jié)構(gòu)體(protoIdsSize 與 protoIdsOff 字段)
是一個(gè)方法聲明結(jié)構(gòu)體,方法聲明 = 返回類(lèi)型 + 參數(shù)列表
typedef struct _DexProtoId
{
u4 shortyIdx; // 方法聲明字符串,指向 DexStringId 列表的索引
u4 returnTypeIdx; // 方法返回類(lèi)型字符串,指向 DexStringId 列表的索引
u4 parametersOff; // 方法的參數(shù)列表,指向 DexTypeList 結(jié)構(gòu)體的偏移
}DexProtoId, *PDexProtoId;
shortyIdx:方法聲明字符串,方法聲明 = 返回類(lèi)型 + 參數(shù)列表
returnTypeIdx:方法返回類(lèi)型字符串
parametersOff:指向一個(gè) DexTypeList 結(jié)構(gòu)體,存放了方法的參數(shù)列表
DexTypeList 結(jié)構(gòu)體:
typedef struct _DexTypeList
{
u4 size; // 接下來(lái) DexTypeItem 的個(gè)數(shù)
DexTypeItem* list; // DexTypeItem 結(jié)構(gòu)
}DexTypeList, *PDexTypeList;
size:接下來(lái) DexTypeItem 的個(gè)數(shù)
list:是一個(gè) DexTypeItem 結(jié)構(gòu)體數(shù)組
DexTypeItem 結(jié)構(gòu)體:
typedef struct _DexTypeItem
{
u2 typeIdx; // 指向 DexTypeId 列表的索引
}DexTypeItem, *PDexTypeItem;
typeIdx:DexTypeId 列表的索引
DexFieldId 結(jié)構(gòu)體(fieldIdsSize 與 fieldIdsOff 字段)
指明了字段所有的類(lèi)、字段的類(lèi)型以及字段名
typedef struct _DexFieldId
{
u2 classIdx; // 類(lèi)的類(lèi)型,指向 DexTypeId 列表的索引
u2 typeIdx; // 字段的類(lèi)型,指向 DexTypeId 列表的索引
u4 nameIdx; // 字段名,指向 DexStringId 列表的索引
}DexFieldId, *PDexFieldId;
classIdx:類(lèi)的類(lèi)型
typeIdx:字段的類(lèi)型
nameIdx:字段名
DexMethodId 結(jié)構(gòu)體(methodIdsSize 與 methodIdsOff 字段)
方法結(jié)構(gòu)體
typedef struct _DexMethodId
{
u2 classIdx; // 類(lèi)的類(lèi)型,指向 DexTypeId 列表的索引
u2 protoIdx; // 聲明的類(lèi)型,指向 DexProtoId 列表的索引
u4 nameIdx; // 方法名,指向 DexStringId 列表的索引
}DexMethodId, *PDexMethodId;
classIdx:類(lèi)的類(lèi)型
protoIdx:聲明的類(lèi)型
nameIdx:方法名
DexClassDef 結(jié)構(gòu)體(classDefsSize 和 classDefsOff 字段)
類(lèi)結(jié)構(gòu)體
typedef struct _DexClassDef
{
u4 classIdx; // 類(lèi)的類(lèi)型,指向 DexTypeId 列表的索引
u4 accessFlags; // 訪問(wèn)標(biāo)志
u4 superclassIdx; // 父類(lèi)類(lèi)型,指向 DexTypeId 列表的索引
u4 interfacesOff; // 接口,指向 DexTypeList 的偏移,否則為0
u4 sourceFileIdx; // 源文件名,指向 DexStringId 列表的索引
u4 annotationsOff; // 注解,指向 DexAnnotationsDirectoryItem 結(jié)構(gòu),或者為 0
u4 classDataOff; // 指向 DexClassData 結(jié)構(gòu)的偏移,類(lèi)的數(shù)據(jù)部分
u4 staticValuesOff; // 指向 DexEncodedArray 結(jié)構(gòu)的偏移,記錄了類(lèi)中的靜態(tài)數(shù)據(jù),主要是靜態(tài)方法
}DexClassDef, *PDexClassDef;
classIdx:類(lèi)的類(lèi)型,指向 DexTypeId 列表的索引
accessFlags:訪問(wèn)標(biāo)志,它是以ACC_開(kāi)頭的枚舉值
superclassIdx:父類(lèi)類(lèi)型,指向 DexTypeId 列表的索引
interfacesOff:接口,指向 DexTypeList 的偏移,如果沒(méi)有,則為 0
sourceFileIdx:源文件名,指向 DexStringId 列表的索引
annotationsOff:注解,指向 DexAnnotationsDirectoryItem 結(jié)構(gòu),或者為 0
classDataOff:指向 DexClassData 結(jié)構(gòu)的偏移,類(lèi)的數(shù)據(jù)部分
staticValuesOff:指向 DexEncodeArray 結(jié)構(gòu)的偏移,記錄了類(lèi)中的靜態(tài)數(shù)據(jù),沒(méi)有則為 0
DexClassData 結(jié)構(gòu)體:
typedef struct _DexClassData
{
DexClassDataHeader header; // 指定字段與方法的個(gè)數(shù)
DexField* staticFields; // 靜態(tài)字段,DexField 結(jié)構(gòu)
DexField* instanceFields; // 實(shí)例字段,DexField 結(jié)構(gòu)
DexMethod* directMethods; // 直接方法,DexMethod 結(jié)構(gòu)
DexMethod* virtualMethods; // 虛方法,DexMethod 結(jié)構(gòu)
}DexClassData, *PDexClassData;
header:DexClassDataHeader 結(jié)構(gòu)體,指定字段與方法的個(gè)數(shù)
staticFields:靜態(tài)字段,DexField 結(jié)構(gòu)體數(shù)組
instanceFields:實(shí)例字段,DexField 結(jié)構(gòu)體數(shù)組
directMethods:直接方法,DexMthod 結(jié)構(gòu)體數(shù)組
virtualMethods:虛方法,DexMethod 結(jié)構(gòu)體數(shù)組
DexClassDataHeader 結(jié)構(gòu)體:
typedef struct _DexClassDataHeader
{
uleb128 staticFieldsSize; // 靜態(tài)字段個(gè)數(shù)
uleb128 instanceFieldsSize; // 實(shí)例字段個(gè)數(shù)
uleb128 directMethodsSize; // 直接方法個(gè)數(shù)
uleb128 virtualMethodsSize; // 虛方法個(gè)數(shù)
}DexClassDataHeader, *PDexClassDataHeader;
staticFieldsSize:靜態(tài)字段個(gè)數(shù)
instanceFieldsSize:實(shí)例字段個(gè)數(shù)
directMethodsSize:直接方法個(gè)數(shù)
virtualMethodsSize:虛方法個(gè)數(shù)
DexField 結(jié)構(gòu)體:
typedef struct _DexField
{
uleb128 fieldIdx; // 指向 DexFieldId 的索引
uleb128 accessFlags; // 訪問(wèn)標(biāo)志
}DexField, *PDexField;
fieldIdx:字段描述,指向 DexFieldId 的索引
accessFlags:訪問(wèn)標(biāo)志
DexMethod 結(jié)構(gòu)體:
typedef struct _DexMethod
{
uleb128 methodIdx; // 指向 DexMethodId 的索引
uleb128 accessFlags; // 訪問(wèn)標(biāo)志
uleb128 codeOff; // 指向 DexCode 結(jié)構(gòu)的偏移
}DexMethod, *PDexMethod;
methodIdx:方法描述,指向 DexMethodId 的索引
accessFlags:訪問(wèn)標(biāo)志
codeOff:指向 DexCode 結(jié)構(gòu)的偏移
DexCode 結(jié)構(gòu)體:
typedef struct _DexCode
{
u2 registersSize; // 使用的寄存器個(gè)數(shù)
u2 insSize; // 參數(shù)個(gè)數(shù)
u2 outsSize; // 調(diào)用其他方法時(shí)使用的寄存器個(gè)數(shù)
u2 triesSize; // Try/Catch 個(gè)數(shù)
u4 debbugInfoOff; // 指向調(diào)試信息的偏移
u4 insnsSize; // 指令集個(gè)數(shù),以 2 字節(jié)為單位
u2* insns; // 指令集
}DexCode, *PDexCode;
還有一些不太常見(jiàn)的結(jié)構(gòu)體,要用的時(shí)候再去看看就行了。Dex 文件的整體結(jié)構(gòu)就這樣,就是一個(gè)多層索引的結(jié)構(gòu)。
string_ids(字符串索引)
這一區(qū)域存儲(chǔ)的是Dex文件字符串資源的索引信息,該索引信息是目標(biāo)字符串在Dex文件數(shù)據(jù)區(qū)所在的真實(shí)物理偏移量。
源碼位于 /dalvik/libdex/DexFile.h:DexStringId
struct DexStringId {
u4 stringDataOff; /* file offset to string_data_item */
};
stringDataOff記錄了目標(biāo)字符串在Dex文件中的實(shí)際偏移量,虛擬機(jī)想讀取該字符串時(shí),只需將Dex文件在內(nèi)存中的起始地址加上stringDataOff所指的偏移量,就是該字符串在內(nèi)存中的實(shí)際物理地址。
在Dex文件中,每個(gè)每個(gè)字符串對(duì)應(yīng)一個(gè)DexStringId,大小4B。另外虛擬機(jī)通過(guò)DexHeader中的String_ids_size獲得當(dāng)前Dex文件中的字符串的總數(shù),通過(guò)乘法就可對(duì)該索引資源進(jìn)行訪問(wèn)。
DexLink
struct DexLink {
u1 bleargh;
};
DexFile 在內(nèi)存中的映射
在Android系統(tǒng)中, java 源文件會(huì)被編譯為“ .jar ” 格式的dex類(lèi)型文件, 在代碼中稱(chēng)為dexfile 。在加載Class 之前, 必先讀取相應(yīng)的jar文件。通常我們使用read()函數(shù)來(lái)讀取文件中的內(nèi)容。但在Dalvik中使用mmap() 函數(shù)。和read()不同, mmap()函數(shù)會(huì)將dex文件映射到內(nèi)存中,這樣通過(guò)普通的內(nèi)存讀取操作即可訪問(wèn)dexfile中的內(nèi)容。
Dexfile的文件格式如圖12 所示, 主要有三部分組成:頭部,索引,數(shù)據(jù)。通過(guò)頭部可知索引的位置和數(shù)同,可知數(shù)據(jù)區(qū)的起始位置。其中classDefsOff 指定了ClassDef 在文件的起始位置, dataOff 指定了數(shù)據(jù)在文件的起始位置, ClassDef 即可理解為Class 的索引。通過(guò)讀取ClassDef 可獲知Class 的基本信息,其中classDataOff 指定了Class 數(shù)據(jù)在數(shù)據(jù)區(qū)的位置。
大圖這里
圖12 DexFile的文件格式
在將dexfile文件映射到內(nèi)存后,會(huì)調(diào)用dexFileParse()函數(shù)對(duì)其分析,分析的結(jié)果存放于名為DexFile的數(shù)據(jù)結(jié)構(gòu)中。DexFile 中的baseAddr指向映射區(qū)的起始位置, pClassDefs 指向ClassDefs(即class索引)的起始位置。由于在查找class 時(shí),都是使用class的名字進(jìn)行查找的,所以為了加快查找速度, 創(chuàng)建了一個(gè)hash表。在hash表中對(duì)class 名字進(jìn)行hash,并生成index。這些操作都是在對(duì)文件解析時(shí)所完成的,這樣雖然在加載過(guò)程中比較耗時(shí),但是在運(yùn)行過(guò)程中可節(jié)省大量查找時(shí)間。
解析完后, 接下來(lái)開(kāi)始加載class文件。在此需要將加載類(lèi)用ClassObject來(lái)保存,所以在此需要先分析和ClassObject 相關(guān)的幾個(gè)數(shù)據(jù)結(jié)構(gòu)。
首先在文件Object.h 中可以看到如下對(duì)結(jié)構(gòu)體Object 的定義。(android2.3.7源碼)
typedef struct Object {
/* ptr to class object /
ClassObject clazz;
} Object;
通過(guò)結(jié)構(gòu)體Object定義了基本類(lèi)的實(shí)現(xiàn),這里有如下兩個(gè)變量。
lock : 對(duì)應(yīng)Obejct 對(duì)象中的鎖實(shí)現(xiàn),即notify wait 的處理。
clazz : 是結(jié)構(gòu)體指針,姑且不看結(jié)構(gòu)體內(nèi)容,這里用了指針的定義。
下面會(huì)有更多的結(jié)構(gòu)體定義:
struct DataObject {
Object obj; /* MUST be first item /
/ variable #of u4 slots; u8 uses 2 slots /
u4 instanceData[1];
};
struct StringObject {
Object obj; / MUST be first item /
/ variable #of u4 slots; u8 uses 2 slots */
u4 instanceData[1];
};
我們看到最熟悉的一個(gè)詞StringObject ,把這個(gè)結(jié)構(gòu)體展開(kāi)后是下面的樣子。
struct StringObject {
/* ptr t o class object /
ClassObject clazz ;
/* variable #of u4 slots; u8 uses 2 slots */
u4 lock;
u4 instanceData[1];
};
由此不難發(fā)現(xiàn), 任何對(duì)象的內(nèi)存結(jié)構(gòu)體中第一行都是Object結(jié)構(gòu)體,而這個(gè)結(jié)構(gòu)體第一個(gè)總是一個(gè)ClassObejct,第二個(gè)總是lock 。按照C++中的技巧,這些結(jié)構(gòu)體可以當(dāng)成Object結(jié)構(gòu)體使用,因此所有的類(lèi)在內(nèi)存中都具有“對(duì)象”的功能,即可以找到一個(gè)類(lèi)(ClassObject),可以有一個(gè)鎖(lock) 。
StringObject是對(duì)String類(lèi)進(jìn)行管理的數(shù)據(jù)對(duì)象,ArrayObejct是數(shù)據(jù)相關(guān)的管理。
ClassObject-Class 在加載后的表現(xiàn)形式
在解析完文件后, 接下來(lái)需要加載Class 的具體內(nèi)容。在Dalvik中, 由數(shù)據(jù)結(jié)構(gòu)ClassObject負(fù)責(zé)存放加載的信息。如圖13所示,加載過(guò)程會(huì)在內(nèi)存中alloc幾個(gè)區(qū)域,分別存放directMethods 、virtualMethods 、sfields 、ifields 。這些信息是從dex 文件的數(shù)據(jù)區(qū)中讀取的,首先會(huì)讀取Class 的詳細(xì)信息,從中獲得directMethod 、virtua!Method 、sfield 、ifield 等的信息,然后再讀取。在此需要注意, 在C lassObj ect 結(jié)構(gòu)中有個(gè)名為super 的成員,通過(guò)super成員可以指向它的超類(lèi)。
大圖這里
圖13 加載過(guò)程
Android dex 文件優(yōu)化
對(duì)Android dex 文件進(jìn)行優(yōu)化來(lái)說(shuō), 需要注意的一點(diǎn)是dex文件的結(jié)構(gòu)是緊湊的,但是我們還是要想方設(shè)法地進(jìn)行提高程序的運(yùn)行速度,我們就仍然需要對(duì)dex文件進(jìn)行進(jìn)一步優(yōu)化。
調(diào)整所有字段的字節(jié)序( LITTLE_ENDIAN),和對(duì)齊結(jié)構(gòu)中的每一個(gè)域來(lái)驗(yàn)證dex文件中的所有類(lèi),并對(duì)一些特定的類(lèi)進(jìn)行優(yōu)化或?qū)Ψ椒ɡ锏牟僮鞔a進(jìn)行優(yōu)化。優(yōu)化后的文件大小會(huì)有所增加, 大約是原Android dex文件的1~4 倍。
優(yōu)化時(shí)機(jī)
優(yōu)化發(fā)生的時(shí)機(jī)有兩個(gè):
對(duì)于預(yù)置應(yīng)用來(lái)說(shuō),可以在系統(tǒng)編譯后,生成優(yōu)化文件,以O(shè)DEX 結(jié)尾。這樣在發(fā)布時(shí)除APK文件(不包含dex)以外,還有一個(gè)相應(yīng)的Android dex 文件。
對(duì)于非預(yù)置應(yīng)用, 包含在APK文件里的dex 文件會(huì)在運(yùn)行時(shí)被優(yōu)化,優(yōu)化后的文件將被保存在緩存中。
如下圖14所示代碼調(diào)用流程
圖14 代碼調(diào)用流程
每一個(gè)Android應(yīng)用都運(yùn)行在一個(gè)Dalvik虛擬機(jī)實(shí)例里,而每一個(gè)虛擬機(jī)實(shí)例都是一個(gè)獨(dú)立的進(jìn)程空間。虛擬機(jī)的線(xiàn)程機(jī)制,內(nèi)存分配和管理, Mutex等都是依賴(lài)底層操作系統(tǒng)而實(shí)現(xiàn)的。
所有Android應(yīng)用的線(xiàn)程都對(duì)應(yīng)一個(gè)Linux線(xiàn)程(可參考----理解Android線(xiàn)程創(chuàng)建流程),虛擬機(jī)因而可以更多地依賴(lài)操作系統(tǒng)的線(xiàn)程調(diào)度和管理機(jī)制。不同的應(yīng)用在不同的進(jìn)程空間里運(yùn)行,加之對(duì)不同來(lái)源的應(yīng)用都使用不同的Linux用戶(hù)來(lái)運(yùn)行,可以最大限度地保護(hù)應(yīng)用的安全和獨(dú)立運(yùn)行。
Zygote是一個(gè)虛擬機(jī)進(jìn)程,同時(shí)也是一個(gè)虛擬機(jī)實(shí)例的孵化器,每當(dāng)系統(tǒng)要求執(zhí)行一個(gè)Android應(yīng)用程序,Zygote就會(huì)孵化出一個(gè)子進(jìn)程來(lái)執(zhí)行該應(yīng)用程序。這樣做的好處顯而易見(jiàn):Zygote進(jìn)程是在系統(tǒng)啟動(dòng)時(shí)產(chǎn)生的,它會(huì)完成虛擬機(jī)的初始化,庫(kù)的載,預(yù)置類(lèi)庫(kù)的加載和初始化等操作,而在系統(tǒng)需要一個(gè)新的虛擬機(jī)實(shí)例時(shí),Zygote通過(guò)復(fù)制自身,最快速地提供一個(gè)虛擬機(jī)實(shí)例。另外,對(duì)于一些只讀的系統(tǒng)庫(kù),所有虛擬機(jī)實(shí)例都和Zygote 共享一塊內(nèi)存區(qū)域,大大節(jié)省了內(nèi)存開(kāi)銷(xiāo)。
Android 應(yīng)用所使用的編程語(yǔ)言是Java語(yǔ)言,和Java SE 一樣,編譯時(shí)使用Oracle JDK 將Java源程序編程成標(biāo)準(zhǔn)的Java 字節(jié)碼文件(. class 文件)。而后通過(guò)工具軟件DX 把所有的字節(jié)碼文件轉(zhuǎn)成Android dex 文件(classes . dex) 。最后使用Android 打包工具(aapt)將dex 文件、資源文件以及AndroidManifest.xml 文件(二進(jìn)制格式)組合成一個(gè)應(yīng)用程序包(APK) 。應(yīng)用程序包可以被發(fā)布到手機(jī)上運(yùn)行。
圖15 Android應(yīng)用編譯及運(yùn)行流程
odex 介紹
odex 是Optimized dex 的簡(jiǎn)寫(xiě),也就是優(yōu)化后的dex 文件。為什么要優(yōu)化呢?主要還是為了提高Dalvik 虛擬機(jī)的運(yùn)行速度。但是odex 不是簡(jiǎn)單的、通用的優(yōu)化,而是在其優(yōu)化過(guò)程中,依賴(lài)系統(tǒng)已經(jīng)編譯好的其他模塊,簡(jiǎn)單點(diǎn)說(shuō):
從Class 文件到dex 文件是針對(duì)Android 平臺(tái)的一種優(yōu)化,是一種通用的優(yōu)化。優(yōu)化過(guò)程中,唯一的輸入是Class 文件。
odex 文件就是dex 文件具體在某個(gè)系統(tǒng)(不同手機(jī),不同手機(jī)的OS,不同版本的OS 等)上的優(yōu)化。odex 文件的優(yōu)化依賴(lài)系統(tǒng)上的幾個(gè)核心模塊( 由BOOTCLASSPATH 環(huán)境變量給出, 一般是/system/framework/下的jar 包,尤其是core.jar)。odex 的優(yōu)化就好像是把中那些本來(lái)需要在執(zhí)行過(guò)程中做的類(lèi)校驗(yàn)、調(diào)用其他類(lèi)函數(shù)時(shí)的解析等工作給提前處理了。
通過(guò)利用dexopt得到test.odex,接著利用dexdump得到其內(nèi)容,最后可以利用Beyond Compare比較這兩個(gè)文件的差異。
如下圖所示
圖16 test.dex 和test.odex 差異
圖16中,綠色框中是test.dex的內(nèi)容,紅色框中是test.odex的內(nèi)容,這也是兩個(gè)文件的差異內(nèi)容:
test.dex中,TestMain類(lèi)僅僅是PUBLIC的,但test.odex則增加了VERIFIED和OPTIMIZED兩項(xiàng)。VERIFIED是表示該類(lèi)被校驗(yàn)過(guò)了,至于校驗(yàn)什么東西,以后再說(shuō)。
然后就是一些方法的不同了。優(yōu)化后的odex文件,一些字節(jié)碼指令變成了xxx-quick。比如圖中最后一句代碼對(duì)于的字節(jié)碼中,未優(yōu)化前invoke-virtual指令表示從method table指定項(xiàng)(圖中是0002)里找到目標(biāo)函數(shù),而優(yōu)化后的odex使用了invoke-virtual-quick表示從vtable中找到目標(biāo)函數(shù)(圖中是000b)。
vtable是虛表的意思,一般在OOP實(shí)現(xiàn)中用得很多。vtable一定比methodtable快么?那倒是有可能。我個(gè)人猜測(cè):
method表應(yīng)該是每個(gè)dex文件獨(dú)有的,即它是基于dex文件的。
根據(jù)odex文件的生成方法(后面會(huì)講),我覺(jué)得vtable恐怕是把dex文件及依賴(lài)的類(lèi)(比如Java基礎(chǔ)類(lèi),如Object類(lèi)等)放一起進(jìn)行了處理,最終得到一張大的vtable。這個(gè)odex文件依賴(lài)的一些函數(shù)都放在vtable中。運(yùn)行時(shí)直接調(diào)用指定位置的函數(shù)就好,不需要再解析了。以上僅是我的猜測(cè)。
注意:
odex文件由dexopt生成,這個(gè)工具在SDK里沒(méi)有,只能由源碼生成。odex文件的生成有三種方式:
preopt:即OEM廠商(比如手機(jī)廠商),在制作鏡像的時(shí)候,就把那些需要放到鏡像文件里的jar包,APK等預(yù)先生成對(duì)應(yīng)的odex文件,然后再把classes.dex文件從jar包和APK中去掉以節(jié)省文件體積。
installd:當(dāng)一個(gè)apk安裝的時(shí)候,PackageManagerService會(huì)調(diào)用installd的服務(wù),將apk中的class.dex進(jìn)行處理。當(dāng)然,這種情況下,APK中的class.dex不會(huì)被剔除。
dalvik VM:preopt是廠商的行為,可做可不做。如果沒(méi)有做的話(huà),dalvik VM在加載一個(gè)dex文件的時(shí)候,會(huì)先生成odex。所以,dalvik VM實(shí)際上用得是odex文件。以后我們研究dalvik VM的時(shí)候會(huì)看到這部分內(nèi)容。
實(shí)際上dex轉(zhuǎn)odex是利用了dalvik vm,里邊也會(huì)運(yùn)行dalvik vm的相關(guān)方法。
總結(jié):
以標(biāo)準(zhǔn)角度來(lái)看,Class文件是由Java VM規(guī)范定義的,所以通用性更廣。dex或者是odex只不過(guò)是規(guī)范在Android平臺(tái)上的一種具體實(shí)現(xiàn)罷了,而且dex/odex在很多地方也需要遵守規(guī)范。因?yàn)閐ex文件的來(lái)源其實(shí)還是Class文件。
對(duì)于初學(xué)者而言,我建議了解Class文件的結(jié)構(gòu)為主。另外,關(guān)于dex/odex的文件結(jié)構(gòu),除非有明確需求(比如要自己修改字節(jié)碼等),否則以了解原理就可以。而且,將來(lái)我們看到dalvik vm的實(shí)際代碼后,你會(huì)發(fā)現(xiàn)dex的文件內(nèi)容還是會(huì)轉(zhuǎn)換成代碼里的那些你很熟悉的類(lèi)型,數(shù)據(jù)結(jié)構(gòu)。比如dex存儲(chǔ)字符串是一種優(yōu)化后的方法,但是到vm代碼中,還不是只能用字符串來(lái)表示嗎?
另外,你還會(huì)發(fā)現(xiàn),Class、dex還是odex文件都存儲(chǔ)了很多源碼中的信息,比如類(lèi)名、函數(shù)名、參數(shù)信息、成員變量信息等,而且直接用得是字符串。這和Native的二進(jìn)制比起來(lái),就容易看懂多了。
參考鏈接
深入理解Android之Java虛擬機(jī)Dalvik
Androidsource之Dalvik 字節(jié)碼
Androidsource之Dalvik 可執(zhí)行文件格式(dex文件)
Android安全–Dex文件格式詳解
詳細(xì)描述了dex/odex指令的格式----->Dalvik opcodes
解釋器中對(duì) 標(biāo)號(hào) 的使用
A deep dive into DEX file format
Dex文件格式詳解
android中Dex文件結(jié)構(gòu)詳解
Dex文件及Dalvik字節(jié)碼格式解析
Dex 文件格式詳解
Dex文件格式詳解
Android關(guān)于Dex拆分(MultiDex)技術(shù)詳解
總結(jié)
以上是生活随笔為你收集整理的2020-10-27(dex文件解析)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 2020-10-30(smali复杂类解
- 下一篇: 2020-10-30(APK-逆向2)