Journey Into the Object Manager Executive Subsystem: Object Header and Object Type

0x00 Abstract

Almost all the actions carried out by user mode applications and Windows executive subsystems (e.g. I/O Manager, Memory Manager) have to deal with Windows resources (aka objects). These actions can be related to physical objects like devices or logical objects such as processes, threads, tokens and files. For this specific reason, the Object Manager, which is a executive subsystem, is responsible for providing a standardised, uniform and singular way to manage, create, release and access objects. Among other things, this ensures that other user mode applications and executive subsystems do not re-invent the wheel when they are interacting with objects, which might cause duplication of code and logic, resulting in security issues and logical errors.

Even if everything related to objects has to go through the Object Manager, defining Windows as an object oriented operating system would be an overstatement as many kernel structures are not objects. Additionally, the Object Manager is not a new thing. In fact, executive subsystems have relied on the Object Manager since Windows NT.

This series of paper around the Windows Object Manager Executive Subsystem are based on at the time of writing knowledge, research, what I reverse engineered and what I learned from others. Additionally, the aim of this series is to evolve over time. Therefore, if some elements are incorrect, or not as accurate as they should be, please let me know.

The following resources helped me building this series: Ultimately, all test structures, tests and reverse engineering was conducted on a Windows 10 Professional system version 2004.

0x01 Object Header

Objects are nothing more than a piece of memory, a data structure with two components: a header and a body. The header is common to every object and the body depends on the object type. The object manager only manages the header of the object and the body is managed by an executive subsystem according to the object type. For example, the body of a file object will be managed by the I/O Manager executive subsystem.

The object header structure is defined as follows:

kd> dt 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

Example with a process object:

kd> !process 0 0 notepad.exe
PROCESS ffffe5038a8cb080
    SessionId: 1  Cid: 25cc    Peb: c0d47d3000  ParentCid: 1b20
    DirBase: 4fe7c9000  ObjectTable: ffffc08fd89b0a00  HandleCount: 498.
    Image: notepad.exe
kd> !object ffffe5038a8cb080
Object: ffffe5038a8cb080  Type: (ffffe5036cac1bc0) Process
    ObjectHeader: ffffe5038a8cb050 (new version)
    HandleCount: 7  PointerCount: 222572
kd> dt nt!_OBJECT_HEADER ffffe5038a8cb050
   +0x000 PointerCount         : 0n222556
   +0x008 HandleCount          : 0n7
   +0x008 NextToFree           : 0x00000000`00000007 Void
   +0x010 Lock                 : _EX_PUSH_LOCK
   +0x018 TypeIndex            : 0x81 ''
   +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             : 0xd
   +0x020 ObjectCreateInfo     : 0xffffe503`75c8da40 _OBJECT_CREATE_INFORMATION
   +0x020 QuotaBlockCharged    : 0xffffe503`75c8da40 Void
   +0x028 SecurityDescriptor   : 0xffffc08f`8d15eae4 Void
   +0x030 Body                 : _QUAD

Please note that in x64 architecture the size of the _OBJECT_HEADER is of 0x30 bytes and in x86 of 0x18 bytes.

From this structure, there are interesting fields that deserve some explanation. First, there are the PointerCount and HandleCount fields. The PointerCount represents the total number of references to the object and is increased every time a component references the object, for example when querying further information about the object. The HandleCount field represents the total number of open handles to the object. Both fields are used as part of the retention mechanism of the Object Manager. This means that when and solely when the number of references to the object drops to zero, the object will be released and the memory freed, unless the object is created with the OBJ_PERMANENT flag.

Second, there is the TypeIndex field which is an 8-bit value used to find the object type. This type will dictate which executive subsystem will manage the body of the object. More about that in the next section.

