直接调用MmCopyVirtualMemory会蓝屏或返回STATUS_ACCESS_DENIED,核心原因是未在目标进程上下文中执行、缺乏SE_DEBUG_PRIVILEGE权限、地址无效或页保护不匹配,且必须配合KeStackAttachProcess/KeUnstackDetachProcess正确切换CR3并验证VAD。

为什么直接用 MmCopyVirtualMemory 会蓝屏或返回 STATUS_ACCESS_DENIED
核心原因是调用上下文不满足要求:该函数只能在目标进程的上下文中执行(即必须先通过 KeStackAttachProcess 切换到目标进程的地址空间),且调用者线程必须拥有 SE_DEBUG_PRIVILEGE 权限;同时,源/目标地址范围必须已正确提交、可访问,且不能跨页保护边界(比如尝试读写只读页或未映射页)。常见错误是直接在系统进程上下文中调用,结果触发内存管理器校验失败。
MmCopyVirtualMemory 的参数陷阱和正确调用顺序
四个关键参数中,SourceProcess 和 TargetProcess 必须是有效的 EPROCESS* 指针(不能是 PsGetCurrentProcess() 硬编码);SourceAddress 和 TargetAddress 是用户态虚拟地址,但它们的“有效性”由当前执行上下文决定——不是由传入的进程对象自动保证。所以必须:
- 用
PsLookupProcessByProcessId获取目标EPROCESS*,并确保引用计数正确(最后调用ObDereferenceObject) - 用
KeStackAttachProcess切换到目标进程上下文(注意保存旧的KAPC_STATE) - 在附着状态下调用
MmCopyVirtualMemory,此时SourceAddress才被解释为该进程的用户地址 - 调用完立即
KeUnstackDetachProcess恢复原上下文 - 检查返回值是否为
STATUS_SUCCESS,非成功时不要假设数据已拷贝
读写前必须绕过页表保护和用户态地址验证
MmCopyVirtualMemory 不会自动提升页保护级别(如将 PAGE_READONLY 改为可写),也不会跳过 MM_SECURE_KERNEL_ADDRESS 或 MM_USER_PROBE_ADDRESS 校验。如果目标地址位于 VAD 中标记为不可读/不可写,或属于映射的设备内存(如显存)、Guard Page、或者处于 MEM_FREE 状态,函数会失败。实际做法是:
- 先用
MmIsAddressValid粗略判断地址是否在合法用户空间范围内(0x00000000–0x7FFFFFFFon x86,0x00000000'00000000–0x00007FFF'FFFFFFFFon x64) - 更可靠的是调用
MmQueryStatistics+ 手动遍历目标进程的 VAD 树(EPROCESS->VadRoot),确认地址落在MMVAD_SHORT描述的StartingVpn–EndingVpn内,且u.VadFlags.Protection允许所需操作 - 若需写入只读页,必须先用
MmProtectMdlSystemAddress或临时修改页表项(PTE),但这属于高风险操作,不推荐在稳定驱动中使用
兼容性与替代方案提醒
MmCopyVirtualMemory 在 Windows 10 RS1+ 后行为更严格,部分版本对 KernelMode → UserMode 的拷贝增加了额外校验;在 WDK 22H2 中,微软明确标注该函数为“internal use only”,不承诺导出稳定性。如果你的驱动需要长期维护:
- 优先考虑用
ZwReadVirtualMemory/ZwWriteVirtualMemory(需在用户模式代理进程里调用,绕开内核权限问题) - 若必须内核态操作,可用
ProbeForRead/ProbeForWrite配合memcpy(仅限当前进程上下文,且目标地址必须是调用者自己申请的缓冲区) - 所有涉及进程遍历、EPROCESS 访问的操作,在 Windows 10 1809+ 需启用
PsGetProcessId等新 API,并注意 PatchGuard 对 EPROCESS 偏移的随机化(不能硬编码字段偏移)
真正难的从来不是调用那个函数,而是确保整个内存路径上的每个环节——从进程引用、上下文切换、地址合法性、页保护状态到 WDK 版本适配——全部对齐。漏掉任意一环,蓝屏就是最诚实的反馈。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











