dex格式分析

前言

最近接触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格式图,以便有一个宏观的了解.
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,
};

总结图

  在结尾通过一张图把前面的东西串起来,如下所示,便于了解.
dex图

文章目录
  1. 1. 前言
    1. 1.1. dex格式分析
  2. 2. 引言
  3. 3. DEX
  4. 4. DEX结构
    1. 4.1. header
    2. 4.2. DexStringId
    3. 4.3. DexTypeId
    4. 4.4. DexProtoId
    5. 4.5. DexTypeList
    6. 4.6. DexFieldId
    7. 4.7. DexMethodId
    8. 4.8. DexClassDef
    9. 4.9. DexMapList
  5. 5. 总结图
|
Fork me on GitHub