OBJECT_HEADER

Every object that the Object Manager ever creates has an OBJECT_HEADER (formally an _OBJECT_HEADER) immediately before it. The Object Manager sees the object itself as an opaque region of memory that the Object Manager has been asked to provide, to manage and even to guard, but is not to interpret. Everything that the Object Manager knows of the object for the Object Manager’s purposes is reached through the header.

Documentation Status

Just as the Object Manager does not interpret the object, so the object’s owner and users are not to interpret the header.

Unsurprisingly, then, the OBJECT_HEADER is not documented. The usual headers for kernel-mode programming don’t even declare it as an opaque type such as might be pointed to but not interpreted. Microsoft’s only known publication of a C-language definition is in NTOSP.H from the original and Version 1511 editions of the Windows Driver Kit (WDK) for Windows 10. Since this header is in a subdirectory (“minwin”) of a subdirectory named “um” which is in turn full of headers for user-mode programming though the OBJECT_HEADER cannot be visible to user-mode programs, the disclosure is here thought to have been unintended.

Just as unsurprising is that although the OBJECT_HEADER is not disclosed explicitly, it must be revealed implicitly because kernel-mode programmers will need sometimes to examine an object from the Object Manager’s perspective. From the very start, Microsoft’s kernel-mode debuggers have a !object command which interprets an arbitrary object’s OBJECT_HEADER, and other commands such as !handle and !process which interpret the headers of objects that are found in particular ways. Indeed, it is through debugging support that Microsoft comes closest to documenting the OBJECT_HEADER.

Access

To say that every object has an OBJECT_HEADER immediately before it is to take a small liberty. As will be seen below in the Layout section, Microsoft defines the structure as ending with an eight-byte member named Body that stands for the variable-sized object. Every object is immediately preceded by an object header, but on the understanding that the header is not the formally defined OBJECT_HEADER but only that part of the OBJECT_HEADER that precedes the Body.

To anyone who isn’t a programmer at Microsoft writing the Object Manager’s implementation (and even to them while debugging), the obvious practice is to ignore that the formal definition includes the Body. Given an arbitrary object to inspect with the debugger, the OBJECT_HEADER starts 0x18 or 0x30 bytes before the object—in all versions of 32-bit and 64-bit Windows, respectively, back as far as version 3.50.

Knowing this small detail for inspecting the OBJECT_HEADER by sight can be very useful in kernel-mode debugging. For just the simplest example, if the !object command can show an object’s security descriptor, then I suspect I won’t be the only one who keeps forgetting the incantation. It’s anyway so much easier to remember that a pointer to the security descriptor (albeit with slight alteration) is the last member of the object header, i.e., of the OBJECT_HEADER before the Body, and is therefore the pointer immediately before the object.

That every object is preceded by an object header is as old as Windows. Though I know of no clear evidence that the structure was named OBJECT_HEADER in version 3.10, there certainly are 0x10 bytes of header immediately before every object and there is strong evidence that the formulation with the eight-byte Body dates from then too.

Header’s Headers

The OBJECT_HEADER is only the start to what may be accessed about an object as seen from the Object Manager. In all versions, the OBJECT_HEADER has pointers to at least two other structures and can itself be preceded by other sorts of header. These too can be useful for the advanced Windows programmer to recognise by sight when debugging. Version 3.50 had four of these header’s headers. More were defined years later. By now there can be any or all of the following (listed here in historical order):

Perhaps only historians will be interested but these header’s headers arise from an early and presumably pressing optimisation of the memory overhead for managing objects. The header’s headers, the header itself and then the object are one memory block that is retained for the whole life of the object. The header has information that is general for all objects of all types. The header’s headers are designed so that each can be skipped if the corresponding information is not needed (or is not even meaningful) for the particular object. For instance, if an object has no name, then it would be wasteful to keep an OBJECT_HEADER_NAME_INFO.

Windows was not born with this (or any) selectivity of header’s headers. In version 3.10, the object header has two pointers to other structures. The first of these other structures is only ever built immediately before the header. It is the original header’s header, if you like. The second is designed to be in non-paged memory. It precedes the first if the object is non-paged, but must otherwise be in separate memory. At 0x68 bytes the first was relatively large. Retaining this much for the whole of every object’s life was no small overhead. Indeed, for some types of object, this overhead far exceeded the size of the object. Reducing this structure evidently had a high priority: it is one of the most significant reworkings between the first and second Windows versions, i.e., 3.10 and 3.50. In the reworking, the larger of the structures got mostly split into the four original header’s headers for permanent but selective retention. What was left became an OBJECT_CREATE_INFORMATION to discard once the object is fully created. Thus did version 3.50 tidy the header’s headers into recognisably their modern form. It did not, however, attend to the non-paged header that may have to be in separate memory. Once version 3.51 folded this into the header itself, the OBJECT_HEADER got its modern form.

