Search code examples
windbgwdkwindows-kernel

How Windows Handle to associate corresponding object type?


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).


Solution

  • As an example I'm going to take a file handle (0x1c8) from the explorer process (6496):

    enter image description here

    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.