OBJECT_ATTRIBUTES的RootDirectory转wstring及路径拼接遇访问违例求解
问题分析与修复方案
首先得说,你踩了几个内核编程里的典型坑,咱们一步步拆解:
第一个错误:直接把RootDirectory转std::wstring的根源
ObjectAttributes->RootDirectory是内核对象句柄(HANDLE),指向的是内核中的对象,根本不是用户态的std::wstring内存结构!你强行把它转成std::wstring*去解引用,相当于直接访问了随机内核内存地址,不触发访问违例才怪。而且内核环境完全不支持用户态的std::wstring,必须用内核原生的UNICODE_STRING结构来处理字符串。
第二个错误:内核函数加载与内存分配的问题
你的代码里有几个致命的错误点:
- 错误加载NtosKrnl:
GetModuleHandle(TEXT("NtosKrnl.lib"))完全错误,内核模块是ntoskrnl.exe,而且在驱动程序中,你根本不需要用用户态的GetModuleHandle和GetProcAddress来获取内核函数——直接声明使用即可,或者用MmGetSystemRoutineAddress做动态获取。用户态的GetProcAddress在驱动里是未定义行为,这也是你调用ExAllocatePoolWithTag触发错误的核心原因之一。 - ExAllocatePoolWithTag的使用问题:现代Windows内核中,
ExAllocatePoolWithTag已经被标记为废弃,推荐使用ExAllocatePool2,而且必须确保分配的是非分页池(NonPagedPool),因为ZwQueryObject可能在任意IRQL下执行。 - ZwQueryObject的参数错误:最后一个参数
ULONG* ReturnLength不能传NULL,如果你的缓冲区大小不够,函数会返回STATUS_BUFFER_TOO_SMALL,并通过这个参数告诉你需要的准确大小。直接传NULL会导致缓冲区不够时触发内存越界。 - 内存泄漏:你分配的
kObjectName.Buffer、nameInformation、full_path.Buffer都没有释放,这会导致内核内存泄漏,严重的话会拖垮系统。
修复后的完整代码(仅实现路径拼接逻辑)
#include <ntddk.h> // 驱动中直接声明内核导出函数即可,无需动态加载 extern "C" NTSTATUS ZwQueryObject( _In_ HANDLE Handle, _In_ OBJECT_INFORMATION_CLASS ObjectInformationClass, _Out_writes_bytes_opt_(ObjectInformationLength) PVOID ObjectInformation, _In_ ULONG ObjectInformationLength, _Out_opt_ PULONG ReturnLength ); NTSTATUS __stdcall ZwOpenKey_Hook( OUT PHANDLE pKeyHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes ) { NTSTATUS status = STATUS_SUCCESS; if (ObjectAttributes->RootDirectory != NULL && ObjectAttributes->ObjectName != NULL) { UNICODE_STRING full_path = {0}; POBJECT_NAME_INFORMATION name_info = NULL; ULONG required_size = 0; // 第一步:获取RootDirectory名称所需的内存大小 status = ZwQueryObject( ObjectAttributes->RootDirectory, ObjectNameInformation, NULL, 0, &required_size ); if (status != STATUS_BUFFER_TOO_SMALL) { goto Cleanup; } // 分配足够的内存存储名称信息 name_info = (POBJECT_NAME_INFORMATION)ExAllocatePool2( POOL_FLAG_NON_PAGED, required_size, 'mmoP' ); if (!name_info) { status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } // 第二步:实际获取RootDirectory的名称 status = ZwQueryObject( ObjectAttributes->RootDirectory, ObjectNameInformation, name_info, required_size, NULL ); if (!NT_SUCCESS(status)) { goto Cleanup; } // 计算完整路径所需的总大小:根路径长度 + 子路径长度 + 路径分隔符的字节数 ULONG total_size = name_info->Name.Length + ObjectAttributes->ObjectName->Length + sizeof(WCHAR); full_path.Buffer = (PWSTR)ExAllocatePool2( POOL_FLAG_NON_PAGED, total_size, 'mmoP' ); if (!full_path.Buffer) { status = STATUS_INSUFFICIENT_RESOURCES; goto Cleanup; } full_path.Length = 0; full_path.MaximumLength = total_size; // 第三步:拼接路径(避免重复的路径分隔符) RtlCopyUnicodeString(&full_path, &name_info->Name); if (full_path.Length > 0 && full_path.Buffer[full_path.Length / sizeof(WCHAR) - 1] != L'\\') { RtlAppendUnicodeToString(&full_path, L"\\"); } RtlAppendUnicodeStringToString(&full_path, ObjectAttributes->ObjectName); // 这里可以添加你的业务逻辑,比如打印完整路径 DbgPrint("Full Registry Path: %wZ\n", &full_path); Cleanup: // 必须释放所有分配的内核内存 if (full_path.Buffer) { ExFreePool(full_path.Buffer); } if (name_info) { ExFreePool(name_info); } } // 调用原始的ZwOpenKey函数 return ZwOpenKey(pKeyHandle, DesiredAccess, ObjectAttributes); }
关键注意事项
- 内核环境禁用用户态STL:
std::wstring这类用户态容器在内核中没有支持,强行使用会引发各种内存错误。 - 内核函数调用规范:驱动中直接使用内核导出函数即可,不要用用户态的加载方式;如果需要动态获取,用
MmGetSystemRoutineAddress。 - 内存严格管理:内核中分配的内存必须手动释放,哪怕是在错误分支里,否则会造成永久内存泄漏。
- IRQL检查:确保代码运行在合适的IRQL下,
ExAllocatePool2搭配POOL_FLAG_NON_PAGED可以在DISPATCH_LEVEL及以下使用,ZwQueryObject建议在PASSIVE_LEVEL执行。
内容的提问来源于stack exchange,提问作者99Boboster99
相关产品推荐
相关产品推荐