Variability

For a structure that might be an implementation detail for the Object Manager, with no need to be known even elsewhere in the kernel, let alone externally, the OBJECT_HEADER is surprisingly stable. The earliest versions differ—indeed, the OBJECT_HEADER is barely recognisable in version 3.10—but the modern form was well-settled as early as version 3.51. There has been internal reorganisation since then, especially for Windows 7, but it has been done by finding ways to squeeze more in, as if the structure’s size is for all practical effect treated as architectural:

Version Size (x86) Size (x64)
3.10 0x18  
3.50 to 2004 0x20 0x38

This size is of the OBJECT_HEADER as a structure, not as a header. If the header is understood as just what the Object Manager places immediately before the object, then the header is 0x18 or 0x30 bytes in 32-bit and 64-bit Windows, respectively, except for being just 0x10 bytes in version 3.10.

Note that the size of the effective header (disregarding the eight-byte Body) is a multiple of the natural alignment for memory allocation, i.e., eight or sixteen bytes for 32-bit and 64-bit Windows, respectively, in all versions. This surely is architectural. Callers who ask the Object Manager to create an object of whatever type expect the object to have this alignment. Because the memory allocation can instead begin with the OBJECT_HEADER or with any of the header’s headers, these too must all have this alignment.

Layout

The sizes above and the offsets, names and types in the tables below are from type information in public symbol files for the kernel, starting with Windows 2000 SP3. Names are known with slightly less certainty for version 4.0 from the output of the !dso command as implemented by the debugger extension USEREXTS.DLL from the Windows NT 4.0 Device Driver Kit (DDK). What’s known for even earlier versions is something of a guess from inspecting how the kernel works with the structure.

Except in ancient times, the OBJECT_HEADER starts with two pointer-sized counts:

Offset (x86) Offset (x64) Definition Versions
0x00 0x00
union {
    struct {
        LONG_PTR PointerCount;
        LONG_PTR HandleCount;
    };
    LIST_ENTRY Entry;
};
3.51 to 4.0
LONG_PTR PointerCount;
5.0 and higher
0x04  
union {
    LONG_PTR HandleCount;
    SINGLE_LIST_ENTRY *SEntry;
};
5.0 only
0x08
union {
    LONG_PTR HandleCount;
    PVOID NextToFree;
};
5.1 and higher
0x08 0x10
OBJECT_TYPE *Type;
3.51 to 6.0
EX_PUSH_LOCK Lock;
6.1 and higher

Before version 3.51, the PointerCount, HandleCount and Type are in a separate structure (see below, as Archaeology), apparently so they are all in non-paged memory even when the object and its header are not.

Counts

The PointerCount is of how many times the object has been referenced but not dereferenced. After creation, an object is referenced through such functions as ObReferenceObjectByHandle, ObReferenceObjectByName and ObReferenceObjectByPointer. A reference is required for a kernel-mode user of an object to be sure that the object persists through the intended use.

The HandleCount is of how many times the object has been opened but not closed. After a handle is created for an object, more can be opened through such functions as ObOpenObjectByName and ObOpenObjectByPointer. A handle is required for access from user mode, again to ensure that the object persists through the intended use.

That versions 3.51 to 4.0 have the HandleCount at offset 0x04, as for all later x86 versions, is a little disguised by the two pointers being in an unnamed structure within an unnamed union with the eight-byte LIST_ENTRY.

List Entries

That the various list entries Entry, SEntry and NextToFree, depending on the version, can overlay one or both counts is because the list entries matter only when the counts no longer can.

When all of an object’s references are balanced by dereferences, the object has nobody who cares for its continued existence and so the object can be deleted. Though dereferencing is immediate, deleting may have to be deferred. The original reason was that although ObDereferenceObject was in the early years—up to and including the DDK for Windows NT 4.0—documented as being callable only at PASSIVE_LEVEL, its implementation even in version 3.10 anticipates being called at higher IRQL. In version 5.1 and higher, deferred deletion can even be forced from within the kernel and in version 6.0 from outside (because ObDereferenceObjectDeferDelete is exported).

When deferring an object’s deletion, the Object Manager puts the OBJECT_HEADER into a list. Both counts should be zero and because the object should now be unknown outside the Object Manager, neither count can now change. The space they occupied is available to reuse for linking into the list.

The early versions have a double-linked list, inserting at the tail and removing from the head, so that deferred deletions are picked up in the order of their final dereferences. Such ordering is unnecessary: just as nobody should now care about the object’s continued existence, nobody should care how soon it gets destroyed. Version 5.0 changes to a single-linked list, which version 5.1 simplifies. Ordinarily, NextToFree points directly to the OBJECT_HEADER for the next object to delete. It can, of course, be NULL at the end. Less obviously, it can be 1 as an implementation detail in how the list of objects to delete is processed without the earlier versions’ repeated acquisition and release of a lock.

