android dex2oat 编译dex文件分析

一、现象

复制代码
2026-03-27 22:07:26.994 2716-2766/? W/dex2oat: Compilation of java.util.Map c.d.c.b.h.a(java.lang.reflect.Type) took 177.351ms
dex2oat: /system/bin/dex2oat --dex-file=/data/data/com.jiaoyinbrother.monkeyking/.jiagu/classes.dex --dex-file=/data/data/com.jiaoyinbrother.mon

在日常逆向工程中,发现logcat日志中有这两条日志, 通过在线搜索源码定位其关键部位是:

复制代码
http://xrefandroid.com/android-8.1.0_r81/xref/art/compiler/driver/compiler_driver.cc#613

也就是所谓的: static void CompileMethod 函数,本人是在aosp8.1中分析的,此为日志1

日志2:oat_file_assistant.cc - OpenGrok cross reference for /art/runtime/oat_file_assistant.cc

和上述日志非常相似, 然后往下跟看到了Dex2Oat 这个函数,发现最后是执行了:

执行了Exec return Exec(argv, error_msg); // fork + execv → 启动新进程 ,它只是一个wrapper,等价于在 shell 中执行/system/bin/dex2oat --dex-file=xxx --oat-file=xxx,上述的CompileMethod函数说明了dex2Oat在编译的过程中是逐个函数编译的,并且我发现都是系统重启的时候,就会有上述两条log日志,所以,为了便于后续分析,我们要禁用dex2Oat, Dex2Oat函数返回的是bool, 那么我们就直接在Exec前面加上False就行了,然后刷机。

二、android 应用主动调用dex2oat

某空租车app:由壳代码自行调用 /system/bin/dex2oat (通过 Runtime.exec()ProcessBuilder 直接执行 dex2oat 二进制)

cpp 复制代码
2026-03-29 15:46:33.238 2678-2678/? I/dex2oat: /system/bin/dex2oat --dex-file=/data/data/com.jiaoyinbrother.monkeyking/.jiagu/classes.dex --dex-file=/data/data/com.jiaoyinbrother.monkeyking/.jiagu/classes.dex!classes2.dex --dex-file=/data/data/com.jiaoyinbrother.monkeyking/.jiagu/classes.dex!classes3.dex --oat-file=/data/data/com.jiaoyinbrother.monkeyking/.jiagu/oat/arm64/classes.odex --inline-max-code-units=0 --compiler-filter=speed

这个过程完全绕过了 OatFileAssistant::Dex2Oat() ,而是直接执行 /system/bin/dex2oat 这个 独立的可执行文件,为了验证,我们可以在dex2oat.cc main方法中加入一个log日志:

cpp 复制代码
int main(int argc, char** argv) {
	LOG(INFO) << "dex2oat invoked by pid=" << getpid() 
          << " ppid=" << getppid() 
          << " uid=" << getuid();
  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 build or running on valgrind. Note: The Dex2Oat class
  // should not destruct the runtime in this case.
  if (!art::kIsDebugBuild && (RUNNING_ON_MEMORY_TOOL == 0)) {
    _exit(result);
  }
  return result;
}

需要:#include <android-base/logging.h> // 系统LOG #include <unistd.h> // getpid(), getppid() #include <sys/types.h> // getuid()

编译刷机,刷入system.img, log日志就可以看到:2026-03-29 15:46:33.226 2678-2678/? I//system/bin/dex2oat: dex2oat invoked by pid=2678 ppid=2246 uid=10066

使用命令:ps -A | grep 2246 或者 cat /proc/2246/cmdline 就可以知道哪个进程在调用了。

加固壳(.jiagu)的典型行为: 应用进程 (com.jiaoyinbrother.monkeyking, PID 4112) ├── 解密释放 dex 到 .jiagu/ 目录 ├── fork + exec /system/bin/dex2oat ← 直接执行二进制 │ 参数: --dex-file=.jiagu/classes.dex --oat-file=.jiagu/oat/arm64/classes.odex └── 加载编译后的 odex 执行;正常应用的 dex 在安装时由 installd / PackageManagerService 触发 dex2oat 编译。但加固壳的真实 dex 是运行时才解密释放 的,系统完全不知道 .jiagu/classes.dex 的存在,不会帮你编译。如果壳不调用 dex2oat,就只能走解释执行或 JIT,首次启动会非常卡顿。调用 dex2oat 做 AOT 编译后,后续启动直接加载 odex,体验接近正常应用。第一次启动:解密 dex → dex2oat 编译 → 生成 odex → 加载执行(慢) 第二次启动:解密 dex → 发现 odex 已存在 → 直接加载(快)

三、dex2oat 中的脱壳时机

**dex2oat 的执行反而给了脱壳者更多机会,但也带来一些复杂性。**

1dex2oat 过程中的脱壳机会

1. dex 文件落地磁盘

解密前:APK 中的 dex 是加密的,无法直接获取

解密后:壳必须将明文 dex 写入磁盘,供 dex2oat 读取

/data/data/包名/.jiagu/classes.dex ← 明文 dex!

