You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OBJECT_ATTRIBUTES的RootDirectory转wstring及路径拼接遇访问违例求解

问题分析与修复方案

首先得说,你踩了几个内核编程里的典型坑,咱们一步步拆解:

第一个错误:直接把RootDirectory转std::wstring的根源

ObjectAttributes->RootDirectory是内核对象句柄(HANDLE),指向的是内核中的对象,根本不是用户态的std::wstring内存结构!你强行把它转成std::wstring*去解引用,相当于直接访问了随机内核内存地址,不触发访问违例才怪。而且内核环境完全不支持用户态的std::wstring,必须用内核原生的UNICODE_STRING结构来处理字符串。

第二个错误:内核函数加载与内存分配的问题

你的代码里有几个致命的错误点:

  1. 错误加载NtosKrnl:GetModuleHandle(TEXT("NtosKrnl.lib"))完全错误,内核模块是ntoskrnl.exe,而且在驱动程序中,你根本不需要用用户态的GetModuleHandle和GetProcAddress来获取内核函数——直接声明使用即可,或者用MmGetSystemRoutineAddress做动态获取。用户态的GetProcAddress在驱动里是未定义行为,这也是你调用ExAllocatePoolWithTag触发错误的核心原因之一。
  2. ExAllocatePoolWithTag的使用问题:现代Windows内核中,ExAllocatePoolWithTag已经被标记为废弃,推荐使用ExAllocatePool2,而且必须确保分配的是非分页池(NonPagedPool),因为ZwQueryObject可能在任意IRQL下执行。
  3. ZwQueryObject的参数错误:最后一个参数ULONG* ReturnLength不能传NULL,如果你的缓冲区大小不够,函数会返回STATUS_BUFFER_TOO_SMALL,并通过这个参数告诉你需要的准确大小。直接传NULL会导致缓冲区不够时触发内存越界。
  4. 内存泄漏:你分配的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 09:22:46