Dex文件格式

准备Dex文件

我们自己来编译一个Dex文件,供后面分析Dex文件格式.

1
2
3
4
5
public class Hello{
	public static void main(String[] args){
		System.out.println("Hello Dex!");
	}
}
1
2
3
4
5
6
7
all:
	javac Hello.java
	d8 Hello.class

install:
	adb push classes.dex /data/local/tmp
	adb shell /system/bin/dalvikvm -cp /data/local/tmp/classes.dex Hello

数据类型

http://androidxref.com/2.3.7/xref/dalvik/libdex/DexFile.h

在上述Android源码位置,定义了解析Dex文件用到的数据结构,我们先来了解下其中使用到的数据类型.

自定义类型 原类型 含义
s1 int8_t
u1 uint8_t
s2 int16_t
u2 uint16_t
s4 int32_t
u4 uint32_t
s8 int64_t
u8 uint64_t
sleb128 有符号LEB128,可变长度
uleb128 无符号LEB128,可变长度
uleb128p1 无符号LEB128加1,可变长度

LEB128:

sleb128、uleb128、uleb128p1是Dex文件中特有的LEB128类型.在下述Android源码位置可以找到LEB128的实现.

http://androidxref.com/2.3.7/xref/dalvik/libdex/Leb128.h

每个LEB128由1-5字节组成,所有字节组合在一起表示一个32位的数据.每个字节只有7位有效位,如果第1个字节的有效位为1,表示LEB128需要使用第2个字节,如果第2个字节的有效位为1,表示会使用第3个字节,依次类推,直到最后一个字节的最高位为0为止.

sleb128:

读取有符号LEB128的代码如下:

 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
27
28
29
int readUnsignedLeb128(const u1** pStream) {
    const u1* ptr = *pStream;
    int result = *(ptr++);

    if (result > 0x7f) {
        int cur = *(ptr++);
        result = (result & 0x7f) | ((cur & 0x7f) << 7);
        if (cur > 0x7f) {
            cur = *(ptr++);
            result |= (cur & 0x7f) << 14;
            if (cur > 0x7f) {
                cur = *(ptr++);
                result |= (cur & 0x7f) << 21;
                if (cur > 0x7f) {
                    /*
                     * Note: We don't check to see if cur is out of
                     * range here, meaning we tolerate garbage in the
                     * high four-order bits.
                     */
                    cur = *(ptr++);
                    result |= cur << 28;
                }
            }
        }
    }

    *pStream = ptr;
    return result;
}
1
2
3
4
5
6
7
8
从上面代码来看,先读取第一字节,判断其是否小于等于7f,如果小于等于,表示这个字节最高位为0,sleb128编码结束.
以解析0x807f为例.
0x807f对应的二进制为10000000 01111111
8位一组,首字节为0表示leb128编码结束,然后倒着将每组中的后7位重组
如本例中二进制重组为1111111 0000000这里有14位,要左移18位,然后右移18位(14 + 18 = 32)(如果有7位,就要左移25位,然后右移25位, 7 + 25 = 32),
注意最高位为符号位,右移的时候需要补1,
本例中左移18位,又右移18位后的二进制结果为1 111 1111 1111 1111 1111 1111 1000 0000
最高位是符号位,负数以补码形式存在,故取反加1后表示的真实数为-128

uleb128:

读取无符号LEB128的代码如下:

 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
27
28
29
30
31
32
33
34
35
36
37
int readSignedLeb128(const u1** pStream) {
    const u1* ptr = *pStream;
    int result = *(ptr++);

    if (result <= 0x7f) {
        result = (result << 25) >> 25;
    } else {
        int cur = *(ptr++);
        result = (result & 0x7f) | ((cur & 0x7f) << 7);
        if (cur <= 0x7f) {
            result = (result << 18) >> 18;
        } else {
            cur = *(ptr++);
            result |= (cur & 0x7f) << 14;
            if (cur <= 0x7f) {
                result = (result << 11) >> 11;
            } else {
                cur = *(ptr++);
                result |= (cur & 0x7f) << 21;
                if (cur <= 0x7f) {
                    result = (result << 4) >> 4;
                } else {
                    /*
                     * Note: We don't check to see if cur is out of
                     * range here, meaning we tolerate garbage in the
                     * high four-order bits.
                     */
                    cur = *(ptr++);
                    result |= cur << 28;
                }
            }
        }
    }

    *pStream = ptr;
    return result;
}
1
2
3
4
5
6
从上面代码来看,先读取第一个字节,并判断其是否大于0x7f,如果大于的话,则代表这个字节的最高位是1,而不是0.
如果是1的话,则代表还要读下一个字节;如果是0的话,则代表uleb128编码的数值到此为止.
以0x807f解析为例:
0x807f对应的二进制为10000000 01111111
8位一组,首字节为0表示leb128编码结束,然后倒着将每组中的后7位重组
如本例中二进制重组为11 1111 1000 0000对应的16进制为0x3f80,对应的10进制就是16256