Third, the InfoMask is a bitmask used to define which additional object headers are present. In Windows 10 there are 5 optional headers, defined as follows:

   +0x000 TypeList                     : _LIST_ENTRY
   +0x010 CreatorUniqueProcess         : Ptr64 Void
   +0x018 CreatorBackTraceIndex        : Uint2B
   +0x01a Reserved1                    : Uint2B
   +0x01c Reserved2                    : Uint4B
   +0x000 Directory                    : Ptr64 _OBJECT_DIRECTORY
   +0x008 Name                         : _UNICODE_STRING
   +0x018 ReferenceCount               : Int4B
   +0x01c Reserved                     : Uint4B
   +0x000 HandleCountDataBase          : Ptr64 _OBJECT_HANDLE_COUNT_DATABASE
   +0x000 SingleEntry                  : _OBJECT_HANDLE_COUNT_ENTRY
   +0x000 PagedPoolCharge              : Uint4B
   +0x004 NonPagedPoolCharge           : Uint4B
   +0x008 SecurityDescriptorCharge     : Uint4B
   +0x00c Reserved1                    : Uint4B
   +0x010 SecurityDescriptorQuotaBlock : Ptr64 Void
   +0x018 Reserved2                    : Uint8B
   +0x000 ExclusiveProcess             : Ptr64 _EPROCESS
   +0x008 Reserved                     : Uint8B

Each header has different information :
  • The _OBJECT_HEADER_CREATOR_INFO structure contains a list of all the objects of the same type and a pointer to the process that created the object;
  • The _OBJECT_HEADER_NAME_INFO structure contains the name of the object if this a named object and a pointer to an _OBJECT_DIRECTORY structure, which is where;
  • The _OBJECT_HEADER_HANDLE_INFO structure is used to keep track of all the processes that have an open handle to the object and therefore contains either a _OBJECT_HANDLE_COUNT_ENTRY structure or a _OBJECT_HANDLE_COUNT_DATABASE structure which has a list of _OBJECT_HANDLE_COUNT_ENTRY and the total number of entries;
  • The _OBJECT_HEADER_QUOTA_INFO structure contains information related to the memory resources allocated to the object; and
  • The _OBJECT_HEADER_PROCESS_INFO structure has a pointer to the executive process structure of the owner of the object.
As mentioned, they are optional headers and therefore not always present, as the allocation of optional headers depends on how an object was created. Nevertheless, theses headers are on top of the _OBJECT_HEADER structure and are always set in the same order and they always have the same size. This means that the address of an optional header cannot be predicted and must be calculated dynamically on a per-object basis, as an offset from the _OBJECT_HEADER base address. This is where the ObpInfoMaskToOffset table comes into play, as this table has all the possible offsets for all the different combinations of optional headers. The process is to first check the InfoMask field to know which optional headers are present and then use the InfoMask in conjunction with the ObpInfoMaskToOffset table to get the address of the option header.

The location of the headers is calculated as follows:
Header Offset
OBJECT_HEADER_NAME_INFO OBJECT_HEADER address - ObpInfoMaskToOffset[InfoMask & 0x3]
OBJECT_HEADER_HANDLE_INFO OBJECT_HEADER address - ObpInfoMaskToOffset[InfoMask & 0x7]
OBJECT_HEADER_QUOTA_INFO OBJECT_HEADER address - ObpInfoMaskToOffset[InfoMask & 0xF]
OBJECT_HEADER_PROCESS_INFO OBJECT_HEADER address - ObpInfoMaskToOffset[InfoMask & 0x1F]
Fourth, the Flag field is another bitmask used to modify the behaviour of an object during creation time and/or during specific operations (e.g. when the object is accessed). This field is populated with some data from the Attributes field of the _OBJECT_ATTRIBUTES structure, which is passed to the Object Manager upon creation of the object.

   +0x000 Length                   : Uint4B
   +0x008 RootDirectory            : Ptr64 Void
   +0x010 ObjectName               : Ptr64 _UNICODE_STRING
   +0x018 Attributes               : Uint4B
   +0x020 SecurityDescriptor       : Ptr64 Void
   +0x028 SecurityQualityOfService : Ptr64 Void

