Windows Handle Table & Object

[0x00] Overview

기본적으로 핸들에 대한 개념은 위키 내 잘 서술되어 있습니다.

Windows 커널에서 핸들 테이블과 핸들, 오브젝트의 연관성 및 알고리즘에 대한 내용입니다.

Windows 10, 20H2(19042.572) 에서 테스트 되었습니다.

[0x01] Windows Kernel Handle Table

EPROCESS 구조 내 ObjectTable 이라는 필드가 존재합니다. 이는 HANDLE_TABLE 구조로 이루어져 있습니다.

3: kd> !process 4 0 
Searching for Process with Cid == 4
PROCESS ffffd78d98c8f040
    SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 001ad002  ObjectTable: ffff840e0ec8be00  HandleCount: 2342.
    Image: System

3: kd> dt_EPROCESS ffffd78d98c8f040 ObjectTable
ntdll!_EPROCESS
   +0x570 ObjectTable : 0xffff840e`0ec8be00 _HANDLE_TABLE

3: kd> dx -id 0,0,ffffd78d98c8f040 -r1 ((ntdll!_HANDLE_TABLE *)0xffff840e0ec8be00)
((ntdll!_HANDLE_TABLE *)0xffff840e0ec8be00)                 : 0xffff840e0ec8be00 [Type: _HANDLE_TABLE *]
    [+0x000] NextHandleNeedingPool : 0x3000 [Type: unsigned long]
    [+0x004] ExtraInfoPages   : 0 [Type: long]
    [+0x008] TableCode        : 0xffff840e120d7001 [Type: unsigned __int64]
    [+0x010] QuotaProcess     : 0x0 [Type: _EPROCESS *]
    [+0x018] HandleTableList  [Type: _LIST_ENTRY]
    [+0x028] UniqueProcessId  : 0x4 [Type: unsigned long]
    [+0x02c] Flags            : 0x0 [Type: unsigned long]
    [+0x02c ( 0: 0)] StrictFIFO       : 0x0 [Type: unsigned char]
    [+0x02c ( 1: 1)] EnableHandleExceptions : 0x0 [Type: unsigned char]
    [+0x02c ( 2: 2)] Rundown          : 0x0 [Type: unsigned char]
    [+0x02c ( 3: 3)] Duplicated       : 0x0 [Type: unsigned char]
    [+0x02c ( 4: 4)] RaiseUMExceptionOnInvalidHandleClose : 0x0 [Type: unsigned char]
    [+0x030] HandleContentionEvent [Type: _EX_PUSH_LOCK]
    [+0x038] HandleTableLock  [Type: _EX_PUSH_LOCK]
    [+0x040] FreeLists        [Type: _HANDLE_TABLE_FREE_LIST [1]]
    [+0x040] ActualEntry      [Type: unsigned char [32]]
    [+0x060] DebugInfo        : 0x0 [Type: _HANDLE_TRACE_DEBUG_INFO *]

간략히 System 프로세스를 대상으로 확인하였습니다. 그 이유는 익스포트 되지 않는 전역변수 ObpKernelHandleTable 때문입니다. ObpKernelHandleTable 은 바로 이 System 프로세스의 ObjectTable을 의미합니다.

0: kd> dt nt!_HANDLE_TABLE poi(ObpKernelHandleTable)
   +0x000 NextHandleNeedingPool : 0x3000
   +0x004 ExtraInfoPages   : 0n0
   +0x008 TableCode        : 0xffff840e`120d7001
   +0x010 QuotaProcess     : (null) 
   +0x018 HandleTableList  : _LIST_ENTRY [ 0xffff840e`0ec88dd8 - 0xfffff805`17f2db78 ]
   +0x028 UniqueProcessId  : 4
   +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)

ObpKernelHandleTable 의 살펴보면 ObInitSystem 루틴에서 ExCreateHandleTable 을 통해 초기화 됩니다.

즉, 커널 핸들 테이블의 기준이 System 프로세스의 ObjectTable 으로 이해할 수 있습니다. 또한 특이한 점은, ObpKernelHandleTable의 경우 QuotaProcess 가 비어있습니다. 그 외에는 모두 존재합니다.

