首页 技术杂谈 正文
  • 本文约4592字,阅读需23分钟
  • 140
  • 0

常见的Android文件结构 - class.dex

class.dex文件

class.dex 是 Android 应用的中间字节码文件,包含所有 Java/Kotlin 代码经编译后的指令集合,供 Android Runtime(ART/Dalvik)执行

DEX的目录结构

  • 主 DEX 文件:classes.dex(默认)。
  • MultiDEX 生成:classes2.dex,classes3.dex 等。
  • 原生库和其他文件路径
    ├── classes.dex
    ├── classes2.dex
    ├── lib/        # 原生库(armeabi-v7a、arm64-v8a 等)
    └── assets/     # 原始资源(未编译)

DEX文件结构解析

通过010editor编辑器打开class.dex文件,可以查看其结构
DEX 文件由 多个结构化的 Section(区段) 组成,各 Section 通过头部(Header)定义的偏移量进行定位

通过代码格式理解其文件格式

struct DexFile {
    DexHeader          header;         // 文件头
    DexStringId        string_ids[];   // 字符串索引表
    DexTypeId          type_ids[];     // 类型索引表
    DexProtoId         proto_ids[];    // 方法原型索引表
    DexFieldId         field_ids[];    // 字段索引表
    DexMethodId        method_ids[];   // 方法索引表
    DexClassDef        class_defs[];   // 类定义表
    DexData            data;           // 数据区(字节码、注解等)
    DexLink            link_data;      // 静态链接数据(可选)
};

各 Section 的详细结构

文件头(DexHeader)

头部位于 DEX 文件起始位置,定义了所有 Section 的偏移量和数据规模,固定大小为 0x70 字节

  • 关键字段示例:
    • string_ids_size/string_ids_off:字符串表的数量和偏移。
    • class_defs_size/class_defs_off:类定义表的数量和偏移
struct DexHeader {
    u1  magic[8];           // Magic Number("dex\n", 后跟版本标识,如 "035\0")
    u4  checksum;           // 文件的 Adler-32 校验和(不含 magic 和 checksum 本身)
    u1  signature[20];      // SHA-1 哈希,用于防篡改
    u4  file_size;          // 整个文件的大小(字节)
    u4  header_size;        // 头部大小(固定 0x70)
    u4  endian_tag;         // 字节序标记(0x12345678 小端)
    u4  link_size;          // link_data 区段的大小(静态链接使用)
    u4  link_off;           // link_data 区段的偏移量
    u4  map_off;            // map_list 的偏移量(DexMapList 结构,描述各 Section 的位置)
    // ...其他字段定义各 Section 的偏移和数量
};

字符串索引表(DexStringId)

  • 存储所有字符串在数据区的偏移量(去重处理)
struct DexStringId {
    u4 string_data_off;    // 字符串数据的偏移量(指向 DexStringItem)
};
  • 字符串数据格式(DexStringItem):
    • 字符串长度(LEB128 编码)+ UTF-8 数据(无终止符)
struct DexStringItem {
    uleb128  utf16_size;  // UTF-16 字符数(并非字节长度!)
    u1       data[];      // UTF-8 字节流
};

类型索引表(DexTypeId)

  • 存储所有 类型描述符字符串的索引(指向字符串表)
struct DexTypeId {
    u4 descriptor_idx;    // 对应字符串表中的索引(如 "Ljava/lang/String;")
};

方法原型索引表(DexProtoId)

  • 定义方法原型(包含返回类型和参数类型列表)
struct DexProtoId {
    u4 shorty_idx;       // 短描述符索引(如 "VI" 表示 void func(int))
    u4 return_type_idx;  // 返回值类型索引(对应 DexTypeId)
    u4 parameters_off;   // 参数类型列表偏移(指向 DexTypeList)
};
  • 参数列表(DexTypeList)
struct DexTypeList {
    u4          size;       // 参数数量
    DexTypeItem list[size]; // 参数类型数组
};
struct DexTypeItem {
    u2 type_idx;           // 类型索引(DexTypeId)
};

字段/方法索引表(DexFieldId / DexMethodId)

  • 定义字段和方法的元信息

    • DexFieldId 结构

      struct DexFieldId {
      u2 class_idx;        // 所属类索引(DexTypeId)
      u2 type_idx;         // 字段类型索引(DexTypeId)
      u4 name_idx;         // 字段名索引(DexStringId)
      };
    • DexMethodId 结构

      struct DexMethodId {
      u2 class_idx;        // 所属类索引(DexTypeId)
      u2 proto_idx;        // 方法原型索引(DexProtoId)
      u4 name_idx;         // 方法名索引(DexStringId)
      };

