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