해당 기준을 이용하여 HandleTableList 멤버를 통해 리스트 순회가 가능합니다.

0: kd> dx -r1 (*((ntdll!_LIST_ENTRY *)0xffff840e0ec8be18))
(*((ntdll!_LIST_ENTRY *)0xffff840e0ec8be18))                 [Type: _LIST_ENTRY]
    [+0x000] Flink            : 0xffff840e0ec88dd8 [Type: _LIST_ENTRY *]
    [+0x008] Blink            : 0xfffff80517f2db78 [Type: _LIST_ENTRY *]

0: kd> dt_HANDLE_TABLE 0xffff840e0ec88dd8-0x18
ntdll!_HANDLE_TABLE
   +0x000 NextHandleNeedingPool : 0x400
   +0x004 ExtraInfoPages   : 0n0
   +0x008 TableCode        : 0xffff840e`0ecb2000
   +0x010 QuotaProcess     : 0xffffd78d`98cb6080 _EPROCESS
   +0x018 HandleTableList  : _LIST_ENTRY [ 0xffff840e`0ffacd18 - 0xffff840e`0ec8be18 ]
   +0x028 UniqueProcessId  : 0x6c
   +0x02c Flags            : 0x10
   +0x02c StrictFIFO       : 0y0
   +0x02c EnableHandleExceptions : 0y0
   +0x02c Rundown          : 0y0
   +0x02c Duplicated       : 0y0
   +0x02c RaiseUMExceptionOnInvalidHandleClose : 0y1
   +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_EPROCESS 0xffffd78d`98cb6080 ImageFileName
ntdll!_EPROCESS
   +0x5a8 ImageFileName : [15]  "Registry"

[-] HANDLE_TABLE_ENTRY

HANDLE_TABLE_ENTRY 의 경우 시간이 걸렸습니다. 기존 인터넷 상에 존재하는 정보들은 모두 x86 기준이거나 최신 윈도우의 정보와 조금 다릅니다.

HANDLE_TABLE 내 TableCodeHANDLE_TABLE_ENTRY 포인터입니다. 이 TableCode 를 아래와 같은 과정을 통해 연산하면 해당하는 핸들의 오브젝트를 구할 수 있습니다.

2: kd> !handle 4

PROCESS ffffb986186a7040
    SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 001ad002  ObjectTable: ffff9d8573a8be00  HandleCount: 2378.
    Image: System

Kernel handle table at ffff9d8573a8be00 with 2378 entries in use

0004: Object: ffffb986186a7040  GrantedAccess: 001fffff (Protected) Entry: ffff9d8573a9e010
Object: ffffb986186a7040  Type: (ffffb986186c4e80) Process
    ObjectHeader: ffffb986186a7010 (new version)
        HandleCount: 6  PointerCount: 212365

2: kd> dt_HANDLE_TABLE ffff9d8573a8be00
ntdll!_HANDLE_TABLE
   +0x000 NextHandleNeedingPool : 0x3800
   +0x004 ExtraInfoPages   : 0n0
   +0x008 TableCode        : 0xffff9d85`73e61001
   +0x010 QuotaProcess     : (null) 
   +0x018 HandleTableList  : _LIST_ENTRY [ 0xffff9d85`73a88dd8 - 0xfffff805`0ff2db78 ]
   +0x028 UniqueProcessId  : 4
   +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)