uleb128p1:

它的值为uleb128的值加1

文件格式

Dex文件结构

Dex文件整体结构如下:

dex header:Dex文件头

string_ids到class_def:“索引结构区”

data:真实的数据

link_data:静态链接数据区

Dex文件结构

Dex文件由DexFile结构体表示,其定义如下:

 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
27
28
29
30
typedef 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;

    /*
     * These are mapped out of the "auxillary" section, and may not be
     * included in the file.
     */
    const DexClassLookup* pClassLookup;
    const void*         pRegisterMapPool;       // RegisterMapClassPool

    /* points to start of DEX file data */
    const u1*           baseAddr;

    /* track memory overhead for auxillary structures */
    int                 overhead;

    /* additional app-specific data structures associated with the DEX */
    //void*               auxData;
} DexFile;

DexHeader

DexHeader是Dex文件的头部信息,其定义如下:

 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
typedef struct DexHeader {
	u1  magic[8];			//Dex版本表示
	u4  checksum;           //adler32检验,如果修改了Dex文件,需要修正这个值,否则会运行不起来
	u1  signature[kSHA1DigestLen]; //SHA-1散列值,Android不检测这个值,但如果修改了Dex文件,最好先修下这个值,然后修checksum
	u4  fileSize;           //整个文件的大小
	u4  headerSize;         //DexHeader结构的大小,固定为0x70
	u4  endianTag;          //字节序标记,这个字段按小尾方式读出来为0x12345678,那整个Dex文件就是小尾方式.如果按大尾方式读出来为0x12345678,那整个Dex文件就是大尾方式
	u4  linkSize;			//链接段大小
	u4  linkOff;			//链接段偏移
	u4  mapOff;				//DexMapList文件偏移
	u4  stringIdsSize;		//DexStringId个数
	u4  stringIdsOff;		//DexStringId文件偏移
	u4  typeIdsSize;		//DexTypeId个数
	u4  typeIdsOff;			//DexTypeId文件偏移
	u4  protoIdsSize;		//DexProtoId个数
	u4  protoIdsOff;		//DexProtoId文件偏移
	u4  fieldIdsSize;		//DexFieldId个数
	u4  fieldIdsOff;		//DexFieldId文件偏移
	u4  methodIdsSize;		//DexMethodId个数
	u4  methodIdsOff;		//DexMethodId文件偏移
	u4  classDefsSize;		//DexClassDef个数
	u4  classDefsOff;		//DexClassDef文件偏移
	u4  dataSize;			//数据段大小
	u4  dataOff;			//数据段文件偏移
} DexHeader;

DexMapList

DexHeader中的mapOff字段指明了DexMapList在Dex文件中的偏移,其定义如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
typedef struct DexMapList {
    u4  size;               //DexMapItem结构的个数
    DexMapItem list[1];     //DexMapItem结构
} DexMapList;

typedef struct DexMapItem {
    u2  type;              //KDexType开头的类型
    u2  unused;            //未使用,用于字节对齐
    u4  size;              //指定类型的个数
    u4  offset;            //指定类型数据的文件偏移
} DexMapItem;

