2316 字
12 分钟
dex文件结构

0、准备工作#

要了解dex肯定要生成dex文件

  1. 先写一段java代码(Test.java)

    public class Test{
    int a = 0;
    static String b = "Hello";
    public int getNumber(int i, int j) {
    int c = 3;
    return c + i + j;
    }
    public static void main(String[] args){
    int d = 1;
    int e = 2;
    Test test = new Test();
    String sayNumber = String.valueOf(test.getNumber(d, e));
    System.out.println("sh4d0w" + sayNumber);
    }
    }
  2. 用javac编译为class文件

    javac Test.java

  3. 用d8(SDK28以上叫d8,以下叫dx)生成dex文件

    D:\Android_tools\as\SDK\build-tools\36.0.0\d8 --output . Test.class

然后把classes.dex文件放入010editor,就可以开始学习了

数据类型

自定义类型原类型含义
s1int8_t有符号单字节
u1uint8_t无符号单字节
s2int16_t
u2uint16_t
s4int32_t
u4uint32_t
s8int64_t
u8uint64_t
sleb128有符号LEB128,可变长度
uleb128无符号LEB123,可变长度
uleb128p1等于ULEB128 + 1,可变长度

1、dex文件结构#

图1自己画的概图,图2来自52pj Hawcett

e0a5d9552e601a92c1c09508e10db48dDEX文件格式

dex文件结构比pe文件简单,主要包含以上数据体

数据名称解释
Headerdex头部文件,记录dex文件属性
String_ids字符串数据索引,记录每个字符串在数据区的偏移量
Type_ids类似数据索引,记录了每个类型的字符串索引
Proto_ids原型数据索引,记录了方法声明的字符串,返回类型字符串,参数列表
Field_ids字段数据索引,记录方法所属类,类型以及方法名
Method_ids类方法索引,记录方法所属类名,方法声明以及方法名等信息
Class_defs类定义数据索引,记录制定类各类信息,包括接口、超类、类数据偏移量
Data数据区,记录了各个类的真实数据
Link_data链接数据区
typedef struct DexHeader {
u1 magic[8]; //Dex版本号 dex.035 .035即为版本号
u4 checksum; //adler32检验,如果修改了Dex文件,需要修正这个值,否则会运行不起来
u1 signature[kSHA1DigestLen]; //SHA-1值,Android不检测该值,但如果修改了Dex文件,最好修复该值,再修checksum
u4 fileSize; //整个dex文件的大小
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;

我们可以在010editor里查看文件结构a001f4dfc7d258360b83278963f3d904_720

这里已经比较直观了,也可以写个脚本直接查询

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
// DEX文件头结构定义(基于Android 5.0+的DEX格式)
#pragma pack(push, 1)
typedef struct
{
uint8_t magic[8]; // 文件标识,固定为 "dex\n035\0" 或 "dex\n037\0"
uint32_t checksum; // Adler32校验和
uint8_t signature[20]; // SHA-1哈希值
uint32_t fileSize; // 文件总大小(字节)
uint32_t headerSize; // 头部大小(字节)
uint32_t endianTag; // 字节序标记
uint32_t linkSize; // 链接段大小(字节)
uint32_t linkOff; // 链接段偏移(字节)
uint32_t mapOff; // map_list偏移(字节)
uint32_t stringIdsSize; // string_ids数量
uint32_t stringIdsOff; // string_ids偏移(字节)
uint32_t typeIdsSize; // type_ids数量
uint32_t typeIdsOff; // type_ids偏移(字节)
uint32_t protoIdsSize; // proto_ids数量
uint32_t protoIdsOff; // proto_ids偏移(字节)
uint32_t fieldIdsSize; // field_ids数量
uint32_t fieldIdsOff; // field_ids偏移(字节)
uint32_t methodIdsSize; // method_ids数量
uint32_t methodIdsOff; // method_ids偏移(字节)
uint32_t classDefsSize; // class_defs数量
uint32_t classDefsOff; // class_defs偏移(字节)
uint32_t dataSize; // 数据段大小(字节)
uint32_t dataOff; // 数据段偏移(字节)
} DexHeader;
#pragma pack(pop)
// 打印十六进制字节数组
void printHexBytes(const uint8_t *bytes, int len)
{
for (int i = 0; i < len; i++)
{
printf("%02x", bytes[i]);
}
printf("\n");
}
// 读取DEX文件并打印头部信息
int printDexHeader(const char *dexFilePath)
{
FILE *file = fopen(dexFilePath, "rb");
if (!file)
{
printf("无法打开文件: %s\n", dexFilePath);
return -1;
}
// 分配内存读取文件头
DexHeader header;
size_t readSize = fread(&header, sizeof(DexHeader), 1, file);
if (readSize != 1)
{
printf("读取DEX头部失败\n");
fclose(file);
return -1;
}
// 验证magic字段(DEX文件标识)
const char *expectedMagic = "dex\n035\0";
if (memcmp(header.magic, expectedMagic, 8) != 0)
{
printf("警告: magic字段不匹配,可能不是有效的DEX文件\n");
printf("实际magic: ");
printHexBytes(header.magic, 8);
printf("预期magic: ");
printHexBytes((uint8_t *)expectedMagic, 8);
}
// 打印DEX头部信息
printf("DEX文件头信息:\n");
printf(" magic: ");
printHexBytes(header.magic, 8);
printf(" checksum: 0x%08x\n", header.checksum);
printf(" signature: ");
printHexBytes(header.signature, 20);
printf(" fileSize: 0x%08x \n", header.fileSize, header.fileSize);
printf(" headerSize: 0x%08x \n", header.headerSize, header.headerSize);
printf(" endianTag: 0x%08x\n", header.endianTag);
printf(" linkSize: 0x%08x \n", header.linkSize, header.linkSize);
printf(" linkOff: 0x%08x \n", header.linkOff, header.linkOff);
printf(" mapOff: 0x%08x \n", header.mapOff, header.mapOff);
printf(" stringIdsSize: 0x%08x \n", header.stringIdsSize, header.stringIdsSize);
printf(" stringIdsOff: 0x%08x \n", header.stringIdsOff, header.stringIdsOff);
printf(" typeIdsSize: 0x%08x \n", header.typeIdsSize, header.typeIdsSize);
printf(" typeIdsOff: 0x%08x \n", header.typeIdsOff, header.typeIdsOff);
printf(" protoIdsSize: 0x%08x \n", header.protoIdsSize, header.protoIdsSize);
printf(" protoIdsOff: 0x%08x \n", header.protoIdsOff, header.protoIdsOff);
printf(" fieldIdsSize: 0x%08x \n", header.fieldIdsSize, header.fieldIdsSize);
printf(" fieldIdsOff: 0x%08x \n", header.fieldIdsOff, header.fieldIdsOff);
printf(" methodIdsSize: 0x%08x \n", header.methodIdsSize, header.methodIdsSize);
printf(" methodIdsOff: 0x%08x \n", header.methodIdsOff, header.methodIdsOff);
printf(" classDefsSize: 0x%08x \n", header.classDefsSize, header.classDefsSize);
printf(" classDefsOff: 0x%08x \n", header.classDefsOff, header.classDefsOff);
printf(" dataSize: 0x%08x \n", header.dataSize, header.dataSize);
printf(" dataOff: 0x%08x \n", header.dataOff, header.dataOff);
fclose(file);
return 0;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
printf("用法: %s <classes.dex路径>\n", argv[0]);
return 1;
}
return printDexHeader(argv[1]);
}

