关键词: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/<pid>/cmdline 推断;只指定 --uid 时使用 uid_<num>/。当前 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