前言 最近接触Android逆向,就开始熟悉dex格式,顺便记录下数据结构,方便下次直接查询.
dex格式分析
dex: 什么是DEX
dex结构: 详细的dex数据结构
引言 很久之前曾接触过Android,那时候对逆向感到畏惧,当真正的开始接触他的时候,感觉其实也就那样.需要很大的耐心去看.
DEX Dex是Android平台上可执行文件的类型。包含应用程序的全部操作指令以及运行时数据。由于dalvik是一种针对嵌入式设备而特殊设计的java虚拟机,所以dex文件与标准的class文件在结构设计上有着本质的区别.当java程序编译成class后,还需要使用dx工具将所有的class文件整合到一个dex文件,目的是其中各个类能够共享数据,在一定程度上降低了冗余,同时也是文件结构更加经凑,实验表明,dex文件是传统jar文件大小的50%左右.
DEX结构 下面就逐步开始描述这个dex文件,首先先上一张整体dex格式图,以便有一个宏观的了解. 通过图我们可以直观的发现整个结构的分布. 其数据结构为:1 struct DexFile
{
DexHeader Header;
DexStringId StringIds[stringIdsSize];
DexTypeId TypeIds[typeIdsSize];
DexProtoId ProtoIds[protoIdsSize];
DexFieldId FieldIds[fieldIdsSize];
DexMethodId MethodIds[methodIdsSize];
DexClassDef ClassDefs[classDefsSize];
DexData Data[];
DexLink LinkData;
};
名称
格式
描述
header
header_item
文件头。
string_ids
string_id_item[]
字符串索引表,记录了各个字符所在的偏移值,使用UTF-16编码。
type_ids
type_id_item[]
类型数据索引,记录了各个类型的字符串索引。
proto_id
proto_id_item[]
函数原型数据索引,记录了方法声明的字符串,返回类型和参数列表。
field_ids
field_id_item[]
字段数据索引,记录了所属类,声明类型和方法名等信息。
method_ids
method_id_item[]
类方法索引,记录了方法所属类,声明类型以及方法名等信息。
class_defs
class_def_item[]
类定义数据,记录了指定类的各类信息,包括接口,超类,类数据偏移量等。
data
type_id_item[]
数据区,保存着各个类的数据
link_data
ubyte[]
静态连接数据
header的结构如下所示.1 typedef struct _DexHeader
{
u1 magic[8]; // dex 版本标识,"dex.035"
u4 checksum; // adler32 检验
u1 signature[kSHA1DigestLen]; // SHA-1 哈希值,20个字节
u4 fileSize; // 整个 dex 文件大小
u4 headerSize; // DexHeader 结构大小,0x70
u4 endianTag; // 字节序标记,小端 "0x12345678",大端"0x78563412"
u4 linkSize; // 链接段大小
u4 linkOff; // 链接段偏移
u4 mapOff; // DexMapList 的偏移
u4 stringIdsSize; // DexStringId 的个数
u4 stringIdsOff; // DexStringId 的偏移 字符串
u4 typeIdsSize; // DexTypeId 的个数
u4 typedeIdsOff; // 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, *PDexHeader;
magic: 它代表dex中的文件标识,一般被称为魔数。是用来识别dex这种文件的,它可以判断当前的dex文件是否有效,可以看到它用了8个1字节的无符号数来表示.
checksum: 它是dex文件的校验和,通过它可以判断dex文件是否被损坏或者被篡改。它占用4个字节.
signature[kSHA1DigestLen]: signature字段用于检验dex文件,其实就是把整个dex文件用SHA-1签名得到的一个值。这里占用20个字节.
fileSize: 表示整个文件的大小,占用4个字节。
headerSize: 表示DexHeader头结构的大小,占用4个字节。
endianTag: 代表 字节序标记,用于指定dex运行环境的cpu,预设值为0x12345678
linkSize ,linkOff: 这两个字段,它们分别指定了链接段的大小和文件偏移,通常情况下它们都为0。linkSize为0的话表示静态链接
mapOff: 它指定了DexMapList的文件偏移
DexStringId 1 struct DexStringId {
u4 stringDataOff; /* string_data_item 偏移地址*/
};
struct string_data_item
{
uleb128 utf16_size;
ubyte data;
}
其中 data 保存的就是字符串的值。string_ids 是比较关键的,后续的区段很多都是直接指向 string_ids 的 index
DexTypeId 1 struct DexTypeId{
u4 descriptorIdx; /*指向DexStringId列表的索引*/
}
DexProtoId 1 struct DexProtoId{
u4 shortyIdx; /*指向DexStringId列表的索引*/
u4 returnTypeIdx; /*指向DexTypeId列表的索引*/
u4 parametersOff; /*指向DexTypeList的位置偏移*/
}
shorty_idx: 跟 type_ids 一样,它的值是一个 string_ids 的 index 号,最终是一个简短的字符串描述,用来说明该 method 原型。
return_type_idx: 它的值是一个 type_ids 的 index 号 ,表示该 method 原型的返回值类型。
parameters_off: 指向 method 原型的参数列表 type_list,若 method 没有参数,值为0。参数列表的格式是 type_list,下面会有描述。DexTypeList 1 struct DexTypeList{
u4 size; /*DexTypeItem的个数*/
DexTypeItem list[1]; /*DexTypeItem结构*/
}
struct DexTypeItem{
u2 typeIdx; /*指向DexTypeId列表的索引*/
}
DexFieldId 1 struct DexFieldId{
u2 classIdx; /*类的类型,指向DexTypeId列表的索引*/
u2 typeIdx; /*字段类型,指向DexTypeId列表的索引*/
u4 nameIdx; /*字段名,指向DexStringId列表的索引*/
}
class_idx: 表示 field 所属的 class 类型,class_idx 的值是 type_ids 的一个 index,并且必须指向一个 class 类型。
type_idx: 表示本 field 的类型,它的值也是 type_ids 的一个 index 。
name_idx: 表示本 field 的名称,它的值是 string_ids 的一个 index 。
DexMethodId 1 struct DexMethodId{
u2 classIdx; /*类的类型,指向DexTypeId列表的索引*/
u2 protoIdx; /*声明类型,指向DexProtoId列表的索引*/
u4 nameIdx; /*方法名,指向DexStringId列表的索引*/
}
class_idx: 表示 method 所属的 class 类型,class_idx 的值是 type_ids 的一个 index,并且必须指向一个 class 类型。ushort类型也是为什么我们说一个 dex 只能有 65535 个方法的原因,多了必须分包。
proto_idx: 表示 method 的类型,它的值也是 type_ids 的一个 index。
name_idx: 表示 method 的名称,它的值是 string_ids 的一个 index。
DexClassDef 1 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结构的偏移*/
}
class_idx: 描述具体的 class 类型,值是 type_ids 的一个 index 。值必须是一个 class 类型,不能是数组类型或者基本类型。
access_flags: 描述 class 的访问类型,诸如 public , final , static 等。在 dex-format.html 里 “access_flags Definitions” 有具体的描述 。
superclass_idx: 描述 supperclass 的类型,值的形式跟 class_idx 一样 。
interfaces_off: 值为偏移地址,指向 class 的 interfaces,被指向的数据结构为 type_list 。class 若没有 interfaces 值为 0。
source_file_idx: 表示源代码文件的信息,值是 string_ids 的一个 index。若此项信息缺失,此项值赋值为 NO_INDEX=0xffff ffff。
annotions_off: 值是一个偏移地址,指向的内容是该 class 的注释,位置在 data 区,格式为 annotations_direcotry_item。若没有此项内容,值为 0 。
class_data_off: 值是一个偏移地址,指向的内容是该 class 的使用到的数据,位置在 data 区,格式为 class_data_item。若没有此项内容值为 0。该结构里有很多内容,详细描述该 class 的 field、method, method 里的执行代码等信息,后面会介绍 class_data_item。
static_value_off: 值是一个偏移地址 ,指向 data 区里的一个列表 (list),格式为 encoded_array_item。若没有此项内容值为 0。
1 struct DexTypeList
{
uint size;
type_item list [size]
}
struct type_item
{
ushort type_idx //-->type_ids
}
struct DexAnnotationsDirectoryItem
{
uint class_annotations_off; //-->annotation_set_item
uint fields_size;
uint annotated_methods_size;
uint annotated_parameters_size;
field_annotation field_annotations[fields_size];
method_annotation method_annotations[annotated_methods_size];
parameter_annotation parameter_annotations[annotated_parameters_size];
}
struct field_annotation
{
uint field_idx;
uint annotations_off; //-->annotation_set_item
}
struct method_annotation
{
uint method_idx;
uint annotations_off; //-->annotation_set_item
}
struct parameter_annotation
{
uint method_idx;
uint annotations_off; //-->annotation_set_ref_list
}
class_annotations_off: 这个偏移指向了 annotation_set_item 具体的可以看 dex-format.html 上的介绍.
fields_size: 表示属性的个数
annotated_methods_size: 表示方法的个数
annotated_parameters_size: 表示参数的个数
1
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 encoded_field
{
uleb128 filed_idx_diff;
uleb128 access_flags;
}
struct encoded_method
{
uleb128 method_idx_diff;
uleb128 access_flags;
uleb128 code_off;
}
method_idx_diff: 前缀 methd_idx 表示它的值是 method_ids 的一个 index ,后缀 _diff 表示它是于另 外一个 method_idx 的一个差值 ,就是相对于 encodeed_method [] 数组里上一个元素的 method_idx 的差值 。 其实 encoded_filed - > field_idx_diff 表示的也是相同的意思 ,只是编译出来的 Hello.dex 文件里没有使用到 class filed 所以没有仔细讲 ,详细的参考 dex_format.html 的官网文档。
access_flags: 访问权限,比如 public、private、static、final 等。
code_off: 一个指向 data 区的偏移地址,目标是本 method 的代码实现。被指向的结构是code_item,有近 10 项元素。
1 struct code_item
{
ushort registers_size;
ushort ins_size;
ushort outs_size;
ushort tries_size;
uint debug_info_off;
uint insns_size;
ushort insns [insns_size];
ushort paddding; // optional
try_item tries [tyies_size]; // optional
encoded_catch_handler_list handlers; // optional
}
code_item 结构里描述着某个 method 的具体实现.
registers_size: 本段代码使用到的寄存器数目。
ins_size: method 传入参数的数目 。
outs_size: 本段代码调用其它 method 时需要的参数个数 。
tries_size: try_item 结构的个数 。
debug_off: 偏移地址,指向本段代码的 debug 信息存放位置,是一个 debug_info_item 结构。
insns_size: 指令列表的大小,以 16-bit 为单位。 insns 是 instructions 的缩写 。
padding: 值为 0,用于对齐字节 。
tries 和 handlers: 用于处理 java 中的 exception,常见的语法有 try catch。
1 struct encoded_array_item
{
encoded_array value;
}
struct encoded_array
{
uleb128 size;
encoded_value values[size];
}
encoded_array_item是class_def_item->static_value_off 偏移指向的区段数据。
size : 表示encoded_value 个数1 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[1];//指令集
/* followed by optional u2 padding */
/* followed by try_item[triesSize] */
/* followed by uleb handlersSize */
/* followed by catch_handler_item[handlersSize] */
};
DexMapList 1 struct DexMapList {
u4 size; /* 个数 */
DexMapItem list[1]; /* DexMapItem的结构 */
};
struct DexMapItem {
u2 type; /* kDexType开头的类型 */
u2 unused; /*未使用,用于字节对齐 */
u4 size; /* 类型的个数 */
u4 offset; /* 类型的文件偏移 */
};
enum {
kDexTypeHeaderItem = 0x0,
kDexTypeStringIdItem = 0x1,
kDexTypeTypeIdItem = 0x2,
kDexTypeProtoIdItem = 0x3,
kDexTypeFieldIdItem = 0x4,
kDexTypeMethodIdItem = 0x5,
kDexTypeClassDefItem = 0x6,
kDexTypeMapList = 0x0,
kDexTypeTypeList = 0x1,
kDexTypeAnnotationSetRefList = 0x2,
kDexTypeAnnotationSetItem = 0x3,
kDexTypeClassDataItem = 0x0,
kDexTypeCodeItem = 0x1,
kDexTypeStringDataItem = 0x2,
kDexTypeDebugInfoItem = 0x3,
kDexTypeAnnotationItem = 0x4,
kDexTypeEncodedArrayItem = 0x5,
kDexTypeAnnotationsDirectoryItem = 0x6,
};
总结图 在结尾通过一张图把前面的东西串起来,如下所示,便于了解.