dex文件格式

dex文件格式

dex文件中的數(shù)據(jù)結構

類型 含義
u1 等同uint8_t,表示1字節(jié)的無符號數(shù)
u2 等同于uint16_t,表示2字節(jié)的無符號數(shù)
u4 等同于uint32_t,表示4字節(jié)的無符號數(shù)
u8 等同于uint64_t,表示8字節(jié)的無符號數(shù)
sleb128 有符號LEB128,可變長度1~5字節(jié)
uleb128 無符號LEB128,可變長度1~5字節(jié)
uleb128p1 無符號LEB128值加1,可變長度1~5字節(jié)

每個LB125由1~5個字節(jié)組成,所有的字節(jié)組合在一起表示一個32位的數(shù)據(jù)。每個字節(jié)只有7位有效位,如果第一個字節(jié)的最高位為1,表示LEB128需要使用到第2個字節(jié),如果LEB第二個字節(jié)的最高位為1,表示會使用到第3個字節(jié),以此類推。LEB128最多使用5個字節(jié),如果讀取5個字節(jié)后下一個字節(jié)最高位仍為1,則表示該dex文件無效。

以字符序列“c0 83 92 25”為例,計算它的uleb128的值:

第1個字節(jié)0xc0 > 0x7c ,表示會用到第2個字節(jié)。result = 0x0c & 0x7f

第2個字節(jié)0x83 > 0x7c,表示會用到第3個字節(jié)。result = result + 0x83 & 0x7f << 7

第3個字節(jié)0x92 > 0x7c,表示會用到第4個字節(jié)。result = result + 0x92 & 0x7c << 14

第4個字節(jié)0x25 < 0x7c,表示到了結尾。result = result + 0x25 & 0x7c << 21

計算結果為0x40 + 0x180 + 0x48000 +0x4a00000 = 0x4a481c0

以字符序列“d1 c2 b3 40”為例,計算它的sleb128值。

result = 0xd1 & 0x7f

result=result + 0xc2 & 0x7f << 7

result=result + b3 & 0x7f << 14

result=(result + 40 & 0x7f <<21)<<4)>>4

dex文件整體結構

dex文件由7個部分組成。dex header為dex文件頭,它指定了dex文件的一些屬性,并記錄了其他6部分數(shù)據(jù)結構在dex文件中的物理偏移。string_idsclass_def 結構可以裂解為索引結構區(qū),真實的數(shù)據(jù)存放在data中。最后的link_data為靜態(tài)鏈接數(shù)據(jù)區(qū),對于目前生成的dex文件而言,它始終為空。

dex head
string_ids
type_ids
proto_ids
field_ids
method_ids
class_def
data
link_data

未經過優(yōu)化的dex文件結構表示如下:

struct DexFile {
    DexHeader     header;
    DexStringId     StringIds[StringIdsSize];
    DexTypeId      TypeIds[typeIdsSize];
    DexProtoId     ProtoIds[protoIdsSize];
    DexFieldId      FieldIds[fieldIdsSize];
    DexMethodId   MethodIds[methodIdsSize];
    DexClassDef    Data[];
    DexLink        LinkData;
}

DexFile結構的聲明在Android系統(tǒng)源碼dalvik\libdex\DexFile.h文件中。

dex文件頭

字段名稱 偏移量字節(jié) 長度(byte) 字段描述
magic[8] 0x0 0x8 dex版本標識
checksum 0x8 0x4 alder32算法, 去除了magic和checksum 字段之外的所有內容的校驗碼
signature 0xc 0x14 sha-1簽名, 去除了magic、checksum和 signature字段之外的所有內容的簽名
fileSize 0x20 0x4 整個dex的文件大小
headerSize 0x24 0x4 整個dex文件頭的大小 (固定大小為0x70)
endianTag 0x28 0x4 字節(jié)序 (大尾方式、小尾方式) 默認為小尾方式 <--> 0x12345678
linkSize 0x2c 0x4 鏈接段的大小, 默認為0表示靜態(tài)鏈接
linkOff 0x30 0x4 鏈接段開始偏移
mapOff 0x34 0x4 DexMapList的文件偏移
stringIdsSize 0x38 0x4 DexStringId個數(shù)
stringIdsOff 0x3c 0x4 DexStringId偏移
typeIdsSize 0x40 0x4 DexTypeId個數(shù)
typeIdsOff 0x44 0x4 DexTypeId偏移
protoIdsSize 0x48 0x4 DexProtoId個數(shù)
protoIdsOff 0x4c 0x4 DexProtoId偏移
fieldIdsSize 0x50 0x4 DexFieldId個數(shù)
fieldIdsOff 0x54 0x4 DexFieldId偏移
methodIdsSize 0x58 0x4 DexMethodId個數(shù)
methodIdsOff 0x5c 0x4 DexMethodId偏移
classDefsSize 0x60 0x4 DexClassDef個數(shù)
classDefsOff 0x64 0x4 DexClassDef偏移
dataSize 0x68 0x4 數(shù)據(jù)段大小
dataOff 0x6c 0x4 數(shù)據(jù)段偏移

