Object Headers, Handles and Types

This article is going to cover a few different areas of object management and how objects are structured under the Object Manager.

Pool Allocations and Object Header:

Every object managed by the Object Manager will have the same general structure within a pool page, that is, it will always begin with the pool header, followed by optional object headers; the main object header itself and then the object body. It is very important and useful to understand this, since it can be a tremendous asset when debugging crashes, especially those which involve corrupted objects. To illustrate this point, let’s dump the pool page for a given object.

2: kd> !pool ffff91880ef86080 1
Pool page ffff91880ef86080 region is Nonpaged pool
*ffff91880ef86000 size:  a00 previous size:    0  (Allocated) *Thre
        Pooltag Thre : Thread objects, Binary : nt!ps
    ffff91880ef86010  00000988 00010010 00000000 00000000 << Pool Header
    ffff91880ef86020  00000005 00000000 00000000 00000020 << Optional Object Header (Padding)
    ffff91880ef86030  00000000 00000960 00000048 c85cb2db << Optional Object Header (Quota)
    ffff91880ef86040  59c53700 fffff802 00000000 00000000
    ffff91880ef86050  00000000 00000000 00000800 00000000 << Object Header
    ffff91880ef86060  00000000 00000000 008800c9 0d3ceb16
    ffff91880ef86070  59c53700 fffff802 0669aeac ffffa487
    ffff91880ef86080  00200006 00000001 0ef86088 ffff9188 << Thread - Object Body
    ffff91880ef86090  0ef86088 ffff9188 00000000 00000000
    ffff91880ef860a0  101a8724 00000000 00000000 00000000
    ffff91880ef860b0  26d7b000 ffffa20a 26d81000 ffffa20a
    ffff91880ef860c0  00000000 00000000 097526d0 00000000
    ffff91880ef860d0  00000000 0003e77f 26d806b0 ffffa20a
    ffff91880ef860e0  26d80cc0 ffffa20a 00000000 00000000
    ffff91880ef860f0  00000001 00000001 000220c4 02080500
    ffff91880ef86100  00070053 00000002 00000000 00000000
    ffff91880ef86110  26d80b00 ffffa20a 0ef86118 ffff9188
    ffff91880ef86120  0ef86118 ffff9188 0ef86128 ffff9188
    ffff91880ef86130  0ef86128 ffff9188 0f7ba080 ffff9188
    ffff91880ef86140  09000000 00000003 00000000 00000000
    ffff91880ef86150  0ef861c0 ffff9188 00000000 00000000
    ffff91880ef86160  147dd158 ffff9188 00000000 00000000
    ffff91880ef86170  00000000 00000000 00000000 00000000
    ffff91880ef86180  003a0008 00000000 0ef86250 ffff9188
    ffff91880ef86190  0ef86250 ffff9188 70eacb08 00000000
    ffff91880ef861a0  11caa1a0 ffff9188 14f461a0 ffff9188
    ffff91880ef861b0  c39c514f 519a49ad 00010000 00000000
    ffff91880ef861c0  26d80808 ffffa20a 26d80808 ffffa20a
    ffff91880ef861d0  00000501 000008a5 0ef86080 ffff9188
    ffff91880ef861e0  26d80800 ffffa20a 00000000 00000000
    ffff91880ef861f0  26d7fce0 ffffa20a 26d7fce0 ffffa20a
    ffff91880ef86200  00010501 00000004 0ef86080 ffff9188
    ffff91880ef86210  26d7fcd8 ffffa20a 00000000 00000000
    ffff91880ef86220  00000000 00000000 00000000 00000000
    ffff91880ef86230  00000000 00002f3c 0ef86080 ffff9188
    ffff91880ef86240  00000000 00000000 00000000 00000000
    ffff91880ef86250  0ef86188 ffff9188 0ef86188 ffff9188
    ffff91880ef86260  01020401 00000000 0ef86080 ffff9188
    ffff91880ef86270  00000000 00000000 00000000 00000000
    ffff91880ef86280  00000000 00000000 00000000 00000000
    ffff91880ef86290  00000000 00000000 00000003 00000000
    ffff91880ef862a0  0f7ba080 ffff9188 00000fff 00000000
    ffff91880ef862b0  08010000 00000000 00000054 00000000
    ffff91880ef862c0  00000fff 00000000 01000000 00000003
    ffff91880ef862d0  00000005 00000000 0ef862d8 ffff9188
    ffff91880ef862e0  0ef862d8 ffff9188 0ef862e8 ffff9188
    ffff91880ef862f0  0ef862e8 ffff9188 00000000 00000000
    ffff91880ef86300  16000000 00000000 06580112 00000001
    ffff91880ef86310  0ef86080 ffff9188 0ef86118 ffff9188
    ffff91880ef86320  0ef86118 ffff9188 59397d00 fffff802
    ffff91880ef86330  59397d00 fffff802 5929ab70 fffff802
    ffff91880ef86340  0ef86080 ffff9188 00000000 00000000
    ffff91880ef86350  00000000 00000000 00000000 00000003
    ffff91880ef86360  00060000 00000001 0ef86368 ffff9188
    ffff91880ef86370  0ef86368 ffff9188 0e464378 ffff9188
    ffff91880ef86380  0ee2b378 ffff9188 0ef86388 ffff9188
    ffff91880ef86390  0ef86388 ffff9188 0000003f 00000000
    ffff91880ef863a0  0ef866d0 ffff9188 00000001 00000000
    ffff91880ef863b0  00000001 00000000 00000000 00000000
    ffff91880ef863c0  00000000 00000000 00000000 00000000
    ffff91880ef863d0  00000000 00000000 00000000 00000000
    ffff91880ef863e0  00000000 00000000 00000000 00000000
    ffff91880ef863f0  00000001 00000000 00000000 00000000
    ffff91880ef86400  00000001 00000000 00000009 00000000
    ffff91880ef86410  000000d4 00000000 00037f5b 00000000
    ffff91880ef86420  000003de 00000000 000019a4 00000000
    ffff91880ef86430  00000000 00000000 00000000 00000000
    ffff91880ef86440  00000000 00000000 00000000 00000000
    ffff91880ef86450  00000000 00000000 00000000 00000000
    ffff91880ef86460  00000001 00000000 000024fb 00000000
    ffff91880ef86470  00000001 00000000 00000000 00000000
    ffff91880ef86480  00000000 00000000 00000000 00000000
    ffff91880ef86490  00000000 00000000 00000000 00000000
    ffff91880ef864a0  00000000 00000000 00000000 00000000
    ffff91880ef864b0  8b4d69be 01d7467d e387db34 01d7467d
    ffff91880ef864c0  0ef864b8 ffff9188 00000000 00000000
    ffff91880ef864d0  ba982630 00007ffb 00000000 00000000
    ffff91880ef864e0  00000000 00000000 0ef864e8 ffff9188
    ffff91880ef864f0  0ef864e8 ffff9188 0000148c 00000000
    ffff91880ef86500  000007ec 00000000 00080005 00000000
    ffff91880ef86510  0ef86510 ffff9188 0ef86510 ffff9188
    ffff91880ef86520  00000001 00000000 00000000 00000000
    ffff91880ef86530  0ef86530 ffff9188 0ef86530 ffff9188
    ffff91880ef86540  00000000 00000000 00000000 00000000
    ffff91880ef86550  73444b60 00000000 00000000 00000000
    ffff91880ef86560  00000000 00000000 0e116568 ffff9188
    ffff91880ef86570  0ef89568 ffff9188 00000001 00000000
    ffff91880ef86580  00000000 00000000 00000007 00000000
    ffff91880ef86590  00005403 00000000 00000000 00000000
    ffff91880ef865a0  00000000 00000000 00000000 00000000
    ffff91880ef865b0  00000000 00000000 00000000 00000000
    ffff91880ef865c0  00000000 00000000 00000000 00000000
    ffff91880ef865d0  00000000 00000000 00000000 00000000
    ffff91880ef865e0  0ef865e0 ffff9188 0ef865e0 ffff9188
    ffff91880ef865f0  0ef865f0 ffff9188 0ef865f0 ffff9188
    ffff91880ef86600  00000000 00000000 00000000 00000000
    ffff91880ef86610  00000000 00000000 00000000 00000000
    ffff91880ef86620  00000000 00000000 00000000 00000000
    ffff91880ef86630  00000000 00000000 00000000 00000000
    ffff91880ef86640  00000000 00000000 0ef86648 ffff9188
    ffff91880ef86650  0ef86648 ffff9188 00000000 00000000
    ffff91880ef86660  00000000 00000000 00359000 00000000
    ffff91880ef86670  00000000 00000000 0ef86918 ffff9188
    ffff91880ef86680  00000000 00000000 fffffffd ffffffff
    ffff91880ef86690  00000000 00000000 00000000 00000000
    ffff91880ef866a0  00323bea af620000 0ef866a8 ffff9188
    ffff91880ef866b0  0ef866a8 ffff9188 00000000 00000000
    ffff91880ef866c0  0ef866c0 ffff9188 0ef866c0 ffff9188
    ffff91880ef866d0  00000000 00000000 00000000 00000000
    ffff91880ef866e0  00000000 00000000 00000065 00000000
    ffff91880ef866f0  00000000 00000000 ffffffff 00000000
    ffff91880ef86700  00000000 00000000 00000000 00000000
    ffff91880ef86710  00000000 00000000 00000000 00000000
    ffff91880ef86720  00000000 00000000 00000000 00000000
    ffff91880ef86730  00000000 00000000 00000000 00000000
    ffff91880ef86740  00000000 00000000 0000006b 00000000
    ffff91880ef86750  00000000 00000000 ffffffff 00000000
    ffff91880ef86760  00000000 00000000 00000000 00000000
    ffff91880ef86770  00000000 00000000 00000000 00000000
    ffff91880ef86780  00000000 00000000 00000000 00000000
    ffff91880ef86790  00000000 00000000 00000000 00000000
    ffff91880ef867a0  00000000 00000000 00000071 00000000
    ffff91880ef867b0  00000000 00000000 ffffffff 00000000
    ffff91880ef867c0  00000000 00000000 00000000 00000000
    ffff91880ef867d0  00000000 00000000 00000000 00000000
    ffff91880ef867e0  00000000 00000000 00000000 00000000
    ffff91880ef867f0  00000000 00000000 00000000 00000000
    ffff91880ef86800  00000000 00000000 00000077 00000000
    ffff91880ef86810  00000000 00000000 ffffffff 00000000
    ffff91880ef86820  00000000 00000000 00000000 00000000
    ffff91880ef86830  00000000 00000000 00000000 00000000
    ffff91880ef86840  00000000 00000000 00000000 00000000
    ffff91880ef86850  00000000 00000000 00000000 00000000
    ffff91880ef86860  00000000 00000000 0000007d 00000000
    ffff91880ef86870  00000000 00000000 00000000 00000000
    ffff91880ef86880  00000000 00000000 00000000 00000000
    ffff91880ef86890  00000000 00000000 00000000 00000000
    ffff91880ef868a0  00000000 00000000 00000000 00000000
    ffff91880ef868b0  00000000 00000000 00000000 00000000
    ffff91880ef868c0  00000000 00000000 00000083 00000000
    ffff91880ef868d0  00000000 00000000 00000000 00000000
    ffff91880ef868e0  00000000 00000000 00000000 00000000
    ffff91880ef868f0  00000000 00000000 00000000 00000000
    ffff91880ef86900  00000000 00000000 00000000 00000000
    ffff91880ef86910  00000000 00000000 00000000 00000000
    ffff91880ef86920  00000000 00000000 00000000 00000000
    ffff91880ef86930  00000000 00000000 00000000 00000000
    ffff91880ef86940  00000000 00000000 097526d0 00000000
    ffff91880ef86950  00000000 00000000 00000000 00000000
    ffff91880ef86960  00000000 00000000 00000000 00000000
    ffff91880ef86970  00000000 00000000 00000000 00000000
    ffff91880ef86980  00000000 00000000 00000000 00000000
    ffff91880ef86990  00000000 00000000 00000000 00000000
    ffff91880ef869a0  00000000 00000000 00000000 00000000
    ffff91880ef869b0  00000000 00000000 00000000 00000000
    ffff91880ef869c0  00000000 00000000 00000000 00000000
    ffff91880ef869d0  00000000 00000000 0000002e 3c962221
    ffff91880ef869e0  00000000 00000000 00000000 00000000
    ffff91880ef869f0  00000000 00000000 00000000 00000000