Offset (x86) Offset (x64) Definition Versions
0x00 (3.50)  
ULONG ObjectBodySize : 24;
ULONG Flags : 8;
3.50 only
0x04 (3.50);
0x0C
0x18
UCHAR NameInfoOffset;
3.50 to 6.0
UCHAR TypeIndex;
6.1 and higher
0x05 (3.50);
0x0D
0x19
UCHAR HandleInfoOffset;
3.50 to 6.0
UCHAR TraceFlags;
6.1 only
union {
    UCHAR TraceFlags;
    struct {
        /*  bit fields, follow link  */
    };
};
6.2 and higher
0x06 (3.50);
0x0E
0x1A
UCHAR QuotaInfoOffset;
3.50 to 6.0
UCHAR InfoMask;
6.1 and higher
0x07 (3.50);
0x0F
 
UCHAR CreatorInfoOffset;
3.50 only
0x1B
UCHAR Flags;
3.51 to 6.2
union {
    UCHAR Flags;
    struct {
        /*  bit fields, follow link  */
    };
};
6.3 and higher
  0x1C
ULONG Spare;
6.2 to 1511
ULONG Reserved;
1607 and higher

The name ObjectBodySize is proposed for what version 3.50 has in the first three bytes. It is taken directly from the ObCreateObject argument that is later known to be named ObjectBodySize. That the ObjectBodySize is formally a bit field is almost certain from the binary code. That the Flags are too is suggested by some access that tests the whole dword against flags as immediate 32-bit data. How exactly these two are defined within this first dword, e.g., wrapped in a structure or in union with an integral type, may never be known. The Flags persist as a UCHAR in all later versions, including after Windows 8.1 formalises the individual flags as bit fields.

The NameInfoOffset, HandleInfoOffset, QuotaInfoOffset and CreatorInfoOffset correspond to the original header’s headers. Each is ordinarily the offset from the start of the OBJECT_HEADER back to the corresponding structure, else is zero if the corresponding structure is not present. As noted above, these offsets must be multiples of eight, even for 32-bit Windows, and so at least three bits in each byte are wasted. Version 6.0 uses this to squeeze a little more meaning into the QuotaInfoOffset: its low two bits are an early implementation of the TraceFlags.

The plain purpose to retaining these structures selectively is to reduce how much memory the Object Manager adds for each object. More savings come from knowing that the header’s headers, whichever are present, are always in a particular order:

Since the OBJECT_HEADER_CREATOR_INFO, if present at all, can only end where the OBJECT_HEADER begins, the CreatorInfoOffset can only be either zero or the structure’s (known) size, and so version 3.51 did away with the waste of keeping a whole byte for it. Indeed, the offsets to each structure can be calculated just from knowing which other structures are present. This needs only one bit for each of the possible structures. Version 6.1 built this into the InfoMask and thus recovered two bytes.

The more notable new use that was opened by this recovery is the TypeIndex since it brought the saving of not keeping a whole pointer for the Type. That each type of object has a 0-based TypeIndex is ancient, just not for the Object Manager’s own purposes. It is instead a sequence number that is convenient to report in the SYSTEM_OBJECTTYPE_INFORMATION structure when objects are enumerated through ZwQuerySystemInformation. and later as the ObjectTypeIndex in the SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX when enumerating handles.

Starting with version 6.1, pointers to the created OBJECT_TYPE structures are kept in an array and the TypeIndex truly is an index. Instead of keeping a pointer in every OBJECT_HEADER just to find the the OBJECT_TYPE, the Object Manager keeps just the 1-byte index. The space that had been taken by the pointer is reclaimed so that for the first time, objects can be locked independenty of one another.

For reasons that are not yet understood well enough to write about with confidence, version 10.0 obfuscates the TypeIndex as kept in the OBJECT_HEADER.

Offset (x86) Offset (x64) Definition Versions
0x00 (3.10)   unknown dword 0x0B0B0B0B 3.10 only
0x04 (3.10);
0x08 (3.50);
0x10
 
<unknown-type> *PagedObjectHeader;
3.10 only
0x20
union {
    OBJECT_CREATE_INFORMATION *ObjectCreateInfo;
    PVOID QuotaBlockCharged;
};
3.50 and higher
0x08 (3.10);
0x0C (3.50)
 
<unknown-type> *NonPagedObjectHeader;
3.10 to 3.50
0x10 (3.50);
0x14
0x28
PVOID SecurityDescriptor;
3.50 and higher
0x14 (3.50)   unaccounted dword 3.50 only
0x0C (3.10)   unknown dword 0x1B1B1B1B 3.10 only
0x10 (3.10);
0x18
0x30
QUAD Body;
all

