readprocessmemory是windows跨进程内存读取的唯一合法方式,裸指针仅能访问本进程地址空间;需先申请本地缓冲区,再通过该api将目标进程内存拷贝至本地,最后在本地缓冲区上用指针进行特征扫描。

为什么不能直接用裸指针扫描内存特征
反病毒引擎里所谓“内存扫描”,本质是读取目标进程的虚拟内存空间,而 C++ 裸指针(如 uint8_t*)只能访问当前进程的合法地址空间。如果你拿到一个目标进程的地址(比如 0x7ff8a1234000),直接把它强转成指针并解引用:*(p + offset),大概率触发 ACCESS_VIOLATION 或 segfault —— 操作系统不会让你越界读别人家的内存。
真正能跨进程读内存的是系统 API,比如 Windows 的 ReadProcessMemory,Linux 的 process_vm_readv 或 /proc/pid/mem。指针在这里只起“缓冲区地址”作用,不是用来直接寻址目标内存的。
如何用 ReadProcessMemory 配合指针做特征扫描(Windows)
核心思路:申请本地缓冲区 → 用 ReadProcessMemory 把目标内存块拷贝进来 → 在本地缓冲区上用指针逐字节/模式匹配。
-
ReadProcessMemory第二个参数是目标地址(LPCVOID),它只是个数值,不是可解引用的指针;第三个参数才是你本地的LPVOID缓冲区指针 - 必须确保目标地址在目标进程里已提交且可读(可用
VirtualQueryEx预检) - 缓冲区大小别硬写 4KB,特征可能跨页;建议按页对齐分配(
VirtualAlloc分配MEM_COMMIT | MEM_RESERVE),再循环读 - 扫描时用
uint8_t*遍历缓冲区没问题,但别忘了检查边界:if (buf + i + pattern_len
// 示例:读取一段内存并用朴素子串搜索扫描
uint8_t* buf = (uint8_t*)VirtualAlloc(nullptr, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
SIZE_T bytes_read;
if (ReadProcessMemory(hProc, (LPCVOID)target_addr, buf, size, &bytes_read)) {
uint8_t pattern[] = {0x48, 0x83, 0xEC, 0x28}; // x64 函数序言
for (size_t i = 0; i <h3>Linux 下用 <code>process_vm_readv</code> 扫描要注意什么</h3><p>Linux 没有等价于 <code>ReadProcessMemory</code> 的单次调用,<code>process_vm_readv</code> 是更底层、更灵活的接口,但它不自动处理页保护或地址有效性 —— 如果目标地址未映射或不可读,它直接返回 <code>-EFAULT</code>,不会抛异常。</p>
- 传入的
struct iovec中iov_base必须是你本地 malloc 的缓冲区地址(uint8_t*),不是目标地址 -
lvec描述你要读的目标地址范围,rvec描述你本地接收缓冲区,两者长度要严格一致 - 无法一次读超大区域(内核限制
max_pages_per_read,通常 65536 页),需分段调用 - 普通用户进程默认无权读其他进程内存,需
ptrace(PTRACE_ATTACH, pid, ...)或开启ptrace_scope(不推荐生产环境关)
特征扫描中指针误用的三个典型坑
很多反病毒模块崩溃不是因为算法错,而是指针语义混淆:
- 把目标进程地址(如
0x7fff12345678)当成uint64_t*强转后解引用 —— 这是在读自己进程的 0x7fff12345678 地址,大概率未分配 - 用
std::vector<uint8_t></uint8_t>存储特征模式,却写&vec[0] + offset做指针运算,没检查offset是否越界,导致未定义行为 - 多线程扫描时共享同一块缓冲区指针(
uint8_t* buf),但没加锁或用线程局部存储,结果 A 线程刚读完,B 线程就覆盖了缓冲区内容
真正的难点不在指针语法,而在于明确区分“地址值”和“可解引用指针”这两个概念——前者是数据,后者是语言设施。反病毒场景下,99% 的“指针操作”其实只是算术偏移,背后全是系统调用在搬运数据。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