DexHeader結構下面的數(shù)據(jù)為“索引結構區(qū)”與“數(shù)據(jù)區(qū)”。

“索引結構區(qū)”中各數(shù)據(jù)結構的偏移地址都是從DexHeader結構的stringIdsOff~classDefsOff字段的值指定的,它們并非真正的類數(shù)據(jù),而是指向dex文件的data數(shù)據(jù)區(qū)的偏移或數(shù)據(jù)結構索引。

DexHeader結構聲明如下:

struct DexHeader {
    u1  magic[8];           
    u4  checksum;          
    u1  signature[kSHA1DigestLen];
    u4  fileSize;           
    u4  headerSize;        
    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;
};

dex文件結構分析

DexMapItem

Dalvik虛擬機解析dex文件的內容,最終將其映射成DexMapList數(shù)據(jù)結構,DexHeader結構的mapOff字段指明了DexMapList結構在dex文件中的偏移,它的聲明如下。

struct DexMapList {
    u4  size;               /* DexMapItem的個數(shù) */
    DexMapItem list[1];     /* DexMapItem的結構 */
};

size字段表示接下來有多少個DexMapItem結構,DexMapItem的結構聲明如下。

struct DexMapItem {
    u2 type;              /* kDexType開頭的類型 */
    u2 unused;            /*未使用,用于字節(jié)對齊 */
    u4 size;              /* 指定類型的個數(shù) */
    u4 offset;            /* 指定類型的文件偏移 */
};

type字段為一個枚舉常量,如下所示,通過類型名稱很容易判斷它的具體類型。

enum {
    kDexTypeHeaderItem               = 0x0000,
    kDexTypeStringIdItem             = 0x0001,
    kDexTypeTypeIdItem               = 0x0002,
    kDexTypeProtoIdItem              = 0x0003,
    kDexTypeFieldIdItem              = 0x0004,
    kDexTypeMethodIdItem             = 0x0005,
    kDexTypeClassDefItem             = 0x0006,
    kDexTypeMapList                  = 0x1000,
    kDexTypeTypeList                 = 0x1001,
    kDexTypeAnnotationSetRefList     = 0x1002,
    kDexTypeAnnotationSetItem        = 0x1003,
    kDexTypeClassDataItem            = 0x2000,
    kDexTypeCodeItem                 = 0x2001,
    kDexTypeStringDataItem           = 0x2002,
    kDexTypeDebugInfoItem            = 0x2003,
    kDexTypeAnnotationItem           = 0x2004,
    kDexTypeEncodedArrayItem         = 0x2005,
    kDexTypeAnnotationsDirectoryItem = 0x2006,
};

以hello.dex為例,用winhex打開文件。

dex header

在偏移0x34處找到mapOff,即MapList的偏移:0x0290

MapList偏移

讀取0x290處的一個雙字值為0x0d,表明接下來會有13個DexMapItem。(此處占四個字節(jié)?)

根據(jù)前文中提到的DexMapItem結構,一個DexMapItem占12個字節(jié),1-2個字節(jié)表示類別,5-8個字節(jié)表示個數(shù),9-12個字節(jié)表示偏移量。

所有DexMapItem如下表:

類型 個數(shù) 偏移
kDexTypeHeaderItem 0x1 0x0
kDexTypeStringIdItem 0x10 0x70
kDexTypeTypeIdItem 0x7 0xb0
kDexTypeProtoIdItem 0x4 0xcc
kDexTypeFieldIdItem 0x1 0xfc
kDexTypeMethodIdItem 0x5 0x104
kDexTypeClassDefItem 0x1 0x12c
kDexTypeCodeItem 0x3 0x1b4
kDexTypeTypeList 0x3 0x1ca
kDexTypeStringDataItem 0x10 0x16c
kDexTypeDebugInfoItem 0x3 0x267
kDexTypeClassDataItem 0x1 0x27b
kDexTypeMapList 0x1 0x290

