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

C++中通过NtQuerySystemInformation获取FILE_OBJECT时遇访问违例

问题分析与解决方案

核心错误原因

你触发访问违例的根本原因是:SYSTEM_HANDLE_INFORMATION结构体中的Object字段是内核虚拟地址,用户态进程没有权限直接读写内核地址空间。直接对该地址调用memcpy或解引用,会被系统的内存保护机制拦截,触发访问错误。

正确的访问方式

要获取文件对象的权限、文件名等信息,必须通过Windows内核提供的官方接口NtQueryObject来查询,而不是直接访问内核地址。以下是具体步骤和代码示例:

1. 过滤目标句柄

遍历NtQuerySystemInformation返回的SYSTEM_HANDLE_INFORMATION条目,筛选出当前进程PID、且对象类型为文件的句柄(建议通过类型名称判断,而非固定索引37,避免版本兼容性问题)。

2. 调用NtQueryObject获取对象信息

针对每个符合条件的句柄,调用NtQueryObject并指定不同的信息类,获取所需数据:

获取文件路径(ObjectNameInformation)

#include <ntifs.h>
#include <winternl.h>

// 需要链接ntdll.lib
#pragma comment(lib, "ntdll.lib")

void GetFileName(HANDLE hFile) {
    UNICODE_STRING* pObjectName = nullptr;
    ULONG returnLen = 0;

    // 先调用一次获取所需缓冲区大小
    NtQueryObject(hFile, ObjectNameInformation, nullptr, 0, &returnLen);
    if (returnLen == 0) return;

    PVOID buffer = LocalAlloc(LPTR, returnLen);
    if (!buffer) return;

    NTSTATUS status = NtQueryObject(hFile, ObjectNameInformation, buffer, returnLen, &returnLen);
    if (NT_SUCCESS(status)) {
        pObjectName = static_cast<UNICODE_STRING*>(buffer);
        if (pObjectName->Buffer != nullptr) {
            wprintf(L"文件路径: %s\n", pObjectName->Buffer);
        }
    }

    LocalFree(buffer);
}

获取读写权限(ObjectBasicInformation)

通过OBJECT_BASIC_INFORMATION中的GrantedAccess字段,解析文件对象的访问权限掩码:

void GetFileAccessRights(HANDLE hFile) {
    OBJECT_BASIC_INFORMATION objBasicInfo = {0};
    ULONG returnLen = 0;

    NTSTATUS status = NtQueryObject(hFile, ObjectBasicInformation, &objBasicInfo, sizeof(objBasicInfo), &returnLen);
    if (!NT_SUCCESS(status)) return;

    BOOL hasRead = (objBasicInfo.GrantedAccess & FILE_READ_DATA) != 0;
    BOOL hasWrite = (objBasicInfo.GrantedAccess & FILE_WRITE_DATA) != 0;

    printf("是否可读: %s\n", hasRead ? "是" : "否");
    printf("是否可写: %s\n", hasWrite ? "是" : "否");
}

验证对象类型(ObjectTypeInformation)

为了避免依赖固定的对象类型索引,通过OBJECT_TYPE_INFORMATION获取类型名称来确认是否为文件对象:

BOOL IsFileObject(HANDLE hObject) {
    OBJECT_TYPE_INFORMATION* pObjTypeInfo = nullptr;
    ULONG returnLen = 0;

    NtQueryObject(hObject, ObjectTypeInformation, nullptr, 0, &returnLen);
    if (returnLen == 0) return FALSE;

    PVOID buffer = LocalAlloc(LPTR, returnLen);
    if (!buffer) return FALSE;

    NTSTATUS status = NtQueryObject(hObject, ObjectTypeInformation, buffer, returnLen, &returnLen);
    BOOL isFile = FALSE;
    if (NT_SUCCESS(status)) {
        pObjTypeInfo = static_cast<OBJECT_TYPE_INFORMATION*>(buffer);
        // 比较类型名称是否为"File"
        if (RtlCompareUnicodeString(&pObjTypeInfo->TypeName, &RTL_CONSTANT_STRING(L"File"), TRUE) == 0) {
            isFile = TRUE;
        }
    }

    LocalFree(buffer);
    return isFile;
}

3. 完整流程示例

void EnumerateCurrentProcessFileHandles() {
    ULONG bufferSize = 0x10000;
    PSYSTEM_HANDLE_INFORMATION pHandleInfo = nullptr;
    NTSTATUS status;

    // 动态分配缓冲区获取系统句柄信息
    while (true) {
        pHandleInfo = static_cast<PSYSTEM_HANDLE_INFORMATION>(LocalAlloc(LPTR, bufferSize));
        if (!pHandleInfo) break;

        status = NtQuerySystemInformation(SystemHandleInformation, pHandleInfo, bufferSize, &bufferSize);
        if (NT_SUCCESS(status)) {
            break;
        } else if (status == STATUS_INFO_LENGTH_MISMATCH) {
            LocalFree(pHandleInfo);
            bufferSize *= 2;
        } else {
            LocalFree(pHandleInfo);
            return;
        }
    }

    if (!pHandleInfo) return;

    HANDLE hCurrentProcess = GetCurrentProcess();
    DWORD currentPid = GetCurrentProcessId();

    // 遍历所有句柄
    for (ULONG i = 0; i < pHandleInfo->HandleCount; ++i) {
        SYSTEM_HANDLE_TABLE_ENTRY_INFO handleEntry = pHandleInfo->Handles[i];
        if (handleEntry.UniqueProcessId != currentPid) continue;

        // 复制句柄到当前进程(确保句柄可访问)
        HANDLE hDuplicate = nullptr;
        status = NtDuplicateObject(hCurrentProcess, reinterpret_cast<HANDLE>(handleEntry.HandleValue),
                                   hCurrentProcess, &hDuplicate, 0, 0, DUPLICATE_SAME_ACCESS);
        if (!NT_SUCCESS(status)) continue;

        // 判断是否为文件对象
        if (IsFileObject(hDuplicate)) {
            wprintf(L"句柄值: 0x%X\n", handleEntry.HandleValue);
            GetFileName(hDuplicate);
            GetFileAccessRights(hDuplicate);
            wprintf(L"-------------------------\n");
        }

        NtClose(hDuplicate);
    }

    LocalFree(pHandleInfo);
}

注意事项

  • 确保代码链接ntdll.lib,并包含winternl.h头文件。
  • NtQuerySystemInformation和NtQueryObject属于未公开API,使用时需注意Windows版本兼容性。
  • 避免硬编码对象类型索引(如37),通过类型名称判断兼容性更好。

内容的提问来源于stack exchange,提问作者MasiaDunajski

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 06:32:33