前言
在 Android 安全对抗的高维博弈中,/proc/self/maps(进程内存映射表)就像是反作弊框架的“照妖镜”。无论你的 Native 代码经过多少层 VMP 混淆,只要它是通过 dlopen 加载的,其底层的物理路径就会无所遁形地暴露在内存映射表中:
7f7c000000-7f7c100000 r-xp 00000000 ... /data/app/~~/com.example.app/lib/arm64/libHack.so
反作弊系统只需一次简单的文件路径扫描,就能精准锁定未经授权的第三方 SO。那么,有没有一种方法,能在代码正常执行的前提下,让库的文件信息在内存表中彻底消失?
今天我们来分析一个极具代表性的隐藏方案:Android-Library-Remap-Hide,看看它如何通过“内存重映射”实现对物理路径的降维打击。
一、 核心痛点:文件路径的阿喀琉斯之踵
即便你使用了再复杂的加载器,只要内存映射(VMA)是基于物理文件创建的(File-backed Mapping),系统就会保留链接信息。
- 1. 静态特征提取:
/data/app/ 及其子目录下的任何非应用原厂 SO 都是高风险目标。 - 2. 动态扫描:反作弊模块每秒轮询内存映射,查找特征字符串如 "libtest", "UnityCore" 等。
- 3. 基址追踪:即便修改了文件名,反作弊仍可通过内存地址溯源到加载请求。
二、 方案解析:内存重映射 (Remapping) 魔法
该方案的设计思路借鉴了 Riru 和 Zygisk 等知名注入框架,通过**“偷梁换柱”**的方式改变内存映射的属性。
1. 识别目标内存布局
首先,代码需要精准定位目标 SO 的所有内存地址范围。它通过解析 /proc/self/maps 获取起始、结束地址以及权限:
// RemapTools.h 核心逻辑std::vector<ProcMapInfo> maps = ListModulesWithName(name); // 查找 libTest.sofor (ProcMapInfo info : maps) { void *address = (void *)info.start; size_t size = info.end - info.start; // ... 开始变换}
2. 匿名内存替代方案
最关键的一步是赋予内存新的身份。程序申请了一块匿名内存 (Anonymous Memory),这块内存不再关联任何磁盘文件:
// 申请一块私有的匿名可写内存void *map = mmap(0, size, PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
3. mremap 降维打击
这是整个方案的灵魂。通过 Linux 系统的 mremap 调用,将原本“文件映射”的地址范围强行替换为新申请的“匿名映射”:
// 拷贝原内容std::memmove(map, address, size);// 将 map 处的内存段移动到 original_start,并固定 (MREMAP_FIXED)mremap(map, size, size, MREMAP_MAYMOVE | MREMAP_FIXED, info.start);// 恢复原始内存权限 (rx/rw)mprotect((void *)info.start, size, info.perms);
原理:MREMAP_FIXED 允许我们将一块内存重映射到指定的已存在地址。执行后,那个地址上的条目会被新的“匿名映射”覆盖。文件路径消失了,取而代之的是 [anon]。
三、 技术总结与思考
Android-Library-Remap-Hide 的精妙之处在于它不破坏链接器的加载逻辑,而是在加载完成后进行动态修补:
- • 高效性:利用 Linux 系统调用级联操作,对性能几乎无影响。
- • 兼容性:支持 arm64-v8a 架构,适配 Android 10-13+ 系统版本。
- • 深度隐藏:相比于修改
struct link_map 等上层手段,mremap 的底层程度更高。
对于安全研究人员来说,这类“去 API 化”和“去物理化”的手段正成为 Native 安全的主流。底层映射的透明度,往往决定了安全攻防的生死线。
本文仅用于技术学习与安全研究,请勿用于非法用途!
如果你对 Android Native 逆向、加固对抗感兴趣,欢迎关注我们,获取更多硬核技术文章。