2: kd> dp poi(0xffff9d85`73e61001&0xfffffffffffffffc)
ffff9d85`73a9e000  00000000`00000000 00000000`00000000
ffff9d85`73a9e010  b986186a`7010ff4d 00000000`001fffff
ffff9d85`73a9e020  b986187c`b110fff5 00000000`001fffff
ffff9d85`73a9e030  9d857534`3de0fff3 00000000`000f0001
ffff9d85`73a9e040  b986186a`2890fe6f 00000000`001f0001
ffff9d85`73a9e050  9d8573a0`fad0fff3 00000000`000f000f
ffff9d85`73a9e060  9d8573a8`9880fd99 00000000`000f000f
ffff9d85`73a9e070  b9861869`c310fff3 00000000`001f0003

2: kd> dt_HANDLE_TABLE_ENTRY ffff9d85`73a9e010
ntdll!_HANDLE_TABLE_ENTRY
   +0x000 VolatileLowValue : 0n-5078344684387893427
   +0x000 LowValue         : 0n-5078344684387893427
   +0x000 InfoTable        : 0xb986186a`7010ff4d _HANDLE_TABLE_ENTRY_INFO
   +0x008 HighValue        : 0n2097151
   +0x008 NextFreeHandleEntry : 0x00000000`001fffff _HANDLE_TABLE_ENTRY
   +0x008 LeafHandleValue  : _EXHANDLE
   +0x000 RefCountField    : 0n-5078344684387893427
   +0x000 Unlocked         : 0y1
   +0x000 RefCnt           : 0y0111111110100110 (0x7fa6)
   +0x000 Attributes       : 0y000
   +0x000 ObjectPointerBits : 0y10111001100001100001100001101010011100000001 (0xb986186a701)
   +0x008 GrantedAccessBits : 0y0000111111111111111111111 (0x1fffff)
   +0x008 NoRightsUpgrade  : 0y0
   +0x008 Spare1           : 0y000000 (0)
   +0x00c Spare2           : 0

2: kd> ? (b986186a`7010ff4d>>>10)&fffffffffffffff0
Evaluate expression: -77489390325744 = ffffb986`186a7010

2: kd> dt_OBJECT_HEADER ffffb986`186a7010
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n212365
   +0x008 HandleCount      : 0n6
   +0x008 NextToFree       : 0x00000000`00000006 Void
   +0x010 Lock             : _EX_PUSH_LOCK
   +0x018 TypeIndex        : 0x70 'p'
   +0x019 TraceFlags       : 0 ''
   +0x019 DbgRefTrace      : 0y0
   +0x019 DbgTracePermanent : 0y0
   +0x01a InfoMask         : 0 ''
   +0x01b Flags            : 0x2 ''
   +0x01b NewObject        : 0y0
   +0x01b KernelObject     : 0y1
   +0x01b KernelOnlyAccess : 0y0
   +0x01b ExclusiveObject  : 0y0
   +0x01b PermanentObject  : 0y0
   +0x01b DefaultSecurityQuota : 0y0
   +0x01b SingleHandleEntry : 0y0
   +0x01b DeletedInline    : 0y0
   +0x01c Reserved         : 0
   +0x020 ObjectCreateInfo : 0xfffff805`0fe53900 _OBJECT_CREATE_INFORMATION
   +0x020 QuotaBlockCharged : 0xfffff805`0fe53900 Void
   +0x028 SecurityDescriptor : 0xffff9d85`73a3ee61 Void
   +0x030 Body             : _QUAD

2: kd> !object ffffb986`186a7010+30
Object: ffffb986186a7040  Type: (ffffb986186c4e80) Process
    ObjectHeader: ffffb986186a7010 (new version)
    HandleCount: 6  PointerCount: 212365

단계별로 설명하겠습니다. 먼저 ObjectTable(HANDLE_TABLE) 을 확인합니다. 해당 ObjectTable 에는 TableCode 필드가 존재합니다. 0xffffffff'fffffffc 로 AND 연산을 하는 이유는 최하위 2비트를 지우기 위함입니다. 이를 통해 HANDLE_TABLE_ENTRY 의 시작 주소를 얻을 수 있습니다.

물론 시작 주소이기 때문에 확인해보면 0x10 만큼 떨어진 위치부터 엔트리가 시작됩니다. 좀더 정확한 핸들의 HANDLE_TABLE_ENTRY 를 얻기 위해서 ExpLookupHandleTableEntry 함수를 확인합니다.

_HANDLE_TABLE_ENTRY *__fastcall ExpLookupHandleTableEntry(_HANDLE_TABLE *HandleTable, _EXHANDLE ExHandle)
{
  unsigned __int64 Handle; // rdx
  volatile unsigned __int64 TableCode; // r8

  Handle = ExHandle.Value & 0xFFFFFFFFFFFFFFFCui64;
  if ( Handle >= HandleTable->NextHandleNeedingPool )
    return 0i64;
  TableCode = HandleTable->TableCode;
  if ( (TableCode & 3) == 1 )
    return (*(TableCode + 8 * (Handle >> 0xA) - 1) + 4 * (Handle & 0x3FF));
  if ( (TableCode & 3) != 0 )
    return (*(*(TableCode + 8 * (Handle >> 0x13) - 2) + 8 * ((Handle >> 10) & 0x1FF)) + 4 * (Handle & 0x3FF));
  return (TableCode + 4 * Handle);
}

핸들 값도 마찬가지로 4의 배수인데 하위 2비트를 지우는건 어떤 상황인지 궁금합니다. 어쨋든, 위의 내용을 토대로 볼 때 HANDLE_TABLETableCode 를 3으로 AND 연산하여 1인 경우와 1보다 큰 경우, 0인 경우로 리턴하는 값이 다른 것을 확인할 수 있습니다. 위의 내용을 토대로 간단히 실습을 해보면 아래와 같이 정확한 HANDLE_TABLE_ENTRY 주소를 얻을 수 있습니다.

3: kd> !process 0n3248 0
Searching for Process with Cid == cb0
PROCESS ffffb9861e7b0080
    SessionId: 1  Cid: 0cb0    Peb: 33c2165000  ParentCid: 10d4
    DirBase: 5af68002  ObjectTable: ffff9d857ce7f180  HandleCount: 256.
    Image: notepad.exe

3: kd> .process ffffb9861e7b0080
Implicit process is now ffffb986`1e7b0080

3: kd> !handle 0

PROCESS ffffb9861e7b0080
    SessionId: 1  Cid: 0cb0    Peb: 33c2165000  ParentCid: 10d4
    DirBase: 5af68002  ObjectTable: ffff9d857ce7f180  HandleCount: 256.
    Image: notepad.exe

Handle table at ffff9d857ce7f180 with 256 entries in use

0004: Object: ffffb9861f7d4960  GrantedAccess: 001f0003 (Protected) (Inherit) Entry: ffff9d857a5f9010
Object: ffffb9861f7d4960  Type: (ffffb986186c5820) Event
    ObjectHeader: ffffb9861f7d4930 (new version)
        HandleCount: 1  PointerCount: 32768

...
생략
...

0010: Object: ffffb9861f214bc0  GrantedAccess: 001f0003 (Protected) Entry: ffff9d857a5f9040
Object: ffffb9861f214bc0  Type: (ffffb986186e0da0) IoCompletion
    ObjectHeader: ffffb9861f214b90 (new version)
        HandleCount: 1  PointerCount: 32762

위와 같은 구조로 되어 있을 때, notepad 프로세스의 0x10 핸들의 HANDLE_TABLE_ENTRY 를 구해보겠습니다. 위의 정보에서 EntryHANDLE_TABLE_ENTRY 이며, 계산으로 정확한 엔트리를 구할 수 있는지 확인합니다. 먼저 notepadObjectTable 을 확인합니다.

3: kd> dt_HANDLE_TABLE ffff9d857ce7f180
ntdll!_HANDLE_TABLE
   +0x000 NextHandleNeedingPool : 0x800
   +0x004 ExtraInfoPages   : 0n0
   +0x008 TableCode        : 0xffff9d85`7a619001
   +0x010 QuotaProcess     : 0xffffb986`1e7b0080 _EPROCESS
   +0x018 HandleTableList  : _LIST_ENTRY [ 0xffff9d85`7ce7e998 - 0xffff9d85`7ce7e698 ]
   +0x028 UniqueProcessId  : 0xcb0
   +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)

TableCode 필드의 값이 0xffff9d85'7a619001 으로 확인됩니다.

3: kd> ? 0xffff9d85`7a619001&3
Evaluate expression: 1 = 00000000`00000001

AND 3 의 연산 결과가 1이므로 아래와 같은 계산식을 세울 수 있습니다.

poi(0xffff9d85`7a619001+8*(10>>a)-1)+4*(10&0x3ff)
//*(TableCode+8*(Handle>>0xA)-1)+4*(Handle&0x3FF)

2: kd> ? poi(0xffff9d85`7a619001 + 8 *(10>>a)-1) + 4 *(10&3ff)
Evaluate expression: -108278367416256 = ffff9d85`7a5f9040

==

0010: Object: ffffb9861f214bc0  GrantedAccess: 001f0003 (Protected) Entry: ffff9d857a5f9040
Object: ffffb9861f214bc0  Type: (ffffb986186e0da0) IoCompletion
    ObjectHeader: ffffb9861f214b90 (new version)
        HandleCount: 1  PointerCount: 32762

계산한 값과 Entry 의 값이 일치하는 것을 확인할 수 있습니다. 그럼 좀 더 정확하게 오브젝트를 확인해보겠습니다.

2: kd> dt_HANDLE_TABLE_ENTRY ffff9d85`7a5f9040
nt!_HANDLE_TABLE_ENTRY
   +0x000 VolatileLowValue : 0n-5078337301951479837
   +0x000 LowValue         : 0n-5078337301951479837
   +0x000 InfoTable        : 0xb9861f21`4b90ffe3 _HANDLE_TABLE_ENTRY_INFO
   +0x008 HighValue        : 0n2031619
   +0x008 NextFreeHandleEntry : 0x00000000`001f0003 _HANDLE_TABLE_ENTRY
   +0x008 LeafHandleValue  : _EXHANDLE
   +0x000 RefCountField    : 0n-5078337301951479837
   +0x000 Unlocked         : 0y1
   +0x000 RefCnt           : 0y0111111111110001 (0x7ff1)
   +0x000 Attributes       : 0y000
   +0x000 ObjectPointerBits : 0y10111001100001100001111100100001010010111001 (0xb9861f214b9)
   +0x008 GrantedAccessBits : 0y0000111110000000000000011 (0x1f0003)
   +0x008 NoRightsUpgrade  : 0y0
   +0x008 Spare1           : 0y000000 (0)
   +0x00c Spare2           : 0

GrantedAccessBits 를 제외하고는 이게 맞는 값인가 싶습니다.

[-] Object

하지만 다음과 같은 계산을 통해 해당 핸들의 오브젝트를 구할 수 있습니다.

ObjectHeader = (HandleTableEntry.VolatileLowValue >> 0x10) & 0xFFFFFFFF`FFFFFFF0

2: kd> dt_OBJECT_HEADER
nt!_OBJECT_HEADER
   +0x000 PointerCount     : Int8B
   +0x008 HandleCount      : Int8B
   +0x008 NextToFree       : Ptr64 Void
   +0x010 Lock             : _EX_PUSH_LOCK
   +0x018 TypeIndex        : UChar
   +0x019 TraceFlags       : UChar
   +0x019 DbgRefTrace      : Pos 0, 1 Bit
   +0x019 DbgTracePermanent : Pos 1, 1 Bit
   +0x01a InfoMask         : UChar
   +0x01b Flags            : UChar
   +0x01b NewObject        : Pos 0, 1 Bit
   +0x01b KernelObject     : Pos 1, 1 Bit
   +0x01b KernelOnlyAccess : Pos 2, 1 Bit
   +0x01b ExclusiveObject  : Pos 3, 1 Bit
   +0x01b PermanentObject  : Pos 4, 1 Bit
   +0x01b DefaultSecurityQuota : Pos 5, 1 Bit
   +0x01b SingleHandleEntry : Pos 6, 1 Bit
   +0x01b DeletedInline    : Pos 7, 1 Bit
   +0x01c Reserved         : Uint4B
   +0x020 ObjectCreateInfo : Ptr64 _OBJECT_CREATE_INFORMATION
   +0x020 QuotaBlockCharged : Ptr64 Void
   +0x028 SecurityDescriptor : Ptr64 Void
   +0x030 Body             : _QUAD

Object = (HandleTableEntry.VolatileLowValue >> 0x10) & 0xFFFFFFFF`FFFFFFF0 + 0x30

위의 공식을 토대로 notepad 의 핸들 값 0x10 을 가지는 오브젝트는 아래와 같습니다.

2: kd> ? ((0xb9861f21`4b90ffe3 >>> 0x10) & 0xFFFFFFFFFFFFFFF0) + 0x30
Evaluate expression: -77489277678656 = ffffb986`1f214bc0

2: kd> !object ffffb986`1f214bc0
Object: ffffb9861f214bc0  Type: (ffffb986186e0da0) IoCompletion
    ObjectHeader: ffffb9861f214b90 (new version)
    HandleCount: 1  PointerCount: 32762

