使用NTQuerySystemInformation枚举系统句柄时触发Access Violation
问题描述
我正在编写一个C#类,用来返回指定进程ID对应的所有系统句柄。研究未公开函数调用及返回值后遇到以下问题:
- Handlecount与任务管理器报告的句柄数大致匹配(相差约5%)
- 执行
Marshal.ReadInt64(handle.POwnerPID)(位于GetHandles()方法中)获取ProcessID指针值时,触发Access Violation异常,提示内存受保护或已损坏 - 程序及调试器均以管理员身份运行,怀疑是结构体定义不正确,但查阅多个资料均指向相同的结构体大小
单个句柄示例数据
AccessMask 0 uint CreatorBackTraceIndex 0 ushort HandleFlags 3503129008 uint ObjectType 0 ushort PHandleValue 0x002c00000012007f System.IntPtr PObject 0x0000000000000004 System.IntPtr POwnerPID 0x00000000000007f8 System.IntPtr Reserve 4294956941 uint
补充说明
选择SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX而非基础函数的原因:基础函数仅支持ushort范围的PID。
当前实现代码
private enum NTSTATUS : uint { STATUS_SUCCESS = 0x00000000, STATUS_INFO_LENGTH_MISMATCH = 0xC0000004 } [Flags] private enum SYSTEM_INFORMATION_CLASS : uint { SystemHandleInformation = 16, SYSTEM_EXTENDED_HANDLE_INFORMATION = 64, } [StructLayout(LayoutKind.Sequential)] public struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX { public IntPtr PObject; // public IntPtr POwnerPID; public IntPtr PHandleValue; public uint AccessMask; public ushort CreatorBackTraceIndex; public ushort ObjectType; public uint HandleFlags; public uint Reserve; } static List<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> GetAllHandles() { int bufferSize = 0x10000; IntPtr buffer = Marshal.AllocHGlobal(bufferSize); int requiredSize; NTSTATUS status = NtQuerySystemInformation( SYSTEM_INFORMATION_CLASS.SYSTEM_EXTENDED_HANDLE_INFORMATION, buffer, bufferSize, out requiredSize ); while (status == NTSTATUS.STATUS_INFO_LENGTH_MISMATCH) { Marshal.FreeHGlobal(buffer); bufferSize = requiredSize; buffer = Marshal.AllocHGlobal(bufferSize); status = NtQuerySystemInformation( SYSTEM_INFORMATION_CLASS.SYSTEM_EXTENDED_HANDLE_INFORMATION, buffer, bufferSize, out requiredSize ); } if (status != NTSTATUS.STATUS_SUCCESS) { Marshal.FreeHGlobal(buffer); return new List<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX>(); } List<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> handles = new List<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX>(); long baseAddress = buffer.ToInt64(); long handleCount = Marshal.ReadInt64(buffer); int structSize = Marshal.SizeOf(typeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX)); for (long i = 0; i < handleCount; i++) { IntPtr current = new IntPtr(baseAddress + (2*IntPtr.Size) + (i * structSize)); //EDIT1: Array of handles starts after two pointer sizes in struct, see comments for more info. SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handleInfo = Marshal.PtrToStructure<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX>(current); handles.Add(handleInfo); } Marshal.FreeHGlobal(buffer); return handles; } public static List<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> GetHandles(Process targetProcess, List<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> handles) { List<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> processHandles = new List<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX>(); foreach (var handle in handles) { long proid = Marshal.ReadInt64(handle.POwnerPID); if (Marshal.ReadInt32(handle.POwnerPID) == targetProcess.Id) { processHandles.Add(handle); } } return processHandles; }
解决方案
核心问题是对SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX结构体的POwnerPID字段理解错误:它不是指向PID的指针,而是直接存储PID的数值,无需通过Marshal读取指针指向的内存。
修正步骤
- 修正结构体定义:将
POwnerPID改为UIntPtr类型,适配32/64位系统的数值存储:[StructLayout(LayoutKind.Sequential)] public struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX { public IntPtr PObject; public UIntPtr POwnerPID; // 直接存储PID数值,非指针 public IntPtr PHandleValue; public uint AccessMask; public ushort CreatorBackTraceIndex; public ushort ObjectType; public uint HandleFlags; public uint Reserve; } - 修改GetHandles方法:直接读取
POwnerPID的数值,无需调用Marshal.ReadInt64:public static List<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> GetHandles(Process targetProcess, List<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> handles) { List<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> processHandles = new List<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX>(); uint targetId = (uint)targetProcess.Id; foreach (var handle in handles) { if (handle.POwnerPID.ToUInt32() == targetId) { processHandles.Add(handle); } } return processHandles; } - 确认函数声明正确性:确保
NtQuerySystemInformation的调用约定匹配Windows API:[DllImport("ntdll.dll", SetLastError = true)] private static extern NTSTATUS NtQuerySystemInformation( SYSTEM_INFORMATION_CLASS systemInformationClass, IntPtr systemInformation, int systemInformationLength, out int returnLength );
原因解释
SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX中的POwnerPID是进程的句柄值,Windows中该句柄的数值与进程ID直接一致。你之前错误地将其视为指向PID的指针,尝试读取其指向的内存(比如示例中的0x7f8),但该地址属于内核态内存或无效地址,用户态程序无法访问,因此触发Access Violation异常。
内容的提问来源于stack exchange,提问作者mrpiggy
相关产品推荐
相关产品推荐

