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

版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/

FART 简介

在 Android ART 环境中,市面上一些加固方案会通过“函数抽取壳”将应用的核心逻辑剥离,运行时再动态加载执行,从而增加逆向分析的难度。

FART 是一种基于主动调用的自动化脱壳方案,能够在 ART 虚拟机的执行流程中精准捕获并还原这些被抽空的函数,实现对被加固的 dex 文件整体 dump。

FART 框架

word/media/image1.png

FART 框架的 3 大组件:

  • 脱壳组件:将内存中的 Dex 数据完整 dump 出来

  • 主动调用组件:构造主动调用链,完成对函数粒度的主动调用并完成 CodeItem 的 dump

  • 修复组件:利用脱壳组件得到的 dex 和主动调用 dump 下来的函数体,完成函数粒度的修复

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 的编译驱动会对函数逐个编译

word/media/image2.png
https://www.processon.com/i/6825c4e0c168ca282c1b9fb4?full_name=PO_iNeoB2

模板函数 CompileDexFile:编译 dex 文件中的所有类和方法,最后会调用 compile_fn 进行函数粒度编译

// 模板函数:编译 dex 文件中的所有类和方法
template <typename CompileFn>
static void CompileDexFile(CompilerDriver* driver,
                           jobject class_loader,
                           const DexFile& dex_file,
                           const std::vector<const DexFile*>& dex_files,
                           ThreadPool* thread_pool,
                           size_t thread_count,
                           TimingLogger* timings,
                           const char* timing_name,
                           CompileFn compile_fn) {
  // 用于性能分析记录这段编译过程的时间
  TimingLogger::ScopedTiming t(timing_name, timings);

  // 创建一个用于并行编译的上下文管理器
  ParallelCompilationManager context(Runtime::Current()->GetClassLinker(),
                                     class_loader,
                                     driver,
                                     &dex_file,
                                     dex_files,
                                     thread_pool);

  // 编译单个类的回调函数
  auto compile = [&context, &compile_fn](size_t class_def_index) {
    const DexFile& dex_file = *context.GetDexFile();
    SCOPED_TRACE << "compile " << dex_file.GetLocation() << "@" << class_def_index;

    ClassLinker* class_linker = context.GetClassLinker();
    jobject jclass_loader = context.GetClassLoader();
    ClassReference ref(&dex_file, class_def_index);
    const dex::ClassDef& class_def = dex_file.GetClassDef(class_def_index);
    ClassAccessor accessor(dex_file, class_def_index);
    CompilerDriver* const driver = context.GetCompiler();

    // 跳过验证失败的类(这些类在运行时也会失败)
    if (driver->GetCompilerOptions().GetVerificationResults()->IsClassRejected(ref)) {
      return;
    }

    // 进入托管代码环境,访问 Java 对象
    ScopedObjectAccess soa(Thread::Current());
    StackHandleScope<3> hs(soa.Self());

    // 解码 class_loader 对象
    Handle<mirror::ClassLoader> class_loader(
        hs.NewHandle(soa.Decode<mirror::ClassLoader>(jclass_loader)));

    // 查找类对象
    Handle<mirror::Class> klass(
        hs.NewHandle(class_linker->FindClass(soa.Self(), accessor.GetDescriptor(), class_loader)));

    Handle<mirror::DexCache> dex_cache;
    if (klass == nullptr) {
      // 类加载失败,清除异常并使用 dex cache
      soa.Self()->AssertPendingException();
      soa.Self()->ClearException();
      dex_cache = hs.NewHandle(class_linker->FindDexCache(soa.Self(), dex_file));
    } else if (SkipClass(jclass_loader, dex_file, klass.Get())) {
      // 判断是否跳过该类(如外部类、系统类等)
      return;
    } else if (&klass->GetDexFile() != &dex_file) {
      // 重复类(已从另一个 dex 文件加载),跳过
      return;
    } else {
      dex_cache = hs.NewHandle(klass->GetDexCache());
    }

    // 没有方法的类无需编译
    if (accessor.NumDirectMethods() + accessor.NumVirtualMethods() == 0) {
      return;
    }

    // 进入 native 状态,避免阻塞 GC
    ScopedThreadSuspension sts(soa.Self(), kNative);

    // 判断是否启用 dex-to-dex 编译(可能是省略优化过程)
    optimizer::DexToDexCompiler::CompilationLevel dex_to_dex_compilation_level =
        GetDexToDexCompilationLevel(soa.Self(), *driver, jclass_loader, dex_file, class_def);

    // 编译类中所有 direct 和 virtual 方法
    int64_t previous_method_idx = -1;
    for (const ClassAccessor::Method& method : accessor.GetMethods()) {
      const uint32_t method_idx = method.GetIndex();
      if (method_idx == previous_method_idx) {
        // 处理非法 smali 文件:可能多个 method 共用同一个 method_idx(重复定义)
        continue;
      }
      previous_method_idx = method_idx;

      // 调用外部传入的 compile_fn 进行实际方法编译
      compile_fn(soa.Self(),
                 driver,
                 method.GetCodeItem(),
                 method.GetAccessFlags(),
                 method.GetInvokeType(class_de
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值