FART 框架的修复组件与 VMP 还原

FART 脱壳后的 DEX 问题分析

FART 脱壳虽然能够自动 dump DEX 数据,但 dump 出来的结果并不总是完美的。根据目标应用使用的壳类型不同,脱壳后可能存在以下两类主要问题:

DEX 文件不完整

某些壳程序(特别是新一代的加固方案)不会一次性将完整的 DEX 加载到内存中,而是按需加载——只有当某个类被实际使用时,才将其对应的 DEX 数据解密到内存。FART 在 LinkClass 阶段只能 dump 已经加载过的类,对于那些尚未被使用的类,其 DEX 数据可能仍然处于加密状态或尚未被加载。

常见表现:

  • dump 出的 DEX 文件大小小于原始 DEX
  • 使用 dexdump 分析时发现部分类定义缺失
  • 反编译工具报错:“classes.dex is not a valid dex file”

方法字节码缺失

这是 DEX 抽取壳的典型问题。壳程序将方法体字节码从原始 DEX 中抽取出来,替换为空方法壳。FART 在 LinkClass 阶段 dump 方法字节码时,某些方法可能还没有被回填,导致 dump 到的字节码仍然是空壳(通常只是一条 return 指令)。

常见表现:

  • 反编译后看到大量空方法体
  • jadx 等工具显示 // Method was not decompiled
  • FART 的方法 dump 文件中,部分方法的 insns_size 为 1(只有一条 return 指令)

dex_fix 修复工具的使用方法与原理

FART 项目自带了一个修复工具 dex_fix,专门用于修复 FART dump 后存在问题的 DEX 文件。

dex_fix 的工作原理

dex_fix 的核心思路是利用 FART 在 LinkClass 阶段 dump 的方法级字节码数据,将其回填到 dump 的 DEX 文件中对应的方法体位置。具体步骤如下:

  1. 读取 FART dump 的 DEX 文件
  2. 解析 DEX 文件格式,找到每个方法的 code_item 结构
  3. 读取 FART dump 的方法字节码文件(.txt 格式)
  4. 将 dump 的字节码数据回填到 DEX 文件的 code_item
  5. 重新计算 DEX 的 checksumsignature
  6. 输出修复后的 DEX 文件

code_item 回填技术

DEX 文件中,方法的字节码存储在 code_item 结构中。理解 code_item 的格式是修复工作的关键:

code_item 结构(以 DEX35 为例):

+0x00: uint16_t registers_size     // 使用的寄存器数量
+0x02: uint16_t ins_size            // 输入参数占用的寄存器数
+0x04: uint16_t outs_size           // 调用其他方法时需要的参数寄存器数
+0x06: uint16_t tries_size          // try/catch 块数量
+0x08: uint32_t debug_info_off      // 调试信息偏移
+0x0C: uint32_t insns_size          // 指令数量(单位:16位)
+0x10: uint16_t insns[insns_size]   // 方法字节码
+...: try_item[tries_size]          // try 块信息
+...: encoded_catch_handler_list    // catch handler 信息

回填过程需要修改的关键字段是 insns_size(指令数量)和 insns(指令数据本身)。由于回填后指令长度可能发生变化,code_item 的总大小也可能改变,这需要调整 DEX 文件中后续数据的偏移量。

dex_fix 使用示例

FART dump 后,在 /sdcard/fart/ 目录下会生成以下文件:

/sdcard/fart/
├── com.example.target_0.dex          # dump 的完整 DEX
├── com.example.target_0_method.txt    # 所有方法的字节码 dump
└── com.example.target_1_method.txt    # 另一个 DEX 的方法 dump

方法 dump 文件的格式通常为:

class_name: Lcom/example/TargetClass;
method_idx: 123
code_off: 0x1a2b
insns_size: 30
bytecode: 0e00 1a00 6e20 3200  ...(16位指令序列)
---
class_name: Lcom/example/AnotherClass;
method_idx: 456
...

使用 dex_fix 进行修复的流程:

# 将 dump 文件拉取到 PC
adb pull /sdcard/fart/ ./fart_dump/

# 运行 dex_fix 修复
python dex_fix.py \
    --dex ./fart_dump/com.example.target_0.dex \
    --methods ./fart_dump/com.example.target_0_method.txt \
    --output ./fart_dump/fixed.dex

# 验证修复结果
dexdump -d ./fart_dump/fixed.dex | head -50

# 使用 jadx 反编译验证
jadx -d ./output/ ./fart_dump/fixed.dex

手动修复 DEX 的方法

dex_fix 自动修复失败时,可以尝试手动修复。手动修复的核心步骤如下:

第一步:分析 DEX 结构

使用 dexdump 工具分析 dump 的 DEX 文件:

dexdump -d target.dex > dexdump_output.txt

重点关注以下信息:

  • 类的数量是否完整(对比原始 APK 的类列表)
  • 空方法体的位置和数量
  • DEX 文件的 checksum 是否正确

第二步:定位需要修复的方法

在 FART 的方法 dump 文件中,找到需要修复的方法的字节码数据:

# 从 dump 文件中提取方法的 code_off 和 bytecode
class_name: Lcom/example/TargetClass;
method_idx: 42
code_off: 0x00001a40
insns_size: 128
bytecode: 12 00 1a 00 6e 20 32 00 0c 00 ...

第三步:修改 DEX 文件

使用十六进制编辑器(如 010 Editor 或 HxD)打开 DEX 文件,定位到 code_off 指示的偏移位置,将新的字节码数据写入:

DEX 文件偏移 0x1a40:
原始数据: 0e 00(return-void,空方法壳)
修复后:   12 00 1a 00 6e 20 32 00 ...(完整字节码)

第四步:修复 DEX 校验和

