使用MmCopyMemory从内核写入用户态地址空间失败问题排查
KMDF驱动调用MmCopyMemory触发IRQL_NOT_LESS_OR_EQUAL BSOD的排查与修复
问题根源分析
你的代码存在几个致命问题,直接引发了IRQL_NOT_LESS_OR_EQUAL崩溃:
- 未校验API返回值:
PsLookupProcessByProcessId可能执行失败(比如目标进程已退出),此时process为NULL,后续KeStackAttachProcess会直接触发崩溃。 - 跨地址空间访问栈内存:
Temp是当前线程栈上的数组,调用KeStackAttachProcess切换到目标进程地址空间后,该栈地址在目标进程中完全无效。MmCopyMemory尝试访问这个无效地址时会触发页错误,而如果回调例程(如MyLoadImageNotifyRoutine)运行在DISPATCH_LEVEL及以上IRQL,页错误无法被异常处理捕获,直接导致BSOD。 - 异常处理无效:
__try/__except在DISPATCH_LEVEL及以上IRQL环境下无法正常工作,这类IRQL下的异常会直接引发系统崩溃。
修复方案及代码修正
针对上述问题,给出以下修复步骤及修正后的代码:
- 严格校验API返回值:必须检查
PsLookupProcessByProcessId的返回状态,失败则直接返回,避免后续非法操作。 - 使用非分页内存存储源数据:非分页内存属于系统全局地址空间,在任何进程地址空间中都可访问,彻底避免跨地址空间的无效内存访问问题。
- 保留合理的异常处理:
LoadImageNotifyRoutine运行在PASSIVE_LEVEL,此时异常处理可正常工作,用于捕获内存访问类错误。
修正后的代码:
VOID WriteToMemoryMappedSection(HANDLE ProcessId, PVOID TargetMemoryAddress) { PEPROCESS process = NULL; KAPC_STATE apcState; MM_COPY_ADDRESS virtualSourceAddress = { 0 }; size_t NumberOfBytesTransferred = 0; NTSTATUS status = STATUS_SUCCESS; // 分配非分页内存存储补丁数据 PCHAR Temp = (PCHAR)ExAllocatePool2(POOL_FLAG_NON_PAGED, 2, 'Patc'); if (!Temp) { DbgPrint("Failed to allocate non-paged pool\n"); return; } Temp[0] = 0x90; Temp[1] = 0x90; // 校验进程查找结果 status = PsLookupProcessByProcessId(ProcessId, &process); if (!NT_SUCCESS(status)) { DbgPrint("PsLookupProcessByProcessId failed: 0x%08X\n", status); ExFreePool(Temp); return; } KeStackAttachProcess(process, &apcState); virtualSourceAddress.VirtualAddress = Temp; __try { status = MmCopyMemory( (PVOID)((ULONG64)TargetMemoryAddress + 0x1000), virtualSourceAddress, 2, MM_COPY_MEMORY_VIRTUAL, &NumberOfBytesTransferred ); if (!NT_SUCCESS(status)) { DbgPrint("MmCopyMemory failed: 0x%08X\n", status); } } __except (EXCEPTION_EXECUTE_HANDLER) { DbgPrint("Exception occurred: 0x%08X\n", GetExceptionCode()); } KeUnstackDetachProcess(&apcState); ObDereferenceObject(process); ExFreePool(Temp); } // 回调例程声明 VOID MyLoadImageNotifyRoutine( _In_opt_ PUNICODE_STRING FullImageName, _In_ HANDLE ProcessId, _In_ PIMAGE_INFO ImageInfo );
额外注意事项
- 目标地址有效性:需确保
TargetMemoryAddress + 0x1000是目标进程中已分配且具备可写权限的内存,否则MmCopyMemory仍会失败或触发异常。 - 池内存管理:使用
ExAllocatePool2时需指定唯一的池标签(如示例中的'Patc'),便于调试时追踪内存泄漏问题。 - IRQL确认:可通过
KeGetCurrentIrql()检查当前执行的IRQL,确保所有操作符合对应IRQL的限制(比如PASSIVE_LEVEL下可使用分页内存,但跨地址空间访问仍需谨慎)。
内容的提问来源于stack exchange,提问作者blogger13
相关产品推荐
相关产品推荐