The flags are as follows:
Bitmask Flag Object Manager Flags (ob.h) Description
NewObject OB_FLAG_NEW_OBJECT (0x01) Define that the object was created but not stored into the namespace of the object.
KernelObject OB_FLAG_KERNEL_OBJECT (0x02) Define that the object can only have kernel handles.
KernelOnlyAccess OB_FLAG_KERNEL_ONLY_ACCESS (0x04) Define that only the kernel can open an handle to the object.
ExclusiveObject OB_FLAG_EXCLUSIVE_OBJECT (0x08) Define that only the process that created the object can access and use the object.
PermanentObject OB_FLAG_PERMANENT_OBJECT (0x10) Define that the object will not be disposed when the handle count drop to 0, if named object.
DefaultSecurityQuota OB_FLAG_DEFAULT_SECURITY_QUOTA (0x20) Define that the security descriptor use the default 2-KB quota.
SingleHandleEntry OB_FLAG_SINGLE_HANDLE_ENTRY (0x40) Define that the optional header OBJECT_HEADER_HANDLE_INFO structure only contains one entry and not a list of handle.
DeletedInline OB_FLAG_DELETED_INLINE (0x80) Define that the object is in queue for being deleted by worker thread at IRQL 0. This is used to prevent deadlock and system crashes.
Finally, the SecurityDescriptor field is a pointer to a _SECURITY_DESCRIPTOR structure which could contain security identifiers (SID), a Discretionary Access Control List (DACL) and a System Access Control List (SACL). These components are used for logging purposes and to control access to an object. The security descriptor is also pulled from the _OBJECT_ATTRIBUTES structure at the time of the object's creation.

0x02 Object Types

First of all, Windows has three distinct categories of objects:
  • Kernel objects, which are owned and used by the kernel only. Theses objects are used, amongst other things, to create executive objects;
  • Graphic Device Interface (GDI)/ User objects such as Window, Menu and Icon. Theses objects are managed by Win32k.sys and not by the kernel; and
  • Executive objects such as Process, Threads, Token and file. Theses objects are exposed to user mode applications.
A more detailed list of objects per category can be found here. Note that in the documentation Microsoft describe executive objects as kernel objects.

As mentioned in the previous section, the body of the object is managed and the structure is defined on a per-type basis. Each type have a structure called _OBJECT_TYPE, which is defined as follows:

kd> dt 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

The Name field is a _UNICODE_STRING structure and as the name implies has the name of the type. The TotalNumberOfObjects and TotalNumberOfHandles fields are respectively represent the total number of objects of this type and the total number of open handles on objects of this type. The Index field represents the index of the type in the ObTypeIndexTable table, which is discussed later in this paper. Finally, the TypeInfo field is a pointer to a _OBJECT_TYPE_INITIALIZER structure, which contains the methods and common data shared between objects of the same type.

The structure is defined as follows:

   +0x000 Length                    : Uint2B
   +0x002 ObjectTypeFlags           : Uint2B
   +0x002 CaseInsensitive           : Pos 0, 1 Bit
   +0x002 UnnamedObjectsOnly        : Pos 1, 1 Bit
   +0x002 UseDefaultObject          : Pos 2, 1 Bit
   +0x002 SecurityRequired          : Pos 3, 1 Bit
   +0x002 MaintainHandleCount       : Pos 4, 1 Bit
   +0x002 MaintainTypeList          : Pos 5, 1 Bit
   +0x002 SupportsObjectCallbacks   : Pos 6, 1 Bit
   +0x002 CacheAligned              : Pos 7, 1 Bit
   +0x003 UseExtendedParameters     : Pos 0, 1 Bit
   +0x003 Reserved                  : Pos 1, 7 Bits
   +0x004 ObjectTypeCode            : Uint4B
   +0x008 InvalidAttributes         : Uint4B
   +0x00c GenericMapping            : _GENERIC_MAPPING
   +0x01c ValidAccessMask           : Uint4B
   +0x020 RetainAccess              : Uint4B
   +0x024 PoolType                  : _POOL_TYPE
   +0x028 DefaultPagedPoolCharge    : Uint4B
   +0x02c DefaultNonPagedPoolCharge : Uint4B
   +0x030 DumpProcedure             : Ptr64     void 
   +0x038 OpenProcedure             : Ptr64     long 
   +0x040 CloseProcedure            : Ptr64     void 
   +0x048 DeleteProcedure           : Ptr64     void 
   +0x050 ParseProcedure            : Ptr64     long 
   +0x050 ParseProcedureEx          : Ptr64     long 
   +0x058 SecurityProcedure         : Ptr64     long 
   +0x060 QueryNameProcedure        : Ptr64     long 
   +0x068 OkayToCloseProcedure      : Ptr64     unsigned char 
   +0x070 WaitObjectFlagMask        : Uint4B
   +0x074 WaitObjectFlagOffset      : Uint2B
   +0x076 WaitObjectPointerOffset   : Uint2B

The ObjectTypeFlags field is a bitmask that defines multiple things, such as:
Bitmask Flag Description
CaseInsensitive Define whether the name is case-sensitive
UnnamedObjectsOnly Define whether the object must be unnamed (e.g. processes)
UseDefaultObject Define whether the object has a custom dispatcher for synchronisation
SecurityRequired Define whether a non default security descriptor is required to access the object
MaintainHandleCount Define whether the handle database from the _OBJECT_HEADER_HANDLE_INFO structure should be updated
MaintainTypeList Define whether the list of same object types from the _OBJECT_HEADER_CREATOR_INFO structure should be updated
SupportsObjectCallbacks Define whether the object type supports object call-backs
CacheAligned At time of writing I do not know what this flag is used for
The PoolType field defined if objects of this type will be allocated from paged or non-paged memory. The ValidAccessMask field represents the valid access mask when opening a handle to this object type. The InvalidAttributes field represents the attributes from the Attributes field of the _OBJECT_ATTRIBUTES structure which are invalid for this type of object. The GenericMapping is a pointer to a _GENERIC_MAPPING structure that is used to translate the 4 generic rights (i.e. Read, Write, Execute and All) into specific rights.

Finally, all the fields ending with "Procedure" or "ProcedureEx" are pointers to object methods that are used by the Object Manager for conducting various actions throughout the lifetime of the object. For example, the OpenProcedure function address will be called by the Object Manager whenever a handle is opened. Same thing with the CloseProcedure function address, when an handle is closed. Please note that the "procedure" fields are defined per object type basis, which means that some object type will expose more or less methods.

The Object Manager has a table called ObTypeIndexTable in which each element is a pointer to a _OBJECT_TYPE structure. As shown in the previous section, each _OBJECT_HEADE structure has a TypeIndex field. Prior to Windows 10, the value of that field could be directly used as an index to the ObTypeIndexTable table to get the corresponding _OBJECT_TYPE structure. Now things are different, the address of a _OBJECT_TYPE structure is retrieved by using the ObGetObjectType function exposed by the Object Manager. This function takes a single parameter, which is the address of the body of an object, and returns the address of an _OBJECT_TYPE structure.

The functions is defined as follows:

Line by line the function does the following:
  1. Store into RAX register the address of the _OBJECT_HEADER structure of the object with the load effective address(LEA) operator. The RCX register holds the first parameter of a function;
  2. Move into ECX register the value of the TypeIndex field from the _OBJECT_HEADER structure via the move with zero-extend (MOVZX) operator;
  3. Shift the address of the _OBJECT_HEADER structure of 8 bits to the right with the shift right operator (SHR);
  4. Move into last byte of the register into EAX with the MOVZX register;
  5. Perform a bitwise XOR operation between RAX and RCX, which at this stage are respectively the second byte of the address of the _OBJECT_HEADER structure and the TypeIndex value from the structure;
  6. Move into the ECX register the byte value of the ObHeaderCookie address;
  7. Perform another bitwise XOR operation between RAX and RCX registers, which at this stage are respectively the result of the previous bitwise XOR operation and the byte from the ObHeaderCookie address;
  8. Store the address of the first entry of the ObTypeIndexTable table into the RCX register with the LEA operator;
  9. Move into the RAX register the X element of the table. The address of the X element of the table is calculated by multiplying the index previously calculated by the size of a pointer (8 for x64 and 4 for x86) and summing the result with the address of the table itself; and
  10. Finally, the function is complete and the value from the RAX register is returned to the caller.
Example on how to get the _OBJECT_TYPE structure corresponding to an object, following the logic above:

kd> !object ffffe50373dfdf90
Object: ffffe50373dfdf90  Type: (ffffe5036cac8c40) Mutant
    ObjectHeader: ffffe50373dfdf60 (new version)
    HandleCount: 0  PointerCount: 1
    Directory Object: ffffc08f80e33ce0  Name: PendingRenameMutex
kd> dt nt!_OBJECT_HEADER ffffe50373dfdf60
   +0x000 PointerCount     : 0n1
   +0x008 HandleCount      : 0n0
   +0x008 NextToFree       : (null) 
   +0x010 Lock             : _EX_PUSH_LOCK
   +0x018 TypeIndex        : 0xf8 ''
   +0x019 TraceFlags       : 0 ''
   +0x019 DbgRefTrace      : 0y0
   +0x019 DbgTracePermanent : 0y0
   +0x01a InfoMask         : 0xa ''
   +0x01b Flags            : 0x10 ''
   +0x01b NewObject        : 0y0
   +0x01b KernelObject     : 0y0
   +0x01b KernelOnlyAccess : 0y0
   +0x01b ExclusiveObject  : 0y0
   +0x01b PermanentObject  : 0y1
   +0x01b DefaultSecurityQuota : 0y0
   +0x01b SingleHandleEntry : 0y0
   +0x01b DeletedInline    : 0y0
   +0x01c Reserved         : 0xffffffff
   +0x020 ObjectCreateInfo : 0xfffff800`05c539c0 _OBJECT_CREATE_INFORMATION
   +0x020 QuotaBlockCharged : 0xfffff800`05c539c0 Void
   +0x028 SecurityDescriptor : 0xffffc08f`8178812e Void
   +0x030 Body             : _QUAD
kd> db nt!ObHeaderCookie L1
fffff800`05cfc71c  36                                               6
kd> ?? (((nt!_OBJECT_TYPE**)@@(nt!ObTypeIndexTable))[0xdf ^ 0xf8 ^ 0x36])
struct _OBJECT_TYPE * 0xffffe503`6cac8c40
   +0x000 TypeList         : _LIST_ENTRY [ 0xffffe503`6cac8c40 - 0xffffe503`6cac8c40 ]
   +0x010 Name             : _UNICODE_STRING "Mutant"
   +0x020 DefaultObject    : (null) 
   +0x028 Index            : 0x11 ''
   +0x02c TotalNumberOfObjects : 0x91f
   +0x030 TotalNumberOfHandles : 0xa44
   +0x034 HighWaterNumberOfObjects : 0xb0a
   +0x038 HighWaterNumberOfHandles : 0xd62
   +0x040 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x0b8 TypeLock         : _EX_PUSH_LOCK
   +0x0c0 Key              : 0x6174754d
   +0x0c8 CallbackList     : _LIST_ENTRY [ 0xffffe503`6cac8d08 - 0xffffe503`6cac8d08 ]

Because there is an object type Type, it is possible to get the total number of different object types from the TotalNumberOfObjects field of the _OBJECT_TYPE structure of the object type Type. At the time of writing, there is 67 different executive objects. 2 being the index if the Type executive object in the ObTypeIndexTable table:

kd> ?? (((nt!_OBJECT_TYPE**)@@(nt!ObTypeIndexTable))[2])->TotalNumberOfObjects
unsigned long 0x43

Example on how to list the name of all executive objects types:

kd> r $t1 = 0x43 + 2; .for(r $t0 = 2; @$t0 < @$t1; r $t0 = @$t0+1) { ??  (((nt!_OBJECT_TYPE**)@@(nt!ObTypeIndexTable))[(@$t0)])->Name }
   +0x000 Length           : 8
   +0x002 MaximumLength    : 0xa
   +0x008 Buffer           : 0xffffaf01`e0a06560  "Type"
   +0x000 Length           : 0x30
   +0x002 MaximumLength    : 0x32
   +0x008 Buffer           : 0xffffaf01`e0b62790  "VRegConfigurationContext"

Popular posts from this blog

AppLocker Policy Enumeration in C

GitHub Actions - Doxygen Documentation Deployment

Cobalt Strike Aggressor Scripts-Ception

AMSI Module Remote In-Memory Patch

Journey Into the Object Manager Executive Subsystem: Handles