论坛首页 安全工具分享区 阅读主题

[原创]基于eBPF的Android ART运行时DEX采集与方法字节码回填

202 浏览 16 回复
#1 楼主 2026-06-01 21:09:09
关键词:Android、ART、eBPF、uprobe、DEX、Nterp、CodeItem、Rust、aya项目地址:<671K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6U0K9r3W2F1L8r3g2W2P5W2)9J5c8X3g2n7f1p5k6p5k6i4S2p5N6h3#2H3k6i4u0Q4x3X3c8J5M7#2)9J5y4X3N6@1i4K6y4n7适用环境:Android 13-17 / ARM64 / root / 支持 eBPF 的内核Android 加固方案在高版本系统上逐渐从完整 DEX 加密演化为方法级抽取、运行时短暂回填、Nterp 路径覆盖以及 native buffer 中转等多种形态。传统依赖自定义 ROM、Frida inline hook 或 ptrace 的脱壳方式通常存在侵入性强、易被检测、跨 Android 版本维护成本高等问题。本文以开源项目 eBPFDexDumper-rs 为例,介绍一种基于 Linux eBPF uprobe/uretprobe 的 Android ART 运行时 DEX 采集方案。该方案不向目标进程注入 so,也不修改目标进程中的用户态代码逻辑;它通过在内核侧观察 ART 解释器入口、DexFile 生命周期入口以及 libc native buffer 操作,捕获 DEX 起始地址、文件大小和执行过的方法字节码,再由 Rust 用户态完成分块拼装、缺页兜底读取、DEX 去重、CodeItem 反扫和方法字节码回填。文章重点讨论四个工程问题:第一,如何在 Android 13-17 的 ART 变化中定位可用 hook 目标;第二,如何把 ART 私有对象布局以运行时参数注入 eBPF 程序,降低 ROM 差异带来的重编译成本;第三,如何在 BPF verifier 限制下传输大体积 DEX 与变长方法字节码;第四,如何把采集到的 insns 回填到 DEX 并重算 SHA-1 / Adler-32,使输出结果能被 jadx、baksmali 等工具继续处理。需要说明的是,本文讨论的是授权设备、授权应用和安全研究场景下的动态观测技术。它不试图绕过所有反分析能力,也不保证覆盖完全 native 化或 VMP 化的极端样本。近几年常见 Android 加固方案有几个明显趋势:因此,一个实用的运行时 DEX 采集工具不能只依赖单个 hook 点,也不能把 ART 私有布局写死在用户态或内核态逻辑里。eBPF 的价值在于把“观测点”放到内核侧:BPF 程序在 uprobe 命中时读取寄存器和用户态内存,然后把事件写入 ringbuf。用户态只负责加载程序、attach 探针、消费事件和落盘。eBPFDexDumper-rs 是一个 Rust 项目,主二进制为 eBPFDexDumper。与本文相关的核心模块如下:命令行分为三个子命令:整体数据流如下:输出目录按目标自动分组:使用 --name 时以包名建目录;只指定 --pid 时尽量从 /proc/&lt;pid&gt;/cmdline 推断;只指定 --uid 时使用 uid_&lt;num&gt;/。当前 dump 主流程默认 attach 的 ART 主入口包括:这组 hook 负责捕获“正在执行的方法”。入口参数根据 ABI 分为两类:拿到 ArtMethod* 后,BPF 程序尝试沿 ART 对象链解析 DEX:Android 64 位 ART 通常使用 32-bit HeapReference。因此实现中会先判断压缩引用,避免把低 32 位对象引用当作 64 位用户态地址直接读取。同时,ARM64 顶字节可能带 MTE/PAC tag,读取前会统一 untag。仅靠解释器入口会漏掉尚未执行的方法和某些 DexFile 生命周期事件。因此项目还会根据 src/art.rs 的解析结果 attach:DexFile::DexFile 和 Android 16/17 上常见的 DexFileLoader::OpenOne 都符合“x1 位置可取得 DEX begin”的形态,因此可以复用同一个 BPF handler。RegisterDexFile 则从 DexFile* 读取 begin_,再检查 DEX header。仓库中也包含 uprobe_libart_verifyClass 的 BPF handler 和 VerifyClass 目标定位逻辑,但当前 dump 主流程没有默认 attach 该探针。文档和使用预期应以实际 attach 路径为准,不能把它算作默认采集链路。为覆盖“DEX 先在 native buffer 中短暂出现”的场景,项目默认尝试 attach 以下 libc 探针:entry probe 记录参数,uretprobe 在函数返回后检查目标地址是否出现 dex\n magic 和合理的 file_size。BPF 侧只做轻量判断,完整 DEX header 校验、范围扫描和解析放到用户态完成,以减少 verifier 压力。Android 高版本中 libart.so 的符号保留情况差异很大。src/art.rs 使用纯 ELF 解析定位目标,不依赖在目标进程中 dlopen 或执行 ART 代码。主要策略包括:代码里的 TargetSource 目前分为 manual、symbol、pattern、string-ref 四类;分支扫描属于 pattern 路径的一种实现细节。eBPF 程序通过 art_layout_t 描述读取 ART/DEX 关键字段所需的偏移:默认布局对应 Android 13+ AOSP 主线 ARM64 常见结构:用户态加载 BPF 后会把 layout 写入 art_layout_map[0]。这个 map 使用 BPF_MAP_TYPE_HASH 而不是单元素 array,是为了让 BPF 程序在用户态尚未写入时 lookup 返回 NULL,从而回退到内置的 default_art_layout,避免读到全 0 偏移。Class.dex_cache_ 和 DexCache.dex_file_ 在不同 ROM 上可能出现在相邻 slot。主路径失败时,BPF 会在有限范围内做 4x4 网格尝试:这里使用固定边界和 #pragma unroll 是为了满足 BPF verifier 对循环可证明性的要求。DEX 文件可能达到几十甚至上百 MiB,不能作为单条 ringbuf 记录输出。项目用 dex_chunks ringbuf 加 dexProgress_map 实现分块续传:每次同一个 DEX 再次被 hook 命中时,BPF 从 next_offset 继续发送固定大小 chunk。当前 chunk 记录大小为 RINGBUF_SIZE = 1 << 17,也就是 128 KiB;单次 BPF 调用最多推进 MAX_CHUNKS_PER_CALL = 128 个 chunk。当 bpf_r

