天天看點

Android Dex檔案結構解析

Java源檔案通過Java編譯器生成CLASS檔案,再通過dx工具轉換為classes.dex檔案。

DEX檔案從整體上來看是一個索引的結構,類名、方法名、字段名等資訊都存儲在常量池中,這樣能夠充分減少存儲空間,相較于Java位元組碼檔案更适合手機裝置。

DEX檔案的相關結構聲明定義在/dalvik/libdex/DexFile.h檔案中,下面我們先來看一下DEX檔案中使用到的資料結構。

表1 dex檔案使用到的資料結構
類型 含義
u1 等同于uint8_t,表示1位元組的無符号數
u2 等同于uint16_t,表示2位元組的無符号數
u4 等同于uint32_t,表示4位元組的無符号數
u8 等同于uint64_t,表示8位元組的無符号數
sleb128 有符号LEB128,可變長度1~5位元組
uleb128 無符号LEB128,可變長度1~5位元組
uleb128p1 無符号LEB128加1,可變長度1~5位元組

DEX檔案的基本結構如下圖所示:

header
string_ids
type_ids
proto_ids
field_ids
method_ids
class_def
data
link_data
圖1 DEX檔案結構

1. header(基本資訊)

header是DEX檔案頭,包含magic字段、alder32校驗值、SHA-1哈希值、string_ids的個數以及偏移位址等。DEX檔案的頭結構很固定,占用0x70個位元組,具體定義代碼如下所示(摘自DexFile.h):

/*
 * Direct-mapped "header_item" struct.
 */


struct DexHeader {
    u1  magic[];           /* includes version number */  
    u4  checksum;           /* adler32 checksum */  
    u1  signature[kSHA1DigestLen]; /* SHA- 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;
};
           

magic[8]:共8個位元組。目前為固定值dex\n035。

checksum:檔案校驗碼,使用alder32算法校驗檔案除去magic、checksum外餘下的所有檔案區域,用于檢查檔案錯誤。

signature:使用 SHA-1算法hash除去magic,checksum和signature外餘下的所有檔案區域 ,用于唯一識别本檔案 。

fileSize:DEX檔案的長度。

headerSize:header大小,一般固定為0x70位元組。

endianTag:指定了DEX運作環境的cpu位元組序,預設值ENDIAN_CONSTANT等于0x12345678,表示預設采用Little-Endian位元組序。

linkSize和linkOff:指定連結段的大小與檔案偏移,大多數情況下它們的值都為0。link_size:LinkSection大小,如果為0則表示該DEX檔案不是靜态連結。link_off用來表示LinkSection距離DEX頭的偏移位址,如果LinkSize為0,此值也會為0。

mapOff:DexMapList結構的檔案偏移。

stringIdsSize和stringIdsOff:DexStringId結構的資料段大小與檔案偏移。

typeIdsSize和typeIdsOff:DexTypeId結構的資料段大小與檔案偏移。

protoIdsSize和protoIdsSize:DexProtoId結構的資料段大小與檔案偏移。

fieldIdsSize和fieldIdsSize:DexFieldId結構的資料段大小與檔案偏移。

methodIdsSize和methodIdsSize:DexMethodId結構的資料段大小與檔案偏移。

classDefsSize和classDefsOff:DexClassDef結構的資料段大小與檔案偏移。

dataSize和dataOff:資料段的大小與檔案偏移。

下面我們來看某apk中classes.dex的解析結果,确實與上面的結構一緻:

Android Dex檔案結構解析

2.DexMapList區段(大綱)

Dalvik虛拟機解析DEX檔案的内容,最終将其映射成DexMapList資料結構,它實際上包含所有其他區段的結構大綱。DexHeader中的mapOff字段指明了DexMapList結構在DEX檔案中的偏移。具體定義代碼如下所示:

struct DexMapList {
    u4 size;               /* DexMapItem的個數 */
    DexMapItem list[];    /* DexMapItem的結構 */
};

struct DexMapItem {   
    u2 type;      /* kDexType開頭的類型 */
    u2 unused;  /* 未使用,用于位元組對齊 */
    u4 size;    /* type指定類型的個數,它們在dex檔案中連續存放 */
    u4 offset;  /* 指定類型資料的檔案偏移 */
};

