第四章 常见 Android 文件格式(二)(classes.dex)

classes.dex

  • 其中包含 APK 的可执行代码,是分析 Android 软件时最常见的目标

DEX 文件结构

  • 在 Android 源码文件 dalvik/libdex/DexFile.h 中,有 DEX 文件可能用到的所有数据结构和常量定义
  • 先了解 DEX 文件使用的数据类型:
自定义类型 原类型 含义
s1 int8_t 8 位有符号整型
u1 uint8_t 8 位无符号整型
s2 int16_t 16 位有符号整型,小端字节序
u2 uint16_t 16 位无符号整型,小端字节序
s4 int32_t 32 位有符号整型,小端字节序
u4 uint32_t 32 位无符号整型,小端字节序
s8 int64_t 64 位有符号整型,小端字节序
u8 uint64_t 64 位无符号整型,小端字节序
sleb128 有符号 LEB128,可变长度
uleb128 无符号 LEB128,可变长度
uleb128p1 无符号 LEB128 加 1,可变长度
  • u1 ~ u8 好理解,表示 1 ~ 8 字节的无符号数;s1 ~ s8 表示 1 ~ 8 字节的有符号数;sleb128、uleb128、uleb128p1 则是 DEX 文件中特有的 LEB128 数据类型
  • 每个 LEB128 由 1 ~ 5 字节组成,所有的字节组合在一起表示一个 32 位的数据,如下图。每个字节只有 7 位为有效位,若第一个字节的最高位为 1,表示 LEB128 要使用第二个字节;若第二个字节的最高位为 1,表示 LEB128 要使用第三个字节;依此类推,直到最后一个字节的最高位为 0。当然,LEB128 最多使用 5 字节,若读取 5 字节后下一个字节的最高位仍为 1,则表示该 DEX 文件无效,Dalvik 虚拟机在验证 DEX 文件时会失败并返回
    在这里插入图片描述
  • 在 Android 系统源码 dalvik/libdex/Leb128.h 中可找到 LEB128 的实现。读取无符号 LEB128(uleb128)数据的代码:
DEX_INLINE int readUnsignedLeb128(const u1** pStream) {
    const u1* ptr = *pStream;
    int result = *(ptr++);
    // 大于 0x7f,表示第一个字节最高位为 1
    if (result > 0x7f) {
        // 第二个字节
        int cur = *(ptr++);
        // 前两个字节的组合
        result = (result & 0x7f) | ((cur & 0x7f) << 7);
        // 大于 0x7f,表示第二个字节最高位仍为 1
        if (cur > 0x7f) {
            // 第三个字节
            cur = *(ptr++);
            // 前三个字节的组合
            result |= (cur & 0x7f) << 14;
            if (cur > 0x7f) {
                // 第四个字节
                cur = *(ptr++);
                // 前四个字节的组合
                result |= (cur & 0x7f) << 21;
                if (cur > 0x7f) {
                    // 第五个字节
                    cur = *(ptr++);
                    // 前五个字节的组合
                    result |= cur << 28;
                }
            }
        }
    }
    *pStream = ptr;
    return result;
}
  • 有符号的 LEB128(sleb128)与无符号的 LEB128 的计算方法大致相同,区别是无符号的 LEB128 的最后一个字节的最高有效位进行了符号扩展。读取有符号的 LEB128 数据的代码:
DEX_INLINE int readSignedLeb128(const u1** pStream) {
    const u1* = *pStream;
    int result = *(ptr++);
    // 小于 0x7f,表示第一个字节的最高位不为 1
    if (result <= 0x7f) {
        // 对第一个字节的最高有效位进行符号扩展
        result = (result << 25) >> 25;
    }
    else {
        // 第二个字节
        int cur = *(ptr++);
        // 前两个字节的组合
        result = (result & 0x7f) | ((cur & 0x7f) << 7);
        if (cur <= 0x7f) {
            // 对结果进行符号位扩展
            result = (result << 18) >> 18;
        }
        else {
            // 第三个字节
            cur = *(ptr++);
            // 前三个字节的组合
            result |= (cur & 0x7f) << 14;
            if (cur <= 0x7f) {
                // 对结果进行符号位扩展
                result = (result << 11) >> 11;
            }
            else {
                // 第四个字节
                cur = *(ptr++);
                // 前四个字节的组合
                result |= (cur & 0x7f) << 21;
                if (cur <= 0x7f) {
                    // 对结果进行符号位扩展
                    result = (result << 4) >> 4;
                }
                else {
                    // 第五个字节
                    cur = *(ptr++);
                    // 前五个字节的组合
                    result |= cur << 28;
                }
            }
        }
    }
    *pStream = ptr;
    return result;
}
  • uleb128p1 类型很简单,其值为 uleb128 的值加 1
  • 计算字符序列“c0 83 92 25”的 uleb128 值:
  • 第一个字节 0xc0 大于 0x7f,表示要使用第二个字节,即 result1 = 0xc0 & 0x7f
  • 第二个字节 0x83 大于 0x7f,要使用第三个字节,即 result2 = result1 + (0x83 & 0x7f) << 7
  • 第三个字节 0x92 大于 0x7f,要使用第四个字节,即 result3 = result2 + (0x92 & 0x7f) << 14
  • 第四个字节 0x25 小于 0x7f,表示到了结尾,即 result4 = result3 + (0x25 & 0x7f) << 21
  • 结果为:0x40 + 0x180 + 0x1200000 + 0x4a00000000 = 0x4a012001c0
  • 计算字符序列“d1 c2 b3 40”的 sleb128 值:
  • 第一个字节 0xd1 大于 0x7f,要使用第二个字节,即 result1 = 0xd1 & 0x7f
  • 第二个字节 0xc2 大于 0x7f,要使用第三个字节,即 result2 = result1 + (0xc2 & 0x7f) << 7
  • 第三个字节 0xb3 大于 0x7f,要使用第四个字节,即 result3 = result2 + (0xb3 & 0x7f) << 14
  • 第四个字节 0x40 小于 0x7f,表示到了结尾,即 result4 = ((result3 + (0x40 & 7f) << 21) << 4) >> 4
  • 结果为:((0x51 + 0x2100 + 0x3300000 + 0x8000000000) << 4) >> 4 = ‭0x8003302151‬
  • DEX 文件由多个结构体组合而成。如下图,一个 DEX 文件由七部分组成:dex header 为 DEX 文件头,指定了 DEX 文件的一些属性并记录了其他数据结构在 DEX 文件中的物理偏移;string_ids 到class_def 部分可理解为“索引结构区”;真实的数据存放在 data 数据区;link_data 为静态链接数据区
dex header
string_ids
type_ids
proto_ids
field_ids
method_ids
class_def
data
link_data

在这里插入图片描述

  • DEX 文件由 DexFile 结构体表示,定义如下:
struct DexFile {
    // directly-mapped "opt" header
    const DexOptHeader* pOptHeader;
    
    // pointers to directly-mapped structs and arrays in base DEX
    const DexHeader* pHeader;
    const DexStringId* pStringIds;
    const DexTypeId* pTypeIds;
    const DexFileId* pFileIds;
    const DexMethodId* pMethodIds;
    const DexProtoId* pProtoIds;
    const DexClassDef* pClassDefs;
    const DexLink* pLinkData;
    
    // These are mapped out of the "auxillary" section,
    // and may not be included in the file
    const DexClassLookup* pClassLookup;
    const void* pRegisterMapPool;        // RegisterMapClassPool
    
    // points to start of DEX file data
    const u1* baseAddr;
    
    // track memory overhead for auxillary structures
    int overhead;
    
    // additional app-specific data structures associated with the DEX
    //void* auxData;
};
  • DexOptHeader 是 ODEX 的头。DexHeader 是 DEX 文件的头部信息,定义:
struct DexHeader {
    u1 magic[8];        // DEX 版本标识
    u4 checksum;        // adler32 检验
    u1 signature[kSHA1DigestLen];        // SHA-1 散列值
    u4 fileSize;        // 整个文件的大小
    u4 headerSize;        // DexHeader 结构的大小
    u4 endianTag;        // 字节序标记
    u4 linkSize;        // 链接段的大小
    u4 linkOff;        // 链接段的偏移量
    u4 mapOff;        // DexMapList 的文件偏移
    u4 stringIdsSzie;        // 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 字段:表示这是一个有效的 DEX 文件,目前它的值固定为“64 65 78 0A 30 33 35 00”,转换为字符串格式为“dex.035.”
    在这里插入图片描述
  • checksum 字段:DEX 文件的校验和,可通过它判断 DEX 文件是否已损坏或被篡改
  • signature 字段:用于识别未经 dexopt 优化的 DEX 文件
  • fileSize 字段:记录包括 DexHeader 在内的整个 DEX 文件的大小
  • headerSize 字段:记录 DexHeader 结构本身占用的字节数
  • endianTag 字段:指定 DEX 运行环境的 CPU 字节序,预设值 ENDIAN_CONSTANT 等于 0x12345678,表示默认采用小端字节序
  • linkSize、linkOff 字段:分别指定链接段的大小和文件偏移,大多数情况下它们的值为 0
  • mapOff 字段:指定 DexMapList 结构的文件偏移
  • 剩余字段:分别表示 DexStringId、DexTypeId、DexProto
  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值