There are two files. One is LearnHanle.exe (It is spelled incorrectly, should be LearnHandle.exe), and the other is pop.exe.
Source code of these files are here:
// LearnHanle.cpp
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
int main() {
STARTUPINFOW startup = { 0 };
startup.cb = sizeof startup;
PROCESS_INFORMATION pi = { 0 };
BOOL createProcResult = FALSE;
if (!CreateProcessW(L"C:\\pop.exe", NULL, NULL, NULL, TRUE, 0, NULL, NULL, &startup, &pi))
MessageBoxA(NULL, "Create Process Failed", "Alert", MB_OK);
char handle[80] = { 0 };
sprintf_s(handle, "Process Handle: %llu ProcessId: %llu", pi.hProcess, pi.dwProcessId);
MessageBoxA(NULL, handle, "Process", MB_OK);
sprintf_s(handle, "Thread Handle: %llu ThreadId: %llu", pi.hThread, pi.dwThreadId);
MessageBoxA(NULL, handle, "Thread", MB_OK);
system("pause");
CloseHandle(pi.hThread);
CloseHandle(pi.hProcess);
}
// pop.cpp
#include <windows.h>
#include <stdlib.h>
#include <stdio.h>
int main() {
MessageBox(0, 0, 0, 0);
// Just sleep
Sleep(6000000);
}
LearnHanle.exe just creates pop.exe process then suspends itself.
I viewed the details of LearnHanle.exe process with Process Explorer, and I found the HANDLE, which associates with pop.exe process, and then I got the HANDLE address, as shown here:
Basic Information
Name: pop.exe(9092)
Type: Process
Description: Contains threads, an address space, and handles.
Address: 0xFFFFE786E04AF080
I debugged Windows 11 kernel with WinDbg, and I used !object command to view the HANDLE address. The output is here:
0: kd> !object 0xffffe786e04af080
Object: ffffe786e04af080 Type: (ffffe786da2cfd20) Process
ObjectHeader: ffffe786e04af050 (new version)
HandleCount: 9 PointerCount: 293899
The ObjectHeader pointer points at the Object Header of _EPROCESS of pop.exe process:
0: kd> dt _OBJECT_HEADER ffffe786e04af050
nt!_OBJECT_HEADER
+0x000 PointerCount : 0n293899
+0x008 HandleCount : 0n9
+0x008 NextToFree : 0x00000000`00000009 Void
+0x010 Lock : _EX_PUSH_LOCK
+0x018 TypeIndex : 0x47 'G'
+0x019 TraceFlags : 0 ''
+0x019 DbgRefTrace : 0y0
+0x019 DbgTracePermanent : 0y0
+0x01a InfoMask : 0x88 ''
+0x01b Flags : 0 ''
+0x01b NewObject : 0y0
+0x01b KernelObject : 0y0
+0x01b KernelOnlyAccess : 0y0
+0x01b ExclusiveObject : 0y0
+0x01b PermanentObject : 0y0
+0x01b DefaultSecurityQuota : 0y0
+0x01b SingleHandleEntry : 0y0
+0x01b DeletedInline : 0y0
+0x01c Reserved : 0
+0x020 ObjectCreateInfo : 0xffffe786`dca5ccc0 _OBJECT_CREATE_INFORMATION
+0x020 QuotaBlockCharged : 0xffffe786`dca5ccc0 Void
+0x028 SecurityDescriptor : 0xffffd705`63f2962f Void
+0x030 Body : _QUAD
However,
0: kd> dq 0xffffe786e04af080
ffffe786`e04af080 00000000`00000003 ffffe786`e04af088
ffffe786`e04af090 ffffe786`e04af088 ffffe786`e04af098
ffffe786`e04af0a0 ffffe786`e04af098 00000000`9e0b9000
ffffe786`e04af0b0 ffffe786`df8ec378 ffffe786`df8ec378
ffffe786`e04af0c0 00000000`00000000 00000000`00000000
ffffe786`e04af0d0 00000000`00200001 00000000`0000000f
ffffe786`e04af0e0 00000000`00000000 00000000`00000000
ffffe786`e04af0f0 00000000`00000000 00000000`00000000
I don't know what the address stores, and how !object command gets pop.exe process' information.
Then I used !process command to view the host process LearnHanle.exe.
0: kd> !process 0 0 LearnHanle.exe
PROCESS ffffe786e04020c0
SessionId: 1 Cid: 22a8 Peb: 0036b000 ParentCid: 1344
DirBase: 999c2000 ObjectTable: ffffd70567953400 HandleCount: 121.
Image: LearnHanle.exe
I looked at LearnHanle.exe _HANDLE_TABLE.
0: kd> dt _HANDLE_TABLE ffffd70567953400
nt!_HANDLE_TABLE
+0x000 NextHandleNeedingPool : 0x400
+0x004 ExtraInfoPages : 0n0
+0x008 TableCode : 0xffffd705`6645a000
+0x010 QuotaProcess : 0xffffe786`e04020c0 _EPROCESS
+0x018 HandleTableList : _LIST_ENTRY [ 0xffffd705`67953bd8 - 0xffffd705`67133918 ]
+0x028 UniqueProcessId : 0x22a8
+0x02c Flags : 0
+0x02c StrictFIFO : 0y0
+0x02c EnableHandleExceptions : 0y0
+0x02c Rundown : 0y0
+0x02c Duplicated : 0y0
+0x02c RaiseUMExceptionOnInvalidHandleClose : 0y0
+0x030 HandleContentionEvent : _EX_PUSH_LOCK
+0x038 HandleTableLock : _EX_PUSH_LOCK
+0x040 FreeLists : [1] _HANDLE_TABLE_FREE_LIST
+0x040 ActualEntry : [32] ""
+0x060 DebugInfo : (null)
0: kd> dt _HANDLE_TABLE_ENTRY
nt!_HANDLE_TABLE_ENTRY
+0x000 VolatileLowValue : Int8B
+0x000 LowValue : Int8B
+0x000 InfoTable : Ptr64 _HANDLE_TABLE_ENTRY_INFO
+0x008 HighValue : Int8B
+0x008 NextFreeHandleEntry : Ptr64 _HANDLE_TABLE_ENTRY
+0x008 LeafHandleValue : _EXHANDLE
+0x000 RefCountField : Int8B
+0x000 Unlocked : Pos 0, 1 Bit
+0x000 RefCnt : Pos 1, 16 Bits
+0x000 Attributes : Pos 17, 3 Bits
+0x000 ObjectPointerBits : Pos 20, 44 Bits
+0x008 GrantedAccessBits : Pos 0, 25 Bits
+0x008 NoRightsUpgrade : Pos 25, 1 Bit
+0x008 Spare1 : Pos 26, 6 Bits
+0x00c Spare2 : Uint4B
I want to know how handle table entry in handle table of process to associates with corresponding object type.
And I would also like to know the purpose of these two members: struct _HANDLE_TABLE_ENTRY::ObjectPointerBits, struct _OBJECT_HEADER::TypeIndex and struct _KPROCESS::Header(_DISPATCHER_HEADER).
As an example I'm going to take a file handle (0x1c8) from the explorer process (6496):
Start with the handle table from the
_EPROCESS:The
TableCodefield points to an array of tables, the lower bits determining which table should be used:The code from the
nt!ExpLookupHandleTableEntryhelps to determine how to use the table array (first param is a_HANDLE_TABLE*in RCX, and second param is the handle, in RDX):In my case it's just the first table in the array, so the handle entry is at
table_base + (handle * 4). Even though each table entry is a_HANDLE_TABLE_ENTRY(sizeof 0x10), each handle is a multiple of 4 (so 0x10 / 4 = 4):The table entry:
At least two interesting stuff here:
GrantedAccessBits: which is the granted access mask for the object (here we have0x100001, which for a file object it's justSYNCHRONIZE (0x10000) | FILE_READ_DATA (0x1)ObjectPointerBits: which after a calculation will be a pointer to the_OBJECT_HEADERof the object.From the
ObjectPointerBitsvalue you'll need to do the following to get the object header address:(ObjectPointerBits << 4) | 0xffff000000000000From the object header you can get the object type by doing the following calculation:
*((BYTE*)nt!ObHeaderCookie) ^ second_byte_of_object_header_address ^ _OBJECT_HEADER.TypeIndexThe above result (0x28 in this case) is an index into the kernel types array, which is a global variable named
nt!ObTypeIndexTable(an array of_OBJECT_TYPE):So it's a file object ( represented by a
nt!FILE_OBJECT).If an object is waitable (i.e. its handle can be passed to
WaitForSingleObjectorWaitForMutipleObjects) then it has a_DISPATCH_HEADER.