类定义表(DexClassDef)

  • 描述类的完整元数据
    struct DexClassDef {
    u4 class_idx;         // 类类型索引(DexTypeId)
    u4 access_flags;      // 访问标志(public, final, etc.)
    u4 superclass_idx;    // 父类索引(DexTypeId)
    u4 interfaces_off;    // 接口列表偏移(DexTypeList)
    u4 source_file_idx;   // 源文件名索引(DexStringId,可选)
    u4 annotations_off;   // 注解信息偏移(DexAnnotationsDirectoryItem,可选)
    u4 class_data_off;    // 类数据偏移(DexClassData 结构)
    u4 static_values_off; // 静态字段初始值偏移(DexEncodedArray)
    };

类数据(DexClassData)

  • 存储类的实际字段和方法数据,包含可变长度结构(LEB128 编码压缩)
struct DexClassData {
    uleb128 static_fields_size;   // 静态字段数量
    uleb128 instance_fields_size; // 实例字段数量
    uleb128 direct_methods_size;  // 直接方法数量(static + private)
    uleb128 virtual_methods_size; // 虚方法数量
    DexField[static_fields_size];     // 静态字段数组
    DexField[instance_fields_size];   // 实例字段数组
    DexMethod[direct_methods_size];   // 直接方法数组
    DexMethod[virtual_methods_size];  // 虚方法数组
};

// 字段定义(动态解析)
struct DexField {
    uleb128 field_idx;    // 字段索引(DexFieldId)
    uleb128 access_flags; // 访问标志
};

// 方法定义(动态解析)
struct DexMethod {
    uleb128 method_idx;   // 方法索引(DexMethodId)
    uleb128 access_flags; // 访问标志
    uleb128 code_off;     // 方法代码偏移(DexCode 结构)
};

方法代码(DexCode)

  • 存储方法的字节码、寄存器信息、异常表等
    struct DexCode {
    u2  registers_size;     // 使用寄存器总数
    u2  ins_size;           // 输入参数占用的寄存器数
    u2  outs_size;          // 输出参数占用的寄存器数(用于调用其他方法)
    u2  tries_size;         // try-catch 块数量
    u4  debug_info_off;     // 调试信息偏移(DexDebugInfoItem)
    u4  insns_size;         // 指令数组的大小(以 2 字节为单位)
    u2  insns[insns_size];  // Dalvik 指令数组
    // 可选的 try-catch 数据(DexTryItem)和 handlers 信息
    };

关键数据结构特性

  • (1) 索引化设计
    • 所有字符串、类型、方法等均通过 索引引用(而非直接存储),极大减少冗余数据。
    • 例如:方法名通过 name_idx 指向 DexStringId 列表中的一个条目。
  • (2) LEB128 编码
    • 在变长字段(如 uleb128 类型)中使用 LEB128 (Little-Endian Base 128) 压缩编码:
    • 目的:优化空间效率,尤其是在类数据(DexClassData)中,字段/方法数量大但实际值较小。
  • (3) 紧密排列
    • 各 Section 的数据在文件内紧密排列,通过 偏移量快速跳转,避免了不必要的 IO 开销

DEX文件的验证与优化

  • 验证过程
  • 安装时验证(由 installd 触发):
    • Magic Number 检查:确认文件头是否为有效 DEX 签名(64 65 78 0A → "dex\n")。
    • Checksum/签名验证:比对 APK 签名中的 DEX 完整性。
    • 结构校验:检查 DEX 偏移量是否正确,避免恶意篡改后的越界访问。
  • Dalvik 字节码验证:
    • 格式验证:方法指令是否符合规范。
    • 类型验证:寄存器使用和类型匹配。
    • 控制流验证:跳转指令是否合法。
  • 优化过程
    • 工具:dex2oat(取代旧版 dexopt)
  • 模式:
    • AOT(Ahead-of-Time):安装时将 DEX 编译为本地机器码(OAT 格式)。
    • JIT(Just-in-Time):运行时动态优化高频代码

DEX文件的修改

  • 反编译与修改工具

    • Apktool:
      • 解包 APK → 得到 classes.dex。
      • 使用 d2j-dex2jar 转换为 JAR,或直接修改 smali 代码。
  • baksmali/smali(汇编与反汇编):

    baksmali classes.dex -o smali/           # DEX → smali 文本
    smali smali/ -o modified_classes.dex     # 重编译为 DEX
  • 动态加载技术

    // 使用 DexClassLoader 加载插件化 DEX
    DexClassLoader loader = new DexClassLoader(
    dexPath, optimizedDir, null, getClassLoader()
    );
    Class<?> clazz = loader.loadClass("com.plugin.MyClass");
    clazz.getMethod("invoke").invoke(null);

MultiDEX

  • 问题背景
    • 方法数限制:单个 DEX 文件最多支持 65536 (2^16) 个方法引用(源于 DEX 格式设计)。
    • 触发条件:大型应用依赖库过多(如 Google Play Services、Facebook SDK 等)→ 生成多个 DEX 文件。
  • 启用Multidex
    <GRADLE>
    // build.gradle
    android {
    defaultConfig {
        multiDexEnabled true
    }
    }
    dependencies {
    implementation 'androidx.multidex:multidex:2.0.1'
    }
标签:Android逆向
评论