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
相关产品推荐
相关产品推荐