kDexTypeHeaderItem描述了DexHeader部分,它占用了文件0x70個字節(jié)的空間。而接下來的kDexTypeStringIdItem~kDexTypeClassDataItem與DexHeader當中對應的類型個數(shù)字段的值是相同的。

kDexTypeStringIdItem

比如kDexTypeStringIdItem對應了DexHeader的stringIdsSize與stringIdsOff字段,表明了在0

x70偏移處,有0x10個DexStringId對象。DexStringId結構的聲明如下。

struct DexStringId {
    u4 stringDataOff;      /* 字符串數(shù)據(jù)偏移 */
};

DexStringId結構只有一個stringDataOff字段,直接指向字符串數(shù)據(jù),從0x70開始,有0x10個DexStringId對象,一個對象是4個字節(jié)代表字符串偏移。一共16個字段如下:

DexStringId對象
序號 偏移 字符串
0x0 0x1ca <init>
0x1 0x1d2 Hello.java
0x2 0x1de I
0x3 0x1e1 III
0x4 0x1e6 LHello;
0x5 0x1ef Ljava/io/PrintStream;
0x6 0x206 Ljava/lang/Object;
0x7 0x21a Ljava/lang/System;
0x8 0x22e V
0x9 0x231 VI
0xa 0x235 VL
0xb 0x392 [Ljava/lang/String;
0xc 0x24e foo
0xd 0x253 main
0xe 0x259 out
0xf 0x25e println

上表中的字符串并非普通的ascii字符串,他們是有MUTF-8編碼表示的。MUTF-8含義為Modified UTF-8。

kDexTypeTypeIdItem

他對應DexHeader中的typeIdsSize和typeIdsOff字段,指向的結構體為:

struct DexTypeId {
    u4  descriptorIdx;      /* 指向DexStringId列表的索引 */
};

descriptorIdx是指向DexStringId列表的索引 ,他對應的字符串代表具體類的類型,我們根據(jù)上面字段可知:從0xb0起有0x7個DexTypeId結構:

DexTypeId結構
類型索引 字符串索引 字符串
0 0x2 I
1 0x4 LHello;
2 0x5 Ljava/io/PrintStream;
3 0x6 Ljava/lang/Object;
4 0x7 Ljava/lang/System;
5 0x8 V
6 0xb [Ljava/lang/String;

kDexTypeProtoIdItem

對應DexHeader中的protoIdsSize與protoIdsOff字段,聲明如下:

struct DexProtoId {
    u4  shortyIdx;          /* 指向DexStringId列表的索引 */
    u4  returnTypeIdx;      /* 指向DexTypeId列表的索引 */
    u4  parametersOff;      /* 指向DexTypeList的偏移 */
};

他是一個方法的聲明結構體,shortyIdx為方法聲明字符串,returnTypeIdx為方法返回類型字符串,parametersOff指向一個DexTypeList的結構體存放了方法的參數(shù)列表 。DexTypeList聲明如下:

struct  DexTypeList{
    u4  size;               /* 接下來DexTypeItem的個數(shù) */
    DexTypeItem list[1];    /* DexTypeItem結構 */
};

DexTypeItem聲明如下:

struct DexTypeItem {
    u2  typeIdx;            /* 指向DexTypeId列表的索引 */
};

從0xcc開始,有4個DexProtoId,如表:

DexProtoId結構表

索引 方法聲明 返回類型 參數(shù)列表
0 III I 2個參數(shù)I、I
1 V V 無參數(shù)
2 VI V 1個參數(shù)I
3 VL V 1個參數(shù)[Ljava/lang/String;

通過上表可以發(fā)現(xiàn),方法聲明由返回類型與參數(shù)列表組成,并且返回類型位于參數(shù)列表的前面。

kDexTypeFieldIdItem

對應DexHeader中的fieldIdsSize和fieldIdsOff字段,指向DexFieldId結構體 :

struct DexFieldId {
    u2  classIdx;           /* 類的類型,指向DexTypeId列表索引 */
    u2  typeIdx;            /* 字段類型,指向DexTypeId列表索引 */
    u4  nameIdx;            /* 字段名,指向DexStringId列表 */
};

DexFieldId結構體中的數(shù)據(jù)全部是索引值,指明了字段所在的類,字段的類型以及字段名,從0xfc開始共有1個DexFieldId結構。結果如圖:

類類型 字段類型 字段名
Ljava/lang/System; Ljava/io/PrintStream; out

kDexTypeMethodIdItem

它對應DexHeader中的methodIdsSize與methodIdsOff字段,指向的結構體DexMethodId

struct DexMethodId {
    u2  classIdx;           /* 類的類型,指向DexTypeId列表的索引 */
    u2  protoIdx;           /* 聲明類型,指向DexProtoId列表索引 */
    u4  nameIdx;            /* 方法名,指向DexStringId列表的索引 */
};

數(shù)據(jù)也是索引,指明了方法所在的類,方法聲明和方法名。從0x104有0x5個kDexTypeMethodIdItem

類類型 方法聲明 方法名
LHello V <init>
LHello III foo
LHello VL main
Ljava/io/PrintStream; VI println
Ljava/lang/Object; V <init>

kDexTypeClassDefItem

對應DexHeader中的classDefsSize和classDefsOff字段,指向結構體DexClassDef

struct DexClassDef {
    u4  classIdx;           /* 類的類型,指向DexTypeId列表索引 */
    u4  accessFlags;        /* 訪問標志 */
    u4  superclassIdx;      /* 父類的類型,指向DexTypeId列表的索引 */
    u4  interfacesOff;      /* 實現(xiàn)了哪些接口,指向DexTypeList結構的偏移 */
    u4  sourceFileIdx;      /* 源文件名,指向DexStringId列表的索引 */
    u4  annotationsOff;     /* 注解,指向DexAnnotationsDirectoryItem結構的偏移 */
    u4  classDataOff;       /* 指向DexClassData結構的偏移 */
    u4  staticValuesOff;    /* 指向DexEncodedArray結構的偏移 */
};

  • classIdx:索引值,表明類的類型
  • accessFlags:類的訪問標志,它是以ACC_開頭的一個枚舉值
  • superclassIdx:父類類型索引值,
  • interfacesOff:如果類中含有接口聲明或實現(xiàn),字段會指向1個DexTypeList結構,否則這里為0
  • sourceFileIdx :字符串索引值,表示類所在的源文件名稱
  • annotationsOff :指向注解目錄結構
  • classDataOff :指向DexClassData結構,這是數(shù)據(jù)部分
  • staticValuesOff :指向DexEncodedArray結構,記錄了類中的靜態(tài)數(shù)據(jù)

DexClassData

struct DexClassData {
    DexClassDataHeader header; //指向DexClassDataHeader,字段和方法個數(shù)
    DexField*          staticFields; //靜態(tài)字段
    DexField*          instanceFields; //實例字段
    DexMethod*         directMethods; //直接方法
    DexMethod*         virtualMethods;//虛方法
};

DexClassDataHeader

記錄了當前類中的字段和方法的數(shù)目,聲明如下:

struct DexClassDataHeader {
    u4 staticFieldsSize; //靜態(tài)字段個數(shù)
    u4 instanceFieldsSize;//實例字段個數(shù)
    u4 directMethodsSize;//直接方法個數(shù)
    u4 virtualMethodsSize;//虛方法個數(shù)
};

DexField

描述了字段類型與訪問標志,聲明如下:

struct DexField {
    u4 fieldIdx;    /* 指向DexFieldId列表的索引 */
    u4 accessFlags; /* 訪問標志 */
};

DexMethod

struct DexMethod {
    u4 methodIdx;    /* 指向DexMethodId列表的索引 */
    u4 accessFlags;  /* 訪問標志 */
    u4 codeOff;      /* 指向DexCode結構的偏移 */
};

codeOff字段指向一個DexCode結構體,聲明如下:

/dalvik/libdex/DexFile.h

struct DexCode {
    u2  registersSize;//使用寄存器個數(shù)
    u2  insSize;//參數(shù)個數(shù)
    u2  outsSize;//調用其他方法時使用的寄存器個數(shù)
    u2  triesSize;//try/catch個數(shù)
    u4  debugInfoOff;//指向調試信息的偏移
    u4  insnsSize;//指令集個數(shù),以2字節(jié)為單位
    u2  insns[1];//指令集
    /* followed by optional u2 padding */
    /* followed by try_item[triesSize] */
    /* followed by uleb handlersSize */
    /* followed by catch_handler_item[handlersSize] */
};
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容