Dex文件格式

Dex文件是 Android 系统中特有的一种可执行文件格式,全称为 Dalvik Executable。它是 Android 应用程序中的 Java 字节码文件在打包过程中经过编译和优化后的产物,供 Android 虚拟机(Dalvik 或 ART)运行使用。

参考文章

Android逆向笔记 —— DEX 文件格式解析 – 知乎

Dalvik 可执行文件格式  |  Android Open Source Project

Android杂记 | 早点睡zZZ

Dex文件生成与运行

public class Hello {
    private static String HELLO_WORLD = "Hello World!";
    public static void main(String[] args) {
        System.out.println(HELLO_WORLD);
    }
}

首先 javac 编译成 Hello.class 文件,然后利用 Sdk 自带的 dx 工具生成 DEX 文件,dx 工具位于 Sdk 的 build-tools 目录下这里。

直接编译再利用dx生成Dex文件报错了,似乎是编译成class文件用的jdk版本太高的原因。参考这篇文章的方法

class文件转dex文件错误Caused by: com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe)… – 简书

javac -target 1.6 -source 1.6 Hello.java

如果是低版本的Sdk是dx,但是高版本的Sdk的build-tools找不到dx了,好像变成d8了,不过d8我咋弄都报错。

可以利用adb运行Dex文件。

dalvikvm -cp ./Hello.dex Hello

Dex文件结构

这是网上广为流传的一图流

然后010 Editor有模板方便学习和查看结构

先给Dex文件分个层,分层图片如下

我能找到的源码的结构体如下,因为网上说的源码网站我上不去,在这个网站看的

https://android.googlesource.com/platform/dalvik/+/android-4.4.2_r2/libdex/DexFile.h

struct DexFile {
    /* 直接映射的“opt”头部 */
    const DexOptHeader* pOptHeader;
    /* 指向基础 DEX 中直接映射的结构体和数组的指针 */
    const DexHeader*    pHeader;         // DEX 文件头
    const DexStringId*  pStringIds;      // 字符串标识符数组
    const DexTypeId*    pTypeIds;        // 类型标识符数组
    const DexFieldId*   pFieldIds;       // 字段标识符数组
    const DexMethodId*  pMethodIds;      // 方法标识符数组
    const DexProtoId*   pProtoIds;       // 原型标识符数组
    const DexClassDef*  pClassDefs;      // 类定义数组
    const DexLink*      pLinkData;       // 链接数据
    /*
     * 这些来自“辅助”部分(auxillary section),可能不会包含在 DEX 文件中。
     */
    const DexClassLookup* pClassLookup;  // 类查找表
    const void*         pRegisterMapPool; // 寄存器映射池(RegisterMapClassPool)
    /* 指向 DEX 文件数据的起始位置 */
    const u1*           baseAddr;
    /* 用于追踪辅助结构体的内存开销 */
    int                 overhead;
    /* 与该 DEX 关联的额外应用程序特定的数据结构 */
    //void*               auxData;
};

header

结构体如下

struct DexHeader{
    u1 magic[8];                    /*魔数字段,格式如"dex\n035\x00",其中035表示结构的版本*/
    u4 checksum;                    /*校验码, 是小端序*/
    u1 signature[kSHA1DigertLen];   /*SHA-1签名,长度20*/
    u4 file_Size;                   /*Dex文件总长度*/
    u4 header_Size;                 /*文件头长度,009版本=0x5C,035版本=0x70*/
    u4 endian_Tag;                  /*标识字节顺序的常量,根据这个常量可以判断是否交换了字节顺序,缺省情况下=0x78563412*/
    u4 link_Size;                   /*连接段大小,0表示静态链接*/
    u4 link_Offset;                 /*连接段开始位置,从本文件头开始算起。 上为0此也为0*/
    u4 map_Offset;                  /*map数据基地址*/
    u4 string_ids_size;             /*字符串列表字符串个数*/
    u4 string_ids_off;              /**/
    u4 type_ids_size;               /*类型列表里类型个数*/
    u4 type_ids_offset;             /**/
    u4 proto_ids_size;              /*原型列表里原型个数*/
    u4 proto_ids_offset;            /**/
    u4 field_ids_size;              /*字段列表里字段个数*/
    u4 field_ids_offset;            /**/
    u4 method_ids_size;             /*方法列表里方法个数*/
    u4 method_ids_offset;           /**/
    u4 class_defs_size;             /*类定义列表里类的个数*/
    u4 class_defs_offset;           /**/
    u4 data_size;                   /*数据段大小,必须以4自己对齐*/
    u4 data_offset;                 /**/
}

其中的 u 表示无符号数,u1 就是 8 位无符号数,u4 就是 32 位无符号数。

具体介绍一下一些比较复杂、重要的成员。

checksum 是对去除 magic 、 checksum 以外的文件部分作 alder32 算法得到的校验值,用于判断 DEX 文件是否被篡改。

signature 是对除去 magic 、 checksum 、 signature 以外的文件部分作 sha1 得到的文件哈希值,即去掉头0x20个字节。

file_Size指明整个dex文件的大小

