不能直接在内核中对用户态地址用memcpy,因为CR3指向内核页表,目标用户页表项可能未映射、已换出或不可见,且用户地址可能非法(如NULL页、受保护栈),memcpy无权限校验,将触发缺页异常或系统崩溃。

为什么不能直接 memcpy 用户态地址
在内核中对用户态地址做 memcpy 会触发页错误或系统崩溃,因为当前进程的 CR3 指向的是内核页表,而目标进程的用户页表项(PTE)可能未映射、被换出,或根本不在当前上下文可见。更危险的是,用户地址可能指向非法区域(如 NULL 页、受保护的堆栈),memcpy 不做访问权限校验,直接访存等于邀请 BSOD。
MmCopyVirtualMemory 的调用前提和参数陷阱
这个函数是 Windows 内核提供的唯一推荐方式,但它不是“开箱即用”——必须满足三个硬性条件:
-
SourceProcess和TargetProcess都必须是有效的PEPROCESS指针,不能为NULL;常用PsLookupProcessByProcessId获取,但记得检查返回值是否为STATUS_SUCCESS - 源地址
SourceAddress必须在目标进程的用户空间范围内(0x00010000 ~ 0x7FFFFFFF 或 0x00000000`00010000 ~ 0x00000000`7FFFFFFF),且该页已提交(committed);否则MmCopyVirtualMemory返回STATUS_ACCESS_VIOLATION -
PreviousMode参数必须设为UserMode(当从用户请求触发时)或KernelMode(当内核自己发起读写时);设错会导致访问被拒绝,尤其在沙箱进程(如 Chromium 渲染器)中表现明显
读取前必须做的两件事:Probe + 权限绕过
即使 MmCopyVirtualMemory 成功,也不能跳过安全前置检查。常见蓝屏原因就是漏掉这步:
- 用
ProbeForRead或ProbeForWrite对用户传入的地址做轻量级合法性探测(仅检查是否在用户地址空间、是否对齐),它不真正访问内存,但能提前捕获非法指针 - 若目标地址处于只读页(如代码段、常量数据),
MmCopyVirtualMemory仍会失败;此时需先用MmProtectMdlSystemAddress或临时切换 CR3(高风险,不推荐)来绕过保护,但更稳妥的做法是:在用户态用VirtualProtectEx提前改页属性,驱动只负责拷贝
64 位驱动下容易忽略的地址截断问题
很多驱动在 32 位时代习惯把地址强转为 ULONG,到 x64 下直接丢高 32 位,导致读写偏移错乱。所有地址参数(SourceAddress、TargetAddress)必须声明为 PVOID 或 ULONG_PTR,且传入前确认没被隐式截断。实操中建议:
- 打印调试时用
DbgPrint("addr: %p", SourceAddress)而非%x - 如果从用户态传入地址,确保 IOCTL 输入缓冲区结构体字段是
ULONG64(而非DWORD) - 调用
PsLookupProcessByProcessId后,立即用PsGetProcessImageFileName打印进程名,验证是否真的拿到了目标进程,避免 PID 误传导致读写到 system 进程内存
最常被跳过的环节是:没确认目标进程是否处于活跃状态。一旦进程正在退出,PEPROCESS 可能已标记为删除,但引用计数尚未归零,此时调用 MmCopyVirtualMemory 会返回 STATUS_PROCESS_IS_TERMINATING,但很多人只检查成功与否,不检查具体错误码。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











