指针扫描 Server 端实现计划
CE 的做法(用户态,慢)
VirtualQueryEx枚举所有可读 VMAReadProcessMemory逐区域读到用户态 buffer- 遍历 buffer 每个对齐地址,检查 8 字节值是否落在任何 VMA 范围内
- 命中 → 存入 16 叉基数树(key=指针值, value=源地址列表)
- 两遍扫描(第一遍计数预分配,第二遍填充)
- 从 target 反向 BFS,在基数树中范围查询 [target-max_offset, target]
- 递归展开直到 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; // 指针的值(指向的地址)
};内核态实现:
VMA_ITERATOR遍历所有VM_READ的 VMA- 建立 VMA 范围表(排序数组,用于二分查找验证指针值有效性)
- 对每个 VMA:
- 页表 walk →
kmap_local_page映射物理页 - 遍历页内每个 8 字节对齐地址
- 读出 8 字节值,二分查找是否落在任何 VMA 范围内
- 命中 →
copy_to_user写一个ptr_entry到输出 buffer
- 页表 walk →
- 每 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 个指针太多了,需要剪枝:
- 只收集指向堆的指针(跳过 stack, mmap, vdso)
- 只收集来自可写区域的指针(代码段的”指针”大多是指令编码,不是真指针)
- 预过滤:只收集 target 在 [target_addr - max_total_offset, target_addr + max_total_offset] 范围附近的
- 但这要求 server 知道 target… 那就变成方案 B 了
- 分块传输:server 流式返回,客户端边收边建树
模块信息
现有 CMD_MODULE_LIST (0xCA) 已经返回模块列表。客户端用这个判断哪些地址是 static base。
指针链解析
现有 _resolve_pointer 在 main_window.py 已经实现了指针链解析(逐层 dereference + offset)。只需要把扫描结果存成 AddrEntry(is_pointer=True, pointer_offsets=[...]) 格式。
实现步骤
Phase 1:纯客户端 fallback(不改 server)
- 用现有
virtual_query_full枚举 VMA - 用现有
read_memory逐区域读 - Python 端建基数树 + BFS
- UI 完整可用,验证正确性
- 预计扫描时间:10-30 秒(TCP 传输是瓶颈)
Phase 2:内核态收集加速(改 server + ko)
- 加
IOCTL_PTRSCAN_COLLECT - 加
CMD_PTRSCAN_COLLECT(0xD5) - 客户端改用新 CMD 获取指针对
- BFS 仍在客户端
- 预计扫描时间:1-3 秒
Phase 3:服务端 BFS(可选)
- server 端建基数树 + BFS
- 只返回指针链
- 预计扫描时间:0.5-1 秒
Last updated on