The object in question is a Thread object as shown by the Thre pool tag. The highlighted address is the start address of the pool allocation for the thread object. Now, I’ve highlighted each individual section which was mentioned previously in the pool page. It’s important to understand that not all the offsets shown in the above example will be the same for all objects but the layout will remain the same. For example, a device object will have a smaller object body section and may be created with additional object headers, therefore the device object itself may start at entirely different offset from the pool allocation start address.

The object header contains metadata about the given object. Every object will have an object header associated to it. The easiest method to find the object header for an object is by using the !object command with the address of the object. This would be in fact the address of the object body.

2: kd> !object ffff91880ef86080
Object: ffff91880ef86080  Type: (ffff9188026c9f00) Thread
    ObjectHeader: ffff91880ef86050 (new version)
    HandleCount: 2048  PointerCount: 0
2: kd> dt _OBJECT_HEADER ffff91880ef86050
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n0
   +0x008 HandleCount      : 0n2048
   +0x008 NextToFree       : 0x00000000`00000800 Void
   +0x010 Lock             : _EX_PUSH_LOCK
   +0x018 TypeIndex        : 0xc9 ''
   +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         : 0xd3ceb16
   +0x020 ObjectCreateInfo : 0xfffff802`59c53700 _OBJECT_CREATE_INFORMATION
   +0x020 QuotaBlockCharged : 0xfffff802`59c53700 Void
   +0x028 SecurityDescriptor : 0xffffa487`0669aeac Void
   +0x030 Body             : _QUAD