/* type字段為一個枚舉常量,通過類型名稱很容易判斷它的具體類型。 */
/* map item type codes */
enum {
    kDexTypeHeaderItem               = ,
    kDexTypeStringIdItem             = ,
    kDexTypeTypeIdItem               = ,
    kDexTypeProtoIdItem              = ,
    kDexTypeFieldIdItem              = ,
    kDexTypeMethodIdItem             = ,
    kDexTypeClassDefItem             = ,
    kDexTypeMapList                  = ,
    kDexTypeTypeList                 = ,
    kDexTypeAnnotationSetRefList     = ,
    kDexTypeAnnotationSetItem        = ,
    kDexTypeClassDataItem            = ,
    kDexTypeCodeItem                 = ,
    kDexTypeStringDataItem           = ,
    kDexTypeDebugInfoItem            = ,
    kDexTypeAnnotationItem           = ,
    kDexTypeEncodedArrayItem         = ,
    kDexTypeAnnotationsDirectoryItem = ,
};
           

下面我們來看一下010Editor對某classes.dex檔案的解析出的DexMapList結構。上面DexMapList結構中的size字段表示list數組的成員個數,即DexMapItem結構的數量:圖中是11h,表示共有17個DexMapItem結構,與圖中的list數組大小相符。

Android Dex檔案結構解析

然後我們再來看下DexMapItem的結構。例如對于下圖中的DexMapItem的第一項來說,type等于0說明其是kDexTypeHeaderItem類型的結構;unused一般都為0;size為1代表該結構僅有一個,即隻有一個Dex檔案頭;offset為0代表Dex檔案頭從0h開始。

Android Dex檔案結構解析

最後我們将所有DexMapItem結構整理成下表:

類型(type) 個數(size) 偏移(offset)
kDexTypeHeaderItem(0x0000) 0x1 0x0
kDexTypeStringIdItem(0x0001) 0xA115 0x70
kDexTypeTypeIdItem(0x0002) 0x1D38 0x284C4
kDexTypeProtoIdItem(0x0003) 0x2505 0x2F9A4
kDexTypeFieldIdItem(0x0004) 0x9FB9 0x4B5E0
kDexTypeMethodIdItem(0x0005) 0xC344 0x9B3A8
kDexTypeClassDefItem(0x0006) 0x189D 0xFCDC8
kDexTypeAnnotationSetItem(0x1003) 0x10E0 0x12E168
kDexTypeCodeItem(0x2001) 0x96DB 0x138A34
kDexTypeAnnotationsDirectoryItem(0x2006) 0xCE6 0x4A3EEC
kDexTypeTypeList(0x1001) 0x1620 0x4B9894
kDexTypeStringDataItem(0x2002) 0xA115 0x4C74CA
kDexTypeDebugInfoItem(0x2003) 0x8FCC 0x5C8544
kDexTypeAnnotationItem(0x2004) 0x101C 0x63FBC1
kDexTypeEncodedArrayItem(0x2005) 0x10E 0x653536
kDexTypeClassDataItem(0x2000) 0x184F 0x65B97A
kDexTypeMapList(0x1000) 0x1 0x6B8828

可以看出,其中區段的offset與header中的off是完全相等的。

3.DexStringId區段(字元串)

struct DexStringId {
    u4 stringDataOff;   /* 字元串資料偏移 */
}
           

DexStringId結構隻有一個stringDataOff字段,直接指向字元串資料。這個區段中包含了DEX檔案中用到的所有字元串。

4.DexTypeId區段(類名/類型名稱字元串)

struct DexTypeId {
    u4 descriptorIdx;    /* 指向 DexStringId清單的索引 */
};
           

descriptorIdx為指向DexStringId清單的索引,它對應的字元串代表了具體類的類型(DEX檔案中用到的所有基本資料類型和類的名稱)。如下圖中的第一項值為0xAEB,表示其是DexStringId中第0xAEB(2795)項;而第8項值為0x1969,表示其是DexStringId中第0x1969(6505)項。經過我們的驗證,以上分析是正确的。