DexHeader的大小固定为70个字节,即header_Size的值。

endianTag 用于标记 DEX 文件是大端表示还是小端表示。由于 DEX 文件是运行在 Android 系统中的,所以一般都是小端表示,这个值也是恒定值 0x12345678。

其余部分分别标记了 DEX 文件中其他各个数据结构的个数和其在数据区的偏移量。根据偏移量我们就可以轻松的获得各个数据结构的内容

string_ids

struct DexStringId {
    u4 stringDataOff;
};

string_ids 是一个偏移量数组,stringDataOff 表示每个字符串在 data 区的偏移量。根据偏移量在 data 区拿到的数据中,第一个字节表示的是字符串长度,后面跟着的才是字符串数据。

指向的字符串中包含了变量名,方法名,文件名等等。

如图,这里字符串“HELLO_WORLD”的偏移为0x01BC

偏移指向的数据第一个是字符串的长度,字符串的结尾也同样有 ‘\0’

type_ids

struct DexTypeId {
    u4  descriptorIdx;
};

type_ids 表示的是类型信息,descriptorIdx 的值为字符串索引,指向 string_ids 中的内容,通过字符串索引得到的值在 data 池中得到字符串,最后根据所得字符串解析对应类型。

这里举个例子,通过 descriptorIdx 的值 0x8 指向 string_ids 中的 string_id[8],通过 string_id[8] 的值找到字符串 ‘V’,字符串 ‘V’ 对应类型就是 void。

proto_ids

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

proto_ids 表示方法声明信息,它包含以下三个变量:

  • shortyIdx : 指向 string_ids ,表示方法声明的字符串
  • returnTypeIdx : 指向 type_ids ,表示方法的返回类型的字符串
  • parametersOff : 方法参数列表的偏移量,0代表没有参数

方法参数列表的数据结构在 DexFile.h 中用 DexTypeList 来表示:

struct DexTypeList {
    u4  size;               /* #of entries in list */
    DexTypeItem list[1];    /* entries */
};
struct DexTypeItem {
    u2  typeIdx;            /* index into typeIds */
};

如图,得到获得 DexStringId、DexTypeId 列表的索引值 0xB、0x5 以及 DexTypeList 的偏移

所以方法声明为VL,返回值类型为void,参数列表为1个参数,类型为java.lang.String

fieId_ids

method_ids

由 DexMethodId 结构体对象组成,结构中的数据也均为索引值,指明了方法所在的类、方法的声明以及方法名,结构体定义如下:

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

属性含义如下

  • classIdx : 指向 type_ids ,表示类的类型
  • protoIdx : 指向 type_ids ,表示方法声明
  • nameIdx : 指向 string_ids ,表示方法名

class_def

由 DexClassDef 结构体组成,该结构体的声明如下

struct DexClassDef 
{
    u4  classIdx;           /* 类的类型,指向 DexTypeId 列表的索引 */
    u4  accessFlags;        /* 访问标志 */	   
    u4  superclassIdx;      /* 父类类型,指向 DexTypeId 列表的索引 */
    u4  interfacesOff;      /* 接口,指向 DexTypeList 的偏移 */
    u4  sourceFileIdx;      /* 源文件名,指向 DexTypeStringId 列表的索引 */
    u4  annotationsOff;     /* 注解,指向 DexAnnotationsDirectoryItem 结构 */
    u4  classDataOff;       /* 指向 DexClassData 结构的偏移 */
    u4  staticValuesOff;    /* 指向 DexEncodedArray 结构的偏移 */
};

class_def 是 DEX 文件结构中最复杂也是最核心的部分,它表示了类的所有信息,对应 DexFile.h 中的 DexClassDef :

  • classIdx : 指向 type_ids ,表示类信息
  • accessFlags : 访问标识符
  • superclassIdx : 指向 type_ids ,表示父类信息
  • interfacesOff : 指向 DexTypeList 的偏移量,表示接口信息
  • sourceFileIdx : 指向 string_ids ,表示源文件名称
  • annotationOff : 注解信息
  • classDataOff : 指向 DexClassData 的偏移量,表示类的数据部分
  • staticValueOff :指向 DexEncodedArray 的偏移量,表示类的静态数据

accessFlags的定义可以在参考文章的官方网站中看

其中的 DexClassData 结构声明在 DexClass.h 文件中,声明如下

struct DexClassData 
{
    DexClassDataHeader header;			// 指定字段与方法的个数
    DexField*          staticFields;	//  静态字段,DexField 结构
    DexField*          instanceFields;	//  实例字段,DexField 结构
    DexMethod*         directMethods;   //  直接方法,DexMethod 结构
    DexMethod*         virtualMethods;  //  虚方法,DexMethod 结构
};

//其中的 DexClassDataHeader 结构记录了当前类中字段与方法的数目,在 DexClass.h 文件中声明,声明如下 
struct DexClassDataHeader 
{
    u4 staticFieldsSize;	// 静态字段个数
    u4 instanceFieldsSize;  // 实例字段个数
    u4 directMethodsSize;   // 直接方法个数 
    u4 virtualMethodsSize;  // 虚方法个数
};

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