FART 框架介绍,选择脱壳点
FART 框架概述
FART(First Android Unpack Tool,Android 第一款 ART 环境自动化脱壳机)是由安全研究员星痕(hanbinglengyue)开发的开源 Android 脱壳框架。FART 的出现填补了 Android 5.0(ART 运行时)环境下自动化脱壳工具的空白,在逆向工程社区中具有里程碑意义。
在 FART 之前,大多数脱壳工作依赖于手动操作——逆向分析人员需要通过调试器找到壳程序的解密逻辑,手动在内存中搜索 DEX 数据并 dump。这种方式对分析者的技术水平要求高,且效率低下。FART 的核心理念是自动化:通过修改 Android 系统源码中的关键函数,在 DEX 加载的关键节点自动 dump 内存中的 DEX 数据,实现"一键脱壳"。
FART 的源码托管在 GitHub(https://github.com/hanbinglengyue/FART),基于 Android 6.0 和 7.0 的 AOSP 源码进行了修改。FART 脱壳后的数据输出到 /sdcard/fart 目录,包括完整的 DEX 文件和每个类的方法字节码 dump 数据。
FART 的设计目标和技术路线
设计目标
FART 的设计目标可以概括为以下三点:
- 全自动化脱壳:不需要手动分析壳程序的解密逻辑,在系统层面自动完成 DEX 的 dump
- 广泛兼容性:支持 DEX 整体加密壳和 DEX 抽取壳两种主流壳类型
- 完整数据输出:不仅 dump 完整的 DEX 文件,还 dump 每个类的方法字节码,用于修复抽取壳的空方法体
技术路线
FART 采用了**修改 Android 系统源码(AOSP)**的技术路线,而不是基于 Frida 等 hook 框架。这个选择有其深刻的原因:
- 时机精确:直接在系统源码中修改,可以在 DEX 加载的最关键节点执行 dump 逻辑,时序上无延迟
- 权限完整:系统级代码拥有最高权限,可以直接访问所有内存区域,不受进程权限限制
- 稳定性高:不依赖外部 hook 框架的稳定性,避免了 Frida 检测和反调试问题
- 兼容性强:在类链接阶段 dump DEX,无论壳程序使用何种解密方式,只要壳完成了 DEX 的加载,FART 就能捕获到
FART 的整体技术架构:
修改 AOSP 源码
↓
编译自定义 ROM(刷入手机)
↓
修改 ClassLinker::LinkClass(添加 dump 逻辑)
↓
修改 DexFile::Open(添加完整 DEX dump 逻辑)
↓
应用运行时自动 dump
↓
输出到 /sdcard/fart/
├── *.dex(完整 DEX 文件)
└── *.txt(每个类的方法字节码 dump)
脱壳点选择的三大原则
FART 选择脱壳点时遵循三个核心原则,这些原则也适用于其他脱壳方案的脱壳点选择:
完整性原则
脱壳点处的 DEX 数据必须包含尽可能完整的类信息和方法体。完整性是脱壳工作的首要目标——dump 出来的 DEX 如果缺少类定义或方法字节码,对于逆向分析来说价值就大打折扣。
对于 DEX 整体加密壳,壳程序在 Application.attachBaseContext() 中完成 DEX 解密后,后续的 ClassLoader 加载过程中 DEX 数据就是完整的。因此,任何在解密完成之后的加载阶段都可以获取完整 DEX。
对于 DEX 抽取壳,情况更复杂。抽取壳将方法字节码从 DEX 中移除,在运行时动态回填。因此,脱壳点必须位于方法字节码回填完成之后,否则 dump 的 DEX 中方法体将是空的。
时效性原则
脱壳点必须在壳程序完成解密操作之后。如果脱壳点过早,DEX 数据仍然是加密状态,dump 出来的数据无法使用。
判断时机的关键指标:
- 壳程序的
attachBaseContext()方法执行完毕(DEX 解密通常在此阶段完成) DexClassLoader已经被创建并开始加载类- 类的
defineClass/LoadClass已经被调用
FART 选择在 ClassLinker::LinkClass 阶段进行 dump,确保此时壳程序已经完成了所有解密和加载准备工作。
通用性原则
脱壳点应尽可能适用于不同类型的壳和不同版本的 Android 系统。过于依赖特定壳程序的内部实现细节的脱壳点,其通用性较差。
例如,如果选择 hook 壳程序的特定解密函数来 dump DEX,这种方法只对这一种壳有效。而选择在通用的类加载路径上 hook,则可以应对多种不同壳类型。
FART 选择 ClassLinker::LinkClass 作为脱壳点,正是因为这个函数是 ART 虚拟机类加载的必经之路,任何壳程序都无法绕过。
FART 在 ART 中的脱壳点实现
ClassLinker::LinkClass Hook
FART 的核心脱壳点位于 ClassLinker::LinkClass 函数。这个函数在 ART 虚拟机的类加载流程中负责将一个已经定义(LoadClass 完成)的类进行链接操作。
FART 的修改逻辑如下:
// art/runtime/class_linker.cc(FART 修改后的代码)
bool ClassLinker::LinkClass(Thread* self,
Handle<mirror::Class> klass,
Handle<mirror::ObjectArray<mirror::Class>> interfaces,
Handle<mirror::ClassLoader> class_loader) {
// ... 原始 LinkClass 逻辑 ...
// ========== FART 注入的 dump 逻辑 ==========
// 获取该类所属的 DexFile
const DexFile& dex_file = klass->GetDexFile();
// dump 该类的所有方法字节码
for (uint32_t i = 0; i < klass->NumDirectMethods(); i++) {
ArtMethod* method = klass->GetDirectMethodUnchecked(i, kPointerSize);
dumpDexMethod(method, dex_file);
}
for (uint32_t i = 0; i < klass->NumVirtualMethods(); i++) {
ArtMethod* method = klass->GetVirtualMethodUnchecked(i, kPointerSize);
dumpDexMethod(method, dex_file);
}
// ========================================
}
DexFile::Open Hook
除了 LinkClass 中的方法级 dump,FART 还在 DexFile::Open 函数中添加了完整 DEX 文件的 dump 逻辑:
// art/runtime/dex_file.cc(FART 修改后的代码)
bool DexFile::Open(const uint8_t* base, size_t size,
const std::string& location, uint32_t location_checksum,
const OatDexFile* oat_dex_file, std::string* error_msg) {
// ... 原始 Open 逻辑 ...
// ========== FART 注入的 dump 逻辑 ==========
// 将完整的 DEX 数据 dump 到文件
dumpDexFile(base, size, location);
// ========================================
}
dumpDexMethod 的实现细节
dumpDexMethod 是 FART 的核心函数之一,负责 dump 单个方法的字节码信息:
void dumpDexMethod(ArtMethod* method, const DexFile& dex_file) {
// 获取方法的 CodeItem 结构(包含字节码)
const DexFile::CodeItem* code_item = method->GetCodeItem();
if (code_item == nullptr) {
return; // 抽象方法或 native 方法,无字节码
}
// 获取方法的 DEX 文件偏移
uint32_t method_dex_idx = method->GetDexMethodIndex();
// 获取方法名
const std::string method_name = method->GetName();
// 获取 CodeItem 的字节码大小
uint32_t insns_size = code_item->insns_size_in_code_units_;
// 将字节码数据写入输出文件
char output_path[256];
snprintf(output_path, sizeof(output_path),
"/sdcard/fart/%s_%s.txt",
dex_file.GetLocation().c_str(),
method_name.c_str());
FILE* fp = fopen(output_path, "ab");
if (fp != nullptr) {
// 写入方法索引、CodeItem 偏移、字节码大小
fprintf(fp, "method_idx: %d, code_off: 0x%x, insns_size: %d\n",
method_dex_idx,
method->GetCodeItemOffset(),
insns_size);
// 写入实际的字节码
const uint16_t* insns = code_item->insns_;
for (uint32_t i = 0; i < insns_size; i++) {
fprintf(fp, "%04x ", insns[i]);
}
fprintf(fp, "\n");
fclose(fp);
}
}
Frida 版 FART 的脱壳点实现差异
虽然 FART 原版是通过修改 AOSP 源码实现的,但社区也开发出了基于 Frida 的 FART 实现(通常称为 Frida-FART 或 frida-fart)。两者在脱壳点选择上存在一些差异:
AOSP 版 FART
- 实现方式:直接修改
libart.so的 C++ 源码,重新编译系统 - 脱壳点:
ClassLinker::LinkClass内部直接插入 dump 逻辑 - 时机:在类链接过程中同步执行 dump
- 优势:稳定、无检测风险、时机精确
- 劣势:需要刷机、版本固定、部署成本高
Frida 版 FART
- 实现方式:通过 Frida 的
Interceptor.attachhookClassLinker::LinkClass的符号地址 - 脱壳点:通过符号名查找
LinkClass函数地址,在函数入口/出口处执行 dump - 时机:在 hook 回调中异步执行 dump
- 优势:无需刷机、灵活、可跨版本使用
- 劣势:可能被壳程序检测到 Frida、需要 root 权限
Frida 版的核心实现差异:
// Frida 版 FART 的 LinkClass hook 示例
var artModule = Process.findModuleByName("libart.so");
// 查找 LinkClass 函数
var linkClassSym = artModule.findExportByName(
"_ZN3art11ClassLinker9LinkClassEPNS_6ThreadEPNS_6HandleINS_6mirror5ClassEEEPNS3_INS_6ObjectArrayIS5_EEEPNS3_INS_6mirror11ClassLoaderEEE"
);
if (linkClassSym) {
Interceptor.attach(linkClassSym, {
onEnter: function(args) {
// arg1: mirror::Class 指针
this.klass = args[1];
},
onLeave: function(retval) {
if (retval.toInt32() === 1) { // LinkClass 返回 true
dumpClassMethods(this.klass);
}
}
});
}
脱壳点的 Android 版本兼容性分析
FART 原版基于 Android 6.0 和 7.0 开发,随着 Android 版本的不断更新,ClassLinker::LinkClass 的函数签名和内部结构也在发生变化。
Android 6.0 - 7.0(FART 原版支持)
这是 FART 原版的直接支持范围。ClassLinker::LinkClass 的函数签名和参数布局完全匹配 FART 的 hook 代码。所有 dump 功能正常工作。
Android 8.0 - 9.0
从 Android 8.0 开始,ART 进行了较大的架构调整:
ClassLinker::LinkClass的函数签名发生变化,参数数量和类型有调整ArtMethod的内部布局重新设计,GetCodeItem()的实现方式改变DexFile结构体成员位置发生变化
需要针对这些版本适配 FART 的 dump 逻辑。社区已有适配 Android 8.1 和 9.0 的 FART 修改版本。
Android 10+
Android 10 引入了更多安全机制:
- 内存访问权限更加严格
ClassLinker的内部实现大幅重构- 部分 ART 内部符号不再导出
hiddenapi限制影响了反射访问
这些变化使得 FART 的兼容性适配变得更加困难。对于 Android 10+ 的脱壳,通常建议使用 Frida 版 FART 或其他基于动态分析的脱壳方案。
各版本脱壳点选择建议
| Android 版本 | 推荐脱壳方案 | 主要挑战 |
|---|---|---|
| 5.0 - 7.1 | FART AOSP 版 | 部分壳有 Frida 检测 |
| 8.0 - 9.0 | FART AOSP 适配版 / Frida-FART | 函数签名变化 |
| 10 - 11 | Frida-FART | hiddenapi 限制、符号不导出 |
| 12+ | Frida-FART + 定制化适配 | 安全机制更强 |
总结
FART 框架通过修改 ART 虚拟机的 ClassLinker::LinkClass 函数,在类链接阶段自动 dump DEX 数据,实现了 Android ART 环境下的自动化脱壳。脱壳点选择的三大原则——完整性、时效性、通用性——指导了 FART 的设计决策。虽然 FART 原版受限于 Android 版本,但其设计理念被广泛借鉴到 Frida 版和其他脱壳工具中。理解 FART 的脱壳点选择原理,有助于在实际逆向工作中灵活选择和定制脱壳方案。