Search code examples
c#pointerswinapi

Access Violation using NTQuerySystemInformation to iterate Windows system handles and filter by ProcessId


I am trying to create a C# class that is able to return all system handles of a process given by its Process ID. After some research of the mostly undocumented function calls and return values needed, I encountered the following problem.

Handlecount roughly matches the handle count reported by Task Manager (5%-ish less?). However, whenever I try to get the ProcessID's pointer value:

Marshal.ReadInt64(handle.POwnerPID) // in GetHandles()

I get an Access Violation exception --> either memory is protected or corrupted. However, program and debugger are run as admin. My guess is that my structures are not correct? I have searched multiple sources which all point to the same structure size.

An example of a handle from the list:

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

Sidenotes:

Why SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX is used and not the base function (base only supports PIDs up to ushort size):

Why can't get process id that more than 65535 by 'ntQuerySystemInformation' in Win7 64bit?

Used documentation:

https://www.geoffchappell.com/studies/windows/km/ntoskrnl/api/ex/sysinfo/handle_table_entry_ex.htm?ts=0,219

Current implementation:

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;
}

Solution

  • POwnerPID isn't a pointer, it's just a pointer-sized handle. You should not de-reference it.

    So you can simplify the code significantly as well.

    public static IEnumerable<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> GetHandles(Process targetProcess, IEnumerable<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX> handles)
    {
        return handles.Where(h => h.POwnerPID == targetProcess.Id);
    }