Uninformed: Informative Information for the Uninformed

Vol 1» 2005.May


The Problem

The critical phase of process execution that the summary refers to is the period between the time that the new process object instance is created by nt!ObCreateObject and the time the new process object is inserted into the process object type list by nt!ObInsertObject. The reason this phase is so critical is because it is not safe for things to attempt to obtain a handle to the process object, such as can be done by calling nt!ObOpenObjectByPointer. If an application were to attempt to obtain a handle to the process object before it had been inserted into the process object list by nt!ObInsertObject, critical creation state information that is stored in the process object's header would be overwritten with state information that is meant to be used after the process has passed the initial security validation phase that is handled by nt!ObInsertObject. In some cases, overwriting the creation state information prior to calling nt!ObInsertObject can lead to invalid pointer references when nt!ObInsertObject is eventually called, thus leading to an evil blue screen that some users are all too familiar with.

To better understand this problem it is first necessary to understand the way in which nt!PspCreateProcess creates and initializes the process object and the process handle that is passed back to callers. The object creation portion is accomplished by making a call to nt!ObCreateObject in the following fashion:

ObCreateObject(
        KeGetPreviousMode(),
        PsProcessType,
        ObjectAttributes,
        KeGetPreviousMode(),
        0,
        0x258,
        0,
        0,
        &ProcessObject);

If the call is successful, a process object of the supplied size is created and initialized using the attributes supplied by the caller. In this case, the object is created using the nt!PsProcessType object type. The size argument that is supplied to nt!ObCreateObject, which in this case is 0x258, will vary between various versions of Windows as new fields are added and removed from the opaque EPROCESS structure. The process object's instance, as with all objects, is prefixed with an OBJECT_HEADER that may or may not also be prefixed with optional object information. For reference, the OBJECT_HEADER structure is defined as follows:

OBJECT_HEADER:
   +0x000 PointerCount     : Int4B
   +0x004 HandleCount      : Int4B
   +0x004 NextToFree       : Ptr32 Void
   +0x008 Type             : Ptr32 _OBJECT_TYPE
   +0x00c NameInfoOffset   : UChar
   +0x00d HandleInfoOffset : UChar
   +0x00e QuotaInfoOffset  : UChar
   +0x00f Flags            : UChar
   +0x010 ObjectCreateInfo : Ptr32 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : Ptr32 Void
   +0x014 SecurityDescriptor : Ptr32 Void
   +0x018 Body             : _QUAD

When an object is first returned from nt!ObCreateObject, the Flags attribute will indicate if the ObjectCreateInfo attribute is pointing to valid data by having the OB_FLAG_CREATE_INFO, or 0x1 bit, set. If the flag is set then the ObjectCreateInfo attribute will point to an OBJECT_CREATE_INFORMATION structure which has the following definition:

OBJECT_CREATE_INFORMATION:
   +0x000 Attributes       : Uint4B
   +0x004 RootDirectory    : Ptr32 Void
   +0x008 ParseContext     : Ptr32 Void
   +0x00c ProbeMode        : Char
   +0x010 PagedPoolCharge  : Uint4B
   +0x014 NonPagedPoolCharge : Uint4B
   +0x018 SecurityDescriptorCharge : Uint4B
   +0x01c SecurityDescriptor : Ptr32 Void
   +0x020 SecurityQos      : Ptr32 _SECURITY_QUALITY_OF_SERVICE
   +0x024 SecurityQualityOfService : _SECURITY_QUALITY_OF_SERVICE

When nt!ObInsertObject is finally called, it is assumed that the object still has the OB_FLAG_CREATE_INFO bit set. This will always be the case unless something has caused the bit to be cleared, as will be illustrated later in this chapter. The flow of execution within nt!ObInsertObject begins first by checking to see if the process' object header has any name information, which is conveyed by the NameInfoOffset of the OBJECT_HEADER. Regardless of whether or not the object has name information, the next step taken is to check to see if the object type that is associated with the object that is supplied to nt!ObInsertObject requires a security check to be performed. This requirement is conveyed through the TypeInfo attribute of the OBJECT_TYPE structure which is defined below:

OBJECT_TYPE:
   +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:
   +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
   +0x030 OpenProcedure    : Ptr32
   +0x034 CloseProcedure   : Ptr32
   +0x038 DeleteProcedure  : Ptr32
   +0x03c ParseProcedure   : Ptr32
   +0x040 SecurityProcedure : Ptr32
   +0x044 QueryNameProcedure : Ptr32
   +0x048 OkayToCloseProcedure : Ptr32

The specific boolean field that is checked by nt!ObInsertObject is the TypeInfo.SecurityRequired flag. If the flag is set to TRUE, which it is for the nt!PsProcessType object type, then nt!ObInsertObject uses the access state that is passed in as the second argument or creates a temporary access state that it uses to validate the access mask that is supplied as the third argument to nt!ObInsertObject. Prior to validating the access state, however, the SecurityDescriptor attribute of the ACCESS_STATE structure is set to the SecurityDescriptor of the OBJECT_CREATE_INFORMATION structure. This is done without any checks to ensure that the OB_FLAG_CREATE_INFO flag is still set in the object's header, thus making it potentially dangerous if the flag has been cleared and the union'd attribute no longer points to creation information.

