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) {

// args2 = descriptor (类名)

// args7 = DexFile*

// 从 DexFile 中 dump 完整内容

}

});

```

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

相关推荐
传奇开心果编程13 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程15 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程15 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程15 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓15 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程15 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer
supabc12316 小时前
Celium:连接 Windows、Mac、Linux 与 Android,让远程访问和设备管理更简单
android·linux·windows·macos·远程访问·网络管理·celium
Dovis(誓平步青云)16 小时前
导览音频切换太快,旧讲解不能覆盖新展品
android·前端·javascript·ecmascript·音视频·宠物
五彩小白1 天前
自回归和上下文
android
事圆则缓1 天前
Android 多渠道打包实战:Product Flavors、签名、AAB 与发包校验
android