The more complex fields such as TypeIndex and InfoMask will be explained in the later sections. The fields which we’ll be discussing in this section have been highlighted in blue. The Lock field is a push lock which is acquired by a thread when it wishes to make any form of modification to the object header. The TraceFlags field, is a union of two other fields DbgRefTrace and DbgTracePermanent, these are used along with some of the object tracing APIs to assist with debugging. The PermanentObject field is an additional flag which is used to determine if an object should be deleted once its reference count has reached 0. Almost all objects are temporary objects and therefore will eventually be deleted once their reference count has reached 0. I say eventually because if an object has been allocated using non-paged pool, then the object may not be deleted until the IRQL of the processor is less than DISPATCH_LEVEL. It’s something to bear in mind since you may find objects which have a reference count of 0 and are temporary.

The ObjectCreateInfo field is a pointer to a structure called OBJECT_CREATE_INFORMATION. This structure is used to maintain information about the creation of the object such as its namespace directory and the amount of pool required until the object has been fully instantiated.

2: kd> dt _OBJECT_CREATE_INFORMATION fffff802`59c53700
nt!_OBJECT_CREATE_INFORMATION
   +0x000 Attributes       : 0xf399b3
   +0x008 RootDirectory    : 0x00000000`01037a1f Void
   +0x010 ProbeMode        : 0 ''
   +0x014 PagedPoolCharge  : 0
   +0x018 NonPagedPoolCharge : 0
   +0x01c SecurityDescriptorCharge : 0
   +0x020 SecurityDescriptor : (null)
   +0x028 SecurityQos      : (null)
   +0x030 SecurityQualityOfService : _SECURITY_QUALITY_OF_SERVICE

Handles and the Handle Table:

Every process will have a handle table associated with it, the handle table is used to maintain all the open handles which a process has to a given set of objects. A handle in essence is an indirect pointer to an object. A handle is then used to index into the handle table which returns the reference to the object which the handle table entry refers to.

You can find the associated handle table for a process by examining the ObjectTable field of the _EPROCESS structure.

2: kd> dt _EPROCESS -y ObjectTable
nt!_EPROCESS
   +0x570 ObjectTable : Ptr64 _HANDLE_TABLE

The handle table for a given process is represented by a structure called HANDLE_TABLE. We can dump this using WinDbg like so:

2: kd> dt _HANDLE_TABLE ffffa4870d1cf3c0
nt!_HANDLE_TABLE
   +0x000 NextHandleNeedingPool : 0x2400
   +0x004 ExtraInfoPages   : 0n0
   +0x008 TableCode        : 0xffffa487`0e0fd001
   +0x010 QuotaProcess     : 0xffff9188`0f7ba080 _EPROCESS
   +0x018 HandleTableList  : _LIST_ENTRY [ 0xffffa487`0d1a0c18 - 0xffffa487`0d1cd3d8 ]
   +0x028 UniqueProcessId  : 0x148c
   +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)

There are a few interesting fields here. First of all, the UniqueProcessId field indicates the Id of the process which the handle table belongs to. Every handle table for each process can be traversed through the linked list which is stored in the HandleTableList field. The FreeLists field is an array which contains one entry for the _HANDLE_TABLE_FREE_LIST structure. This holds two pointers which correspond to the first and last free handle table entries for the handle table.

The handle table itself consists of multiple tables, or arrays of pointers, which in turn refer to each handle table entry mentioned earlier. On x64 systems, there is a maximum of three different tables; by default, there is only one table created upon the initialisation of a process, the other tables are constructed as they needed. The following diagram illustrates this structure:

Windows Internals 7th Edition – Part 2

You can find the number of present tables by using the following:

2: kd>  ?? 0xffffa487`0e0fd001 & 7
unsigned int64 1

As we can see, there is only currently one table present. To find the base address of the first table, then we take the value from the TableCode field and then bitwise AND it with ~0x07. Each handle index is then 0x4 bytes in size.

2: kd> ?? 0xffffa487`0e0fd001 & ~7
unsigned int64 0xffffa487`0e0fd000

The highlighted portion is a pointer into one of the elements of the array.

2: kd> dq ffffa487`0e0fd000
ffffa487`0e0fd000  ffffa487`0d1ee000 ffffa487`0e0c3000
ffffa487`0e0fd010  ffffa487`168f1000 ffffa487`14cff000
ffffa487`0e0fd020  ffffa487`129ff000 ffffa487`18035000
ffffa487`0e0fd030  ffffa487`19ffe000 ffffa487`104ff000
ffffa487`0e0fd040  ffffa487`13bfb000 00000000`00000000
ffffa487`0e0fd050  00000000`00000000 00000000`00000000
ffffa487`0e0fd060  00000000`00000000 00000000`00000000
ffffa487`0e0fd070  00000000`00000000 00000000`00000000
2: kd> db ffffa487`0d1ee000
ffffa487`0d1ee000  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................ << Index 0x00
ffffa487`0d1ee010  ff ff 30 b9 7d 0f 88 91-03 00 1f 00 00 00 00 00  ..0.}........... << Index 0x4
ffffa487`0d1ee020  ff ff 60 78 83 0f 88 91-04 08 00 00 00 00 00 00  ..`x............ << Index 0x8
ffffa487`0d1ee030  fd ff b0 ba 7d 0f 88 91-03 00 1f 00 00 00 00 00  ....}........... << Index 0x0c
ffffa487`0d1ee040  ff ff e0 9e 62 0f 88 91-01 00 00 00 00 00 00 00  ....b........... << Index 0x10
ffffa487`0d1ee050  f9 ff 50 1e 54 0f 88 91-03 00 1f 00 00 00 00 00  ..P.T........... << Index 0x14
ffffa487`0d1ee060  fb ff f0 cc 75 0f 88 91-ff 00 0f 00 00 00 00 00  ....u........... << Index 0x18
ffffa487`0d1ee070  ff ff 90 d5 74 0f 88 91-02 00 10 00 00 00 00 00  ....t........... << Index 0x1c

The first valid index into the handle table is 0x4, as demonstrated below:

2: kd> !handle 4

PROCESS ffff91880f7ba080
    SessionId: 0  Cid: 148c    Peb: 00304000  ParentCid: 03f8
    DirBase: 1a1ce1000  ObjectTable: ffffa4870d1cf3c0  HandleCount: 2136.
    Image: GameManagerService.exe

Handle table at ffffa4870d1cf3c0 with 2136 entries in use

0004: Object: ffff91880f7db960  GrantedAccess: 001f0003 (Protected) (Inherit) Entry: ffffa4870d1ee010
Object: ffff91880f7db960  Type: (ffff9188026c8bc0) Event
    ObjectHeader: ffff91880f7db930 (new version)
        HandleCount: 1  PointerCount: 32768

Each handle table entry is represented by the _HANDLE_TABLE_ENTRY structure.

2: kd> dt _HANDLE_TABLE_ENTRY ffffa487`0d1ee000+0x10
nt!_HANDLE_TABLE_ENTRY
   +0x000 VolatileLowValue : 0n-7960095308725026817
   +0x000 LowValue         : 0n-7960095308725026817
   +0x000 InfoTable        : 0x91880f7d`b930ffff _HANDLE_TABLE_ENTRY_INFO
   +0x008 HighValue        : 0n2031619
   +0x008 NextFreeHandleEntry : 0x00000000`001f0003 _HANDLE_TABLE_ENTRY
   +0x008 LeafHandleValue  : _EXHANDLE
   +0x000 RefCountField    : 0n-7960095308725026817
   +0x000 Unlocked         : 0y1
   +0x000 RefCnt           : 0y0111111111111111 (0x7fff)
   +0x000 Attributes       : 0y000
   +0x000 ObjectPointerBits : 0y10010001100010000000111101111101101110010011 (0x91880f7db93)
   +0x008 GrantedAccessBits : 0y0000111110000000000000011 (0x1f0003)
   +0x008 NoRightsUpgrade  : 0y0
   +0x008 Spare1           : 0y000000 (0)
   +0x00c Spare2           : 0

The Unlocked field specifies if the handle is currently in use by a process, and if so, the field will be cleared i.e. set to 0.

The RefCnt field refers to the reference count of the object which the handle table entry is pointing to. The reference count is encoded as a bitfield due to the size of the structure, and therefore, the simplest way to decode the reference count is to use the .formats command as shown below:

4: kd> .formats 0x7fff
Evaluate expression:
  Hex:     00000000`00007fff
  Decimal: 32767
  Octal:   0000000000000000077777
  Binary:  00000000 00000000 00000000 00000000 00000000 00000000 01111111 11111111
  Chars:   .......
  Time:    Thu Jan  1 09:06:07 1970
  Float:   low 4.59163e-041 high 0
  Double:  1.6189e-319

The reference count is 32767; why is it difference to the value reported by the object header? The reason is due to how the reference count is calculated on Windows 8 and later operating systems. The pointer count found in the object header is the sum of the currently opened handles, handles which have been cached and references made by ObReferenceObject. Remember handles are essentially fancy pointers.

On the other hand, the reference count in the handle table entry is an inverted reference count; when a handle is opened, the reference count is decremented by 1. The reason being due to the size limitations of the pointer – the handle table entry has been reduced – and the fact that memory addresses still must be correctly aligned on x64 systems. This is why you’ll see 32-bit values padded with 0’s in registers. If you find this to be rather confusing, then there is a very simple way to find the true reference count of an object, the !trueref WinDbg command. This command will give the actual number of current references to an object and will discount any cached handles.

2: kd> !trueref ffff91880f7db960
ffff91880f7db960: HandleCount: 1 PointerCount: 32768 RealPointerCount: 1

As we can see, there is only one reference to the object; that is the handle itself.

The ObjectPointerBits field contains a pointer to the object header. The bit field needs to be converted by using the following method:

2: kd> ? (0x91880f7db93 << 4) | 0xffff000000000000
Evaluate expression: -121461415233232 = ffff9188`0f7db930

We bit shift the hexadecimal value by 0x4, and then bitwise OR the resulting value to get an appropriate pointer to the object header. The resulting address is the same address as shown in the handle output. To verify this, let’s dump the _OBJECT_HEADER.

2: kd> dt _OBJECT_HEADER ffff9188`0f7db930
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n32768
   +0x008 HandleCount      : 0n1
   +0x008 NextToFree       : 0x00000000`00000001 Void
   +0x010 Lock             : _EX_PUSH_LOCK
   +0x018 TypeIndex        : 0x8 ''
   +0x019 TraceFlags       : 0 ''
   +0x019 DbgRefTrace      : 0y0
   +0x019 DbgTracePermanent : 0y0
   +0x01a InfoMask         : 0x8 ''
   +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 : 0xfffff802`59c53700 _OBJECT_CREATE_INFORMATION
   +0x020 QuotaBlockCharged : 0xfffff802`59c53700 Void
   +0x028 SecurityDescriptor : (null)
   +0x030 Body             : _QUAD

Now, we can find the corresponding object by the following method:

2: kd> !object ffff9188`0f7db930+0x30
Object: ffff91880f7db960  Type: (ffff9188026c8bc0) Event
    ObjectHeader: ffff91880f7db930 (new version)
    HandleCount: 1  PointerCount: 32768

Object Types:

All of the object types supported by the Object Manager belong to table called the Object Type Index table. This can be dumped with WinDbg accordingly.

2: kd> dq nt!ObTypeIndexTable
fffff802`59cfbe20  00000000`00000000 ffffb581`e9d3f000
fffff802`59cfbe30  ffff9188`026a1380 ffff9188`026a17a0
fffff802`59cfbe40  ffff9188`026a14e0 ffff9188`026a1640
fffff802`59cfbe50  ffff9188`026a1900 ffff9188`026a1a60
fffff802`59cfbe60  ffff9188`026c9f00 ffff9188`026c9140
fffff802`59cfbe70  ffff9188`026c8900 ffff9188`026c92a0
fffff802`59cfbe80  ffff9188`026c9400 ffff9188`026c8220
fffff802`59cfbe90  ffff9188`026c9c40 ffff9188`026c8a60

Now, the process of indexing into the object type table to get the corresponding object type is rather odd, it involves getting three key pieces of information. The second least significant type of the object header address, the first byte of the object header cookie and the type index itself. These three pieces of information are then bitwise OR’d together.

The reason why we need these three parts is due to how the nt!ObGetObjectType function works. This is part of the Object Manager and isn’t documented. The function takes one parameter which is the address of the object which you wish to derive the object type from. The following excerpt illustrates the exact same process we’ll be following in WinDbg momentarily.

2: kd> u nt!ObGetObjectType L9
nt!ObGetObjectType:
fffff802`596ad070 488d41d0        lea     rax,[rcx-30h] (1)
fffff802`596ad074 0fb649e8        movzx   ecx,byte ptr [rcx-18h] (2)
fffff802`596ad078 48c1e808        shr     rax,8 (3)
fffff802`596ad07c 0fb6c0          movzx   eax,al
fffff802`596ad07f 4833c1          xor     rax,rcx (4)
fffff802`596ad082 0fb60d93e66400  movzx   ecx,byte ptr [nt!ObHeaderCookie (fffff802`59cfb71c)]
fffff802`596ad089 4833c1          xor     rax,rcx (5)
fffff802`596ad08c 488d0d8ded6400  lea     rcx,[nt!ObTypeIndexTable (fffff802`59cfbe20)]
fffff802`596ad093 488b04c1        mov     rax,qword ptr [rcx+rax*8] (6)


Firstly, we need to get the address of the _OBJECT_HEADER from the provided object address. We subtract 0x30 bytes from the object address (1) since the _OBJECT_HEADER is 0x30 bytes in size, but why does this work? It’s important to note that the “objects” in Windows are merely a group of object structures stored at a particular address. The object header is always stored 0x30 bytes from the start of the object. We then get the value of the TypeIndex field from the object header which is stored at an offset of 0x18 bytes from the object (2). The TypeIndex is then stored in the ecx register for later use.

Afterwards, we get the least significant second byte from the object address. This is then stored in the lower 8 bits of the eax register; the al register (3). The least significant second byte and the type index are XOR’d together (4). We then get the value of the object header cookie and then XOR the cookie with the value we derived from the previous step (5). Finally, we get the address of the object type index table, and then index into the table using the table address and an offset from our XOR operation. This is then multiplied by 8 (6). The final result is the index into the object index table. Let’s demonstrate this process in WinDbg using a known object address.

2: kd> ? ffff91880ef86080-0x30
Evaluate expression: -121461423972272 = ffff9188`0ef86050

Now, we have the address of the object header, let’s dump the structure and get the TypeIndex value. I’ve highlighted the least significant second byte of the object header in blue.

2: kd> dt _OBJECT_HEADER ffff9188`0ef86050
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n0
   +0x008 HandleCount      : 0n2048
   +0x008 NextToFree       : 0x00000000`00000800 Void
   +0x010 Lock             : _EX_PUSH_LOCK
   +0x018 TypeIndex        : 0xc9 ''
   +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         : 0xd3ceb16
   +0x020 ObjectCreateInfo : 0xfffff802`59c53700 _OBJECT_CREATE_INFORMATION
   +0x020 QuotaBlockCharged : 0xfffff802`59c53700 Void
   +0x028 SecurityDescriptor : 0xffffa487`0669aeac Void
   +0x030 Body             : _QUAD

Let’s now dump the object header cookie value and get the first byte.

2: kd> db nt!ObHeaderCookie L1
fffff802`59cfb71c  a1

We all the required pieces of information, so let’s XOR them together.