Android Dex檔案結構解析
Android Dex檔案結構解析
Android Dex檔案結構解析

5.DexProtoId區段(方法聲明=傳回類型 + 參數清單)

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

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

struct DexTypeItem {
    u2 typeIdx;    /* 指向DexTypeId清單的索引 */
};
           

下面結合執行個體進行分析:

  • DexProtoId
    • shortyIdx:方法聲明字元串,具體而言是由方法的傳回類型與參數清單組成的一個字元串,并且傳回類型位于參數清單的前面。如“III”“V”“VI”“VL”等。在下圖的三個方法聲明中分别為B、BL、DL。
    • returnTypeIdx:方法傳回類型,指向DexTypeId清單。下圖的分别為byte、byte、double。
    • parametersOff:指向一個DexTypeList結構體,存放了方法的參數類型。下圖分别為0、0x4BA78C、0x4BA7BC。值為0表示參數為void。
  • DexTypeList
    • size:DexTypeItem的個數,即參數的數量。下圖分别為?、1、1,表示後兩個方法都隻有一個參數。
    • list:指向size個DexTypeItem項,每一項代表方法的一個參數。
  • DexTypeItem
    • typeIdx:指向DexTypeId清單,最終指向參數類型的字元串。如第三圖61h(97)項在DexTypeId清單中正好指向”Landroid/content/Context;”類型字元串。
Android Dex檔案結構解析
Android Dex檔案結構解析
Android Dex檔案結構解析
Android Dex檔案結構解析

6.DexFieldId區段(字段)

DexFieldId結構中的資料全部是索引值,指明了字段所在的類、字段的類型以及字段名。

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

如下圖,可以看到字段所屬類名為MTT.ThirdAppInfoNew,字段類型為int,字段名為iCoreType。

Android Dex檔案結構解析

7.DexMethodId區段(方法)

DexMethodId結構中的資料全部是索引值,指明了方法所在的類、方法的聲明以及方法名。

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

如下圖,可以看到方法所屬類為MTT.ThirdAppInfoNew,方法聲明為V,方法名為。

Android Dex檔案結構解析

8.DexTypeClassDefItem(類定義)

struct DexClassDef {
    u4 classIdx;    /* 類的類型,指向DexTypeId清單的索引 */
    u4 accessFlags; /* 通路标志 */
    u4 superclassIdx;  /* 父類類型,指向DexTypeId清單的索引 */
    u4 interfacesOff; /* 接口,指向DexTypeList的偏移 */
    u4 sourceFileIdx; /* 源檔案名,指向DexStringId清單的索引 */
    u4 annotationsOff; /* 注解,指向DexAnnotationsDirectoryItem結構 */
    u4 classDataOff;   /* 指向DexClassData結構的偏移 */
    u4 staticValuesOff;  /* 指向DexEncodedArray結構的偏移 */
};

