Skip to Content
Shadow Cheat EngineDev History指针扫描 server 端计划

指针扫描 Server 端实现计划

CE 的做法(用户态,慢)

  1. VirtualQueryEx 枚举所有可读 VMA
  2. ReadProcessMemory 逐区域读到用户态 buffer
  3. 遍历 buffer 每个对齐地址,检查 8 字节值是否落在任何 VMA 范围内
  4. 命中 → 存入 16 叉基数树(key=指针值, value=源地址列表)
  5. 两遍扫描(第一遍计数预分配,第二遍填充)
  6. 从 target 反向 BFS,在基数树中范围查询 [target-max_offset, target]
  7. 递归展开直到 max_depth 或命中 static base(模块内地址)

瓶颈:每次 ReadProcessMemory = 用户态↔内核态切换 + copy,64 位进程可能要读 500MB+

我们的做法(内核态,快)

新 ioctl: IOCTL_PTRSCAN_COLLECT

功能:一次内核态调用,遍历目标进程所有可读 VMA,收集所有有效指针。

struct ptrscan_collect_args { pid_t pid; uint32_t flags; // ALIGNED_ONLY, INCLUDE_STATIC, etc. uint64_t out_buf; // 用户态 buffer 地址 uint64_t out_buf_size; // buffer 大小 uint64_t out_count; // 返回:实际收集的指针数 }; // flags #define PTRSCAN_ALIGNED (1 << 0) // 只扫 8 字节对齐地址 #define PTRSCAN_STATIC_ONLY (1 << 1) // 只收集源地址在模块内的指针 // 输出 buffer 中每个元素: struct ptr_entry { uint64_t source; // 指针存放的地址 uint64_t target; // 指针的值(指向的地址) };

内核态实现

  1. VMA_ITERATOR 遍历所有 VM_READ 的 VMA
  2. 建立 VMA 范围表(排序数组,用于二分查找验证指针值有效性)
  3. 对每个 VMA:
    • 页表 walk → kmap_local_page 映射物理页
    • 遍历页内每个 8 字节对齐地址
    • 读出 8 字节值,二分查找是否落在任何 VMA 范围内
    • 命中 → copy_to_user 写一个 ptr_entry 到输出 buffer
  4. 每 256 页 cond_resched

性能预估

  • 跳过用户态↔内核态的 copy(直接 kmap 读物理页)
  • 二分查找 VMA:O(log N),N ≈ 500 个 VMA
  • 500MB 内存 / 4KB 页 = 128K 页,每页 512 个 8 字节地址 = 65M 次检查
  • 内核态纯计算,预计 200-500ms(CE 在用户态需要 2-5 秒)

新 CMD: CMD_PTRSCAN_COLLECT (0xD5)

TCP 协议:

客户端 → 服务端: [1 byte] CMD = 0xD5 [4 bytes] pid [4 bytes] flags [4 bytes] max_results (0 = unlimited) 服务端 → 客户端: [8 bytes] total_count [N × 16 bytes] ptr_entry[] (source, target)

BFS 在哪做?

方案 A:客户端 BFS(推荐先实现)

  • server 只负责收集 (source, target) 对
  • 客户端 Python 建基数树 + BFS
  • 优点:逻辑灵活,易调试,server 改动最小
  • 缺点:大量数据通过 TCP 传输(65M 指针 × 16 bytes = 1GB,需要压缩)

方案 B:服务端 BFS

  • server 在用户态建基数树 + BFS
  • 只返回最终的指针链
  • 优点:TCP 传输量极小
  • 缺点:server 需要大量内存(基数树),C 代码复杂

方案 C:内核态全做

  • ioctl 直接返回指针链
  • 极致性能,但内核态不适合复杂数据结构(基数树 + BFS)
  • 风险:内核态 OOM / 长时间占用 CPU

推荐路径:先 A(内核收集 + 客户端 BFS),验证正确性后可选 B 优化。

数据量优化

65M 个指针太多了,需要剪枝:

  1. 只收集指向堆的指针(跳过 stack, mmap, vdso)
  2. 只收集来自可写区域的指针(代码段的”指针”大多是指令编码,不是真指针)
  3. 预过滤:只收集 target 在 [target_addr - max_total_offset, target_addr + max_total_offset] 范围附近的
    • 但这要求 server 知道 target… 那就变成方案 B 了
  4. 分块传输:server 流式返回,客户端边收边建树

模块信息

现有 CMD_MODULE_LIST (0xCA) 已经返回模块列表。客户端用这个判断哪些地址是 static base。

指针链解析

现有 _resolve_pointermain_window.py 已经实现了指针链解析(逐层 dereference + offset)。只需要把扫描结果存成 AddrEntry(is_pointer=True, pointer_offsets=[...]) 格式。

实现步骤

Phase 1:纯客户端 fallback(不改 server)

  1. 用现有 virtual_query_full 枚举 VMA
  2. 用现有 read_memory 逐区域读
  3. Python 端建基数树 + BFS
  4. UI 完整可用,验证正确性
  5. 预计扫描时间:10-30 秒(TCP 传输是瓶颈)

Phase 2:内核态收集加速(改 server + ko)

  1. IOCTL_PTRSCAN_COLLECT
  2. CMD_PTRSCAN_COLLECT (0xD5)
  3. 客户端改用新 CMD 获取指针对
  4. BFS 仍在客户端
  5. 预计扫描时间:1-3 秒

Phase 3:服务端 BFS(可选)

  1. server 端建基数树 + BFS
  2. 只返回指针链
  3. 预计扫描时间:0.5-1 秒
Last updated on