效果如下:e19a7d13a06cce9df2c620b03fe0dade_720

string_ids#

是由很多个string_id数据组成,其个数由header里的stringIdsSize决定(下面数据体的个数/长度也是看header)

结构如下:

struct DexStringId {
u4 stringDataOff; /* 字符串的文件偏移量 */
};
//伪结构表示如下:
struct string_data_item {
uleb128 utf16_size; //字符串长度
ubyte[] data; //字符串数据
}

type_ids#

descriptorIdx为DexStringID表的索引,对应字符串表示类的类型

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

proto_ids#

typedef struct DexProtoId {
u4 shortyIdx; //方法声明字符串,指向DexStringId列表的索引
u4 returnTypeIdx; //方法返回类型字符串,指向DexTypeId列表的索引
u4 parametersOff; //方法的参数列表,是DexTypeList文件的偏移
} DexProtoId;

DexTypeList#

typedef struct DexTypeList {
u4 size; //DexTypeItem个数, 即参数个数
DexTypeItem list[size]; //DexTypeItem数组, 按从左到右的顺序保存了方法的参数
} DexTypeList;
typedef struct DexTypeItem {
u2 typeIdx; //指向DexTypeId列表的索引
} DexTypeItem;

field_ids#

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

method_ids#

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

class_defs#

整体结构如下:

struct DexClassDef {
u4 classIdx; /* 类的类型(即全限定类名),指向DexTypeId列表的索引 */
u4 accessFlags; /* 访问标志,以ACC_开头的枚举值,如ACC_PUBLIC(0x1)、ACC_PRIVATE(0x2)*/
u4 superclassIdx; /* 父类类型,指向DexTypeId列表的索引*/
u4 interfacesOff; /* 接口,指向DexTypeList的文件偏移,如果类中不含有接口声明和实现,则值为0 */
u4 sourceFileIdx; /* 类所在源文件的文件名,指向DexStringId列表的索引 */
u4 annotationsOff; /* 注解,指向DexAnnotationsDirectoryItem结构体,根据类型不同会有注解类、注解方法、注解字段与注解参数,如果类中没有注解,则值为0 */
u4 classDataOff; /* 指向DexClassData结构的文件偏移,DexClassData结构是类的数据部分 */
u4 staticValuesOff; /* 指向DexEncodedArray结构的文件偏移,记录类中的静态数据, 没有则为0 */
};

我们将ClassDef结构划分为4部分解析: BasicInfo, Annotations, ClassData, StaticValues

BasicInfo#

从classIdx到sourceFileIx属于BasicInfo

DexAnnotationsDirectoryItem#

annotationsOff指向该结构,用于指向类的所有注解,定义如下:

struct DexAnnotationsDirectoryItem {
u4 classAnnotationsOff; /* 类注解,值为DexAnnotationSetItem的文件偏移量, 为0表示不存在*/
u4 fieldsSize; /* 域注解,值为DexFieldAnnotationsItem的数量 */
u4 methodsSize; /* 方法注解,值为DexMethodAnnotationsItem的数量 */
u4 parametersSize; /* 参数注解。值为DexParameterAnnotationsItem的数量 */
/* 后3结构中存在1个或多个,则在后面追加以下数据,并按顺序排列 */
/* followed by DexFieldAnnotationsItem[fieldsSize] */
/* followed by DexMethodAnnotationsItem[methodsSize] */
/* followed by DexParameterAnnotationsItem[parametersSize] */
};

类注解 DexAnnotationSetItem#

struct DexAnnotationSetItem {
u4 size; /* DexAnnotationItem的数量 */
u4 entries[1]; /* entries数组,存储DexAnnotationItem的文件偏移量 */
};
struct DexAnnotationItem {
u1 visibility; /* 此注释的预期可见性 */
u1 annotation[1]; /* encoded_annotation格式的注释内容 */
};

域注解 DexFieldAnnotationsItem#

struct DexFieldAnnotationsItem {
u4 fieldIdx; /* 指向DexFieldId列表的索引值 */
u4 annotationsOff; /* DexAnnotationSetItem的文件偏移量 */
};

方法注解 DexMethodAnnotationsItem#

/*
* Direct-mapped "method_annotations_item".
*/
struct DexMethodAnnotationsItem {
u4 methodIdx; /* 指向DexMethodId列表的索引值 */
u4 annotationsOff; /* DexAnnotationSetItem的文件偏移量 */
};

参数注解 DexParameterAnnotationsItem#

/*
* Direct-mapped "parameter_annotations_item".
*/
struct DexParameterAnnotationsItem {
u4 methodIdx; /* 指向DexMethodId列表的索引值 */
u4 annotationsOff; /* DexAnotationSetRefList的文件偏移量 */
};

DexClassData#

/* expanded form of class_data_item. Note: If a particular item is
* absent (e.g., no static fields), then the corresponding pointer
* is set to NULL. */
typedef struct DexClassData {
DexClassDataHeader header;
DexField* staticFields; //下面4个连续数组,如果对应长度存在才有效
DexField* instanceFields; //按顺序排列
DexMethod* directMethods;
DexMethod* virtualMethods;
} DexClassData;

staticValuesOff#

指向DexEncodeArray结构的偏移,记录了类中的静态数据,没有则为0

DexEncodeArray#

定义如下:(作用先未知)

struct DexEncodeArray{
u1 array[1];
}

dex_map_list#

Dalvik虚拟机解析dex文件后,映射为DexMapList的数据结构, 该结构由DexHeader.mapOff指明位置

struct DexMapList {
u4 size; /* DexMapItem个数 */
DexMapItem list[1]; /* DexMapItem数组 */
};
struct DexMapItem {
u2 type; /* KDexType开头的类型 */
u2 unused; /* 未使用,用于字节对齐 */
u4 size; /* 类型的个数 */
u4 offset; /* 类型数据的文件偏移 */
};
dex文件结构
https://mizuki.mysqil.com/posts/dex文件结构/
作者
sh4d0w
发布于
2025-11-06
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时