struct DexClassData {
    DexClassDataHeader header; /* 指定字段與方法的個數 */
    DexField* staticFields;    /* 靜态字段,DexField結構 */
    DexField* instanceFields;  /* 執行個體字段,DexField結構 */
    DexMethod* directMethods;  /* 直接方法,DexMethod結構 */
    DexMethod* virtualMethods; /* 虛方法,DexMethod結構 */

struct DexClassDataHeader {
    u4 staticFieldsSize;  /* 靜态字段個數 */
    u4 instanceFieldsSize; /* 執行個體字段個數 */
    u4 directMethodsSize;  /* 直接方法個數 */
    u4 virtualMethodsSize; /* 虛方法個數 */
};

struct DexField {
    u4 fieldIdx;    /* 指向DexFieldId的索引 */
    u4 accessFlags; /* 通路标志 */
};

struct DexMethod {
    u4 methodIdx;   /* 指向DexMethodId的索引 */
    u4 accessFlags; /* 通路标志 */
    u4 codeOff;     /* 指向DexCode結構的偏移 */
};

struct DexCode {
    u2 registersSize;   /* 使用的寄存器個數 */
    u2 insSize;         /* 參數個數 */
    u2 outsSize;        /* 調用其他方法時使用的寄存器個數 */
    u2 triesSize;       /* Try/Catch個數 */
    u4 debugInfoOff;    /* 指向調試資訊的偏移 */
    u4 insnsSize;       /*指令集個數,以2位元組為機關 */
    u2 insns[];        /* 指令集 */


           
  • DexClassDef
    • classIdx:索引值,表明類的類型。下圖中值為0x6,指向類型MTT.ThirdAppInfoNew。
    • accessFlags:類的通路标志,它是以ACC_開頭的一個枚舉值。具體定義可以參考這裡。下圖中值為0x11,表示同時具有ACC_PUBLIC和ACC_FINAL。
    • superclassIdx:父類類型索引值。下圖中值為0x1B13,指向java.lang.Object類。
    • interfacesOff:如果類中含有接口聲明或實作,interfaceOff會指向一個DexTypeList結構,否則這裡的值為0。圖中值為0x4B9894(4954260),指向的DexTypeList結構為java.lang.Cloneable。
    • sourceFileIdx:字元串索引值,表示類所在的源檔案名稱。圖中值為NO_INDEX(0xffffffff),表示該值丢失。
    • annotationsOff:指向注解目錄結構,根據類型不同會有注解類、注解方法、注解字段與注解參數,如果類中沒有注解,這裡的值則為0。圖中值為0,表示類中沒有注解。
    • classDataOff:指向DexClassData結構,它是類的資料部分。圖中為0x65B97A。
    • staticValuesOff:指向DexEncodedArray結構,記錄了類中的靜态資料。圖中為0,表示類中沒有靜态資料。
Android Dex檔案結構解析
  • DexClassData
    • header:一個DexClassDataHeader結構,指定字段與方法的個數。如下圖中的staticFieldsSize為0,表示沒有靜态字段;instanceFieldsSize為0xC,表示有12個執行個體字段;directMethodsSize為0x1,表示有一個直接方法;virtualMethodsSize為0x0,表示沒有虛方法。
      Android Dex檔案結構解析
    • staticFields*:指向一個DexField結構,表示靜态字段的類型與通路标志。由于本例沒有靜态字段,是以該結構無效。
    • directMethods*:指向一個DexField結構,表示執行個體字段的類型與通路标志。如下圖本例中有一個執行個體字段。
      Android Dex檔案結構解析
    • directMethods*:指向一個DexMethod結構,表示直接方法的原型、名稱、通路标志、代碼資料塊。。如下圖本例中有12個執行個體字段。
      Android Dex檔案結構解析
    • virtualMethods*:指向一個DexMethod結構,表示虛方法的原型、名稱、通路标志、代碼資料塊。由于本例沒有靜态字段,是以該結構無效。
  • DexField
    • fieldIdx:指向DexFieldId的索引,表示字段的所屬類、字段類型和字段名。
    • accessFlags:通路标志。
  • DexMethod
    • methodIdx:指向DexMethodId的索引,表示方法的所在類、方法的聲明和方法名。
    • accessFlags:通路标志。
    • codeOff:指向DexCode結構的偏移,圖中為0x138A34。
  • DexCode
    • registersSize:該方法使用的寄存器個數。下圖中為3。
    • insSize:該方法的參數個數,對應smali文法中的”.register”指令。下圖中為1。
    • outsSize:該方法調用其他方法時,對應smali文法中的”.paramter”指令。例如現在有一個方法,使用了5個寄存器,其中有2個為參數,而該方法調用了另一個方法,後者使用了20個寄存器,那麼Dalvik虛拟機在配置設定時,會在配置設定自身方法寄存器空間時加上那20個寄存器空間。下圖中為1。
    • triesSize:方法中Try/Catch的個數。
    • debugInfoOff:如果dex檔案保留了調試資訊,debugInfoOff字段會指向它。
    • insnsSize:指令個數,以2位元組為機關。
    • insns[1]:真正的指令部分。
Android Dex檔案結構解析

參考文檔

  1. 《Android軟體安全與逆向分析》 非蟲
  2. 《.dex — Dalvik Executable Format》 AOSP