type字段是一个枚举常量,其定义如下,通过名称很容易能判断它的具体类型.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
enum {
    kDexTypeHeaderItem               = 0x0000,  //DexHeader
    kDexTypeStringIdItem             = 0x0001,  //对应DexHeader中的stringIdsSize与stringIdsOff字段
    kDexTypeTypeIdItem               = 0x0002,  //对应DexHeader中的typeIdsSize与typeIdsOff字段
    kDexTypeProtoIdItem              = 0x0003,  //对应DexHeader中的protoIdsSize与protoIdsOff字段
    kDexTypeFieldIdItem              = 0x0004,  //对应DexHeader中的fieldIdsSize与fieldIdsOff字段
    kDexTypeMethodIdItem             = 0x0005,  //对应DexHeader中的methodIdsSize与methodIdsOff字段
    kDexTypeClassDefItem             = 0x0006,  //对应DexHeader中的classDefsSize与classDefsOff字段
    kDexTypeMapList                  = 0x1000,  //指向DexMapItem结构自身
    kDexTypeTypeList                 = 0x1001,  //DexTypeList
    kDexTypeAnnotationSetRefList     = 0x1002,  
    kDexTypeAnnotationSetItem        = 0x1003,
    kDexTypeClassDataItem            = 0x2000,  //DexClassData
    kDexTypeCodeItem                 = 0x2001,  //DexCode
    kDexTypeStringDataItem           = 0x2002,  //DexStringId字符串列表的首地址
    kDexTypeDebugInfoItem            = 0x2003,  //调试信息偏移量,与DexCode结构中debugInfoOff字段指向的内容相同
    kDexTypeAnnotationItem           = 0x2004,
    kDexTypeEncodedArrayItem         = 0x2005,
    kDexTypeAnnotationsDirectoryItem = 0x2006,
};

DexStringId

DexStringId

1
2
3
4
5
6
7
8
9
typedef struct DexStringId {
    u4  stringDataOff;      /* file offset to string_data_item */
} DexStringId;

//伪结构表示如下:
struct string_data_item {
 uleb128 utf16_size; //字符串长度
 ubyte[] data;       //字符串数据
}

stringDataOff指向的字符串并非普通的ASCII字符串,而是由MUTF-8编码表示的字符串.在MUTF-8字符串的头部存放的是uleb128编码的字符个数,后面才是字符串数据.

可以使用下述代码读取字符串数据.

1
2
3
4
5
6
const char* dexGetStringData(const DexFile* pDexFile,const DexStringId* pStringId) {
    const u1* ptr = pDexFile->baseAddr + pStringId->stringDataOff;
    // Skip the uleb128 length.
    while (*(ptr++) > 0x7f) /* empty */ ;
    return (const char*) ptr;
}

DexTypeId

DexTypeId

1
2
3
typedef struct DexTypeId {
    u4  descriptorIdx;     //指向DexStringId列表的索引
} DexTypeId;

descriptorIdx为DexStringId列表的索引,它所对应的字符串代表了具体类的类型.

DexProtoId

DexProtoId

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
typedef struct DexProtoId {
    u4  shortyIdx;          //方法声明字符串,指向DexStringId列表的索引
    u4  returnTypeIdx;      //方法返回类型字符串,指向DexTypeId列表的索引
    u4  parametersOff;      //方法的参数列表,指向DexTypeList列表的索引
} DexProtoId;

typedef struct DexTypeList {
    u4  size;               //接下来DexTypeItem结构的个数
    DexTypeItem list[1];    //DexTypeItem结构
} DexTypeList;

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

方法声明由返回类型和参数列表组成,且返回类型在参数列表的前面.

DexFieldId

DexFieldId

1
2
3
4
5
typedef struct DexFieldId {
    u2  classIdx;           //类的类型,指向DexTypeId列表的索引
    u2  typeIdx;            //字段类型,指向DexTypeId列表的索引
    u4  nameIdx;            //字段名,指向DexStringId列表的索引
} DexFieldId;

指明了字段所在的类、字段的类型以及字段名.

DexMethodId

DexMethodId

1
2
3
4
5
typedef struct DexMethodId {
    u2  classIdx;           //类的类型,指向DexTypeId列表的索引
    u2  protoIdx;           //声明类型,指向DexProtoId列表的索引
    u4  nameIdx;            //方法名,指向DexStringId列表的索引
} DexMethodId;

指明了方法所在的类、方法的声明以及方法名.

DexClassDef

