convenientlymike/android-inline-hook-mapping-trap

GitHub: convenientlymike/android-inline-hook-mapping-trap

一个用于诊断和修复 Android/Linux arm64 上 inline hook 因共享库双重映射而零触发问题的 /proc/maps 地址解析工具。

Stars: 0 | Forks: 0

# 🪤 错误映射陷阱 ### 为什么你字节级完美的 Android/Linux inline hook 触发了 **0 次** — 以及 40 行代码的修复方案 [![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/ad/ad5834178f7599af9fdda11629d49cae07f2997beec49821b2920eff5bfd50e7.svg)](https://github.com/convenientlymike/android-inline-hook-mapping-trap/actions/workflows/ci.yml)  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)  ![Python 3.9+](https://img.shields.io/badge/python-3.9%2B-3776AB?logo=python&logoColor=white)  ![Platform](https://img.shields.io/badge/target-Android%20%2F%20Linux%20arm64-3DDC84?logo=android&logoColor=white) *你的 inline patch已存在于 `/proc/pid/mem` 中。安装程序提示 OK。进程也没有崩溃。但它触发了 **零**次 —— 而基于 ptrace 的工具 hook 了“相同”的地址却运行正常。这个仓库解释了其中的原因,并 解析出 CPU **实际执行**的地址。*
## 原因 你编写了一个原生的 inline hook。你逐字节地验证了已修补的字节确实存在于内存中。`mprotect` 成功了。 进程存活且健康。但你的 hook 主体从未运行。 陷阱在于:**一个共享库通常会被多次映射到一个进程中** —— 既包含原始文件的 `mmap`,*也*包含 动态链接器的 ELF `LOAD` 镜像。每个人都会写的那种简易解析器 — ``` module_base = lowest_mapping_of(lib); // ← wrong target = module_base + rva; ``` — 选中的是**原始文件 mmap**,因此你的 patch 落在了一份 **CPU 永远不会执行的**代码副本上。ptrace 工具 (如 gdb,或 frida 风格的 server)之所以有效,是因为其 ELF 解析器针对的是*真正的* `LOAD` 段。相同的 RVA,不同的 mapping,截然相反的结果。为了弄清楚这一点,足足耗费了一整场调试时间(驳回了五个关于“缓存一致性”的假设)。 ## ▶️ 复现问题 ``` $ python mapping_trap.py 4123 libapp.so 0x2000000 resolving 'libapp.so' @ rva 0x2000000: mapping groups for lib : 2 ⚠️ TWICE-MAPPED — the trap is live .text (largest r-xp) : 0x7e0000001000-0x7e0006001000 (96.0 MB) r-xp ELF load base (off==0) : 0x7e0000000000 ✅ executed address : 0x7e0002000000 (load_base + 0x2000000) ❌ naive lowest+rva : 0x700002000000 ← WRONG (this is where a 0-fires hook lands) ``` 一个是实际触发的地址。另一个则是你浪费生命的地方。 ## 修复方案 对 `/proc/pid/maps` 进行两次遍历,与具体库无关: 1. **找到真正的 `.text`** —— 该库*最大的连续 `r-xp`* 段。这就是区分 ELF `LOAD`(一个大的可执行段)和原始文件 mmap(稀疏、部分可执行的页面)的关键 —— 即使原始 mmap 包含一个诱饵的可执行页面。 2. **将该组中 `off==0` 的 mapping 作为 ELF 加载基址**,而实际执行的地址即为 `load_base + rva` (其中 `rva` 是 dumper 的*虚拟地址*偏移量,**而不是**文件偏移量)。这在字节级别上等同于 ptrace 工具的 `module.base + rva`。 ``` from mapping_trap import resolve_executed_addr res = resolve_executed_addr(open(f"/proc/{pid}/maps").read(), "libapp.so", 0x2000000) print(hex(res.address)) # the executed address — patch HERE print(res.mapping_groups) # > 1 ⇒ the trap is live print(hex(res.naive_address)) # where "lowest mapping + rva" would have (wrongly) landed ``` ## 快速开始 ``` git clone https://github.com/convenientlymike/android-inline-hook-mapping-trap cd android-inline-hook-mapping-trap python -m pytest -q # 5 tests, ~0.01s python mapping_trap.py # live, on-device python mapping_trap.py --maps-file maps.txt libapp.so 0x2000000 # offline, from a saved dump ``` 无依赖项 —— 仅使用标准库,兼容 Python 3.9+。 ## 完整方法论 解析器是核心结论;**[METHODOLOGY.md](METHODOLOGY.md)** 是实战指南。它涵盖了: - **诊断特征** —— *存在 + 存活 + 静默 + 无崩溃* 意味着是**地址**问题,而不是一致性问题。在选择 假设之前,请先对照症状表。 - **RVA 算术陷阱** —— 文件偏移量数学运算对比 `load_base + rva`;通过交叉对比两次运行,来区分固定增量偏移(算术问题)还是 ASLR(随机问题)。 - **跨线程一致性工具包** —— `__builtin___clear_cache`、`membarrier(SYNC_CORE)`、`/proc/self/mem`、 `mprotect` 切换 —— *已排序,并说明了各自实际适用的场景*(以及为什么它极少是你的 bug)。 - **无懈可击的热路径读取** —— 故障安全的解引用、版本锚定偏移量、绝对无崩溃验证。 - **元经验** —— 当某一类假设全部失效时,去攻击整个类别所共有的基础假设。 ## 适用人群 任何在 Android/Linux arm64 上进行原生 instrumentation 的人 —— 游戏/应用研究、强化研发、恶意软件分析、 JIT/runtime 相关工作 —— 只要你遇到过*本该*起作用却失效的 hook。如果 `/proc/pid/maps` 显示你的库 有两次映射,这就是你的 bug。 ## 许可证 MIT — 详见 [LICENSE](LICENSE)。
提炼自一场真实的调试马拉松。如果它能为你省下我为此耗费的一小时,那它的使命就完成了。
标签:逆向工具