2: kd> ? 60 ^ 0xc9 ^ a1
Evaluate expression: 8 = 00000000`00000008

Now, using the result of our XOR operation, we can now index into the object type table.

2: kd> dt _OBJECT_TYPE poi(nt!ObTypeIndexTable + (0x8*8))
nt!_OBJECT_TYPE
   +0x000 TypeList         : _LIST_ENTRY [ 0xffff9188`026c9f00 - 0xffff9188`026c9f00 ]
   +0x010 Name             : _UNICODE_STRING "Thread"
   +0x020 DefaultObject    : (null)
   +0x028 Index            : 0x8 ''
   +0x02c TotalNumberOfObjects : 0x13d2
   +0x030 TotalNumberOfHandles : 0x1a5c
   +0x034 HighWaterNumberOfObjects : 0x1539
   +0x038 HighWaterNumberOfHandles : 0x1c8f
   +0x040 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x0b8 TypeLock         : _EX_PUSH_LOCK
   +0x0c0 Key              : 0x65726854
   +0x0c8 CallbackList     : _LIST_ENTRY [ 0xffff9188`026c9fc8 - 0xffff9188`026c9fc8 ]

As we can see, the object type is for a thread object; also, notice how the Index field value is the same as the value we calculated from our XOR operation?

Kernel Handles and Access Rights:

Handles and Access Rights:

I thought it would be useful to mention kernel-mode handles and access rights for objects. These are two key areas which tend to end up causing crashes. Typically, the former will result in a Stop 0xC4 bugcheck and is usually very easy to debug. First of all, we’ll discuss access rights and it’s relationship with opening a handle to an object.

When an object is first created, the Object Manager will request for the permitted access rights for that said object. A few of these access rights are generic and applicable to all objects managed by Object Manager, whereas, there are a few which are specific to that particular object type. We can find the access rights permitted for an object type, by checking the associated object type initialisation structure and then the ValidAccessMask field.

2: kd> dt _OBJECT_TYPE_INITIALIZER ffff9188026c8bc0+0x40 -y ValidAccessMask
nt!_OBJECT_TYPE_INITIALIZER
   +0x01c ValidAccessMask : 0x1f0003

The GenericMapping field contains a pointer to a generic mapping structure, which maps the generic access rights to type-specific ones, in this case, we can see that full access is permissible for this object type.

2: kd> dt _OBJECT_TYPE_INITIALIZER ffff9188026c8bc0+0x40 -y GenericMapping
nt!_OBJECT_TYPE_INITIALIZER
   +0x00c GenericMapping : _GENERIC_MAPPING
2: kd> dt _GENERIC_MAPPING ffff9188`026c8c0c
nt!_GENERIC_MAPPING
   +0x000 GenericRead      : 0x20001
   +0x004 GenericWrite     : 0x20002
   +0x008 GenericExecute   : 0x120000
   +0x00c GenericAll       : 0x1f0003

When a thread wishes to open a handle to an object, it must pass an access mask object to the corresponding API function, for example NtOpenThread to open a handle to an existing thread object. The object manager will then compare the access rights stored within the security descriptor of the object to the access rights passed to it. From here, the object manager will call the callback function registered for the object’s security method. If the access validation checks pass, then a handle will be passed to the calling thread for the given object.

To find the security descriptor and subsequently the access rights for an object, you will need to dump the object header and then the SecurityDescriptor field. The address stored in this field isn’t the direct address of the security descriptor, you’ll need to bitwise AND it with the complement of 0x7.

9: kd> dt _OBJECT_HEADER -y SecurityDescriptor ffffdb8d14b6e410
nt!_OBJECT_HEADER
   +0x028 SecurityDescriptor : 0xffffcb81`801bf365 Void
9: kd> ?? 0xffffcb81`801bf365 & ~0x7
unsigned int64 0xffffcb81`801bf360

The calculated address is then the actual address of the security descriptor object. We can dump the security descriptor using the !sd command.

9: kd> !sd ffffcb81`801bf360
->Revision: 0x1
->Sbz1    : 0x0
->Control : 0x8814
            SE_DACL_PRESENT
            SE_SACL_PRESENT
            SE_SACL_AUTO_INHERITED
            SE_SELF_RELATIVE
->Owner   : S-1-5-5-0-96256
->Group   : S-1-5-19
->Dacl    :
->Dacl    : ->AclRevision: 0x2
->Dacl    : ->Sbz1       : 0x0
->Dacl    : ->AclSize    : 0x3c
->Dacl    : ->AceCount   : 0x2
->Dacl    : ->Sbz2       : 0x0
->Dacl    : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[0]: ->AceFlags: 0x0
->Dacl    : ->Ace[0]: ->AceSize: 0x1c
->Dacl    : ->Ace[0]: ->Mask : 0x001fffff
->Dacl    : ->Ace[0]: ->SID: S-1-5-5-0-96256

->Dacl    : ->Ace[1]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
->Dacl    : ->Ace[1]: ->AceFlags: 0x0
->Dacl    : ->Ace[1]: ->AceSize: 0x18
->Dacl    : ->Ace[1]: ->Mask : 0x00001400
->Dacl    : ->Ace[1]: ->SID: S-1-5-32-544

->Sacl    :
->Sacl    : ->AclRevision: 0x2
->Sacl    : ->Sbz1       : 0x0
->Sacl    : ->AclSize    : 0x1c
->Sacl    : ->AceCount   : 0x1
->Sacl    : ->Sbz2       : 0x0
->Sacl    : ->Ace[0]: ->AceType: SYSTEM_MANDATORY_LABEL_ACE_TYPE
->Sacl    : ->Ace[0]: ->AceFlags: 0x0
->Sacl    : ->Ace[0]: ->AceSize: 0x14
->Sacl    : ->Ace[0]: ->Mask : 0x00000003
->Sacl    : ->Ace[0]: ->SID: S-1-16-16384

Now, each security descriptor will usually have a DACL or discretionary access control list associated to it. The DACL then has a number of ACEs – access control entries – which either permit or deny particular access rights for a user or user group. The SID from the process’ access token is compared to the SIDs in the ACEs, if it matches, then that ACE is evaluated against the access rights being requested.

In our example above, we can see there are two different ACEs corresponding to two different SIDs, one which is a user and the other is a user group. The first ACE refers to the current user who is logged in, they’re the owner of the object and therefore will automatically have full access to the object in question. On the other hand, the other ACE, refers to the Administrators user group and as we can see, they have a slightly different access mask and therefore different access rights.

As we can see, the access mask for the user has been set to a value of 0x001fffff, which corresponds PROCESS_ALL_ACCESS access right. This is a combination of a few different fields, as demonstrated below:

9: kd> ? 0x000F0000 | 0x00100000 | 0xFFFF
Evaluate expression: 2097151 = 00000000`001fffff

The granted access rights for a given handle can be found by either using !handle command or by dumping the individual handle table entry, and then checking the GrantedAccessBits field like so:

3: kd> dt _HANDLE_TABLE_ENTRY -y GrantedAccessBits ffffd8022e0ac010
nt!_HANDLE_TABLE_ENTRY
   +0x008 GrantedAccessBits : 0y0000111111111111111111111 (0x1fffff)

As mentioned previously, this is another bit mask field and therefore setting or clearing individual bits will enable different access rights for that handle.

3: kd> !handle 0x7

PROCESS ffff9888c46b0080
    SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 001ad000  ObjectTable: ffffd8022e022680  HandleCount: 1577.
    Image: System

Kernel handle table at ffffd8022e022680 with 1577 entries in use

0007: Object: ffff9888c46b0080  GrantedAccess: 001fffff (Protected) (Audit) Entry: ffffd8022e0ac010
Object: ffff9888c46b0080  Type: (ffff9888c469e640) Process
    ObjectHeader: ffff9888c46b0050 (new version)
        HandleCount: 4  PointerCount: 131322

Object Attribute Flags

The attribute flags for the object itself is stored within the Flags field of the object header. This is a union type hence why you see several fields with the same offset, you can examine each field underneath Flags to check which object attributes have been set. The Flags field contains a bitmask of these flags bitwise OR’d together.

2: kd> dt _OBJECT_HEADER -y Flags ffff9188029e4390
nt!_OBJECT_HEADER
   +0x01b Flags : 0x12 ''
2: kd> .formats 0x12 
Evaluate expression:
  Hex:     00000000`00000012
  Decimal: 18
  Octal:   0000000000000000000022
  Binary:  00000000 00000000 00000000 00000000 00000000 00000000 00000000 00010010
  Chars:   ........
  Time:    Thu Jan  1 00:00:18 1970
  Float:   low 2.52234e-044 high 0
  Double:  8.89318e-323

As we can see, the KernelObject (0x2) and the PermanentObject (0x10) have been set. If we bitwise OR these two values together then we get the value of 0x12.

2: kd> ? 0x2 | 0x10
Evaluate expression: 18 = 00000000`00000012

Kernel Handles

To check if an object should only be referenced by using a kernel-mode handle, then you can simply check the KernelOnlyAccess field of the object header as shown below:

2: kd> dt _OBJECT_HEADER ffff9188029e4390
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n68
   +0x008 HandleCount      : 0n0
   +0x008 NextToFree       : (null)
   +0x010 Lock             : _EX_PUSH_LOCK
   +0x018 TypeIndex        : 0xc0 ''
   +0x019 TraceFlags       : 0 ''
   +0x019 DbgRefTrace      : 0y0
   +0x019 DbgTracePermanent : 0y0
   +0x01a InfoMask         : 0x2 ''
   +0x01b Flags            : 0x12 '' << Object attribute flags
   +0x01b NewObject        : 0y0
   +0x01b KernelObject     : 0y1
   +0x01b KernelOnlyAccess : 0y0
   +0x01b ExclusiveObject  : 0y0
   +0x01b PermanentObject  : 0y1
   +0x01b DefaultSecurityQuota : 0y0
   +0x01b SingleHandleEntry : 0y0
   +0x01b DeletedInline    : 0y0
   +0x01c Reserved         : 0
   +0x020 ObjectCreateInfo : 0x00000000`00000001 _OBJECT_CREATE_INFORMATION
   +0x020 QuotaBlockCharged : 0x00000000`00000001 Void
   +0x028 SecurityDescriptor : 0xffffa487`06a9f264 Void
   +0x030 Body             : _QUAD

Kernel handles are used by device drivers and system processes, they are handles which should not be accessible from user-mode at all. These handles aren’t tied to a specific process like other handles are, instead they are associated with the System process and each handle subsequently then indexes into the kernel handle table. This can be found by dumping the ObpKernelHandleTable global variable. It is important to note that the kernel handle table will also contain handles which belong to the System process specifically.

2: kd> ? poi(ObpKernelHandleTable)
Evaluate expression: -100575142178752 = ffffa487`06609040
2: kd> dt _HANDLE_TABLE ffffa487`06609040
nt!_HANDLE_TABLE
   +0x000 NextHandleNeedingPool : 0x7000
   +0x004 ExtraInfoPages   : 0n0
   +0x008 TableCode        : 0xffffa487`08152001
   +0x010 QuotaProcess     : (null)
   +0x018 HandleTableList  : _LIST_ENTRY [ 0xffffa487`06643058 - 0xfffff802`59d2db40 ]
   +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)

Optional Object Headers:

In addition to the main object header briefly mentioned earlier, there are a number of optional object headers which are created only when particular conditions are met. Each of these headers can be found directly before the main object header in memory. There are currently eight optional object headers and their presence can be tested by checking the InfoMask field of the main object header as shown below.

2: kd> dt _OBJECT_HEADER ffff9188029e4390
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n68
   +0x008 HandleCount      : 0n0
   +0x008 NextToFree       : (null)
   +0x010 Lock             : _EX_PUSH_LOCK
   +0x018 TypeIndex        : 0xc0 ''
   +0x019 TraceFlags       : 0 ''
   +0x019 DbgRefTrace      : 0y0
   +0x019 DbgTracePermanent : 0y0
   +0x01a InfoMask         : 0x2 ''
   +0x01b Flags            : 0x12 ''
   +0x01b NewObject        : 0y0
   +0x01b KernelObject     : 0y1
   +0x01b KernelOnlyAccess : 0y0
   +0x01b ExclusiveObject  : 0y0
   +0x01b PermanentObject  : 0y1
   +0x01b DefaultSecurityQuota : 0y0
   +0x01b SingleHandleEntry : 0y0
   +0x01b DeletedInline    : 0y0
   +0x01c Reserved         : 0
   +0x020 ObjectCreateInfo : 0x00000000`00000001 _OBJECT_CREATE_INFORMATION
   +0x020 QuotaBlockCharged : 0x00000000`00000001 Void
   +0x028 SecurityDescriptor : 0xffffa487`06a9f264 Void
   +0x030 Body             : _QUAD

