干掉抽取壳!FART自动化脱壳框架与Execute脱壳点解析

admin 2026-01-11 01:11:08 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文深入解析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) {
&nbsp; int result = static_cast<int>(art::Dex2oat(argc, argv));
&nbsp; // Everything was done, do an explicit exit here to avoid running Runtime destructors that take
&nbsp; // time (bug 10645725) unless we're a debug or instrumented build or running on a memory tool.
&nbsp; // Note: The Dex2Oat class should not destruct the runtime in this case.
&nbsp; if (!art::kIsDebugBuild && !art::kIsPGOInstrumentation && !art::kRunningOnMemoryTool) {
&nbsp; &nbsp; _exit(result);
&nbsp; }
&nbsp; 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(
&nbsp; &nbsp; Thread* self,
&nbsp; &nbsp; CompilerDriver* driver,
&nbsp; &nbsp; const dex::CodeItem* code_item,
&nbsp; &nbsp; uint32_t access_flags,
&nbsp; &nbsp; InvokeType invoke_type,
&nbsp; &nbsp; uint16_t class_def_idx,
&nbsp; &nbsp; uint32_t method_idx,
&nbsp; &nbsp; Handle<mirror::ClassLoader> class_loader,
&nbsp; &nbsp; const DexFile& dex_file,
&nbsp; &nbsp; optimizer::DexToDexCompiler::CompilationLevel dex_to_dex_compilation_level,
&nbsp; &nbsp; Handle<mirror::DexCache> dex_cache) {

&nbsp; // 实际执行编译的 lambda 函数,传给 CompileMethodHarness 执行
&nbsp; auto quick_fn = [](
&nbsp; &nbsp; &nbsp; Thread* self,
&nbsp; &nbsp; &nbsp; CompilerDriver* driver,
&nbsp; &nbsp; &nbsp; const dex::CodeItem* code_item,
&nbsp; &nbsp; &nbsp; uint32_t access_flags,
&nbsp; &nbsp; &nbsp; InvokeType invoke_type,
&nbsp; &nbsp; &nbsp; uint16_t class_def_idx,
&nbsp; &nbsp; &nbsp; uint32_t method_idx,
&nbsp; &nbsp; &nbsp; Handle<mirror::ClassLoader> class_loader,
&nbsp; &nbsp; &nbsp; const DexFile& dex_file,
&nbsp; &nbsp; &nbsp; optimizer::DexToDexCompiler::CompilationLevel dex_to_dex_compilation_level,
&nbsp; &nbsp; &nbsp; Handle<mirror::DexCache> dex_cache) {

&nbsp; &nbsp; DCHECK(driver != nullptr);
&nbsp; &nbsp; CompiledMethod* compiled_method = nullptr;
&nbsp; &nbsp; MethodReference method_ref(&dex_file, method_idx); &nbsp;// 方法引用(用于 profile 与验证等)

&nbsp; &nbsp; // 如果是 native 方法
&nbsp; &nbsp; if ((access_flags & kAccNative) != 0) {
&nbsp; &nbsp; &nbsp; // 如果禁用了 JNI 编译但目标平台支持通用 stub,则跳过生成 stub,使用默认实现
&nbsp; &nbsp; &nbsp; if (!driver->GetCompilerOptions().IsJniCompilationEnabled() &&
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; InstructionSetHasGenericJniStub(driver->GetCompilerOptions().GetInstructionSet())) {
&nbsp; &nbsp; &nbsp; &nbsp; // 什么也不做,走 generic jni stub
&nbsp; &nbsp; &nbsp; } else {
&nbsp; &nbsp; &nbsp; &nbsp; // 读取方法上的 @FastNative 或 @CriticalNative 注解(优化调用约定)
&nbsp; &nbsp; &nbsp; &nbsp; access_flags |= annotations::GetNativeMethodAnnotationAccessFlags(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dex_file, dex_file.GetClassDef(class_def_idx), method_idx);

&nbsp; &nbsp; &nbsp; &nbsp; // 使用编译器生成 JNI stub(桥接 Java 和 native 函数的中间代码)
&nbsp; &nbsp; &nbsp; &nbsp; compiled_method = driver->GetCompiler()->JniCompile(
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; access_flags, method_idx, dex_file, dex_cache);
&nbsp; &nbsp; &nbsp; &nbsp; CHECK(compiled_method != nullptr); &nbsp;// 确保 JNI 编译成功
&nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; // 如果是 abstract 方法,无需编译(没有实现体)
&nbsp; &nbsp; } else if ((access_flags & kAccAbstract) != 0) {
&nbsp; &nbsp; &nbsp; // Do nothing

&nbsp; &nbsp; // 普通 Java 方法
&nbsp; &nbsp; } else {
&nbsp; &nbsp; &nbsp; const VerificationResults* results = driver->GetCompilerOptions().GetVerificationResults();
&nbsp; &nbsp; &nbsp; DCHECK(results != nullptr);
&nbsp; &nbsp; &nbsp; const VerifiedMethod* verified_method = results->GetVerifiedMethod(method_ref);

&nbsp; &nbsp; &nbsp; // 判断该方法是否应该被编译
&nbsp; &nbsp; &nbsp; bool compile =
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; results->IsCandidateForCompilation(method_ref, access_flags) &&
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; verified_method != nullptr &&
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; !verified_method->HasRuntimeThrow() && &nbsp;// 验证阶段没有失败
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (verified_method->GetEncounteredVerificationFailures() &
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (verifier::VERIFY_ERROR_FORCE_INTERPRETER | verifier::VERIFY_ERROR_LOCKING)) == 0 &&
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; driver->ShouldCompileBasedOnProfile(method_ref); &nbsp;// 在 profile 中标记为热点

&nbsp; &nbsp; &nbsp; if (compile) {
&nbsp; &nbsp; &nbsp; &nbsp; // 编译方法(返回 CompiledMethod 对象)
&nbsp; &nbsp; &nbsp; &nbsp; compiled_method = driver->GetCompiler()->Compile(code_item,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;access_flags,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;invoke_type,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;class_def_idx,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;method_idx,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;class_loader,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;dex_file,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;dex_cache);

&nbsp; &nbsp; &nbsp; &nbsp; // 根据设置校验 profile 方法是否一定要被编译成功
&nbsp; &nbsp; &nbsp; &nbsp; ProfileMethodsCheck check_type =
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; driver->GetCompilerOptions().CheckProfiledMethodsCompiled();
&nbsp; &nbsp; &nbsp; &nbsp; if (UNLIKELY(check_type != ProfileMethodsCheck::kNone)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bool violation = driver->ShouldCompileBasedOnProfile(method_ref) &&
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;(compiled_method == nullptr);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (violation) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; std::ostringstream oss;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; oss << "Failed to compile "
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; << method_ref.dex_file->PrettyMethod(method_ref.index)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; << "[" << method_ref.dex_file->GetLocation() << "]"
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; << " as expected by profile";

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; switch (check_type) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case ProfileMethodsCheck::kNone:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case ProfileMethodsCheck::kLog:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; LOG(ERROR) << oss.str(); &nbsp;// 仅记录错误日志
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; break;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; case ProfileMethodsCheck::kAbort:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; LOG(FATAL_WITHOUT_ABORT) << oss.str(); &nbsp;// 直接终止程序
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _exit(1);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; // 如果 Quick 编译失败,且允许 Dex-to-Dex 编译,则走 D2D 优化路径
&nbsp; &nbsp; &nbsp; if (compiled_method == nullptr &&
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dex_to_dex_compilation_level !=
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; optimizer::DexToDexCompiler::CompilationLevel::kDontDexToDexCompile) {
&nbsp; &nbsp; &nbsp; &nbsp; DCHECK(!Runtime::Current()->UseJitCompilation()); &nbsp;// AOT 模式
&nbsp; &nbsp; &nbsp; &nbsp; driver->GetDexToDexCompiler().MarkForCompilation(self, method_ref); &nbsp;// 标记用于 D2D
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

&nbsp; &nbsp; return compiled_method; &nbsp;// 返回最终生成的 CompiledMethod 对象或 nullptr
&nbsp; };

&nbsp; // 使用通用包装器调用 lambda,用于处理计时、线程控制、异常处理等
&nbsp; CompileMethodHarness(self,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;driver,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;code_item,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;access_flags,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;invoke_type,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;class_def_idx,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;method_idx,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;class_loader,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;dex_file,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;dex_to_dex_compilation_level,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;dex_cache,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;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

并不是所有函数都会被编译!

比如类的初始化函数 。因此,对于当一个类被初始化时,该类的初始化函数始终运行在 Interpreter 模式

ART 下函数执行模式

ART 下函数执行模式:

  • • Interpreter 模式:使用 ART 自带的解释器逐条解释执行 DEX 字节码
  • • Quick 模式:直接运行 DEX 字节码 通过 dex2oat 编译后的 平台相关的机器码(如 ARM64 指令)

调用 ArtMethod::Invoke 执行一个 Java 方法,执行流程大概如下:

ArtMethod::Invoke(...)
&nbsp;├─ 判断是否需要解释执行(Interpreter 模式)
&nbsp;│ &nbsp; └─ 是:调用 EnterInterpreterFromInvoke(...)
&nbsp;│ &nbsp; &nbsp; &nbsp; &nbsp; └─ 构造 shadow frame(解释器需要的栈帧)
&nbsp;│ &nbsp; &nbsp; &nbsp; &nbsp; └─ 如果是非 native 方法:调用 Execute(...) 开始解释执行
&nbsp;│ &nbsp; &nbsp; &nbsp; &nbsp; └─ 如果是 native 方法:走 InterpreterJni(...)
&nbsp;└─ 否:调用快速入口点 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
&nbsp; &nbsp; &nbsp; &nbsp; ↓
interpreter::EnterInterpreterFromInvoke
&nbsp; &nbsp; &nbsp; &nbsp; ↓
interpreter::Execute ← 解释器执行核心
&nbsp; &nbsp; &nbsp; &nbsp; ↓
[use_mterp ?] → ExecuteMterpImpl
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;ExecuteSwitchImpl

可以看到,对于任何一个运行在 interpreter 模式的 java 函数来说,最终都会进入到 ART 下的解释器中进行解释执行。

Execute

Execute 函数负责在 ART 虚拟机中根据当前执行环境选择合适的解释器(如 Mterp 或 Switch)执行指定的 Java 方法字节码。

static inline JValue Execute(
&nbsp; &nbsp; Thread* self,
&nbsp; &nbsp; const CodeItemDataAccessor& accessor,
&nbsp; &nbsp; ShadowFrame& shadow_frame,
&nbsp; &nbsp; JValue result_register,
&nbsp; &nbsp; bool stay_in_interpreter = false,
&nbsp; &nbsp; bool from_deoptimize = false) REQUIRES_SHARED(Locks::mutator_lock_) {

&nbsp; // 方法不能是 abstract 或 native,因为解释器无法执行它们
&nbsp; DCHECK(!shadow_frame.GetMethod()->IsAbstract());
&nbsp; DCHECK(!shadow_frame.GetMethod()->IsNative());

&nbsp; // 检查当前线程是否使用了正确类型的解释器(比如 mterp)
&nbsp; if (kIsDebugBuild && self->UseMterp() != CanUseMterp()) {
&nbsp; &nbsp; MutexLock tll_mu(self, *Locks::thread_list_lock_);
&nbsp; &nbsp; DCHECK_EQ(self->UseMterp(), CanUseMterp());
&nbsp; }

&nbsp; // 如果不是从 deoptimization 进入(正常调用路径)
&nbsp; if (LIKELY(!from_deoptimize)) {
&nbsp; &nbsp; if (kIsDebugBuild) {
&nbsp; &nbsp; &nbsp; // 新进入方法,DexPC 应为 0,且不能有异常待处理
&nbsp; &nbsp; &nbsp; CHECK_EQ(shadow_frame.GetDexPC(), 0u);
&nbsp; &nbsp; &nbsp; self->AssertNoPendingException();
&nbsp; &nbsp; }

&nbsp; &nbsp; // 获取当前运行时的 instrumentation 组件(用于调试/监控方法调用)
&nbsp; &nbsp; instrumentation::Instrumentation* instrumentation = Runtime::Current()->GetInstrumentation();
&nbsp; &nbsp; ArtMethod* method = shadow_frame.GetMethod();

&nbsp; &nbsp; // 如果注册了方法进入监听器,则调用监听逻辑
&nbsp; &nbsp; if (UNLIKELY(instrumentation->HasMethodEntryListeners())) {
&nbsp; &nbsp; &nbsp; instrumentation->MethodEnterEvent(self,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; shadow_frame.GetThisObject(accessor.InsSize()),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; method,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 0);

&nbsp; &nbsp; &nbsp; // 如果 instrumentation 指定需要强制退出该帧
&nbsp; &nbsp; &nbsp; if (UNLIKELY(shadow_frame.GetForcePopFrame())) {
&nbsp; &nbsp; &nbsp; &nbsp; DCHECK(Runtime::Current()->AreNonStandardExitsEnabled());
&nbsp; &nbsp; &nbsp; &nbsp; DCHECK(PrevFrameWillRetry(self, shadow_frame));
&nbsp; &nbsp; &nbsp; &nbsp; return JValue(); // 不执行,直接返回
&nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; // 如果 instrumentation 导致了异常,也直接返回
&nbsp; &nbsp; &nbsp; if (UNLIKELY(self->IsExceptionPending())) {
&nbsp; &nbsp; &nbsp; &nbsp; instrumentation->MethodUnwindEvent(self,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;shadow_frame.GetThisObject(accessor.InsSize()),
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;method,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0);
&nbsp; &nbsp; &nbsp; &nbsp; return JValue();
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

&nbsp; &nbsp; // 如果允许切换到 JIT 编译执行(非强制 stay_in_interpreter 且非强制解释器)
&nbsp; &nbsp; if (!stay_in_interpreter && !self->IsForceInterpreter()) {
&nbsp; &nbsp; &nbsp; jit::Jit* jit = Runtime::Current()->GetJit();
&nbsp; &nbsp; &nbsp; if (jit != nullptr) {
&nbsp; &nbsp; &nbsp; &nbsp; // 通知 JIT 方法已进入
&nbsp; &nbsp; &nbsp; &nbsp; jit->MethodEntered(self, method);
&nbsp; &nbsp; &nbsp; &nbsp; // 如果该方法已经被编译过了,则可以直接调用机器码
&nbsp; &nbsp; &nbsp; &nbsp; if (jit->CanInvokeCompiledCode(method)) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; JValue result;

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 先弹出 ShadowFrame
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self->PopShadowFrame();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 计算参数偏移量(输入参数寄存器在高位)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; uint16_t arg_offset = accessor.RegistersSize() - accessor.InsSize();
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 通过桥接方法跳转到已编译代码执行
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ArtInterpreterToCompiledCodeBridge(self, nullptr, &shadow_frame, arg_offset, &result);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 执行完成后重新压回 ShadowFrame
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; self->PushShadowFrame(&shadow_frame);

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return result;
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
&nbsp; }

&nbsp; // 获取当前方法
&nbsp; ArtMethod* method = shadow_frame.GetMethod();

&nbsp; // 验证方法静态状态
&nbsp; DCheckStaticState(self, method);

&nbsp; // 如果启用了访问检查,则必须关闭锁计数器检查
&nbsp; DCHECK(!method->SkipAccessChecks() || !method->MustCountLocks());

&nbsp; bool transaction_active = Runtime::Current()->IsActiveTransaction();

&nbsp; // 判断是否跳过访问权限检查
&nbsp; if (LIKELY(method->SkipAccessChecks())) {
&nbsp; &nbsp; // === 进入 "无需访问检查" 模式 ===
&nbsp; &nbsp; if (kInterpreterImplKind == kMterpImplKind) {
&nbsp; &nbsp; &nbsp; // 解释器是 mterp
&nbsp; &nbsp; &nbsp; if (transaction_active) {
&nbsp; &nbsp; &nbsp; &nbsp; // mterp 不支持事务,回退到 switch 模式
&nbsp; &nbsp; &nbsp; &nbsp; return ExecuteSwitchImpl<false, true>(self, accessor, shadow_frame, result_register, false);
&nbsp; &nbsp; &nbsp; } else if (UNLIKELY(!Runtime::Current()->IsStarted())) {
&nbsp; &nbsp; &nbsp; &nbsp; // Runtime 尚未启动,mterp 不可用,回退 switch 模式
&nbsp; &nbsp; &nbsp; &nbsp; return ExecuteSwitchImpl<false, false>(self, accessor, shadow_frame, result_register, false);
&nbsp; &nbsp; &nbsp; } else {
&nbsp; &nbsp; &nbsp; &nbsp; // 可使用 mterp
&nbsp; &nbsp; &nbsp; &nbsp; while (true) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // mterp 不支持调试/断点等,所以如果当前线程不允许用 mterp,就退回 switch
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (!self->UseMterp()) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return ExecuteSwitchImpl<false, false>(self, accessor, shadow_frame, result_register, false);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }

&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 调用 mterp 执行指令
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; bool returned = ExecuteMterpImpl(self,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;accessor.Insns(), &nbsp;// 获取指令序列
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&shadow_frame,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&result_register);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (returned) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // mterp 执行成功(正常返回)
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return result_register;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // mterp 无法处理该指令,改用 switch 解释器单步执行
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; result_register = ExecuteSwitchImpl<false, false>(self, accessor, shadow_frame, result_register, true);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (shadow_frame.GetDexPC() == dex::kDexNoIndex) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 已执行 return 或发生未捕获异常,直接返回
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return result_register;
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; } else {
&nbsp; &nbsp; &nbsp; // 当前解释器类型是 switch
&nbsp; &nbsp; &nbsp; DCHECK_EQ(kInterpreterImplKind, kSwitchImplKind);
&nbsp; &nbsp; &nbsp; if (transaction_active) {
&nbsp; &nbsp; &nbsp; &nbsp; return ExecuteSwitchImpl<false, true>(self, accessor, shadow_frame, result_register, false);
&nbsp; &nbsp; &nbsp; } else {
&nbsp; &nbsp; &nbsp; &nbsp; return ExecuteSwitchImpl<false, false>(self, accessor, shadow_frame, result_register, false);
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
&nbsp; } else {
&nbsp; &nbsp; // === 进入 "需要访问检查" 模式 ===

&nbsp; &nbsp; // 启动路径不应该运行到这里,除非是软验证失败或者在 AOT 编译中
&nbsp; &nbsp; DCHECK(method->GetDeclaringClass()->GetClassLoader() != nullptr
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|| Runtime::Current()->IsVerificationSoftFail()
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|| Runtime::Current()->IsAotCompiler())
&nbsp; &nbsp; &nbsp; &nbsp; << method->PrettyMethod();

&nbsp; &nbsp; if (kInterpreterImplKind == kMterpImplKind) {
&nbsp; &nbsp; &nbsp; // mterp 不支持访问检查,强制使用 switch 模式
&nbsp; &nbsp; &nbsp; if (transaction_active) {
&nbsp; &nbsp; &nbsp; &nbsp; return ExecuteSwitchImpl<true, true>(self, accessor, shadow_frame, result_register, false);
&nbsp; &nbsp; &nbsp; } else {
&nbsp; &nbsp; &nbsp; &nbsp; return ExecuteSwitchImpl<true, false>(self, accessor, shadow_frame, result_register, false);
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; } else {
&nbsp; &nbsp; &nbsp; // switch 模式解释器分支
&nbsp; &nbsp; &nbsp; DCHECK_EQ(kInterpreterImplKind, kSwitchImplKind);
&nbsp; &nbsp; &nbsp; if (transaction_active) {
&nbsp; &nbsp; &nbsp; &nbsp; return ExecuteSwitchImpl<true, true>(self, accessor, shadow_frame, result_register, false);
&nbsp; &nbsp; &nbsp; } else {
&nbsp; &nbsp; &nbsp; &nbsp; return ExecuteSwitchImpl<true, false>(self, accessor, shadow_frame, result_register, false);
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
&nbsp; }
}

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) {
&nbsp; if (transaction_active) {
&nbsp; &nbsp; return ExecuteSwitchImpl<...>(); &nbsp;// Mterp 不支持事务,退回 Switch
&nbsp; } else if (!Runtime::Current()->IsStarted()) {
&nbsp; &nbsp; return ExecuteSwitchImpl<...>(); &nbsp;// VM 没启动也不能用 Mterp
&nbsp; } else {
&nbsp; &nbsp; while (true) {
&nbsp; &nbsp; &nbsp; if (!self->UseMterp()) {
&nbsp; &nbsp; &nbsp; &nbsp; return ExecuteSwitchImpl<...>(); &nbsp;// 当前线程禁用了 Mterp
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; bool returned = ExecuteMterpImpl(...);
&nbsp; &nbsp; &nbsp; if (returned) {
&nbsp; &nbsp; &nbsp; &nbsp; return result_register; &nbsp;// Mterp 成功执行完成
&nbsp; &nbsp; &nbsp; } else {
&nbsp; &nbsp; &nbsp; &nbsp; // Mterp 遇到不支持的指令或状态,单步回退到 Switch
&nbsp; &nbsp; &nbsp; &nbsp; result_register = ExecuteSwitchImpl(...);
&nbsp; &nbsp; &nbsp; &nbsp; if (shadow_frame.GetDexPC() == dex::kDexNoIndex) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return result_register; &nbsp;// 已返回或异常抛出
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
&nbsp; }
}

FART 如何实现脱壳

对于函数抽取壳的 dex 来说,因为需要禁用 dex2oat ,所以都会以解释模式运行,进入到 Execute 函数里面。

Execute 参数中有 ShadowFrame,能拿到 ArtMethod,再通过 GetDexFile() 函数获得 DEX 字节码等信息。

在 dumpDexFileByExecute 中判断 如果 dex 文件不存在就 dump

// 该函数在 ART 执行期间调用,用于在 Execute 函数内完成 dex 脱壳
extern "C" void dumpDexFileByExecute(ArtMethod *artmethod)
&nbsp; &nbsp; SHARED_LOCKS_REQUIRED(Locks::mutator_lock_) {

&nbsp; &nbsp; // 为 dex 文件保存路径分配内存
&nbsp; &nbsp; char *dexfilepath = (char *) malloc(sizeof(char) * 2000);
&nbsp; &nbsp; if (dexfilepath == nullptr) {
&nbsp; &nbsp; &nbsp; &nbsp; // 分配失败,打印日志并返回
&nbsp; &nbsp; &nbsp; &nbsp; LOG(INFO) << "ArtMethod::dumpDexFileByExecute, methodname: "
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; << PrettyMethod(artmethod).c_str()
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; << " malloc 2000 byte failed";
&nbsp; &nbsp; &nbsp; &nbsp; return;
&nbsp; &nbsp; }

&nbsp; &nbsp; // 获取当前进程的 cmdline 名称,用于后续命名脱壳文件
&nbsp; &nbsp; int fcmdline = -1;
&nbsp; &nbsp; char szCmdline[64] = { 0 };
&nbsp; &nbsp; char szProcName[256] = { 0 };
&nbsp; &nbsp; int procid = getpid();
&nbsp; &nbsp; sprintf(szCmdline, "/proc/%d/cmdline", procid);
&nbsp; &nbsp; fcmdline = open(szCmdline, O_RDONLY, 0644);
&nbsp; &nbsp; if (fcmdline > 0) {
&nbsp; &nbsp; &nbsp; &nbsp; read(fcmdline, szProcName, 256);
&nbsp; &nbsp; &nbsp; &nbsp; close(fcmdline);
&nbsp; &nbsp; }

&nbsp; &nbsp; // 若成功获取到进程名
&nbsp; &nbsp; if (szProcName[0]) {
&nbsp; &nbsp; &nbsp; &nbsp; // 获取当前 ArtMethod 所属 dex 文件及其起始地址和大小
&nbsp; &nbsp; &nbsp; &nbsp; const DexFile *dex_file = artmethod->GetDexFile();
&nbsp; &nbsp; &nbsp; &nbsp; const uint8_t *begin_ = dex_file->Begin(); &nbsp;// dex 文件起始地址
&nbsp; &nbsp; &nbsp; &nbsp; size_t size_ = dex_file->Size(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;// dex 文件大小

&nbsp; &nbsp; &nbsp; &nbsp; int size_int_ = (int) size_; &nbsp;// 用于命名文件

&nbsp; &nbsp; &nbsp; &nbsp; // 创建保存路径:/sdcard/fart/<process_name>/
&nbsp; &nbsp; &nbsp; &nbsp; memset(dexfilepath, 0, 2000);
&nbsp; &nbsp; &nbsp; &nbsp; sprintf(dexfilepath, "/sdcard/fart");
&nbsp; &nbsp; &nbsp; &nbsp; mkdir(dexfilepath, 0777); &nbsp;// 创建 fart 目录

&nbsp; &nbsp; &nbsp; &nbsp; memset(dexfilepath, 0, 2000);
&nbsp; &nbsp; &nbsp; &nbsp; sprintf(dexfilepath, "/sdcard/fart/%s", szProcName);
&nbsp; &nbsp; &nbsp; &nbsp; mkdir(dexfilepath, 0777); &nbsp;// 创建子目录为进程名

&nbsp; &nbsp; &nbsp; &nbsp; // 拼接最终保存路径,如:/sdcard/fart/com.xxx.xxx/123456_dexfile_execute.dex
&nbsp; &nbsp; &nbsp; &nbsp; memset(dexfilepath, 0, 2000);
&nbsp; &nbsp; &nbsp; &nbsp; sprintf(dexfilepath,
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "/sdcard/fart/%s/%d_dexfile_execute.dex",
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; szProcName, size_int_);

&nbsp; &nbsp; &nbsp; &nbsp; // 检查该文件是否已经存在,若存在则跳过写入
&nbsp; &nbsp; &nbsp; &nbsp; int dexfilefp = open(dexfilepath, O_RDONLY, 0666); // 以只读方式尝试打开指定路径的 dex 文件
&nbsp; &nbsp; &nbsp; &nbsp; if (dexfilefp > 0) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; close(dexfilefp); &nbsp;// 已存在,关闭文件
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dexfilefp = 0;
&nbsp; &nbsp; &nbsp; &nbsp; } else {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // 不存在则创建并写入 dex 内容
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; dexfilefp = open(dexfilepath, O_CREAT | O_RDWR, 0666);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (dexfilefp > 0) {
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; write(dexfilefp, (void *) begin_, size_);
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; fsync(dexfilefp); &nbsp;// 刷新到磁盘
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; close(dexfilefp); &nbsp;// 关闭文件
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }

&nbsp; &nbsp; // 释放申请的内存
&nbsp; &nbsp; if (dexfilepath != nullptr) {
&nbsp; &nbsp; &nbsp; &nbsp; free(dexfilepath);
&nbsp; &nbsp; &nbsp; &nbsp; dexfilepath = nullptr;
&nbsp; &nbsp; }
}

路径: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脱壳点解析》

评论:0   参与:  0