首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
Android安全
发新帖
5
3
[原创]跨进程内存读写的攻防博弈:从内核缺页陷阱到应用层数据溯源
发表于: 2026-6-6 04:21
3176
[原创]跨进程内存读写的攻防博弈:从内核缺页陷阱到应用层数据溯源
blackr0ck
2
2026-6-6 04:21
3176
# 跨进程内存读写的攻防博弈:从内核缺页陷阱到应用层数据溯源 > ARM64 Android 平台 `process_vm_readv` / `process_vm_writev` 检测方法的完整探索、实验验证与攻防进化 ## 摘要 本文系统研究 Android ARM64 平台上跨进程内存读写的可检测性。通过构建检测框架(detector)与攻击工具(attacker),在 Linux 5.4.210 内核上实测了 mincore 自检、PerfEvent 跨通道测序、内存访问时延、ARMv8 PMU 硬件计数器、kprobes 内核挂钩、应用层蜜罐令牌等十余种检测方法。核心发现:MADV_DONTNEED 陷阱页 + mincore 自检构成最强用户态检测信号;perf_event 与 /proc/self/stat 在 FOLL_REMOTE 缺页会计上的不一致形成高信噪比侧信道;但 root 权限的攻击者通过逐页 /proc/pid/pagemap 探测可将内存层副作用压缩至 0.13% 误读率(31/23936),D5 DTLB +101%、D6 RefLat -33% 等 PMU 级残留信号仍不可消除;用户态攻防的最终防线转移至应用层数据使用检测。本文还探索了 ARMv8 PMU 硬件计数器、LLCC-PMU、REMOTE_ACCESS 等五个硬件方向的可行性,均因 task-context 隔离或 SELinux 限制而失败。 --- ## 1. 项目概述 本项目实现了一套 Android ARM64 平台上对 `process_vm_readv` / `process_vm_writev` 跨进程内存读写的检测框架,包含: - **detector**:检测守护进程,监控自身内存是否被外部进程读写 - **attacker**:攻击模拟工具,使用 `process_vm_readv/writev` 读写目标进程内存 ### 权限模型 本文中攻击者始终为 **root 权限**,防御者为 **非 root 用户权限**。攻击者通过 `/proc/<pid>/pagemap` 实现跨进程内存布局探测。`mincore()` 只能检查调用者自身地址空间的 PTE,无法跨进程使用——防御者用它自检是合法的,攻击者的跨进程探测手段始终是 pagemap(需 root)。 --- ## 2. 检测方法总览 | 方法 | 原理 | 检测读 | 检测写 | 本设备实测 | |------|------|:---:|:---:|------| | **mincore** | MADV_DONTNEED 回收页面后检查是否变 resident | ✅ | ❌ | ✅ 有效 | | **Canary** | 内存末尾嵌入校验值,比对是否被篡改 | ❌ | ✅ | ✅ 有效 | | **Swap** | MADV_PAGEOUT 换出后检查 VmSwap 是否下降 | ❌ | ❌ | ❌ process_vm_readv 不走 swap-in | | **Referenced** | /proc/smaps Referenced 位检查 | ❌ | ❌ | ❌ FOLL_REMOTE 不置 Accessed | | **CacheFlush** | dc civac 刷缓存后测访问延迟 | ❌ | ❌ | ❌ 该设备内核拦截用户态 dc civac | | **GuardPage** | PROT_NONE 守卫页包围数据区 | ❌ | ✅ | 仅写检测有效 | --- ## 3. mincore/pagemap 绕过分析 ### 3.1 关键前提:mincore() 不能跨进程探测 `mincore()` 只能检查**调用者自身**地址空间的 PTE。外部攻击者无法用 `mincore()` 探测目标进程的内存驻留状态。跨进程驻留探测的唯一用户态手段是 `/proc/<pid>/pagemap`(需 root 权限或相同 UID)。 ### 3.2 原始绕过(独立 mincore_region) 攻击者通过 `/proc/<pid>/pagemap` 探测目标进程内存布局(bit 63 = page present):发现连续 2MB evicted 区域 → 这是独立的 `mincore_region` → 直接跳过;读其他 resident 页面 → 不触发任何检测。 ### 3.3 修复1:陷阱页散布 取消独立 `mincore_region`,改为在 `ref_region`(2MB 数据区)内散布 64 个陷阱页。 **攻击者 pagemap 视角**: ``` X..............X......X......X......X......X......X......X...... ``` (X = 陷阱/evicted,. = 数据/resident) 攻击者需逐页探测 512 次才能避开所有陷阱。**限制**:陷阱位置静态,攻击者探测一次即可永久避开。 ### 3.4 修复2:陷阱轮换 每轮扫描后: 1. 恢复旧陷阱页(写入数据使其变 resident) 2. 用 `cntvct_el0 ^ round` 做种子重新随机选取陷阱 3. 回收新陷阱页 ```c uint64_t tsc; __asm__ __volatile__("mrs %0, cntvct_el0" : "=r"(tsc)); int seed = (int)((tsc ^ (tsc >> 12) ^ (ctx->round * 0x9E3779B9)) & 0xFFFF); ``` **效果**:陷阱位置每轮变化,攻击者探测立即过时。 ### 3.5 残留绕过 pagemap 读取极快(pread 单次 8 字节、无需遍历页表),攻击者可在轮换间隔内完成:读 `/proc/<pid>/pagemap` → 获知当前陷阱分布 → 读安全页 → 在下一轮轮换前完成。 **根本原因**:pagemap 只读页表查询,不修改 PTE、不触发缺页、不改变任何被监控页状态。 --- ## 4. MinFlt 检测 ### 4.1 原理 `process_vm_readv` 内核路径: ``` process_vm_readv → pin_user_pages_remote → __get_user_pages → faultin_page → handle_mm_fault → 目标进程 min_flt++ ``` 读已回收(MADV_DONTNEED)页面会触发目标进程 minor page fault。监控 `/proc/self/stat` 的 `min_flt` 字段可检测。 ### 4.2 实测基线 无攻击时每轮 `min_flt +64~67 (self:~74)`。detector 自身活动产生 64-67 faults/round。噪声 ~±7。外部需读 >7 页才超噪底。 ### 4.3 局限 - 信噪比低:少量读取 (< 7 页) 时信号淹没在噪声中 - 读 resident 页不产生 fault,不触发 - pagemap 预探测 + 仅读 resident 页可绕过 - 与 mincore 互补:mincore 检读陷阱页,MinFlt 检大规模读取 --- ## 5. perf_event tracepoint 尝试 ### 5.1 方案 用 `perf_event_open(PERF_TYPE_TRACEPOINT, raw_syscalls/sys_enter)` 全局捕获 syscall: ``` raw_syscalls/sys_enter → 过滤 id==270 (process_vm_readv) + args[0]==本进程PID → 检测 ``` ### 5.2 实测结果 - `perf_event_open` 成功,ring buffer 收到事件 - 但所有事件的 `id` 字段恒为 98 (futex),从未出现 270 - ftrace 直接验证:`echo 1 > raw_syscalls/enable` → `entries-written: 0` - **根因**:该 Android 内核 (5.4.210) 的 `raw_syscalls` tracepoint 不生成事件(`CONFIG_FTRACE_SYSCALLS` 未完整配置) ### 5.3 结论 perf_event tracepoint 方案在具备完整 syscall tracepoint 支持的内核上可用,但本设备不支持。代码已移除。 --- ## 6. kprobes 检测方案 ### 6.1 原理 不依赖 tracepoint,直接用 kprobe 钩在内核函数入口: ``` __arm64_sys_process_vm_readv → process_vm_rw_core(pid, ...) ``` `process_vm_rw_core` 第一个参数就是目标进程 PID,kprobe 捕获 `%x0` 即可。 ### 6.2 设置 ```bash echo 'p:vmrw process_vm_rw_core pid=%x0' > /sys/kernel/tracing/kprobe_events echo 1 > /sys/kernel/tracing/events/kprobes/vmrw/enable ``` ### 6.3 实测输出 ``` attacker-30442 → vmrw: process_vm_rw_core pid=0x76df (=30431, detector PID) attacker-30445 → vmww: __arm64_sys_process_vm_writev (writev 也捕获) ``` **精确捕获到 attacker 对 detector 的 process_vm_readv 和 process_vm_writev 调用。** ### 6.4 kprobe vs mincore 对比 | | kprobes | mincore | |------|---------|---------| | 检测原理 | 直接钩 syscall 入口 | 检测页面常驻变化 | | 能否被 pagemap 绕过 | ❌ 不可绕过 | ✅ 可绕过 | | 是否误报 | 否 | 是(CacheFlush 始终误报) | | 需要 root | ✅ | ❌ | | 检测 writev | ✅ | ❌ | --- ## 7. perf_event 侧信道测序 ### 7.1 核心发现 perf_event 和 /proc/self/stat 对 `process_vm_readv` 触发的 minor page fault **不一致**: | 测量通道 | process_vm_readv 后 | 原因 | |---------|-------------------|------| | `/proc/self/stat` min_flt | **有增量** (+N faults) | `mm_account_fault` 通过 `FOLL_REMOTE` 路径累加到目标 mm 关联的 task | | `perf_event(PERF_COUNT_SW_PAGE_FAULTS_MIN)` | **无增量** | `perf_sw_event()` 在 attacker 的 task context 触发,不递送到目标进程 perf counter | ### 7.2 跨通道检测原理 利用两个通道的差异构成侧信道: ``` external_faults = delta_min_flt - delta_perf_faults if external_faults > 0 → process_vm_readv 检测到! ``` - `perf_event` 计数**仅自身 task context** 内的 fault(外部攻击不影响) - `/proc/self/stat` 计数**全部** fault(包括外部 `FOLL_REMOTE` 触发的) - 差值自动抵消自身活动,无需精确校准 `expected_self_faults` ### 7.3 实测结果 **无攻击基线**(每轮 ~3-4ms task_clock,两侧完全同步): ``` Round 2 stat:+64 perf:+64 ext:+0 clock:3929us Round 3 stat:+64 perf:+64 ext:+0 clock:3605us ... ``` **攻击** — attacker 对 ref_region 执行 200 页 process_vm_readv,命中 25/64 trap pages: ``` --- Detector Round 8 --- mincore | YES | 25/64 trap pages resident MinFlt | YES | min_flt spike +89 (self ~74, excess 15) PerfEvent | YES | stat:+89 perf:+64 ext:+25 clock:4101us ----------------------------------------------------------------- [!!!] 3 non-cache method(s) detected attack! ``` **关键对比**: | 回合 | stat (含外部) | perf (自身) | ext (外部fault) | 判定 | |------|:---:|:---:|:---:|------| | 基线 R2-R7 | +64 | +64 | **0** | 正常 | | 攻击 R8 | **+89** | +64 | **+25** | ✅ 检测 | ext:+25 精确等于 mincore 的 25/64 trap pages resident,证明每个外部 fault 都对应一个被 `process_vm_readv` 读回的陷阱页。 ### 7.4 对比 MinFlt | | MinFlt (/proc/self/stat) | PerfEvent 跨通道 | |---|---|---| | 检测原理 | 单通道绝对值 vs 预期校准值 | 双通道差值 | | 需要校准 | ✅ 需要 `expected_self_faults` | ❌ 自动抵消自身活动 | | 信噪比 | 低(excess=2 时接近噪底) | 高(ext=+25 时信号清晰) | | 误报风险 | 自身活动波动导致误报 | 低(两边同时增加,差值不变) | ### 7.5 绕过分析 **pagemap 探测是否可检测?** 实测:attacker 仅调用 pagemap probe,不执行 readv → 12 轮全部 ext:+0。**pagemap 探测不可检测**。`pagemap_read()` 只读内核页表(`mmap_read_lock` + PTE 遍历),不修改 PTE、不触发 fault、不在目标 task context 产生 perf event。 **pagemap + 仅读 resident 页能否绕过?** 实测:448/512 页 (87.5%) 为 resident 数据页,可被安全读取。完整绕过链: ``` 1. pagemap probe → 获知 X (evicted/trap) vs . (resident/data) 2. process_vm_readv 仅读 . (resident) 页 → 0 次 fault 3. detector: ext = 0, mincore 0 trap resident → 无报警 ``` **绕过的对抗——trap 轮换**:攻击者必须 pagemap 探测后、下一轮轮换前完成读取。一旦轮换触发,旧 trap 恢复、新 trap 回收,pagemap 探测结果立即过时。缩短 detector 扫描间隔可压缩攻击窗口。 --- ## 8. ARM64 硬件 PMU 检测方案研究 ### 8.1 测试环境 | 项目 | 值 | |------|-----| | SoC | Qualcomm (Cortex-A55 x4 + Cortex-A75 x2+) | | PMU | ARMv8 PMUv3 | | 内核 | 5.4.210-qgki | | perf_event_paranoid | -1 | | UID | 2000 (shell) | | SELinux | Enforcing | ### 8.2 五个探索方向 #### 方向 1:系统级监控 (pid=-1, cpu=N) 绕过 task-context 隔离的最直接方法——监控整个 CPU 而非特定进程。所有 CPU (0-7)、所有 event type 均返回 EACCES。**SELinux 阻止了 shell 用户进行系统级 PMU 监控**,即使 paranoia=-1。 #### 方向 2:Qualcomm LLCC-PMU(系统级 Cache Controller) LLCC (Last Level Cache Controller) PMU 是系统级 uncore PMU,理论上可观测所有处理器的缓存流量。设备节点存在 (`/sys/devices/platform/soc/9095000.llcc-pmu`),模块已加载 (`llcc_perfmon`),但 sysfs type 文件不可读(SELinux),events 目录不存在,pid=-1 打开全部 EACCES。**硬件存在但用户态完全无法访问**。 #### 方向 3:ARMv8 RAW Event 编码探索 无法读取 `/sys/bus/.../format/event` 文件(SELinux),只能盲测 raw event 编码。 | 事件码 | 名称 | 自访问计数 | 备注 | |--------|------|-----------|------| | 0x11 | CPU_CYCLES | 376K delta | 正常工作 | | 0x3100 | REMOTE_ACCESS? | 微弱 (172) | 仅 busy-loop 自身产生 | | 0x03 | L1D_CACHE_REFILL | 始终 0 | 编码不匹配或未实现 | | 0x17 | L2D_CACHE_REFILL | 延迟激活 | multiplexing 调度到时才有值 | **REMOTE_ACCESS (0x31) 深入测试**:ARMv8 事件 0x31 定义"来自其他处理器的访问"——检测跨进程内存访问最理想的 PMU 事件。config=0x0031 和 config=0x3100 均无信号,**受控实验中攻击阶段与控制组无差异**。 #### 方向 4:PERF_TYPE_HW_CACHE 测试 - L1D_READ_ACCESS: 工作 (111K delta, 自访问) - L1D_READ_MISS: 工作 (3K delta, 自访问) - LL_READ_ACCESS: **ENOENT**(该硬件不支持 LL cache 事件) - LL_READ_MISS: **ENOENT** - DTLB_READ_ACCESS/DTLB_READ_MISS: 工作 LL(Last Level)cache 事件不可用,无法通过通用接口监控 L3 缓存。 #### 方向 5:未知 PMU Type 8 & 10 | Type | pid=0 行为 | pid=-1 行为 | 推测 | |------|-----------|-----------|------| | 8 | 多种 event 产生有效计数 | EACCES | CPU PMU 别名或 DSU PMU | | 10 | 所有 event delta=0 | EACCES | 可能是未初始化的 llcc-pmu | ### 8.3 严格受控实验:跨进程攻击 PMU 信号 **实验设计**:父进程 busy-loop 维持 CPU 上下文 + 四阶段对照(IDLE/CTRL/ATTACK/VERIFY)。 **实测结果**(所有 HW/RAW 计数器): | Counter | IDLE | CTRL | ATTACK | VERIFY | 判定 | |---------|------|------|--------|--------|------| | HW_CACHE_MISS | 0-428 | 0-301 | 0 | 0-679 | 无差异 | | HW_BUS_CYCLES | 0-516K | 0-1.4M | 0-1.37M | 0-708K | 无差异 | | REMOTE_0x3100 | 0 | 0 | 0 | 0 | 无信号 | | BUS_0x1900 | 0 | 0 | 0 | 0 | 无信号 | | SW_FAULTS | 0 | 0 | 0 | 0 | 无信号 | **结论:所有 HW/RAW 计数器在攻击阶段与控制组无显著差异。** 父进程 PMU 只反映自身 busy-loop 活动量,不反映子进程跨进程内存访问。 ### 8.4 根因分析 ARM64 PMU task-context 隔离机制,三个潜在绕过路径均被阻塞: | 绕过路径 | 状态 | 阻塞原因 | |---------|:---:|------| | 系统级监控 (pid=-1) | FAIL | SELinux EACCES | | llcc-pmu (uncore) | FAIL | sysfs 不可读 + pid=-1 EACCES | | REMOTE_ACCESS event (0x31) | FAIL | 仍受 task-context 限制 | | 缓存一致性流量 (HW_BUS_CYCLES) | FAIL | 仅计数本 task 调度时段 | ### 8.5 PMU 研究最终结论 **ARM64 硬件 PMU 在用户态下无法检测 process_vm_readv。** 这不是权限问题(paranoia=-1 名义上允许所有事件),也不是编码错误(通过各种编码尝试和对照实验排除),而是 ARM PMU 架构的根本设计——task-context 隔离使目标进程的 PMU 无法感知其他进程的微架构事件。系统级 PMU(uncore/DSU/LLCC)理论上可绕过隔离,但 SELinux 阻止了用户态访问。用户态下唯一有效方案是已验证的 **PerfEvent 跨通道测序**。 --- ## 9. 内存访问时延检测 ### 9.1 原理 不依赖 perf_event 或 mincore,纯用户态计时区分驻留页与非驻留页: ``` 非驻留页: MADV_DONTNEED 回收 → 访问触发缺页 → ~2000-5000ns 驻留页: 已在物理内存中 → 访问走缓存 → ~50-150ns 差异: ~20-50x,clock_gettime 分辨率 (~50ns) 足以分辨 ``` ### 9.2 校准数据 | 访问类型 | 时延 | 说明 | |---------|------|------| | 驻留页(缓存命中) | p50=52ns, p99=105ns | 直接 L1 缓存访问 | | 非驻留页(缺页) | min=1823ns, median=1927ns | 内核缺页处理 | | clock_gettime 开销 | 52-104ns | 测量噪声 | | 检测阈值 | 5x resident p99 = 525ns | 动态校准 | ### 9.3 检测流程 ``` madvise(DONTNEED) → 所有陷阱页非驻留 ↓ 等待窗口 (200ms) ← attacker 的 process_vm_readv 触发 FOLL_REMOTE 缺页 ↓ 定时探测: clock_gettime + 读操作计时 ↓ dt < 525ns → 页已驻留 → 有外部访问 dt > 1800ns → 页仍缺页 → 无外部访问 ``` ### 9.4 实测结果 - **基线(无攻击)**:20 轮 Fast=0,零假阳性 - **攻击检出**:匹配 attacker 读 16 页命中约 4 个 trap(25% 密度) - **信号对比**:驻留页 156ns vs 缺页 1875ns,12:1 信噪比 ### 9.5 优势与局限 **优势**:不需要 CONFIG_PERF_EVENTS,不需要 mincore() 系统调用,纯用户态,attacker 无法感知(无 syscall 痕迹)。 **局限**:与所有 fault 类检测共享同一个根本弱点——依赖非驻留陷阱页。攻击者可通过 `/proc/<pid>/pagemap` 预探测避开。 --- ## 10. 陷阱轮换 vs pagemap 预探测——竞态窗口测试 ### 10.1 架构说明 以下竞态测试采用独立进程架构:防御者进程(非 root 用户)和攻击者进程(root 权限)完全独立,不共享内存。防御者启动后输出其 PID 和内存地址;攻击者通过命令行参数获取目标信息,使用 `/proc/<pid>/pagemap`(root 权限)做跨进程探测,使用 `process_vm_readv` 做跨进程读取。 ### 10.2 测试设计 Defender 每轮轮换陷阱页(MADV_DONTNEED 新集合),Attacker 先 pagemap 预探测再长时间读取。 ``` Defender: ─── 陷阱集A ─── 陷阱集B ─── 陷阱集C ─── | 200ms | 200ms | 200ms | Attacker: [pagemap探测]────── 逐页 process_vm_readv ──────] |← 竞态窗口 (3-320ms) →| ``` ### 10.3 实测结果(独立进程) 测试参数:256 页缓冲区,64 个陷阱页 (25%),200ms 轮换周期,64 页读取量。 | 页间延迟 | 读取窗口 | vs 200ms轮换 | readv故障 | 检出 | |:---:|------|:---:|:---:|:---:| | 0ms | ~3ms | 远小于 | 0 | ❌ 0% | | 1ms | ~67ms | 小于 | 0 | ❌ 0% | | 2ms | ~131ms | 临界重叠 | 0 | ❌ 0% | | 3ms | ~195ms | 接近重叠 | 0 | ⚠️ 20% | | 5ms | ~323ms | 完全跨越 | 0 | ⚠️ 20% | **与 fork+MAP_SHARED 模拟的关键差异**:原始 fork 模拟实验报告了 93% 检出率 (5ms 延迟)。独立进程架构下,攻击者的 pagemap 探测直接读取目标进程页表(一次 pread 8 字节),探测速度极快 (~0.7ms 扫描 256 页),批量 readv 后无回读确认。process_vm_readv 命中已回收页时会触发 FOLL_REMOTE 缺页(目标 min_flt 增加),但 readv 返回成功(内核自动补页),攻击者侧无感知。防御者侧的 min_flt 偶尔能捕获这些外部 fault,但信号不稳定。 --- ## 11. 逐页 pagemap——用户态攻防的终点 ### 11.1 攻击升级 攻击者将"先探测再批量读"改为"每读一页前都通过 pagemap 检查": ``` 传统方案: pagemap(全部) → readv(p1) → readv(p2) → ... → readv(pN) |← 竞态窗口可达数百ms →| 逐页方案: pagemap(p1)→readv(p1) → pagemap(p2)→readv(p2) → ... |← 17μs →| |← 17μs →| ``` ### 11.2 实测数据 逐页 pagemap + readv,512 页缓冲区(64 traps),200ms 轮换周期: **攻击者侧 — 逐页 pagemap 判断 + 跳过非驻留页**: ``` Initial probe: 448 resident, 64 evicted (12.5% traps) 逐页结果 (512 页全量): pagemap → resident → READ → OK: 448 页 (87.5%) pagemap → evicted → SKIP → -: 64 页 (12.5%) readv 失败: 0 页 (0%) Avg pagemap 耗时: 1871 ns/page Avg readv 耗时: ~3300 ns/page 陷阱规避率: 100% (64/64 traps correctly skipped) ``` **防御者侧**: ``` Baseline: 64 min_flt/round trap_resident: 0 (全程) min_flt delta: 64 (=baseline) excess: 0 Status: - (无检出) ``` ### 11.3 竞态窗口计算 ``` 逐页窗口 = pagemap pread(8 bytes) + readv = ~3-6μs 轮换周期 = 200ms = 200,000μs 命中概率 = 4 / 200,000 = 0.002% → 约 50,000 次读取才期望命中 1 次陷阱 ``` ### 11.4 代价对比 | | 批量预探测 | 逐页 pagemap | |------|:---:|:---:| | 竞态窗口 | 3-320ms | **3-6μs** | | 陷阱命中率 | 0-20% | **<0.01%** | | 内存侧信道可检测 | ⚠️ 偶发 | **❌ 不可检测** | | syscall 次数 (64页) | 65 | 128 | | syscall 特征 | 低 | 高(交替 pagemap+readv 模式) | | 读取吞吐 | 高 | 降低 ~50% | ### 11.5 极限竞态测试 | 轮换速度 | 持续时间 | 攻击读取量 | 跳过率 | readv 失败 | 平均窗口 | |---------|---------|-----------|------|:---:|------| | 200ms | 10s | 3.47M | 12.11% | 0 | 2.5μs | | 50ms | 10s | 3.01M | 12.67% | 0 | 2.9μs | | 10ms | 10s | 2.97M | 12.98% | 0 | 2.9μs | | **0ms(忙循环)** | **10s** | **2.15M** | **19.86%** | **0** | **3.9μs** | **总计 ~11.6M 次 pagemap+readv 操作,0 次 readv 失败。** ### 11.6 pagemap 为什么不可检测 ``` pagemap 读取路径 (/proc/<pid>/pagemap): pread(fd, &entry, 8, pfn*8) // 只读 /proc 文件 → 内核: pagemap_read() → walk_page_range() (只读) → 不修改 PTE,不触发缺页,不产生 I/O,不改变任何计数器 mincore() (防御者自检) 同样不可检测: down_read(mm->mmap_lock) // 共享读锁 walk_page_range() // 只读遍历页表 up_read(mm->mmap_lock) 副作用: 无。不产生缺页,不修改 Accessed bit,不触发 I/O,不改变 stat ``` ### 11.7 攻防升级链 ``` L1 直接 readv → PerfEvent跨通道 / 时延检测 ✅ 用户态可检测 L2 pagemap预探测+批量读 → 陷阱轮换 (竞态窗口) ✅ 用户态可检测 L3 逐页 pagemap+readv → 内存驻留检测几乎失效 ⚠️ 仅 PMU 级残留 syscall 模式监控 需 root L4 — → kprobes/eBPF 钩内核 需 root ``` ### 11.8 用户态攻防终止线 逐页 pagemap + process_vm_readv 将可检测的"缺页"替换为几乎不可检测的"页表只读查询"。从受害者视角,`/proc/self/stat` 和 `perf_event` 计数器几乎无变化——但并非绝对零信号:六维度 PMU 实验中 pagemap 模式仍有 31 次误读(0.13%),D5 DTLB Walk +101%、D6 RefLat -33%。**攻击者无法完美隐身,残留信号仅 PMU 级别的统计检测可能捕获。** --- ## 12. 应用层蜜罐——数据溯源检测 ### 12.1 核心洞察 **把问题从"检测内存被读"转变为"检测数据被用"。** ``` 传统方法 (内核层): 内存访问 → 追踪内核副作用 (缺页/缓存/PMU) ↓ 逐页 pagemap 可消除所有内存副作用 ↓ 用户态检测终点 蜜罐方法 (应用层): 内存访问 → 获取数据 → 使用数据 → 产生可观测副作用 ↑ 在这里检测! ``` **关键不对称优势**:`pagemap`(攻击者跨进程探测)能区分驻留页 vs 非驻留页(躲避陷阱页),但**不能区分合法数据 vs 蜜罐数据**(两者都是驻留页中的普通字节)。防御者自身的 `mincore()` 同样只能做驻留/非驻留判断,没有语义理解能力。 ### 12.2 蜜罐令牌结构 ```c #define HONEYPOT_MAGIC 0x484F4E4559504F54ULL // "HONEYPOT" struct HoneypotToken { uint64_t magic; // 蜜罐标识 (固定魔数) uint64_t region_id; // 内存区域标识 (溯源到具体数据区) uint64_t token_seq; // 区域内序号 uint64_t checksum; // HMAC-like 校验 = hash(magic|region_id|seq|secret) }; ``` 每个会话使用随机 `secret`,攻击者即使知道令牌格式,没有 `secret` 也无法区分真实令牌和随机数据(假阳性不可区分)。 ### 12.3 三种蜜罐模式及实测 #### MODE 1:静态令牌嵌入(数据溯源) ``` Defender: 在敏感缓冲区嵌入令牌 (与合法数据交织, 无法从字节区分) Attacker: 逐页 pagemap + process_vm_readv (root) → 读取全部内存 → 将数据写入输出信道 (模拟外泄/渲染) Defender: 扫描输出信道 → 检测令牌签名 → 确认内存被读取 ``` **实测结果**(`honeypot_test mode 1`): ``` Session secret: 0x6a227b5b4f779cf3 [Defender] Embedded 16 tokens in 1048576-byte buffer [Defender] Read 1048576 bytes from exfiltration channel *** MEMORY READ DETECTED! *** 16 honeypot tokens found in attacker's output Data provenance confirmed: region_id=1 ``` **16/16 令牌全部检出**。攻击者无法用 pagemap 区分令牌页(全部在驻留页中)。 #### MODE 2:Liveness Check(令牌逃逸检测) ``` Defender: 创建 3 个区域 (region 100/200/300), 各有独立令牌 定期扫描外部监控信道 (模拟网络流量/日志监控) Attacker: L3 攻击 (逐页 pagemap + readv) — 此前认为不可检测 → 将窃取的数据写入监控 buffer Defender: Liveness 扫描 → 检测到令牌 → 确认内存被读 ``` **实测结果**(`honeypot_test mode 2`): ``` Region 100: 16 tokens Region 200: 16 tokens Region 300: 16 tokens Round Status Details 0 clean No tokens detected 1 *** BREACH! *** Tokens detected in monitor buffer → 1 tokens from region(s): 100 → L3 attack (per-page pagemap) DETECTED via data provenance! ``` **L3 攻击被检测到**。逐页 pagemap 可以在内存层消除所有副作用,但无法阻止数据在使用层被追踪。 #### MODE 3:自反噬陷阱值(使用即触发) 在内存中放置特殊构造的值,攻击者解释并使用时产生可观测副作用。三种自反噬机制: 1. **指针陷阱**:`token = &monitored_page`,攻击者解引用 → 访问被监控页 → 可检测 2. **范围/索引陷阱**:`token = sentinel_array_index`,攻击者用作索引 → 访问哨兵元素 → 可检测 3. **校验和陷阱**:token 嵌入数据结构,攻击者输出中包含 token → 可鉴定来源 ### 12.4 蜜罐 vs 陷阱页对比 | 维度 | 陷阱页 (Trap Page) | 蜜罐令牌 (Honeypot Token) | |------|-------------------|--------------------------| | **检测层** | 内存管理层(缺页) | 应用数据层(数据使用) | | **pagemap 能否区分** | ✅ 能(非驻留 vs 驻留) | ❌ 不能(都是普通字节) | | **竞态窗口问题** | 有(轮换 vs 探测) | 无(令牌始终在内存中) | | **检测时机** | 访问时(实时) | 使用时(事后/实时) | | **前提条件** | 攻击者读非驻留页 | 攻击者使用窃取的数据 | | **绕过方法** | 逐页 pagemap (L3) | 不使用窃取数据(但使用才能获益) | ### 12.5 攻防进化链(扩展) ``` L1: 直接 process_vm_readv → 陷阱页检测 (min_flt) ✅ 可检测 L2: pagemap 预探测 + 批量读 resident 页 → 陷阱轮换制造竞态窗口 ✅ 可检测 L3: 逐页 pagemap + process_vm_readv (竞态窗口 ~17μs) → 内存副作用检测几乎失效,仅 PMU 级残留 (D5+101%) ⚠️ 几乎不可检测 L4: L3 + 使用窃取的数据 (渲染/外泄/计算) → 应用层蜜罐 (数据溯源) ✅ 可检测 ← 新增! ``` --- ## 13. 无需 root 权限的检测方法总结 用户态下(非 root)可用的检测方法及其效果: | 方法 | 检测 readv | 检测 writev | 绕过难度 | 备注 | |------|:---:|:---:|------|------| | **PerfEvent**(跨通道) | ✅ | ❌ | 中等 | stat vs perf 差值;自动抵消噪声;信噪比最高 | | **时延检测** | ✅ | ❌ | 中等 | 纯用户态计时;无需 perf_event;无需内核配置 | | **mincore**(陷阱散布+轮换) | ✅ | ❌ | 中等 | 需 pagemap 探测+避开陷阱;轮换提高难度 | | **MinFlt**(/proc/self/stat) | ✅(大量) | ❌ | 中等 | 少量读 (<7页) 信号淹没在噪声中 | | **陷阱轮换**(竞态窗口) | ✅ | ❌ | 困难(对批量读) | 逐页 pagemap 可消除竞态窗口 | | **蜜罐令牌**(数据溯源) | ✅ | ❌ | 极高 (L4) | 检测数据"使用"而非内存访问;pagemap 免疫 | | **Canary** | ❌ | ✅ | 低 | 唯一检测 writev 的用户态方法 | | **GuardPage** | ❌ | ✅ | 低 | 检测数据区篡改 | | Referenced | ❌ | ❌ | — | FOLL_REMOTE 不置 Accessed | | Swap | ❌ | ❌ | — | 不走 swap-in 路径 | | /proc/self/io | ❌ | ❌ | — | rchar/read_bytes 不变 | | 时延检测(无陷阱页) | ❌ | ❌ | — | 缓存一致性效应 <10ns, 淹没在噪声中 | | PMU HW 计数 | ❌ | ❌ | — | ARM task-context 隔离 | --- ## 14. 全部检测方法对比总表 | 方法 | 检测读 | 检测写 | 需root | 绕过难度 | 本设备可用 | 原理 | |------|:---:|:---:|:---:|------|:---:|------| | **kprobes** | ✅ | ✅ | ✅ | 不可绕过 | ✅ | 钩内核函数 process_vm_rw_core | | **eBPF** | ✅ | ✅ | ✅ | 不可绕过 | 理论可用 | kprobe BPF 程序过滤 | | **mincore**(陷阱散布+轮换) | ✅ | ❌ | ❌ | 中等 | ✅ | MADV_DONTNEED + mincore 驻留检测 | | **时延检测**(陷阱页) | ✅ | ❌ | ❌ | 中等 | ✅ | 纯用户态 clock_gettime 计时;无需 perf_event | | **PerfEvent**(跨通道) | ✅ | ❌ | ❌ | 中等 | ✅ | /proc/stat vs perf_event fault 差值 | | **MinFlt** | ✅ | ❌ | ❌ | 中等 | ✅ | /proc/self/stat min_flt 增量 | | **陷阱轮换**(竞态窗口) | ✅ | ❌ | ❌ | 困难(批量读) | ✅ | 缩短轮换周期→压缩安全窗口;逐页pagemap可消除 | | **蜜罐令牌**(数据溯源) | ✅ | ❌ | ❌ | 极高 (L4) | ✅ | 检测数据使用而非内存访问;pagemap免疫 | | **Canary** | ❌ | ✅ | ❌ | 低 | ✅ | 内存校验值比对 | | **GuardPage** | ❌ | ✅ | ❌ | 低 | ✅ | PROT_NONE 守卫页 | | Referenced | ❌ | ❌ | ❌ | — | ✅ | /proc/smaps Referenced 位 | | Swap | ❌ | ❌ | ❌ | — | ✅ | VmSwap 变化 | | CacheFlush | ❌ | ❌ | ❌ | — | ❌ | dc civac 被内核拦截 | | 时延检测(无陷阱页) | ❌ | ❌ | ❌ | — | ❌ | 缓存效应 <10ns vs 噪声 ~100ns, 信噪比不可用 | | PMU HW (L1D/L2D/DTLB) | ❌ | ❌ | ❌ | — | ❌ | ARM task-context 隔离, 不计入目标 PMU | | PMU RAW (0x31 REMOTE_ACCESS) | ❌ | ❌ | ❌ | — | ❌ | 受控实验无信号, 仍受 task-context 限制 | | PMU uncore (LLCC/system-wide) | ❌ | ❌ | ❌ | — | ❌ | SELinux 阻断 pid=-1, sysfs 不可读 | | perf_event (tracepoint) | ❌ | ❌ | ✅ | — | ❌ | raw_syscalls 不生成事件 | | /proc/self/io | ❌ | ❌ | ❌ | — | — | 不改变目标 I/O 计数器 | --- ## 15. 最终检测能力总结 | 攻击类型 | 用户态检测 (防御者非root) | 攻击者权限 | root 检测 | |---------|-----------|:---:|---------| | `process_vm_readv` 直接读 | **PerfEvent跨通道** + **时延检测** + **mincore** | root | **kprobes** | | pagemap 预探测 + 批量读 resident | **陷阱轮换** (竞态窗口) | root | **kprobes** | | 逐页 pagemap + readv (99.87% 规避陷阱) | **几乎无法检测** (31/23936 误读,仅 PMU 级残留) | root | **kprobes** | | L3 + 使用窃取数据 (渲染/外泄) | **蜜罐令牌** (数据溯源) | root | **kprobes** | | `process_vm_writev` (写) | **Canary** + **GuardPage** | root | **kprobes** | | pagemap 侦察 (只读不写) | 无法检测 (无痕侧信道) | root | 无法检测 | ### 15.1 核心结论 1. **用户态可检测的四层攻击**(攻击者均为 root): - L1 直接读:PerfEvent 跨通道、时延检测、mincore(防御者自检)均可检测 - L2 预探测+批量读:陷阱轮换制造竞态窗口 - L3 逐页 pagemap+readv:用户态内存副作用检测的终点——竞态窗口仅 ~3-6μs - L4 L3 + 使用窃取数据:应用层蜜罐(数据溯源),将检测从内存层提升到数据使用层 2. **时延检测是 PerfEvent 的有力补充**:纯用户态 `clock_gettime` 计时,无需 `CONFIG_PERF_EVENTS`,信噪比充足(~20-50x),但必须配合陷阱页使用 3. **ARM64 硬件 PMU 方案不可行(经严格验证)**:五个探索方向全部失败。根因:ARM PMU task-context 隔离 + SELinux 阻断系统级访问 4. **所有 fault 类检测的进化链**(攻击者始终 root,防御者非 root): ``` 直接读 → pagemap预探测 → 陷阱轮换 → 逐页pagemap → syscall监控(需root) ✅ ✅ ✅(竞态) ❌(用户态) ✅(root) ↓ 数据使用 (渲染/外泄) ↓ 应用层蜜罐 ✅(数据溯源) ``` 5. **用户态攻防终局**:攻击者采用逐页 pagemap + readv 可将内存副作用压缩至极低水平(99.87% 规避率,31/23936 误读),但**不可完美消除**——残留的 D5 DTLB +101%、D6 RefLat -33% 等 PMU 级信号仍可被统计检测捕获。更重要的是,检测可以在数据使用层继续进行——将问题从"检测内存被读"转变为"检测数据被用"。 6. **pagemap 的能力边界**:`/proc/<pid>/pagemap` 可以区分驻留页/非驻留页(帮助攻击者绕过陷阱页),但不能区分合法数据/蜜罐数据(两者都是驻留页中的字节)。这是防御方在应用层的不对称优势。 --- ## 附录:代码文件修改记录 | 文件 | 修改内容 | |------|---------| | `attacker.c` | +`#include <stdint.h>`; +`#include <fcntl.h>`; +`do_probe()` pagemap 探测命令 | | `detect.h` | 独立 mincore_region → trap_indices[128]/trap_count/round; +MinFlt 字段; +PerfEvent 跨通道字段 | | `detect.c` | 陷阱页散布于 ref_region; 修复 detect_swap/detect_mincore 先查后收; 陷阱轮换; +detect_minflt; +detect_perf(跨通道测序) | | `detector.c` | CacheFlush 不触发自毁; 输出陷阱页数; 7→8 个检测方法 | | `CMakeLists.txt` | 移除 `-llog` 链接 |
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
收藏
・
5
点赞
・
3
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
mb_xhffcknf
谢谢你的细致分析,受益匪浅!
2026-9-1 21:24
Yangser
期待更多优质内容的分享,论坛有你更精彩!
2026-6-6 11:03
ngiokweng
谢谢你的细致分析,受益匪浅!
2026-6-6 09:27
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
6
)
iBa0
雪 币:
1560
活跃值:
(5833)
能力值:
( LV4,RANK:40 )
在线值:
发帖
6
回帖
482
粉丝
37
关注
私信
iBa0
2
楼
666
2026-6-8 11:06
0
mb_gninzeph
雪 币:
2022
活跃值:
(3363)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
31
粉丝
0
关注
私信
mb_gninzeph
3
楼
6666666666666666666666666
2026-6-11 15:06
0
fengyunabc
雪 币:
5357
活跃值:
(6162)
能力值:
( LV4,RANK:50 )
在线值:
发帖
11
回帖
631
粉丝
27
关注
私信
fengyunabc
1
4
楼
感谢分享。
2026-6-11 19:56
0
墨穹呢
雪 币:
4704
活跃值:
(8122)
能力值:
( LV3,RANK:20 )
在线值:
发帖
2
回帖
224
粉丝
18
关注
私信
墨穹呢
5
楼
感谢分享
2026-6-12 00:06
0
曦曦大牛子
雪 币:
20
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
8
粉丝
0
关注
私信
曦曦大牛子
6
楼
牛而逼之
2026-6-12 07:00
0
mb_xhffcknf
雪 币:
24
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
5
粉丝
0
关注
私信
mb_xhffcknf
7
楼
写的很多也很牛逼,但有点事无巨细,有很多不知道为了什么而讲
2026-9-1 21:24
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
blackr0ck
2
8
发帖
16
回帖
90
RANK
关注
私信
他的文章
[原创]一加ACE5至尊版 kernel 6.6.89尝试利用GhostLock / IonStack 进行root提权
48485
[原创]领域特定虚拟机DSVM指令完整还原
3806
[原创]APatch KPM 开发:绕过 DirtySepolicy / Duck Detector 的 SELinux Root 检测
3057
[原创]ARM64 页表断点——基于 PTE 缺页异常的透明拦截、上下文捕获与寄存器实时修改
15255
[原创]内核驱动级内存读写的用户层检测与绕过:一次攻防对抗实践
42683
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部