In order to validate the access mask, nt!ObInsertObject calls into nt!ObpValidateAccessMask with the initialized ACCESS_STATE as the only argument. This function first checks to see if the ACCESS_STATE's SecurityDescriptor attribute is set to NULL. If it's not, then the function checks to see if the SecurityDescriptor's Control attribute has a flag set. It is at this point that the problem is realized under conditions where the object's ObjectCreateInfo attribute no longer points to creation information. When such a condition occurs, the SecurityDescriptor attribute that is referenced relative to the ObjectCreateInfo attribute will potentially point to invalid memory. This can then lead to an access violation when attempting to reference the SecurityDescriptor that is passed as part of the ACCESS_STATE instance to nt!ObpValidateAccessMask. For reference, the ACCESS_STATE structure is defined below:

ACCESS_STATE:
   +0x000 OperationID      : _LUID
   +0x008 SecurityEvaluated : UChar
   +0x009 GenerateAudit    : UChar
   +0x00a GenerateOnClose  : UChar
   +0x00b PrivilegesAllocated : UChar
   +0x00c Flags            : Uint4B
   +0x010 RemainingDesiredAccess : Uint4B
   +0x014 PreviouslyGrantedAccess : Uint4B
   +0x018 OriginalDesiredAccess : Uint4B
   +0x01c SubjectSecurityContext : _SECURITY_SUBJECT_CONTEXT
   +0x02c SecurityDescriptor : Ptr32 Void
   +0x030 AuxData          : Ptr32 Void
   +0x034 Privileges       : __unnamed
   +0x060 AuditPrivileges  : UChar
   +0x064 ObjectName       : _UNICODE_STRING
   +0x06c ObjectTypeName   : _UNICODE_STRING

Under normal conditions, nt!ObInsertObject is the first routine to create a handle to the newly created object instance. When the handle is created, the creation information that was initialized during the instantiation of the object is used for such things as validating access, as described above. Once the creation information is used it is discarded and replaced with other information that is specific to the type of the object being inserted. In the case of process objects, the Flags attribute has the OB_FLAG_CREATE_INFO bit cleared and the QuotaBlockCharged attribute, which is union'd with the ObjectCreateInfo attribute, is set to an instance of an EPROCESS_QUOTA_BLOCK which is defined below:

EPROCESS_QUOTA_ENTRY:
   +0x000 Usage            : Uint4B
   +0x004 Limit            : Uint4B
   +0x008 Peak             : Uint4B
   +0x00c Return           : Uint4B

EPROCESS_QUOTA_BLOCK:
   +0x000 QuotaEntry       : [3] _EPROCESS_QUOTA_ENTRY
   +0x030 QuotaList        : _LIST_ENTRY
   +0x038 ReferenceCount   : Uint4B
   +0x03c ProcessCount     : Uint4B

The assumptions made by nt!ObInsertObject work flawlessly so long as it is the first routine to create a handle to the object instance. Fortunately, under normal circumstances, nt!ObInsertObject is always the first routine to create a handle to the object. Unfortunately for McAfee, however, they assume that they can safely attempt to obtain a handle to a process object without first checking to see what state of execution the process is in, such as by checking to see if the OB_FLAG_CREATE_INFO flag is set in the object's header. By attempting to obtain a handle to the process object before it is inserted by nt!ObInsertObject, McAfee effectively destroys state that is needed by nt!ObInsertObject to succeed.

To show this problem being experienced in the real world, the following debugger output shows McAfee first attempting to obtain a handle to the process object which is then followed shortly thereafter by nt!ObInsertObject attempting to validate the object's access mask with a bogus SecurityDescriptor which, in turn, results in an unrecoverable access violation:

McAfee attempting to open a handle to the process object before
nt!ObInsertObject has been called:

kd> k
nt!ObpChargeQuotaForObject+0x2f
nt!ObpIncrementHandleCount+0x70
nt!ObpCreateHandle+0x17c
nt!ObOpenObjectByPointer+0x97
WARNING: Stack unwind information not available.
NaiFiltr+0x2e45
NaiFiltr+0x3bb2
NaiFiltr+0x4217
nt!ObpLookupObjectName+0x56a
nt!ObOpenObjectByName+0xe9
nt!IopCreateFile+0x407
nt!IoCreateFile+0x36
nt!NtOpenFile+0x25
nt!KiSystemService+0xc4
nt!ZwOpenFile+0x11
0x80a367b5
nt!PspCreateProcess+0x326
nt!NtCreateProcessEx+0x7e
nt!KiSystemService+0xc4

After which point nt!ObInsertObject attempts to validate the
object's access mask using an invalid SecurityDescriptor:

kd> k
nt!ObpValidateAccessMask+0xb
nt!ObInsertObject+0x1c2
nt!PspCreateProcess+0x5dc
nt!NtCreateProcessEx+0x7e
nt!KiSystemService+0xc4
kd> r
eax=fa7bbb54 ebx=ffa9fc60 ecx=00023994
edx=00000000 esi=00000000 edi=ffb83f00
eip=8057828e esp=fa7bbb40 ebp=fa7bbbb8
iopl=0         nv up ei pl nz na pe nc
cs=0008  ss=0010  ds=0023  es=0023
fs=0030  gs=0000             efl=00000202
nt!ObpValidateAccessMask+0xb:
8057828e f6410210
    test    byte ptr [ecx+0x2],0x10 ds:0023:00023996=??

The method by which this issue was located was by setting a breakpoint on the instruction after the call to nt!ObCreateObject in nt!PspCreateProcess. Once hit, a memory access breakpoint was set on the Flags attribute of the object's header that would break whenever the field was written to. This, in turn, lead to the tracking down of the fact that McAfee was acquiring a handle to the process object prior to nt!ObInsertObject being called, which in turn lead to the OB_FLAG_CREATE_INFO flag being cleared and the ObjectCreateInfo attribute being invalidated.