前言

近几个月接触了挺多安卓方面的东西,但本地笔记杂而乱,于是整理一下

背景

1. Java

写一个Helloworld.java​文件,javac​把它翻译成字节码:Helloworld.class​,JVM 启动。当用到 HelloWorld​ 这个类时,JVM 的 ClassLoader 就会去硬盘上找到那个 .class​ 文件,读进内存,然后执行

问题:对于app来说,通常有很多个类,如果都按照这个机制来查找的话,硬盘里会有 10000 个分散的 .class​ 文件,容易造成空间浪费和性能降低(I/O)

2. Android 的 DEX

为了解决上面的问题,出现了DEX (Dalvik Executable) 格式和 ART 虚拟机

采用 常量池去重 的思路,d8 将这10000个.class​融合成一个classes.dex​文件

Dex 是Android系统中Java字节码的优化格式,相比传统的class文件,Dex具有更高的执行效率和更小的文件体积。

image

数据类型

自定义类型 原类型 含义
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 等于ULEB128加1,可变长度

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

LEB128是一种可变长度类型,每个 LEB128 由 1~5 个字节组成,每个字节只有 7 个有效位。如果第一个字节的最高位为 1,表示需要继续使用第 2 个字节,如果第二个字节最高位为 1,表示需要继续使用第三个字节,依此类推,直到最后一个字节的最高位为 0,至多 5 个字节。除了 LEB128 以外,还有无符号类型 ULEB128。

结构

只记一些重要的内容,其他参考Android逆向笔记 —— DEX 文件格式解析 - 知乎

image

Dex文件采用索引+数据的分离设计,所有索引区在前,实际数据在后。这种设计优化了内存访问和加载速度。

  • header :​ DEX 文件头,记录了一些当前文件的信息以及其他数据结构在文件中的偏移量
  • string_ids :​ 字符串的偏移量
  • type_ids :​ 类型信息的偏移量
  • proto_ids :​ 方法声明的偏移量
  • field_ids :​ 字段信息的偏移量
  • method_ids :​ 方法信息(所在类,方法声明以及方法名)的偏移量
  • class_def :​ 类信息的偏移量
  • data :​ : 数据区
  • link_data :​ 静态链接数据区

从 header 到 data 之间都是偏移量数组,并不存储真实数据,所有数据都存在 data 数据区,根据其偏移量区查找。

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
struct DexHeader {
u1 magic[8]; // 魔数
u4 checksum; // adler 校验值
u1 signature[kSHA1DigestLen]; // sha1 校验值
u4 fileSize; // DEX 文件大小
u4 headerSize; // DEX 文件头大小
u4 endianTag; // 字节序
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; // 数据段偏移量
};

magic:

1
文件标识 dex + 换行符 + DEX 版本 + 0

字符串格式为 dex\n035\0​,十六进制为 0x6465780A30333500​。

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

signature​ 是对除去 magic​ 、 checksum​ 、 signature​ 以外的文件部分作 sha1 得到的文件哈希值。

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

其余部分分别标记了 DEX 文件中其他各个数据结构的个数和其在数据区的偏移量。根据偏移量我们就可以轻松的获得各个数据结构的内容。下面顺着上面的 DEX 文件结构来认识第一个数据结构 string_ids​。

最后贴张图

image

参考资料:

[原创]DEX文件格式解析:深入Android字节码结构

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