/data/data/包名/.jiagu/classes.dex!classes2.dex

/data/data/包名/.jiagu/classes.dex!classes3.dex

**脱壳方法:** 在 dex 写入磁盘后、dex2oat 执行期间,直接拷贝这些文件:

```bash

简单粗暴

cp /data/data/包名/.jiagu/classes.dex /sdcard/dumped.dex

```

2dex2oat 进程能看到完整 dex

dex2oat 作为独立进程,必须**完整读取 dex 文件**才能编译:

```

应用进程 (加密/混淆保护)

│ exec

dex2oat 进程 (独立进程,保护较少)

├── 打开 classes.dex → mmap 到内存

├── 解析所有 class_def、method、code_item

├── 编译每个方法为机器码

└── 输出 odex

```

**脱壳方法:** hook 或修改 dex2oat,在它读取 dex 后 dump。具体的脱壳点:dex2oat.cc - OpenGrok cross reference for /art/dex2oat/dex2oat.cc main方法,然后调用了:

复制代码
bool OatFileAssistant::Dex2Oat  
复制代码
static dex2oat::ReturnCode CompileApp(Dex2Oat& dex2oat)

最后编译app的dex文件,肯定是

复制代码
static void CompileMethod

他会一个一个方法去编译,这地放就是脱壳点。compiler_driver.cc - OpenGrok cross reference for /art/compiler/driver/compiler_driver.cc

```cpp

// 修改 art/dex2oat/dex2oat.cc

// 在 dex 文件被打开后

static int dex2oat(int argc, char** argv) {

// ... 正常逻辑 ...

// dex 文件已加载到内存,此时 dump

for (const auto& dex_file : dex_files) {

// dex_file->Begin() 就是完整的明文 dex

SaveToFile(dex_file->Begin(), dex_file->Size(), "/sdcard/dump.dex");

}

}

```

3生成的 odex 包含完整信息

odex/vdex 反向获取 dex

当然这种方法不常用,借助于frida可以很轻松的脱壳

4各种脱壳时机对比

```

时间线:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

壳解密 dex dex 写入磁盘 dex2oat 执行 odex 生成 加载执行

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

│ │ │ │

│ ▼ ▼ ▼

│ ┌─────────┐ ┌──────────┐ ┌───────────┐

│ │ 拷贝 dex │ │hook dex2oat│ │提取vdex/odex│

│ │ 文件 │ │ dump 内存 │ │ 反编译 │

│ └─────────┘ └──────────┘ └───────────┘

│ 脱壳点1 脱壳点2 脱壳点3 脱壳点4

```

5壳的对抗手段

壳如何防止通过 dex2oat 脱壳

```

对抗手段 效果

─────────────────────────────────────────────────

不调用 dex2oat 只走解释执行,不落地完整 dex

(牺牲性能)

或者系统禁用dex0at,对于二代壳(抽取壳便是如此),对于这种的,开源工具fart可以用,还有其他的。

6实际脱壳策略

在 ART 运行时 hook `ClassLinker::DefineClass` 或 `DexFile::OpenMemory`:

```cpp

// frida 脚本示例

Interceptor.attach(Module.findExportByName("libart.so", "_ZN3art11ClassLinker11DefineClassEPNS_6ThreadEPKcmNS_6HandleINS_6mirror11ClassLoaderEEERKNS_7DexFileERKNS9_8ClassDefE"), {

onEnter: function(args) {

// args[2] = descriptor (类名)

// args[7] = DexFile*

// 从 DexFile 中 dump 完整内容

}

});

```

为了防止dexoat,我们可以修改系统源码禁用掉,或者清空app数据,重新启动,在类的加载流程上去脱壳

相关推荐
恋猫de小郭2 小时前
Flutter 3.41.6 版本很重要,你大概率需要更新一下
android·前端·flutter
野生的码农10 小时前
放过自己,降低预期,及时行乐
android·ai编程
huwuhang11 小时前
索尼PS3游戏合集【中文游戏】8.12T 1430个游戏+PS3模拟器
android·游戏·智能手机·游戏机·电视
Grackers13 小时前
Android Perfetto 系列 5:Android App 基于 Choreographer 的渲染流程
android
踩着两条虫13 小时前
AI驱动的Vue3应用开发平台深入探究(十):物料系统之内置组件库
android·前端·vue.js·人工智能·低代码·系统架构·rxjava
sam.li13 小时前
JADX MCP 原理与使用部署
android·逆向·jadx
冬奇Lab13 小时前
Android 15音频子系统(五):AudioPolicyService策略管理深度解析
android·音视频开发·源码阅读
亚历克斯神14 小时前
Flutter for OpenHarmony: Flutter 三方库 mutex 为鸿蒙异步任务提供可靠的临界资源互斥锁(并发安全基石)
android·数据库·安全·flutter·华为·harmonyos
dalancon15 小时前
SurfaceControl 的事务提交给 SurfaceFlinger,以及 SurfaceFlinger 如何将这些数据设置到对应 Layer 的完整流程
android