DEX 文件头中有两个字段需要更新:

  • SHA-1 签名(偏移 12-31):对 DEX 文件从偏移 32 到文件末尾计算 SHA-1
  • Adler-32 校验和(偏移 8-11):对 DEX 文件从偏移 12 到文件末尾计算 Adler-32
import hashlib
import zlib

def fix_dex_checksum(dex_path):
    with open(dex_path, 'rb') as f:
        data = bytearray(f.read())
    
    # 清除旧的签名校验
    for i in range(32, len(data)):
        pass  # SHA-1 计算范围:32 到末尾
    
    # 计算新的 SHA-1 签名
    sha1 = hashlib.sha1(bytes(data[32:])).digest()
    data[12:32] = sha1
    
    # 计算新的 Adler-32 校验和
    checksum = zlib.adler32(bytes(data[12:])) & 0xFFFFFFFF
    data[8:12] = checksum.to_bytes(4, 'little')
    
    with open(dex_path, 'wb') as f:
        f.write(data)

FART 在 VMP 分析中的应用

VMP 程序的特征识别

VMP(Virtual Machine Protection)保护的程序具有以下典型特征:

  1. 方法体巨大:被 VMP 保护的方法字节码远大于正常方法,因为包含了自定义 VM 的解释器
  2. 大量 switch-case:反编译后可以看到大型的 switch 分支结构(VM 分发器)
  3. 寄存器使用异常:VMP 保护的方法使用大量寄存器进行 VM 状态管理
  4. 字符串加密:代码中的字符串被加密,运行时由 VM 解密
  5. native 方法增多:部分 VMP 实现将 VM 解释器放在 native 层

通过 FART 的 dump 数据,可以快速识别 VMP 保护的类和方法——凡是字节码异常庞大、结构不符合常规 Dalvik 指令模式的方法,大概率是 VMP 保护的方法。

通过 dump 定位 VMP Handler

FART dump 的方法级数据对于分析 VMP 特别有价值。分析流程如下:

  1. 筛选可疑方法:从 FART dump 的方法列表中,筛选 insns_size 异常大的方法
  2. 提取字节码:将可疑方法的字节码提取到独立文件
  3. 模式匹配:分析字节码中的指令模式,识别 VMP 的分发循环(dispatch loop)
  4. 定位 handler 表:在分发循环中找到 VM 操作码与处理函数的映射关系
# 从 FART dump 文件中筛选大方法
def find_large_methods(dump_file, threshold=500):
    """筛选指令数超过阈值的方法"""
    with open(dump_file, 'r') as f:
        content = f.read()
    
    methods = content.split('---')
    large_methods = []
    for method in methods:
        lines = method.strip().split('\n')
        for line in lines:
            if line.startswith('insns_size:'):
                size = int(line.split(':')[1].strip())
                if size > threshold:
                    large_methods.append((method.split('\n')[0], size))
    return large_methods

从 FART dump 数据还原 VMP 保护逻辑

VMP 还原是一个系统性的逆向分析过程,FART 的 dump 数据提供了关键的原始材料:

第一步:理解 VM 架构

通过分析 VMP 分发器的字节码,确定以下信息:

  • VM 使用多少个虚拟寄存器
  • VM 的操作码格式(定长还是变长)
  • VM 的 PC(程序计数器)如何管理
  • VM 有多少个操作码(handler 数量)

第二步:提取操作码定义

从 dump 的字节码中提取每个操作码对应的处理逻辑:

# 分析 VMP 操作码分布
def analyze_vm_opcodes(bytecode):
    """分析字节码中的操作码使用频率"""
    opcodes = {}
    for insn in bytecode:
        opcode = insn & 0xFF  # 假设操作码在低 8 位
        opcodes[opcode] = opcodes.get(opcode, 0) + 1
    return opcodes

第三步:语义还原

根据操作码的定义和参数,还原每个操作码的语义含义。将自定义 VM 的指令逐条翻译回等效的 Dalvik 字节码或高级语言伪代码。

第四步:重建原始逻辑

将还原后的 Dalvik 字节码重新组装成可分析的方法,替换原来的 VMP 保护代码。这一步通常需要手动完成,因为自动化还原的准确性有限。

常见修复问题与解决方案

修复后 DEX 仍然报错

如果修复后的 DEX 文件使用 dexdump 或 jadx 仍然报错,可能的原因和解决方案:

  1. code_item 大小不匹配:回填的字节码长度与原始 code_item 空间不一致,导致后续数据错位。需要重新分配 code_item 空间。
  2. try/catch 信息丢失:部分壳在抽取方法体时也修改了异常处理表。需要手动恢复或移除无效的 try/catch 信息。
  3. 寄存器数量不正确:回填字节码后,registers_sizeins_sizeouts_size 可能需要调整以匹配新的字节码。

FART dump 数据不完整

如果 FART 只 dump 了部分类的数据,可能的原因:

  1. 壳的多阶段加载:壳程序分多个阶段加载 DEX,FART 只捕获了第一阶段的 dump。解决方案:让应用运行更长时间,触发更多类加载。
  2. 延迟加载:某些类只在特定用户操作后才加载。解决方案:遍历应用的各个功能页面,触发更多的类加载。
  3. 反 dump 检测:壳程序检测到了 dump 操作并终止了加载。解决方案:分析壳的反 dump 逻辑并绕过。

总结

FART 脱壳后的 DEX 修复工作是整个脱壳流程中不可或缺的一环。dex_fix 工具和 code_item 回填技术能够有效解决 DEX 抽取壳导致的方法体缺失问题。对于 VMP 保护的程序,FART 的方法级 dump 数据为 VM 指令集分析和逻辑还原始提供了宝贵的原始材料。掌握 DEX 文件格式、code_item 结构和校验和修复技术,是成功完成脱壳后修复工作的关键。