In version 3.10, the OBJECT_CREATE_INFORMATION is retained for the object’s life. In later versions, everything that needs such long retention is extracted to the applicable header’s headers. The OBJECT_CREATE_INFORMATION is freed when the object is charged to the creating process’s quota. The OBJ_FLAG_NEW_OBJECT bit in the Flags is then cleared and the ObjectCreateInfo member is reused as QuotaBlockCharged. Though not shown in Microsoft’s type information, what QuotaBlockCharged points to is specifically an EPROCESS_QUOTA_BLOCK.

As noted above, the structure is a fixed-size header and then a placeholder for the variable-size object. Formally, the object begins at the structure’s eight-byte Body. Given a C-language definition of the OBJECT_HEADER, locating a given object’s header could be conveniently wrapped into an inline routine:

FORCELINE 
OBJECT_HEADER *OBJECT_TO_OBJECT_HEADER (PVOID Object)
{
    return CONTAINING_RECORD (Object, OBJECT_HEADER, Body);
}

which is essentially the macro that the NTOSP.H disclosure confirms is what Microsoft’s programmers have been using.

Evidence is strong that ending the OBJECT_HEADER with the Body is original. Whenever the kernel-mode debugger I386KD.EXE from the Windows NT 3.1 DDK is to learn of an object from what precedes the object, it reads 0x18 bytes from 0x10 bytes before the object.

Archaeology: the Non-Paged Object Header

Both versions 3.10 and 3.50 split the OBJECT_HEADER in two. One part immediately precedes the object and is here taken as the original OBJECT_HEADER, but what later become the PointerCount, HandleCount and Type are in a separate structure. Microsoft’s name for this separate structure is not known (and perhaps never will be). That it is separate is because the object, and thus also its header, may be in paged pool but these versions require that the two counts be non-paged.

Offset Definition Versions Remarks
0x00 (3.10 to 3.50)
PVOID Object;
3.10 to 3.50  
0x04 (3.10 to 3.50)
LONG PointerCount;
3.10 only  
union {
    struct {
        LONG PointerCount;
        LONG HandleCount;
    };
    LIST_ENTRY Entry;
};
3.50 only next at 0x00 in OBJECT_HEADER
0x08 (3.10)
LONG HandleCount;
3.10 only  
0x0C (3.10 to 3.50)
OBJECT_TYPE *Type;
3.10 to 3.50 next at 0x08 in OBJECT_HEADER

The name Object is proposed as obvious for a pointer back to the object. The other members have direct counterparts in the OBJECT_HEADER as known for later versions. For both these early versions, the PointerCount and HandleCount are at offsets 0x04 and 0x08, but continuity of the latter’s position is a little disguised for having been moved into an unnamed structure in an unnamed union.

This is not without merit. For instance, in combination with having the handle table record the address of the corresponding object’s non-paged header it allows that ObReferenceObjectByHandle can at high IRQL quickly obtain a pointer to an object without having to touch paged memory if the handle turns out to be for a type of object that is paged.

Why are the counts in non-paged pool even when the object is not? One reason, here thought to be the main reason, is that these versions look to have been coded to increment and decrement the counts by using the ExInterlockedIncrementLong and ExInterlockedDecrementLong functions. These require a spin lock which they may acquire to protect the operation from concurrent access by another process. The lock and the count must therefore be resident. Both functions were already documented as obsolete as long ago as the Device Driver Kit (DDK) for Windows NT 3.51. Contemporaneous documentation of what were then the new InterlockedIncrement and InterlockedDecrement functions is explicit that these can “be safely used on pageable data” (but leaves as implicit that the old functions cannot). Even for version 3.10, the NTDDK.H from the corresponding DDK redefines the old functions by macro to lose the spin lock and to operate on the count by using the undocumented Exi386InterlockedIncrementLong and Exi386InterlockedDecrementLong—but this is only for the x86 architecture. The separate existence of a non-paged part to the header therefore looks to be a side-effect of using the documented ExInterlockedIncrementLong and ExInterlockedDecrementLong for portability in the code and sticking to its documented constraints.

Another reason for a separate structure that is necessarily in non-paged pool may be incidental. As already noted, if the dereference that brings the PointerCount to zero occurs at high IRQL, then the object’s deletion is deferred. In version 3.10, it is queued as a work item. The WORK_QUEUE_ITEM must be in non-paged pool. Version 3.10 simply repurposes the non-paged header (which is conveniently the right size). Whether this repurposing is modelled in the structure’s definition is, of course, not known. It anyway doesn’t survive even to version 3.50. Instead of wastefully queueing a separate work item for each object that is to be deleted, version 3.50 puts all such objects into a double-linked list (linked through the Entry member) and has one statically allocated work item drain the list.