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:
- Windows, NT Object Manager from Adrian Marinescu
- Inside NT’s Object Manager from Mark Russinovich
- Windows System Internals 6 Edition, Part 1 from Mark Russinovich, Alex Ionescu and David A. Solomon
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
kd>
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
kd>
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:
kd> dt nt!_OBJECT_HEADER_CREATOR_INFO
+0x000 TypeList : _LIST_ENTRY
+0x010 CreatorUniqueProcess : Ptr64 Void
+0x018 CreatorBackTraceIndex : Uint2B
+0x01a Reserved1 : Uint2B
+0x01c Reserved2 : Uint4B
kd> dt nt!_OBJECT_HEADER_NAME_INFO
+0x000 Directory : Ptr64 _OBJECT_DIRECTORY
+0x008 Name : _UNICODE_STRING
+0x018 ReferenceCount : Int4B
+0x01c Reserved : Uint4B
kd> dt nt!_OBJECT_HEADER_HANDLE_INFO
+0x000 HandleCountDataBase : Ptr64 _OBJECT_HANDLE_COUNT_DATABASE
+0x000 SingleEntry : _OBJECT_HANDLE_COUNT_ENTRY
kd> dt nt!_OBJECT_HEADER_QUOTA_INFO
+0x000 PagedPoolCharge : Uint4B
+0x004 NonPagedPoolCharge : Uint4B
+0x008 SecurityDescriptorCharge : Uint4B
+0x00c Reserved1 : Uint4B
+0x010 SecurityDescriptorQuotaBlock : Ptr64 Void
+0x018 Reserved2 : Uint8B
kd> dt nt!_OBJECT_HEADER_PROCESS_INFO
+0x000 ExclusiveProcess : Ptr64 _EPROCESS
+0x008 Reserved : Uint8B
kd>
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_CREATOR_INFO | OBJECT_HEADER address - ObpInfoMaskToOffset[0] |
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.
kd> dt nt!_OBJECT_ATTRIBUTES
+0x000 Length : Uint4B
+0x008 RootDirectory : Ptr64 Void
+0x010 ObjectName : Ptr64 _UNICODE_STRING
+0x018 Attributes : Uint4B
+0x020 SecurityDescriptor : Ptr64 Void
+0x028 SecurityQualityOfService : Ptr64 Void
kd>
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
kd>
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:
kd> dt nt!_OBJECT_TYPE_INITIALIZER
+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
kd>
The ObjectTypeFlags
field is a bitmask that defines multiple things, such as:
Bitmask Flag | Bitmask Flag |
---|---|
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:
- 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; - Move into
ECX
register the value of theTypeIndex
field from the_OBJECT_HEADER
structure via the move with zero-extend (MOVZX) operator; - Shift the address of the
_OBJECT_HEADER
structure of 8 bits to the right with the shift right operator (SHR); - Move into last byte of the register into
EAX
with theMOVZX
register; - Perform a bitwise
XOR
operation betweenRAX
andRCX
, which at this stage are respectively the second byte of the address of the_OBJECT_HEADER
structure and theTypeIndex
value from the structure; - Move into the
ECX
register the byte value of theObHeaderCookie
address; - Perform another bitwise
XOR
operation betweenRAX
andRCX
registers, which at this stage are respectively the result of the previous bitwiseXOR
operation and the byte from theObHeaderCookie
address; - Store the address of the first entry of the
ObTypeIndexTable
table into theRCX
register with theLEA
operator; - 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 - 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 ]
kd>
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 }
struct _UNICODE_STRING
"Type"
+0x000 Length : 8
+0x002 MaximumLength : 0xa
+0x008 Buffer : 0xffffaf01`e0a06560 "Type"
[..snipp..]
struct _UNICODE_STRING
"VRegConfigurationContext"
+0x000 Length : 0x30
+0x002 MaximumLength : 0x32
+0x008 Buffer : 0xffffaf01`e0b62790 "VRegConfigurationContext"
kd>