DexClassDef

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//http://androidxref.com/2.3.7/xref/dalvik/libdex/DexFile.h
typedef struct DexClassDef {
    u4  classIdx;           //类的类型,指向DexTypeId列表的索引
    u4  accessFlags;        //访问标志,以ACC_开头的枚举值
    u4  superclassIdx;      //父类的类型,指向DexTypeId列表的索引
    u4  interfacesOff;      //接口,如类中有接口声明或实现,则是指向DexTypeList的偏移量,否则为0
    u4  sourceFileIdx;      //源文件名,表示类所在源文件的名称,指向DexStringId列表的索引
    u4  annotationsOff;     //注解,如有,则是指向DexAnnotationsDirectoryItem列表的索引,否则为0
    u4  classDataOff;       //类的数据部分,指向DexClassData结构的偏移量
    u4  staticValuesOff;    //记录了类中的静态数据,指向DexEncodedArray结构的偏移量
} DexClassDef;
 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
27
28
29
//http://androidxref.com/2.3.7/xref/dalvik/libdex/DexClass.h
//DexClass.h文件中所有结构的u4类型的字段其实是uleb128类型
struct DexClassData {
    DexClassDataHeader header;          //指定字段与方法的个数
    DexField*          staticFields;    //静态字段
    DexField*          instanceFields;  //实例字段
    DexMethod*         directMethods;   //直接方法
    DexMethod*         virtualMethods;  //虚方法
};

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结构的偏移量
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
//http://androidxref.com/2.3.7/xref/dalvik/libdex/DexFile.h
//描述了方法的详细信息以及方法中的指令内容
typedef struct DexCode {
    u2  registersSize;  //使用寄存器的个数
    u2  insSize;        //参数的个数
    u2  outsSize;       //调用其他方法时使用的寄存器的个数
    u2  triesSize;      //try/catch语句的个数
    u4  debugInfoOff;   //指向调试信息的偏移量
    u4  insnsSize;      //指令集的个数,以2字节为单位
    u2  insns[1];       //指令集
    /* followed by optional u2 padding */
    /* followed by try_item[triesSize] */
    /* followed by uleb128 handlersSize */
    /* followed by catch_handler_item[handlersSize] */
} DexCode;

ODEX

在Android5.0之前,主要使用的虚拟机是Dalvik,而ODEX则是Dalvik对Dex文件进行优化后的产物.

其文件格式比Dex文件多了一个头.

odex文件格式

可使用下述方法将ODEX文件转化为Dex文件:

Dex_odex2dex.7z

  1. 将当前Android系统system/framework下的所有文件复制到本工具包的framework目录下
  2. 将工具包里的123.odex文件替换为自己的odex文件
  3. 双击odex2dex.bat文件即可

OAT

OAT文件是Android4.4中引入的,到了Android5.0之后,系统默认使用的虚拟机是ART.而OAT文件就是ART虚拟机对Dex文件进行优化的产物,是Android定制的Elf文件.

其头部定义结构如下:

 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
27
28
29
30
//http://androidxref.com/5.0.0_r2/xref/art/runtime/oat.h
struct OatHeader {
	uint8_t magic_[4];
	uint8_t version_[4];
	uint32_t adler32_checksum_;

	InstructionSet instruction_set_;
	InstructionSetFeatures instruction_set_features_;
	uint32_t dex_file_count_;	//OAT中包含Dex文件的个数
	uint32_t executable_offset_;
	uint32_t interpreter_to_interpreter_bridge_offset_;
	uint32_t interpreter_to_compiled_code_bridge_offset_;
	uint32_t jni_dlsym_lookup_offset_;
	uint32_t portable_imt_conflict_trampoline_offset_;
	uint32_t portable_resolution_trampoline_offset_;
	uint32_t portable_to_interpreter_bridge_offset_;
	uint32_t quick_generic_jni_trampoline_offset_;
	uint32_t quick_imt_conflict_trampoline_offset_;
	uint32_t quick_resolution_trampoline_offset_;
	uint32_t quick_to_interpreter_bridge_offset_;

	// The amount that the image this oat is associated with has been patched.
	int32_t image_patch_delta_;

	uint32_t image_file_location_oat_checksum_;
	uint32_t image_file_location_oat_data_begin_;

	uint32_t key_value_store_size_;
	uint8_t key_value_store_[0];  // note variable width data at end
};

可使用下述方法进行Dex文件的提取:

  1. 在oat文件中搜索文本035,找到dex035
  2. 将dex035之前的内容全部删除

提取Dex文件2

参考链接

<Android软件安全权威指南>


相关内容

0%