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):
1: kd> !handle 0x1c8 3 0n6496
PROCESS ffffe4856647a080
SessionId: 2 Cid: 1960 Peb: 00238000 ParentCid: 1904
DirBase: 1409e3002 ObjectTable: ffffd10029c47740 HandleCount: 2981.
Image: explorer.exe
Handle table at ffffd10029c47740 with 2981 entries in use
01c8: Object: ffffe48565dd7110 GrantedAccess: 00100001 (Inherit) (Audit) Entry: ffffd10029ff9720
Object: ffffe48565dd7110 Type: (ffffe485608f7220) File
ObjectHeader: ffffe48565dd70e0 (new version)
HandleCount: 1 PointerCount: 32783
Directory Object: 00000000 Name: \Windows\en-US\explorer.exe.mui {HarddiskVolume3}
Start with the handle table from the _EPROCESS
:
0: kd> dt nt!_eprocess ffffe4856647a080 -y ObjectTable
nt!_EPROCESS
+0x570 ObjectTable : 0xffffd100`29c47740 _HANDLE_TABLE
0: kd> dt nt!_handle_table 0xffffd100`29c47740
nt!_HANDLE_TABLE
+0x000 NextHandleNeedingPool : 0x3800
+0x004 ExtraInfoPages : 0n0
+0x008 TableCode : 0xffffd100`29ef4001
+0x010 QuotaProcess : 0xffffe485`6647a080 _EPROCESS
+0x018 HandleTableList : _LIST_ENTRY [ 0xffffd100`29c47418 - 0xffffd100`29c47d98 ]
+0x028 UniqueProcessId : 0x1960
+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)
The TableCode
field points to an array of tables, the lower bits determining which table should be used:
0: kd> dq 0xffffd100`29ef4000
ffffd100`29ef4000 ffffd100`29ff9000 ffffd100`29ef5000
ffffd100`29ef4010 ffffd100`255fd000 ffffd100`29d96000
ffffd100`29ef4020 ffffd100`2a1fd000 ffffd100`2a0f7000
ffffd100`29ef4030 ffffd100`258bc000 ffffd100`25b5a000
ffffd100`29ef4040 ffffd100`25bff000 ffffd100`25cfc000
ffffd100`29ef4050 ffffd100`25b57000 ffffd100`25dfe000
ffffd100`29ef4060 ffffd100`258c6000 ffffd100`2802c000
ffffd100`29ef4070 00000000`00000000 00000000`00000000
The code from the nt!ExpLookupHandleTableEntry
helps to determine how to use the table array (first param is a _HANDLE_TABLE*
in RCX, and second param is the handle, in RDX):
PAGE:00000001407427E0 ExpLookupHandleTableEntry proc near ; CODE XREF: ObDuplicateObject+180↑p
PAGE:00000001407427E0 ; ExMapHandleToPointer+11↑p ...
PAGE:00000001407427E0
PAGE:00000001407427E0 ; FUNCTION CHUNK AT PAGE:0000000140895396 SIZE 00000022 BYTES
PAGE:00000001407427E0
PAGE:00000001407427E0 mov eax, [rcx+_HANDLE_TABLE.NextHandleNeedingPool]
PAGE:00000001407427E2 and rdx, 0FFFFFFFFFFFFFFFCh
PAGE:00000001407427E6 cmp rdx, rax
PAGE:00000001407427E9 jnb short loc_140742820
PAGE:00000001407427EB mov r8, [rcx+_HANDLE_TABLE.TableCode]
PAGE:00000001407427EF mov eax, r8d
PAGE:00000001407427F2 and eax, 3
PAGE:00000001407427F5 cmp eax, 1
PAGE:00000001407427F8 jnz short loc_140742812
PAGE:00000001407427FA mov rax, rdx
PAGE:00000001407427FD shr rax, 0Ah
PAGE:0000000140742801 mov rax, [r8+rax*8-1]
PAGE:0000000140742806
PAGE:0000000140742806 loc_140742806: ; CODE XREF: ExpLookupHandleTableEntry+152BD3↓j
PAGE:0000000140742806 and edx, 3FFh
PAGE:000000014074280C lea rax, [rax+rdx*4]
PAGE:0000000140742810 retn
PAGE:0000000140742810 ; ---------------------------------------------------------------------------
PAGE:0000000140742811 align 2
PAGE:0000000140742812
PAGE:0000000140742812 loc_140742812: ; CODE XREF: ExpLookupHandleTableEntry+18↑j
PAGE:0000000140742812 test eax, eax
PAGE:0000000140742814 jnz loc_140895396
PAGE:000000014074281A lea rax, [r8+rdx*4]
PAGE:000000014074281E retn
PAGE:000000014074281E ; ---------------------------------------------------------------------------
PAGE:000000014074281F align 20h
PAGE:0000000140742820
PAGE:0000000140742820 loc_140742820: ; CODE XREF: ExpLookupHandleTableEntry+9↑j
PAGE:0000000140742820 xor eax, eax
PAGE:0000000140742822 retn
PAGE:0000000140742822 ExpLookupHandleTableEntry endp
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):
1: kd> ? ffffd100`29ff9000 + (0x1c8 * 4)
Evaluate expression: -51676341889248 = ffffd100`29ff9720
The table entry:
1: kd> dt _handle_table_entry ffffd100`29ff9720
nt!_HANDLE_TABLE_ENTRY
+0x000 VolatileLowValue : 0n-1980064459403493377
+0x000 LowValue : 0n-1980064459403493377
+0x000 InfoTable : 0xe48565dd`70e0ffff _HANDLE_TABLE_ENTRY_INFO
+0x008 HighValue : 0n1048577
+0x008 NextFreeHandleEntry : 0x00000000`00100001 _HANDLE_TABLE_ENTRY
+0x008 LeafHandleValue : _EXHANDLE
+0x000 RefCountField : 0n-1980064459403493377
+0x000 Unlocked : 0y1
+0x000 RefCnt : 0y0111111111111111 (0x7fff)
+0x000 Attributes : 0y000
+0x000 ObjectPointerBits : 0y11100100100001010110010111011101011100001110 (0xe48565dd70e)
+0x008 GrantedAccessBits : 0y0000100000000000000000001 (0x100001)
+0x008 NoRightsUpgrade : 0y0
+0x008 Spare1 : 0y000000 (0)
+0x00c Spare2 : 0
At least two interesting stuff here:
GrantedAccessBits
: which is the granted access mask for the object (here we have 0x100001
, which for a file object it's just SYNCHRONIZE (0x10000) | FILE_READ_DATA (0x1)
ObjectPointerBits
: which after a calculation will be a pointer to the _OBJECT_HEADER
of the object.
From the ObjectPointerBits
value you'll need to do the following to get the object header address: (ObjectPointerBits << 4) | 0xffff000000000000
1: kd> ? (0xe48565dd70e << 4) | 0xffff000000000000
Evaluate expression: -30213385916192 = ffffe485`65dd70e0
1: kd> dt _object_header ffffe485`65dd70e0
nt!_OBJECT_HEADER
+0x000 PointerCount : 0n32783
+0x008 HandleCount : 0n1
+0x008 NextToFree : 0x00000000`00000001 Void
+0x010 Lock : _EX_PUSH_LOCK
+0x018 TypeIndex : 0x14 ''
+0x019 TraceFlags : 0 ''
+0x019 DbgRefTrace : 0y0
+0x019 DbgTracePermanent : 0y0
+0x01a InfoMask : 0x4c 'L'
+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 : 0xffffe485`62c5ecc0 _OBJECT_CREATE_INFORMATION
+0x020 QuotaBlockCharged : 0xffffe485`62c5ecc0 Void
+0x028 SecurityDescriptor : (null)
+0x030 Body : _QUAD
From 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.TypeIndex
1: kd> db nt!ObHeaderCookie L1
fffff800`7911ed74 4c
1: kd> ? (ffffe485`65dd70e0 >> 8) & 0xff
Evaluate expression: 112 = 00000000`00000070
1: kd> dt _object_header ffffe485`65dd70e0 -y TypeIndex
nt!_OBJECT_HEADER
+0x018 TypeIndex : 0x14 ''
1: kd> ? 0x4c ^ 0x70 ^ 0x14
Evaluate expression: 40 = 00000000`00000028
The 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
):
1: kd> dt _OBJECT_TYPE poi(nt!ObTypeIndexTable + (0x28 * 8))
nt!_OBJECT_TYPE
+0x000 TypeList : _LIST_ENTRY [ 0xffffe485`608f7220 - 0xffffe485`608f7220 ]
+0x010 Name : _UNICODE_STRING "File"
+0x020 DefaultObject : 0x00000000`0000009b Void
+0x028 Index : 0x28 '('
+0x02c TotalNumberOfObjects : 0x27cc
+0x030 TotalNumberOfHandles : 0xaae
+0x034 HighWaterNumberOfObjects : 0x2a42
+0x038 HighWaterNumberOfHandles : 0xd36
+0x040 TypeInfo : _OBJECT_TYPE_INITIALIZER
+0x0b8 TypeLock : _EX_PUSH_LOCK
+0x0c0 Key : 0x656c6946
+0x0c8 CallbackList : _LIST_ENTRY [ 0xffffe485`608f72e8 - 0xffffe485`608f72e8 ]
So it's a file object ( represented by a nt!FILE_OBJECT
).
_DISPATCHER_HEADER
If an object is waitable (i.e. its handle can be passed to WaitForSingleObject
or WaitForMutipleObjects
) then it has a _DISPATCH_HEADER
.