Since the InfoMask field is a bit field, then we can use the .formats command to convert it to its binary representation. Each bit indicates if a particular header is present or not. In our case, we can see at least one option object header is available. This is the OBJECT_HEADER_NAME_INFO structure.

2: kd> .formats 0x2
Evaluate expression:
  Hex:     00000000`00000002
  Decimal: 2
  Octal:   0000000000000000000002
  Binary:  00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000010
  Chars:   ........
  Time:    Thu Jan  1 00:00:02 1970
  Float:   low 2.8026e-045 high 0
  Double:  9.88131e-324

To calculate the offset to the header which we desire, we can use the following expression:

2: kd> ?? ((unsigned char *)@@masm(nt!ObpInfoMaskToOffset))[0x2 & (0x02 | (0x02-1))]
unsigned char 0x20 ' '

The following excerpt from Code Machine describes what the array is for and why we’re indexing into the array:

Depending on the number and position of the bits set in OBJECT_HEADER->InfoMask a number is calculated which serves as an index into the ObpInfoMaskToOffset[] array. The elements of this array contain the offset of the desired optional header taking into consideration presence of the other optional headers. This array is large enough to accommodate the offsets for all the 2^5 possibilities based on the bits in OBJECT_HEADER->InfoMask.

Code Machine – Windows Object Headers

I prefer the expression provided by the CodeMachine rather than the one mentioned in the Windows Internals book. I find it to be far cleaner and easier to write. The first value (0x2) is the value of the InfoMask field and the other two values are the desired header which we wish to find the offset to. The following table describes each bit and its corresponding value:

Optional Object Header NameBit PositionValue
Creator Information (OBJECT_HEADER_CREATOR_INFO)00x1
Name Information (OBJECT_HEADER_NAME_INFO)10x2
Handle Information (OBJECT_HEADER_HANDLE_INFO)20x4
Quota Information (OBJECT_HEADER_QUOTA_INFO)30x8
Process Information (OBJECT_HEADER_PROCESS_INFO)40x10
Audit Information (OBJECT_HEADER_AUDIT_INFO)50x20
Extended Information (OBJECT_HEADER_EXTENDED_INFO)60x40
Padding Information (OBJECT_HEADER_PADDING_INFO)70x80
Optional Object Header Values

As you may recall, the optional object headers are stored before the object header itself, therefore we’ll be subtracting the offset from the object header address like so.

2: kd> dt _OBJECT_HEADER_NAME_INFO ffff9188029e4390-0x20
nt!_OBJECT_HEADER_NAME_INFO
   +0x000 Directory        : 0xffffa487`066fb920 _OBJECT_DIRECTORY
   +0x008 Name             : _UNICODE_STRING "ACPI"
   +0x018 ReferenceCount   : 0n0
   +0x01c Reserved         : 0

As we can see, the name of the object in question is ACPI, which is the exact same name as shown by the !object command.

2: kd> !object ffff9188029e4390+0x30
Object: ffff9188029e43c0  Type: (ffff918802747400) Driver
    ObjectHeader: ffff9188029e4390 (new version)
    HandleCount: 0  PointerCount: 68
    Directory Object: ffffa487066fb920  Name: ACPI

I mentioned previously that the optional object headers are only created under certain conditions. The Name header is only available if an object has been created with a name set, whereas, the Quota header is only present if the object was created by the initial or idle system process. The next three optional headers are based upon the setting of the Attributes flags respectively. These flags are OBJ_EXCLUSIVE for the Process header; the maintain handle count flag on the object type for Handle; maintain type list flag on the object type for Creator.

15: kd> dt _OBJECT_HEADER -y ExclusiveObject
nt!_OBJECT_HEADER
   +0x01b ExclusiveObject : Pos 3, 1 Bit

This field must be set to true for the Process header.

15: kd> dt _OBJECT_TYPE_INITIALIZER -y MaintainHandleCount
nt!_OBJECT_TYPE_INITIALIZER
   +0x002 MaintainHandleCount : Pos 4, 1 Bit

This field must be set to true for Handle header.

15: kd> dt _OBJECT_TYPE_INITIALIZER -y MaintainTypeList
nt!_OBJECT_TYPE_INITIALIZER
   +0x002 MaintainTypeList : Pos 5, 1 Bit

This field must be set to true for the Creator header.

The Audit object header is only present for objects which are File objects. You can check this by examining the type of the object.

The Extended object header is available for objects which require an object footer. The object footer sits at the base of the object – if we refer back to our object structure from earlier – and is represented by the OBJECT_FOOTER structure. The footer is present for objects which have been created with the ObCreateObjectEx API and have either of the two fields set in the OB_EXTENDED_CREATION_INFO structure which is passed to aforementioned function. Unfortunately, there are no public symbols available for this structure, but the two fields which either of must be set are AllowHandleRevocation and AllowExtendedUserInfo. The object footer contains two pointers to these:

2: kd> dt _OBJECT_FOOTER
nt!_OBJECT_FOOTER
   +0x000 HandleRevocationInfo : _HANDLE_REVOCATION_INFO
   +0x020 ExtendedUserInfo : _OB_EXTENDED_USER_INFO


Lastly, the Padding object header is only present if the CacheAligned field has been set. This is always the case for thread and process objects.

2: kd> dt _OBJECT_TYPE_INITIALIZER -y CacheAligned
win32k!_OBJECT_TYPE_INITIALIZER
   +0x002 CacheAligned : Pos 7, 1 Bit

References:

Windows Process Internals: A few Concepts to know before jumping on Memory Forensics [Part 5] — A…
A Light on Windows 10’s “OBJECT_HEADER->TypeIndex”
CodeMachine – Article – Windows Object Headers
The Case Of The Bloated Reference Count: Handle Table Entry Changes in Windows 8.1_HackDig
Flags in the OBJECT_HEADER
Reversing Windows Internals (Part 1) – Digging Into Handles, Callbacks & ObjectTypes
Windows Internals 7th Edition – Part 1
Windows Internals 7th Edition – Part 2

This entry was posted in WinDbg, Windows Internals. Bookmark the permalink.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.