文章总结: 本文深入解析FART自动化脱壳框架,详细阐述选择Execute作为脱壳点的技术原理。文章指出函数抽取壳禁用dex2oat后必经解释模式入口Execute,通过在Execute中监控类初始化并dump内存Dex文件,结合主动调用组件修复函数体,实现对抽取壳的有效对抗及加固代码的完整还原。 综合评分: 92 文章分类: 逆向分析,移动安全,安全工具
FART 中的脱壳点
其中 FART 脱壳组件 选择 Execute 作为脱壳点,它是 Interpreter 模式执行所有 Java 方法的统一入口,能够稳定截获和提取所有解释执行的真实方法,从而达到通用脱壳的目的。
ART 下函数在运行时可能是解释执行(Interpreter 模式)或编译执行(Quick 模式)。
为何选择 Execute 作为脱壳点?这就需要先搞懂 dex2oat 编译与 ART 函数调用流程。
dex2oat 编译流程
dex2oat 编译流程入口函数:
int main(int argc, char** argv) {
int result = static_cast<int>(art::Dex2oat(argc, argv));
// Everything was done, do an explicit exit here to avoid running Runtime destructors that take
// time (bug 10645725) unless we're a debug or instrumented build or running on a memory tool.
// Note: The Dex2Oat class should not destruct the runtime in this case.
if (!art::kIsDebugBuild && !art::kIsPGOInstrumentation && !art::kRunningOnMemoryTool) {
_exit(result);
}
return result;
}
https://cs.android.com/android/platform/superproject/+/android10-release:art/dex2oat/dex2oat.cc;l=3027
apk 安装时进行的 dex2oat 编译流程,dex2oat 的编译驱动会对函数逐个编译
https://cs.android.com/android/platform/superproject/+/android10-release:art/dex2oat/driver/compiler_driver.cc;l=2559
compile_fn 是一个回调函数,用于处理每个方法的编译过程(通常是 JIT/AOT 编译器提供的函数指针或 Lambda)。
https://cs.android.com/android/platform/superproject/+/android10-release:art/dex2oat/driver/compiler_driver.cc;l=2632
在 dex2oat 编译流程中,compile_fn 是 CompileMethodQuick 函数,它是 Quick 编译器(AOT) 编译每个方法的核心入口。
// 快速编译指定方法的包装函数,用于传入 CompileDexFile 进行批量编译。
static void CompileMethodQuick(
Thread* self,
CompilerDriver* driver,
const dex::CodeItem* code_item,
uint32_t access_flags,
InvokeType invoke_type,
uint16_t class_def_idx,
uint32_t method_idx,
Handle<mirror::ClassLoader> class_loader,
const DexFile& dex_file,
optimizer::DexToDexCompiler::CompilationLevel dex_to_dex_compilation_level,
Handle<mirror::DexCache> dex_cache) {
// 实际执行编译的 lambda 函数,传给 CompileMethodHarness 执行
auto quick_fn = [](
Thread* self,
CompilerDriver* driver,
const dex::CodeItem* code_item,
uint32_t access_flags,
InvokeType invoke_type,
uint16_t class_def_idx,
uint32_t method_idx,
Handle<mirror::ClassLoader> class_loader,
const DexFile& dex_file,
optimizer::DexToDexCompiler::CompilationLevel dex_to_dex_compilation_level,
Handle<mirror::DexCache> dex_cache) {
DCHECK(driver != nullptr);
CompiledMethod* compiled_method = nullptr;
MethodReference method_ref(&dex_file, method_idx); // 方法引用(用于 profile 与验证等)
// 如果是 native 方法
if ((access_flags & kAccNative) != 0) {
// 如果禁用了 JNI 编译但目标平台支持通用 stub,则跳过生成 stub,使用默认实现
if (!driver->GetCompilerOptions().IsJniCompilationEnabled() &&
InstructionSetHasGenericJniStub(driver->GetCompilerOptions().GetInstructionSet())) {
// 什么也不做,走 generic jni stub
} else {
// 读取方法上的 @FastNative 或 @CriticalNative 注解(优化调用约定)
access_flags |= annotations::GetNativeMethodAnnotationAccessFlags(
dex_file, dex_file.GetClassDef(class_def_idx), method_idx);
// 使用编译器生成 JNI stub(桥接 Java 和 native 函数的中间代码)
compiled_method = driver->GetCompiler()->JniCompile(
access_flags, method_idx, dex_file, dex_cache);
CHECK(compiled_method != nullptr); // 确保 JNI 编译成功
}
// 如果是 abstract 方法,无需编译(没有实现体)
} else if ((access_flags & kAccAbstract) != 0) {
// Do nothing
// 普通 Java 方法
} else {
const VerificationResults* results = driver->GetCompilerOptions().GetVerificationResults();
DCHECK(results != nullptr);
const VerifiedMethod* verified_method = results->GetVerifiedMethod(method_ref);
// 判断该方法是否应该被编译
bool compile =
results->IsCandidateForCompilation(method_ref, access_flags) &&
verified_method != nullptr &&
!verified_method->HasRuntimeThrow() && // 验证阶段没有失败
(verified_method->GetEncounteredVerificationFailures() &
(verifier::VERIFY_ERROR_FORCE_INTERPRETER | verifier::VERIFY_ERROR_LOCKING)) == 0 &&
driver->ShouldCompileBasedOnProfile(method_ref); // 在 profile 中标记为热点
if (compile) {
// 编译方法(返回 CompiledMethod 对象)
compiled_method = driver->GetCompiler()->Compile(code_item,
access_flags,
invoke_type,
class_def_idx,
method_idx,
class_loader,
dex_file,
dex_cache);
// 根据设置校验 profile 方法是否一定要被编译成功
ProfileMethodsCheck check_type =
driver->GetCompilerOptions().CheckProfiledMethodsCompiled();
if (UNLIKELY(check_type != ProfileMethodsCheck::kNone)) {
bool violation = driver->ShouldCompileBasedOnProfile(method_ref) &&
(compiled_method == nullptr);
if (violation) {
std::ostringstream oss;
oss << "Failed to compile "
<< method_ref.dex_file->PrettyMethod(method_ref.index)
<< "[" << method_ref.dex_file->GetLocation() << "]"
<< " as expected by profile";
switch (check_type) {
case ProfileMethodsCheck::kNone:
break;
case ProfileMethodsCheck::kLog:
LOG(ERROR) << oss.str(); // 仅记录错误日志
break;
case ProfileMethodsCheck::kAbort:
LOG(FATAL_WITHOUT_ABORT) << oss.str(); // 直接终止程序
_exit(1);
}
}
}
}
// 如果 Quick 编译失败,且允许 Dex-to-Dex 编译,则走 D2D 优化路径
if (compiled_method == nullptr &&
dex_to_dex_compilation_level !=
optimizer::DexToDexCompiler::CompilationLevel::kDontDexToDexCompile) {
DCHECK(!Runtime::Current()->UseJitCompilation()); // AOT 模式
driver->GetDexToDexCompiler().MarkForCompilation(self, method_ref); // 标记用于 D2D
}
}
return compiled_method; // 返回最终生成的 CompiledMethod 对象或 nullptr
};
// 使用通用包装器调用 lambda,用于处理计时、线程控制、异常处理等
CompileMethodHarness(self,
driver,
code_item,
access_flags,
invoke_type,
class_def_idx,
method_idx,
class_loader,
dex_file,
dex_to_dex_compilation_level,
dex_cache,
quick_fn);
}
https://cs.android.com/android/platform/superproject/+/android10-release:art/dex2oat/driver/compiler_driver.cc;l=2671
https://cs.android.com/android/platform/superproject/+/android10-release:art/dex2oat/driver/compiler_driver.cc;l=527
并不是所有函数都会被编译!
比如类的初始化函数
ART 下函数执行模式
ART 下函数执行模式:
- • Interpreter 模式:使用 ART 自带的解释器逐条解释执行 DEX 字节码
- • Quick 模式:直接运行 DEX 字节码 通过 dex2oat 编译后的 平台相关的机器码(如 ARM64 指令)
调用 ArtMethod::Invoke 执行一个 Java 方法,执行流程大概如下:
ArtMethod::Invoke(...)
├─ 判断是否需要解释执行(Interpreter 模式)
│ └─ 是:调用 EnterInterpreterFromInvoke(...)
│ └─ 构造 shadow frame(解释器需要的栈帧)
│ └─ 如果是非 native 方法:调用 Execute(...) 开始解释执行
│ └─ 如果是 native 方法:走 InterpreterJni(...)
└─ 否:调用快速入口点 art_quick_invoke_stub 或 art_quick_invoke_static_stub
Interpreter 模式流程
从 ArtMethod 类中的 Invoke 方法开始
https://cs.android.com/android/platform/superproject/+/android10-release:art/runtime/interpreter/interpreter.cc;l=399
Interpreter 模式下,Java 函数最终都会走到 Execute。
总体调用流程
ArtMethod::Invoke
↓
interpreter::EnterInterpreterFromInvoke
↓
interpreter::Execute ← 解释器执行核心
↓
[use_mterp ?] → ExecuteMterpImpl
ExecuteSwitchImpl
可以看到,对于任何一个运行在 interpreter 模式的 java 函数来说,最终都会进入到 ART 下的解释器中进行解释执行。
Execute
Execute 函数负责在 ART 虚拟机中根据当前执行环境选择合适的解释器(如 Mterp 或 Switch)执行指定的 Java 方法字节码。
static inline JValue Execute(
Thread* self,
const CodeItemDataAccessor& accessor,
ShadowFrame& shadow_frame,
JValue result_register,
bool stay_in_interpreter = false,
bool from_deoptimize = false) REQUIRES_SHARED(Locks::mutator_lock_) {
// 方法不能是 abstract 或 native,因为解释器无法执行它们
DCHECK(!shadow_frame.GetMethod()->IsAbstract());
DCHECK(!shadow_frame.GetMethod()->IsNative());
// 检查当前线程是否使用了正确类型的解释器(比如 mterp)
if (kIsDebugBuild && self->UseMterp() != CanUseMterp()) {
MutexLock tll_mu(self, *Locks::thread_list_lock_);
DCHECK_EQ(self->UseMterp(), CanUseMterp());
}
// 如果不是从 deoptimization 进入(正常调用路径)
if (LIKELY(!from_deoptimize)) {
if (kIsDebugBuild) {
// 新进入方法,DexPC 应为 0,且不能有异常待处理
CHECK_EQ(shadow_frame.GetDexPC(), 0u);
self->AssertNoPendingException();
}
// 获取当前运行时的 instrumentation 组件(用于调试/监控方法调用)
instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
ArtMethod* method = shadow_frame.GetMethod();
// 如果注册了方法进入监听器,则调用监听逻辑
if (UNLIKELY(instrumentation->HasMethodEntryListeners())) {
instrumentation->MethodEnterEvent(self,
shadow_frame.GetThisObject(accessor.InsSize()),
method,
0);
// 如果 instrumentation 指定需要强制退出该帧
if (UNLIKELY(shadow_frame.GetForcePopFrame())) {
DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
DCHECK(PrevFrameWillRetry(self, shadow_frame));
return JValue(); // 不执行,直接返回
}
// 如果 instrumentation 导致了异常,也直接返回
if (UNLIKELY(self->IsExceptionPending())) {
instrumentation->MethodUnwindEvent(self,
shadow_frame.GetThisObject(accessor.InsSize()),
method,
0);
return JValue();
}
}
// 如果允许切换到 JIT 编译执行(非强制 stay_in_interpreter 且非强制解释器)
if (!stay_in_interpreter && !self->IsForceInterpreter()) {
jit::Jit* jit = Runtime::Current()->GetJit();
if (jit != nullptr) {
// 通知 JIT 方法已进入
jit->MethodEntered(self, method);
// 如果该方法已经被编译过了,则可以直接调用机器码
if (jit->CanInvokeCompiledCode(method)) {
JValue result;
// 先弹出 ShadowFrame
self->PopShadowFrame();
// 计算参数偏移量(输入参数寄存器在高位)
uint16_t arg_offset = accessor.RegistersSize() - accessor.InsSize();
// 通过桥接方法跳转到已编译代码执行
ArtInterpreterToCompiledCodeBridge(self, nullptr, &shadow_frame, arg_offset, &result);
// 执行完成后重新压回 ShadowFrame
self->PushShadowFrame(&shadow_frame);
return result;
}
}
}
}
// 获取当前方法
ArtMethod* method = shadow_frame.GetMethod();
// 验证方法静态状态
DCheckStaticState(self, method);
// 如果启用了访问检查,则必须关闭锁计数器检查
DCHECK(!method->SkipAccessChecks() || !method->MustCountLocks());
bool transaction_active = Runtime::Current()->IsActiveTransaction();
// 判断是否跳过访问权限检查
if (LIKELY(method->SkipAccessChecks())) {
// === 进入 "无需访问检查" 模式 ===
if (kInterpreterImplKind == kMterpImplKind) {
// 解释器是 mterp
if (transaction_active) {
// mterp 不支持事务,回退到 switch 模式
return ExecuteSwitchImpl<false, true>(self, accessor, shadow_frame, result_register, false);
} else if (UNLIKELY(!Runtime::Current()->IsStarted())) {
// Runtime 尚未启动,mterp 不可用,回退 switch 模式
return ExecuteSwitchImpl<false, false>(self, accessor, shadow_frame, result_register, false);
} else {
// 可使用 mterp
while (true) {
// mterp 不支持调试/断点等,所以如果当前线程不允许用 mterp,就退回 switch
if (!self->UseMterp()) {
return ExecuteSwitchImpl<false, false>(self, accessor, shadow_frame, result_register, false);
}
// 调用 mterp 执行指令
bool returned = ExecuteMterpImpl(self,
accessor.Insns(), // 获取指令序列
&shadow_frame,
&result_register);
if (returned) {
// mterp 执行成功(正常返回)
return result_register;
} else {
// mterp 无法处理该指令,改用 switch 解释器单步执行
result_register = ExecuteSwitchImpl<false, false>(self, accessor, shadow_frame, result_register, true);
if (shadow_frame.GetDexPC() == dex::kDexNoIndex) {
// 已执行 return 或发生未捕获异常,直接返回
return result_register;
}
}
}
}
} else {
// 当前解释器类型是 switch
DCHECK_EQ(kInterpreterImplKind, kSwitchImplKind);
if (transaction_active) {
return ExecuteSwitchImpl<false, true>(self, accessor, shadow_frame, result_register, false);
} else {
return ExecuteSwitchImpl<false, false>(self, accessor, shadow_frame, result_register, false);
}
}
} else {
// === 进入 "需要访问检查" 模式 ===
// 启动路径不应该运行到这里,除非是软验证失败或者在 AOT 编译中
DCHECK(method->GetDeclaringClass()->GetClassLoader() != nullptr
|| Runtime::Current()->IsVerificationSoftFail()
|| Runtime::Current()->IsAotCompiler())
<< method->PrettyMethod();
if (kInterpreterImplKind == kMterpImplKind) {
// mterp 不支持访问检查,强制使用 switch 模式
if (transaction_active) {
return ExecuteSwitchImpl<true, true>(self, accessor, shadow_frame, result_register, false);
} else {
return ExecuteSwitchImpl<true, false>(self, accessor, shadow_frame, result_register, false);
}
} else {
// switch 模式解释器分支
DCHECK_EQ(kInterpreterImplKind, kSwitchImplKind);
if (transaction_active) {
return ExecuteSwitchImpl<true, true>(self, accessor, shadow_frame, result_register, false);
} else {
return ExecuteSwitchImpl<true, false>(self, accessor, shadow_frame, result_register, false);
}
}
}
}
https://cs.android.com/android/platform/superproject/+/android10-release:art/runtime/interpreter/interpreter.cc;l=247
ExecuteMterpImpl 和 ExecuteSwitchImpl
ExecuteMterpImpl 和 ExecuteSwitchImpl 是 ART 虚拟机中两种解释器的执行实现方式,它们的主要区别在于 执行效率、可调试性 和 支持的功能。
- • ExecuteMterpImpl:使用 高性能的汇编模板代码(如 x86、arm64 手写汇编)执行字节码,效率非常高。
- • ExecuteSwitchImpl:基于 C++ 的 switch-case 控制流,每条字节码指令有一个 case 分支。
它们是如何被选择执行的? 在 Execute 函数中:
if (kInterpreterImplKind == kMterpImplKind) {
if (transaction_active) {
return ExecuteSwitchImpl<...>(); // Mterp 不支持事务,退回 Switch
} else if (!Runtime::Current()->IsStarted()) {
return ExecuteSwitchImpl<...>(); // VM 没启动也不能用 Mterp
} else {
while (true) {
if (!self->UseMterp()) {
return ExecuteSwitchImpl<...>(); // 当前线程禁用了 Mterp
}
bool returned = ExecuteMterpImpl(...);
if (returned) {
return result_register; // Mterp 成功执行完成
} else {
// Mterp 遇到不支持的指令或状态,单步回退到 Switch
result_register = ExecuteSwitchImpl(...);
if (shadow_frame.GetDexPC() == dex::kDexNoIndex) {
return result_register; // 已返回或异常抛出
}
}
}
}
}
FART 如何实现脱壳
对于函数抽取壳的 dex 来说,因为需要禁用 dex2oat ,所以都会以解释模式运行,进入到 Execute 函数里面。
Execute 参数中有 ShadowFrame,能拿到 ArtMethod,再通过 GetDexFile() 函数获得 DEX 字节码等信息。
在 dumpDexFileByExecute 中判断 如果 dex 文件不存在就 dump
// 该函数在 ART 执行期间调用,用于在 Execute 函数内完成 dex 脱壳
extern "C" void dumpDexFileByExecute(ArtMethod *artmethod)
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {
// 为 dex 文件保存路径分配内存
char *dexfilepath = (char *) malloc(sizeof(char) * 2000);
if (dexfilepath == nullptr) {
// 分配失败,打印日志并返回
LOG(INFO) << "ArtMethod::dumpDexFileByExecute, methodname: "
<< PrettyMethod(artmethod).c_str()
<< " malloc 2000 byte failed";
return;
}
// 获取当前进程的 cmdline 名称,用于后续命名脱壳文件
int fcmdline = -1;
char szCmdline[64] = { 0 };
char szProcName[256] = { 0 };
int procid = getpid();
sprintf(szCmdline, "/proc/%d/cmdline", procid);
fcmdline = open(szCmdline, O_RDONLY, 0644);
if (fcmdline > 0) {
read(fcmdline, szProcName, 256);
close(fcmdline);
}
// 若成功获取到进程名
if (szProcName[0]) {
// 获取当前 ArtMethod 所属 dex 文件及其起始地址和大小
const DexFile *dex_file = artmethod->GetDexFile();
const uint8_t *begin_ = dex_file->Begin(); // dex 文件起始地址
size_t size_ = dex_file->Size(); // dex 文件大小
int size_int_ = (int) size_; // 用于命名文件
// 创建保存路径:/sdcard/fart/<process_name>/
memset(dexfilepath, 0, 2000);
sprintf(dexfilepath, "/sdcard/fart");
mkdir(dexfilepath, 0777); // 创建 fart 目录
memset(dexfilepath, 0, 2000);
sprintf(dexfilepath, "/sdcard/fart/%s", szProcName);
mkdir(dexfilepath, 0777); // 创建子目录为进程名
// 拼接最终保存路径,如:/sdcard/fart/com.xxx.xxx/123456_dexfile_execute.dex
memset(dexfilepath, 0, 2000);
sprintf(dexfilepath,
"/sdcard/fart/%s/%d_dexfile_execute.dex",
szProcName, size_int_);
// 检查该文件是否已经存在,若存在则跳过写入
int dexfilefp = open(dexfilepath, O_RDONLY, 0666); // 以只读方式尝试打开指定路径的 dex 文件
if (dexfilefp > 0) {
close(dexfilefp); // 已存在,关闭文件
dexfilefp = 0;
} else {
// 不存在则创建并写入 dex 内容
dexfilefp = open(dexfilepath, O_CREAT | O_RDWR, 0666);
if (dexfilefp > 0) {
write(dexfilefp, (void *) begin_, size_);
fsync(dexfilefp); // 刷新到磁盘
close(dexfilefp); // 关闭文件
}
}
}
// 释放申请的内存
if (dexfilepath != nullptr) {
free(dexfilepath);
dexfilepath = nullptr;
}
}
路径:art/runtime/art_method.cc
这时候已经可以把 Dex 整体 dump 下来了,但是还没有把抽空的函数修复,这个就需要 FART 中的主动调用组件来解决了。
相关文章:
- • FART 主动调用组件深度解析:破解 ART 下函数抽取壳的终极武器[3]
- • 拨云见日:安卓APP脱壳的本质以及如何快速发现ART下的脱壳点[4]
引用链接
[1] 打造基于 ART 的 Android 函数抽取壳:原理剖析与完整源码实战: https://cyrus-studio.github.io/blog/posts/%E6%89%93%E9%80%A0%E5%9F%BA%E4%BA%8E-art-%E7%9A%84-android-%E5%87%BD%E6%95%B0%E6%8A%BD%E5%8F%96%E5%A3%B3%E5%8E%9F%E7%90%86%E5%89%96%E6%9E%90%E4%B8%8E%E5%AE%8C%E6%95%B4%E6%BA%90%E7%A0%81%E5%AE%9E%E6%88%98/
[2] 深入解析 dex2oat:vdex、cdex、dex 格式转换全流程实战: https://cyrus-studio.github.io/blog/posts/%E6%B7%B1%E5%85%A5%E8%A7%A3%E6%9E%90-dex2oatvdexcdexdex-%E6%A0%BC%E5%BC%8F%E8%BD%AC%E6%8D%A2%E5%85%A8%E6%B5%81%E7%A8%8B%E5%AE%9E%E6%88%98/
[3] FART 主动调用组件深度解析:破解 ART 下函数抽取壳的终极武器: https://cyrus-studio.github.io/blog/posts/fart-%E4%B8%BB%E5%8A%A8%E8%B0%83%E7%94%A8%E7%BB%84%E4%BB%B6%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90%E7%A0%B4%E8%A7%A3-art-%E4%B8%8B%E5%87%BD%E6%95%B0%E6%8A%BD%E5%8F%96%E5%A3%B3%E7%9A%84%E7%BB%88%E6%9E%81%E6%AD%A6%E5%99%A8/
[4] 拨云见日:安卓APP脱壳的本质以及如何快速发现ART下的脱壳点: https://bbs.kanxue.com/thread-254555.htm
推荐阅读
FART脱壳:实现AJM壳级别的对抗功能+绕过全解析
Android基于ART环境主动调用/FART/通用自动化脱壳系统
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:哆啦安全 《干掉抽取壳!FART自动化脱壳框架与Execute脱壳点解析》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论