ilylty/android_eBPF_dump_dex
GitHub: ilylty/android_eBPF_dump_dex
一款利用 Android ARM64 eBPF uprobe 技术在运行时动态转储受保护应用 DEX 文件的逆向分析工具。
Stars: 2 | Forks: 1
# android_eBPF_dump_dex
Android ARM64 eBPF uprobe DEX 转储工具。
## 快速开始
在设备上使用 `run_dexdump_full.sh`。将以下文件放在同一目录下:
```
/data/local/tmp/dex_dump_bin
/data/local/tmp/dex_dump.bpf.o
/data/local/tmp/run_dexdump_full.sh
```
部署:
```
adb push dex_dump.bpf.o /data/local/tmp/
adb push dex_dump_bin /data/local/tmp/
adb push run_dexdump_full.sh /data/local/tmp/
adb shell chmod +x /data/local/tmp/dex_dump_bin /data/local/tmp/run_dexdump_full.sh
```
从具有完整权限的设备端 root shell 运行:
```
adb shell
su
cd /data/local/tmp
./run_dexdump_full.sh run
```
在脚本运行并等待事件后,打开目标受保护的应用程序并进行交互,直到触发其解包或 DEX 加载逻辑。
后台模式:
```
cd /data/local/tmp
./run_dexdump_full.sh start
./run_dexdump_full.sh logs
./run_dexdump_full.sh stop
```
该脚本会自动:
- 检查 `dex_dump_bin` 和 `dex_dump.bpf.o`
- 将诊断信息写入 `dexdump.log`
- 清理旧的 `dump_pid_*.dex` 文件
- 携带 `-scan` 参数运行
- 默认使用 `MAX_DUMP=0` 转储完整的 DEX 文件
实用的脚本环境变量:
- `EXTRA_ARGS`:传递给 `dex_dump_bin` 的额外参数,例如 `-symbol ...` 或 `-offset 0x...`
- `MAX_DUMP`:每个 DEX 区域转储的最大字节数。默认为 `0`,表示完整转储
- `LIB`:ART DEX 库路径。默认为 `/apex/com.android.art/lib64/libdexfile.so`
- `OUT`:转储输出目录。默认为脚本所在目录
- `WORKDIR`:包含 `dex_dump_bin` 和 `dex_dump.bpf.o` 的目录。默认为脚本所在目录
示例:
```
./run_dexdump_full.sh run
MAX_DUMP=65536 ./run_dexdump_full.sh run
EXTRA_ARGS="-symbol '_ZNK3art16ArtDexFileLoader4OpenEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEE'" ./run_dexdump_full.sh run
EXTRA_ARGS="-offset 0x19a90" ./run_dexdump_full.sh run
```
拉取结果:
```
adb pull /data/local/tmp .
adb pull /data/local/tmp/dexdump.log .
```
## 构建
GitHub Actions 构建两个产物:
- `dex_dump.bpf.o`:eBPF 字节码
- `dex_dump_bin`:Android ARM64 用户空间加载器/转储器
从 Actions 手动运行 `Build Android eBPF Dex Dumper` 工作流,或者推送到 `main` 分支。
## 部署
```
adb push dex_dump.bpf.o /data/local/tmp/
adb push dex_dump_bin /data/local/tmp/
adb shell chmod +x /data/local/tmp/dex_dump_bin
```
## 运行
从具有完整权限的设备端 root shell 运行。在某些设备上,`adb shell su -c` 会继承 `adbd` 受限的 capability bounding set,无法加载 eBPF 程序。
基本运行:
```
adb shell
su
cd /data/local/tmp
./dex_dump_bin -lib /apex/com.android.art/lib64/libdexfile.so -package com.example.target -max-dump 65536
```
如果包已经在运行,`-package` 会解析当前的 PID 并将事件过滤到该 PID。如果应用程序在转储器之后启动,请在不带 `-package` 的情况下启动转储器,或者先启动应用程序,然后再使用 `-package` 运行转储器。
如果不使用 `-package`,将观察所有的 DEX 加载事件:
```
./dex_dump_bin -lib /apex/com.android.art/lib64/libdexfile.so -max-dump 65536
```
测试使用 `readelf` 找到的符号时,请使用显式符号:
```
./dex_dump_bin \
-lib /apex/com.android.art/lib64/libdexfile.so \
-symbol '_ZNK3art16ArtDexFileLoader4OpenEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEE' \
-package com.example.target \
-max-dump 65536
```
如果符号查找失败或符号被剥离,请使用文件偏移量:
```
./dex_dump_bin \
-lib /apex/com.android.art/lib64/libdexfile.so \
-offset 0x19a90 \
-package com.example.target \
-max-dump 65536
```
启用头部扫描作为后备方案。这将在启动后和 DEX 事件发生后扫描可读内存范围以查找 DEX 头部:
```
./dex_dump_bin -lib /apex/com.android.art/lib64/libdexfile.so -package com.example.target -scan -max-dump 65536
```
如果从 ADB root 加载 eBPF 失败,请从真实的 root 上下文(例如通过 Magisk 服务或本地终端 root shell)创建并运行设备端脚本:
```
cd /data/local/tmp
./dex_dump_bin -lib /apex/com.android.art/lib64/libdexfile.so -package com.example.target -scan -max-dump 65536
```
转储的文件写入 `/data/local/tmp//` 下。
文件名格式:
```
dump_pid__0x __.dex
```
拉取结果:
```
adb pull /data/local/tmp .
adb pull /data/local/tmp/dexdump.log .
```
实用标志:
- `-obj`:`dex_dump.bpf.o` 的路径
- `-lib`:ART DEX 库的路径,默认为 `/apex/com.android.art/lib64/libdexfile.so`
- `-symbol`:要进行 uprobe 的可选符号名称。如果未设置,程序将尝试使用 `main.go` 中的内置符号列表
- `-offset`:要进行 uprobe 的可选文件偏移量。如果非零,它将覆盖符号查找
- `-out`:转储输出目录,默认为 `/data/local/tmp`
- `-max-dump`:每个 DEX 区域转储的最大字节数,默认为 `65536`。使用 `0` 转储报告的完整大小
- `-package`:可选的 Android 包名过滤器
- `-scan`:在事件发生后扫描目标进程内存以查找 DEX 头部。需要 `-package` 才能进行初始扫描
## 查找 Hook 符号
不同的 Android 版本和 ROM 将 ART DEX 加载代码放在不同的库中。不要想当然地认为 `libart.so` 总是包含您需要的函数。
本项目目前 hook 的是 DEX 内存打开函数,其中第一个有用的参数是:
```
x0 = this
x1 = const uint8_t* base
x2 = size_t size
```
这与当前的 eBPF 程序相匹配,该程序从寄存器 `x1` 读取 DEX 基址,从 `x2` 读取大小。
### 1. 检查哪个库包含符号
从 `libdexfile.so` 开始,如果需要,再检查 `libart.so`:
```
adb shell "readelf -Ws /apex/com.android.art/lib64/libdexfile.so | grep -iE 'ArtDexFileLoader|DexFileLoader|DexFileC|OpenCommon|OpenWithDataSection'"
adb shell "readelf -Ws /apex/com.android.art/lib64/libart.so | grep -iE 'ArtDexFileLoader|DexFileLoader|DexFileC|OpenCommon|OpenWithDataSection'"
```
有用的符号通常如下所示:
```
art::ArtDexFileLoader::Open(const uint8_t*, size_t, ...)
art::DexFileLoader::Open(const uint8_t*, size_t, ...)
art::DexFileLoader::OpenWithDataSection(const uint8_t*, size_t, ...)
art::DexFile::DexFile(const uint8_t*, size_t, ...)
```
在修饰(mangled)形式中,来自一台测试设备的示例是:
```
_ZNK3art16ArtDexFileLoader4OpenEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEE
_ZNK3art13DexFileLoader4OpenEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEE
_ZN3art7DexFileC2EPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileENS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISG_EEEEb
```
重要的部分是修饰名中的 `EPKhm`。它表示该函数具有 `const uint8_t*` 和 `size_t` 参数,即 DEX 基址和 DEX 大小。
### 2. 忽略未定义的符号
`readelf` 可能会显示 `UND` 条目。这些是导入的,而不是实现。Hook 包含实际地址的库。
好的情况:
```
0000000000019a90 FUNC GLOBAL PROTECTED ... _ZNK3art16ArtDexFileLoader4OpenEPKhm...
```
坏的情况:
```
0000000000000000 FUNC GLOBAL DEFAULT UND _ZNK3art16ArtDexFileLoader4OpenEPKhm...
```
如果该符号在 `libart.so` 中是 `UND`,请检查 `libdexfile.so`。
### 3. 无需重新构建测试符号
如果您在 `libdexfile.so` 中找到了一个符号,请使用 `-symbol` 传递它:
```
./dex_dump_bin \
-lib /apex/com.android.art/lib64/libdexfile.so \
-symbol '_ZNK3art16ArtDexFileLoader4OpenEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEE' \
-package com.example.target \
-max-dump 65536
```
如果符号名被剥离或 `link.Uprobe` 无法解析它们,请使用 `readelf` 中的符号值作为文件偏移量:
```
./dex_dump_bin \
-lib /apex/com.android.art/lib64/libdexfile.so \
-offset 0x19a90 \
-package com.example.target \
-max-dump 65536
```
### 4. 在 `main.go` 中需要更改什么
在大多数情况下,您只需要更改默认库或内置符号列表。
默认库路径位于 `main.go` 中:
```
libPath := flag.String("lib", "/apex/com.android.art/lib64/libdexfile.so", "path to target ART dex library")
```
内置符号位于 `attachDexOpen()` 中:
```
symbols := []string{
"_ZNK3art16ArtDexFileLoader4OpenEPKhm...",
"_ZNK3art13DexFileLoader4OpenEPKhm...",
"_ZN3art7DexFileC2EPKhmS2_m...",
}
```
将您找到的符号添加到此列表中,如果它最适合您的 ROM,最好放在顶部附近。
如果您从命令行使用 `-symbol` 或 `-offset`,则不需要编辑 `main.go`。
### 5. 何时必须更改 `dex_dump.bpf.c`
仅当函数签名不同时,才更改 eBPF 参数寄存器。
当前预期的布局:
```
x1 = DEX base
x2 = DEX size
```
如果您 hook 的函数中 DEX 指针和大小位于不同的参数中,请相应地更新 `dex_dump.bpf.c`。例如:
```
event.base = ctx->regs[1];
event.size = ctx->regs[2];
```
将 `regs[1]` 和 `regs[2]` 更改为您选择的函数正确的 ARM64 参数寄存器。
### 6. 实用工作流程
1. 在 `libdexfile.so` 和 `libart.so` 上运行 `readelf -Ws`。
2. 选择一个包含 `EPKhm` 或等效的 `const uint8_t*, size_t` 签名的已定义符号。
3. 首先使用 `-symbol` 进行测试。
4. 如果符号查找失败,请使用 `readelf` 中的地址通过 `-offset` 进行测试。
5. 如果有效,可选择将该符号添加到 `main.go` 中的 `attachDexOpen()`。
6. 重新构建并重新部署。
## Root 注意事项
在某些 Magisk/root 设备上,`adb shell su -c` 仍然缺乏加载 eBPF 程序所需的 capabilities,因为它继承了 `adbd` 受限的 capability bounding set。
如果加载失败并提示 `operation not permitted` 或 memlock/capability 错误,请从真实的设备端 root 上下文(例如 Magisk 服务脚本或具有完整权限的本地 root shell)运行转储器。
标签:Android, ARM64, Docker镜像, DSL, EVTX分析, uprobe, 云资产清单, 日志审计, 脱壳, 逆向工程