=== result of !handle command
0010: Object: ffffb9861f214bc0  GrantedAccess: 001f0003 (Protected) Entry: ffff9d857a5f9040
Object: ffffb9861f214bc0  Type: (ffffb986186e0da0) IoCompletion
    ObjectHeader: ffffb9861f214b90 (new version)
        HandleCount: 1  PointerCount: 32762

정확히 일치하는 것을 볼 수 있습니다. 해당 공식은 ExQueryProcessHandleInformation 에서 찾을 수 있습니다.

_int64 __fastcall ExQueryProcessHandleInformation(__int64 HandleTable, _QWORD *a2, int a3, int *a4)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

...

  while ( 1 )
  {
    v10 = ExpGetNextHandleTableEntry(HandleTable, v9, v25);
    HandleTableEntry = v10;
    ...
		...
    else if ( ExLockHandleTableEntry(v28, &v10->LowValue) )
    {
      ObjectHeader = ((HandleTableEntry->LowValue >> 0x10) & 0xFFFFFFFFFFFFFFF0ui64);
      v14 = HandleTableEntry->LeafHandleValue.0;
      v15 = (HandleTableEntry->LowValue >> 0x11) & 7 | 8;
      if ( (*&v14 & 0x2000000) == 0 )
        LOBYTE(v15) = (HandleTableEntry->LowValue >> 0x11) & 7;
      v16 = v15 & 7;
      v30 = v16;
      v17 = *(ObTypeIndexTable[ObHeaderCookie ^ *(((HandleTableEntry->LowValue >> 0x10) & 0xFFFFFFFFFFFFFFF0ui64) + 0x18) ^ ((WORD1(HandleTableEntry->LowValue) & 0xFFF0) >> 8)]
            + 0x28);

Windows 커널 오브젝트의 위치는 항상 헤더의 다음입니다. 그렇기 때문에 위의 공식을 이용하여 오브젝트를 구할 수 있습니다.

[-] Object Type

특정 오브젝트가 프로세스라는 가정하에 오브젝트 유형을 찾는 방법에 대해 알아보겠습니다.

2: kd> dt_OBJECT_TYPE
nt!_OBJECT_TYPE
   +0x000 TypeList         : _LIST_ENTRY
   +0x010 Name             : _UNICODE_STRING
   +0x020 DefaultObject    : Ptr64 Void
   +0x028 Index            : UChar
   +0x02c TotalNumberOfObjects : Uint4B
   +0x030 TotalNumberOfHandles : Uint4B
   +0x034 HighWaterNumberOfObjects : Uint4B
   +0x038 HighWaterNumberOfHandles : Uint4B
   +0x040 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x0b8 TypeLock         : _EX_PUSH_LOCK
   +0x0c0 Key              : Uint4B
   +0x0c8 CallbackList     : _LIST_ENTRY

위에서 말한 것과 같이 특정 오브젝트 앞에는 OBJECT_HEADER가 존재합니다.

System 프로세스의 EPROCESS 오브젝트를 이용해보겠습니다.

0: kd> !process 4 0
Searching for Process with Cid == 4
PROCESS ffffb986186a7040
    SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 001ad002  ObjectTable: ffff9d8573a8be00  HandleCount: 2320.
    Image: System

0: kd> dt_OBJECT_HEADER ffffb986186a7040-30
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n179132
   +0x008 HandleCount      : 0n5
   +0x008 NextToFree       : 0x00000000`00000005 Void
   +0x010 Lock             : _EX_PUSH_LOCK
   +0x018 TypeIndex        : 0x70 'p'
   +0x019 TraceFlags       : 0 ''
   +0x019 DbgRefTrace      : 0y0
   +0x019 DbgTracePermanent : 0y0
   +0x01a InfoMask         : 0 ''
   +0x01b Flags            : 0x2 ''
   +0x01b NewObject        : 0y0
   +0x01b KernelObject     : 0y1
   +0x01b KernelOnlyAccess : 0y0
   +0x01b ExclusiveObject  : 0y0
   +0x01b PermanentObject  : 0y0
   +0x01b DefaultSecurityQuota : 0y0
   +0x01b SingleHandleEntry : 0y0
   +0x01b DeletedInline    : 0y0
   +0x01c Reserved         : 0
   +0x020 ObjectCreateInfo : 0xfffff805`0fe53900 _OBJECT_CREATE_INFORMATION
   +0x020 QuotaBlockCharged : 0xfffff805`0fe53900 Void
   +0x028 SecurityDescriptor : 0xffff9d85`73a3ee63 Void
   +0x030 Body             : _QUAD

헤더에서 TypeIndex를 확인합니다. 0x70로 되어 있습니다.

오브젝트 유형을 알아내기 위한 루틴으로 ObGetObjectType 이라는 Export 함수가 존재합니다.

__int64 __fastcall ObGetObjectType(__int64 Object)
{
  return ObTypeIndexTable[ObHeaderCookie ^ *(Object - 0x18) ^ ((Object - 0x30) >> 8)];
}
PAGE:0000000140682110                         ObGetObjectType proc near               ; DATA XREF: .pdata:0000000140101AB4↑o
PAGE:0000000140682110 48 8D 41 D0                             lea     rax, [rcx-30h]
PAGE:0000000140682114 0F B6 49 E8                             movzx   ecx, byte ptr [rcx-18h]
PAGE:0000000140682118 48 C1 E8 08                             shr     rax, 8
PAGE:000000014068211C 0F B6 C0                                movzx   eax, al
PAGE:000000014068211F 48 33 C1                                xor     rax, rcx
PAGE:0000000140682122 0F B6 0D F3 A5 67 00                    movzx   ecx, byte ptr cs:ObHeaderCookie
PAGE:0000000140682129 48 33 C1                                xor     rax, rcx
PAGE:000000014068212C 48 8D 0D DD AC 67 00                    lea     rcx, ObTypeIndexTable
PAGE:0000000140682133 48 8B 04 C1                             mov     rax, [rcx+rax*8]
PAGE:0000000140682137 C3                                      retn

이제 nt!ObGetObjectType 의 연산을 참조하여 오브젝트 유형을 구해보도록 하겠습니다. 먼저 오브젝트 헤더(ffffb986186a7010) 의 하위 1바이트를 제거하고, 마지막 1바이트 값과 OBJECT_HEADER.TypeIndex 값을 XOR 연산합니다.

1. (ffffb986186a7010 >> 8)
2. ffffb986186a70 => 0x70
3. TypeIndex = 0x70

다음으로 ObHeaderCookie 의 1바이트 값과 XOR 연산을 진행합니다.

0: kd> db ObHeaderCookie l1
fffff805`0fefc71c  07

즉 아래와 같은 공식이 성립됩니다.

0x70(&ObjectHeader>>8 의 하위 1바이트)
XOR
0x70(ObjectHeader 의 TypeIndex)
XOR
0x07(ObHeaderCookie)
0: kd> dt_OBJECT_TYPE poi(ObTypeIndexTable+(7*8))
nt!_OBJECT_TYPE
   +0x000 TypeList         : _LIST_ENTRY [ 0xffffb986`186c4e80 - 0xffffb986`186c4e80 ]
   +0x010 Name             : _UNICODE_STRING "Process"
   +0x020 DefaultObject    : (null) 
   +0x028 Index            : 0x7 ''
   +0x02c TotalNumberOfObjects : 0x74
   +0x030 TotalNumberOfHandles : 0x42a
   +0x034 HighWaterNumberOfObjects : 0x98
   +0x038 HighWaterNumberOfHandles : 0x523
   +0x040 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x0b8 TypeLock         : _EX_PUSH_LOCK
   +0x0c0 Key              : 0x636f7250
   +0x0c8 CallbackList     : _LIST_ENTRY [ 0xffff9d85`73e9fd40 - 0xffff9d85`73e9fd40 ]

[0x02] Reference

  1. Reversing Windows Internals
  2. ReactOS