Kernel Objects

Microsoft Windows is an object oriented kernel. Files, Processes, Threads - everything is an object. And all those kernel objects share a common data structure and interface. In this post we'll have a look at how objects are created by the kernel and stored in memory.

Creation routines for the various objects, like IoCreateDriver or PspCreateProcess, call the generic ObCreateObject and pass it a pointer to an appropriate _OBJECT_TYPE structure.

The _OBJECT_TYPE structure is like a blueprint that describes a class of objects:

kd> dt _OBJECT_TYPE
struct _OBJECT_TYPE, 12 elements, 0x190 bytes
   +0x000 Mutex            : _ERESOURCE
   +0x038 TypeList         : _LIST_ENTRY
   +0x040 Name             : _UNICODE_STRING
   +0x048 DefaultObject    : Ptr32 Void
   +0x04c Index            : Uint4B
   +0x050 TotalNumberOfObjects : Uint4B
   +0x054 TotalNumberOfHandles : Uint4B
   +0x058 HighWaterNumberOfObjects : Uint4B
   +0x05c HighWaterNumberOfHandles : Uint4B
   +0x060 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x0ac Key              : Uint4B
   +0x0b0 ObjectLocks      : [4] _ERESOURCE

_OBJECT_TYPE_INITIALIZER provides some more information about the class and how it is implemented:

kd> dt _OBJECT_TYPE_INITIALIZER
struct _OBJECT_TYPE_INITIALIZER, 20 elements, 0x4c bytes
   +0x000 Length           : Uint2B
   +0x002 UseDefaultObject : UChar
   +0x003 CaseInsensitive  : UChar
   +0x004 InvalidAttributes : Uint4B
   +0x008 GenericMapping   : _GENERIC_MAPPING
   +0x018 ValidAccessMask  : Uint4B
   +0x01c SecurityRequired : UChar
   +0x01d MaintainHandleCount : UChar
   +0x01e MaintainTypeList : UChar
   +0x020 PoolType         : _POOL_TYPE
   +0x024 DefaultPagedPoolCharge : Uint4B
   +0x028 DefaultNonPagedPoolCharge : Uint4B
   +0x02c DumpProcedure    : Ptr32     void 
   +0x030 OpenProcedure    : Ptr32     long 
   +0x034 CloseProcedure   : Ptr32     void 
   +0x038 DeleteProcedure  : Ptr32     void 
   +0x03c ParseProcedure   : Ptr32     long 
   +0x040 SecurityProcedure : Ptr32     long 
   +0x044 QueryNameProcedure : Ptr32     long 
   +0x048 OkayToCloseProcedure : Ptr32     unsigned char

Objects are usually identified by their start address. Let's take the System process for example. It always has a process ID of 4, so we can use that to select it in the debugger:

kd> !process 4 0
Searching for Process with Cid == 4
PROCESS 812927c0  SessionId: none  Cid: 0004    Peb: 00000000  ParentCid: 0000
    DirBase: 00039000  ObjectTable: e1000a80  HandleCount: 216.
    Image: System

The process is identified by its base address, 0x812927c0. Now let's have a look at the generic object information:

kd> !object 812927c0
Object: 812927c0  Type: (81292e38) Process
    ObjectHeader: 812927a8 (old version)
    HandleCount: 2  PointerCount: 57

The object belongs to the class "Process" and starts at address 0x812927c0. We already knew that. But if you look carefully, you'll find that the generic object header starts at an lower address, at 0x812927a8.

Other objects may have more information associated with them, like a Directory Object that contains this object, a Name and a Target String in case of this symbolic link object:

Object: e13ad800  Type: (812be0a8) SymbolicLink
    ObjectHeader: e13ad7e8 (old version)
    HandleCount: 0  PointerCount: 1
    Directory Object: e10076a0  Name: ScsiPort1
    Target String is '\Device\Ide\IdePort1'

The Target String is specific to this class, so it is stored in the object's body. However, the object's Name and the superordinate Directory are common properties, so they are kept in an extension to the generic object header.

Let's go back to the ObCreateObject routine. Before it even dares to allocate memory for the object, it calls ObpCaptureObjectCreateInformation to collect some meta-data about the new object. This in turn calls some more routines. SeCaptureSecurityDescriptor perfoms a lot of sanity checks until it finally allocates a suitably sized chunk of of memory tagged with SeSc from the paged pool and copies the object's security descriptor over from the provided attributes.

Later, ObpCaptureObjectCreateInformation calls ObpCaptureObjectName to perform a similar operation on the new object's name, that may also be provided in the object attributes. If there's a name, then another routine, ObpAllocateObjectNameBuffer, will allocate sufficient space from the nonpaged pool and tag it with ObNm. This is where a pointer to the Directory and the Name string are stored.

Finally, we still need to allocate memory for the object's specific data. This is taken care of by ObpAllocateObject. It looks up the pool type (basically paged or nonpaged) from the _OBJECT_TYPE blueprint. The pool tag is derived from the Key member of the same structure. The most significant bit is set to indicate usage by the system. Microsoft's debugger will mark these allocations as "protected". However, this technique will only protect from accidental usage of the same tag by third-party drivers; the official documentation requires third-party software to use 7-bit clean tags only. Nonetheless it is possible to allocate pool memory through ExAllocatePoolWithTag using an unclean tag. Examples can even be found in the Windows XP SP2 MP kernel, e.g. in CmpAllocateKeyControlBlock.

When ObCreateObject terminates, the memory contains the following structures (from lower to higher addresses):

  1. _POOL_HEADER, 0x08 bytes
  2. _OBJECT_QUOTA_CHARGES, 0x10 bytes, optional
  3. _OBJECT_HANDLE_DB, 0x08 bytes, optional
  4. _OBJECT_NAME, 0x10 bytes, optional, contains pointer to ObNm block
  5. _OBJECT_CREATOR_INFO, 0x10 bytes, optional
  6. _OBJECT_HEADER, 0x18 bytes, may point to SeSc block
  7. object body, the object is identified by this address!

Archives

Imprint

This blog is a project of:
Andreas Schuster
Im Äuelchen 45
D-53177 Bonn
impressum@forensikblog.de

Copyright © 2005-2012 by
Andreas Schuster
All rights reserved.