- May 7, 2013
- 10,400
This article is going to cover a few different areas of object management and how objects are structured under the Object Manager. Since it is a rather lengthy post, I've split it into five different sections as shown below.
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.
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.
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.
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.
The handle table for a given process is represented by a structure called HANDLE_TABLE. We can dump this using WinDbg like so:
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:
Credit: Windows Internals 7th Edition Part 2
You can find the number of present tables by using the following:
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.
The highlighted portion is a pointer into one of the elements of the array.
The first valid index into the handle table is 0x4, as demonstrated below:
Each handle table entry is represented by the _HANDLE_TABLE_ENTRY structure.
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:
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.
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:
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.
Now, we can find the corresponding object by the following method:
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.
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.
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.
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 yellow.
Let's now dump the object header cookie value and get the first byte.
We all the required pieces of information, so let's XOR them together.
Now, using the result of our XOR operation, we can now index into the object type table.
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?
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.
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.
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.
The calculated address is then the actual address of the security descriptor object. We can dump the security descriptor using the !sd command.
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:
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:
As mentioned previously, this is another bit mask field and therefore setting or clearing individual bits will enable different access rights for that handle.
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.
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.
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:
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.
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.
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.
To calculate the offset to the header which we desire, we can use the following expression:
The following excerpt from Code Machine describes what the array is for and why we're indexing into the array:
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:
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.
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.
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.
This field must be set to true for the Process header.
This field must be set to true for Handle header.
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:
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.
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
- Pool Allocations and Object Header
- Handles and Handle Table
- Object Types
- Kernel Handles and Access Rights
- Optional Object Headers
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.
Rich (BB code):
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.
Rich (BB code):
2: kd> !object ffff91880ef86080
Object: ffff91880ef86080 Type: (ffff9188026c9f00) Thread
ObjectHeader: ffff91880ef86050 (new version)
HandleCount: 2048 PointerCount: 0
Rich (BB code):
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.
Rich (BB code):
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.
Rich (BB code):
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:
Rich (BB code):
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:
Credit: Windows Internals 7th Edition Part 2
You can find the number of present tables by using the following:
Rich (BB code):
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.
Rich (BB code):
2: kd> ?? 0xffffa487`0e0fd001 & ~7
unsigned int64 0xffffa487`0e0fd000
The highlighted portion is a pointer into one of the elements of the array.
Rich (BB code):
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
Rich (BB code):
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:
Rich (BB code):
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.
Rich (BB code):
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:
Rich (BB code):
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.
Rich (BB code):
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:
Rich (BB code):
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.
Rich (BB code):
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:
Rich (BB code):
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.
Rich (BB code):
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.
Rich (BB code):
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.
Rich (BB code):
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 yellow.
Rich (BB code):
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.
Rich (BB code):
2: kd> db nt!ObHeaderCookie L1
fffff802`59cfb71c a1
We all the required pieces of information, so let's XOR them together.
Rich (BB code):
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.
Rich (BB code):
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.
Rich (BB code):
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.
Rich (BB code):
2: kd> dt _OBJECT_TYPE_INITIALIZER ffff9188026c8bc0+0x40 -y GenericMapping
nt!_OBJECT_TYPE_INITIALIZER
+0x00c GenericMapping : _GENERIC_MAPPING
Rich (BB code):
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.
Rich (BB code):
9: kd> dt _OBJECT_HEADER -y SecurityDescriptor ffffdb8d14b6e410
nt!_OBJECT_HEADER
+0x028 SecurityDescriptor : 0xffffcb81`801bf365 Void
Rich (BB code):
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.
Rich (BB code):
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:
Rich (BB code):
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:
Rich (BB code):
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.
Rich (BB code):
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.
Rich (BB code):
2: kd> dt _OBJECT_HEADER -y Flags ffff9188029e4390
nt!_OBJECT_HEADER
+0x01b Flags : 0x12 ''
Rich (BB code):
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.
Rich (BB code):
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:
Rich (BB code):
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.
Rich (BB code):
2: kd> ? poi(ObpKernelHandleTable)
Evaluate expression: -100575142178752 = ffffa487`06609040
Rich (BB code):
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.
Rich (BB code):
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.
Rich (BB code):
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:
Rich (BB code):
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.
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 Name | Bit Position | Value |
Creator Information (OBJECT_HEADER_CREATOR_INFO) | 0 | 0x1 |
Name Information (OBJECT_HEADER_NAME_INFO) | 1 | 0x2 |
Handle Information (OBJECT_HEADER_HANDLE_INFO) | 2 | 0x4 |
Quota Information (OBJECT_HEADER_QUOTA_INFO) | 3 | 0x8 |
Process Information (OBJECT_HEADER_PROCESS_INFO) | 4 | 0x10 |
Audit Information (OBJECT_HEADER_AUDIT_INFO) | 5 | 0x20 |
Extended Information (OBJECT_HEADER_EXTENDED_INFO) | 6 | 0x40 |
Padding Information (OBJECT_HEADER_PADDING_INFO) | 7 | 0x80 |
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.
Rich (BB code):
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.
Rich (BB code):
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.
Rich (BB code):
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.
Rich (BB code):
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.
Rich (BB code):
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:
Rich (BB code):
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.
Rich (BB code):
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
Last edited: