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

使用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读取指针指向的内存。

修正步骤

  1. 修正结构体定义:将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;
    }
    
  2. 修改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;
    }
    
  3. 确认函数声明正确性:确保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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 13:04:57