...(已截断)

---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-291116.htm
#2 2026-06-01 21:09:09
感觉有点东西啊
#3 2026-06-01 21:09:09
感谢分享
#4 2026-06-01 21:09:09
谢谢分享
#5 2026-06-01 21:09:09
tql
#6 2026-06-01 21:09:09
谢谢分享
#7 2026-06-01 21:09:09
tql
#8 2026-06-01 21:09:09
这已经在生产上使用了吗
#9 2026-06-01 21:09:09
1. [但是由于eBPF的局限性,其无法替代FART等基于主动调用的脱壳工具](a3dK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2L8r3I4W2j5i4k6W2M7$3N6Q4x3X3g2@1L8%4m8Q4x3V1k6S2M7Y4c8A6j5$3I4W2i4K6u0r3k6f1u0b7c8V1c8W2P5p5c8#2L8i4m8W2M7W2)9J5z5b7`.`.
有什么局限,导致功能上有什么区别吗
2 它不试图绕过所有反分析能力,也不保证覆盖完全 native 化或 VMP 化的极端样本。能具体详述吗?
#10 2026-06-01 21:09:09
cecini

1. [但是由于eBPF的局限性,其无法替代FART等基于主动调用的脱壳工具](1d4K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6T1L8r3!0Y4i4K6u0W2L8r3I4W2j5i4k6W2M7$3N6Q4x3X3g2@1L8%4m8Q4x3V1k6S2M7Y4c8A6j5$3I4W2i4K6u0r3k6f1u0b7c8V1c8W2P5p5c8#2L8i4m8W2M7W2)9J5z5b7`.`.
有什么局限,导致功能 ...
1. eBPF 局限带来的功能区别
  eBPF/uProbe 方案的限制主要在“只能看到发生过的事”:

  - 没执行到的方法,不一定能拿到真实 CodeItem。壳如果按需解密方法体,只有方法真正进入解释器/ART 路径时,eBPF 才有机会记录。
  - 它不能主动枚举类和方法,也不能替你调用每个方法。FART 的强项正是主动遍历类方法、触发方法解密,再 dump CodeItem 并回填。
  - 如果代码走 AOT/JIT/native 快路径,绕开你挂的 ART 解释器点,eBPF 可能只能拿到整体 DEX,拿不到执行时还原的方法体。
  - 如果内存中没有连续合法 DEX,只是 native 层临时拼出碎片、解密后马上擦除,通用 eBPF DEX 扫描也不一定能拼回完整文件。
  - eBPF 内核侧不能随意做复杂逻辑,受 verifier、栈大小、ring buffer、用户内存读取限制影响。文章里也提到过内核侧分片传 DEX 容易丢数据,后来才转向用户态 process_vm_readv 读取。

  所以功能上可以这么理解:

  - eBPFDexDumper-rs 更适合:低侵入、被动捕获、拿运行时已加载的 DEX、记录已执行方法字节码、适合常规壳和动态加载场景。
  - FART 更适合:代码抽取壳、方法体被 nop/占位替换、需要主动触发每个方法恢复真实 CodeItem 的场景。
  - eBPF 的优势是隐蔽和部署轻;FART 的优势是覆盖率和主动性,但侵入更强,通常依赖 ART/系统改造或更重的运行时控制。

  2. “不绕过所有反分析,不保证 native/VMP 极端样本”具体指什么
  “反分析能力”这里不是一句泛泛而谈,具体包括这些:

  - 目标可以检测 root、调试环境、异常系统属性、可疑文件、SELinux 状态、Magisk 痕迹等。这个项目不会帮你隐藏这些环境特征。
  - uProbe 不是完全无痕。它通常会在目标映射上留下可检测的断点式痕迹,强对抗样本可以校验 libart.so 代码页、扫描异常指令、检测性能/时序异常。
  - 目标可以检测 BPF/tracing 相关状态,比如内核 tracing、perf/uProbe 行为、BPF program/map 痕迹。当前项目提供 --probe-mode lifecycle|maps-only 降低探针面,但不是“反检测框架”。
  - 目标可以不让敏感代码进入 ART 字节码路径,例如把核心逻辑搬到 native .so,Java 层只剩 JNI 壳或空方法。这种情况下 dump 出 DEX 也看不到核心算法。
  - “完全 native 化”指业务逻辑已经变成 ARM64 机器码,DEX 里只有声明、桥接、加载器或少量壳代码。eBPF 盯 ART/DexFile 只能拿 Java 层结构,不能自动还原 native 算法。
  - “VMP 化”指原始字节码被翻译成壳自定义虚拟机指令,运行时由 native VM 解释器执行。此时 DEX 里可能只剩 VM 入口和字节码 blob,真实语义在 native VM 指令集中,dump DEX 不等于还原源码。
  - “代码抽取”指 DEX 方法体被 nop、空实现或占位结构替代,真实 CodeItem 在执行前才临时恢复。被动方案只能覆盖触发过的部分;没触发的仍然缺失。
#11 2026-06-01 21:09:09
PanzerT

1. eBPF 局限带来的功能区别
eBPF/uProbe 方案的限制主要在“只能看到发生过的事”:

- 没执行到的方法,不一定能拿到真实 CodeItem。壳如果按需解密方法体,只有 ...
多谢详尽的解释
#12 2026-06-01 21:09:09
看看
#13 2026-06-01 21:09:09
tql
#14 2026-06-01 21:09:09
tql
#15 2026-06-01 21:09:09
感谢分享。学习了
#16 2026-06-01 21:09:09
感谢分享
‹ 上一页 1 2 下一页 ›

请登录后参与讨论

立即登录 注册账号