Debugging Stop 0x18 - REFERENCE_BY_POINTER

x BlueRobot

Administrator
Staff member
Joined
May 7, 2013
Posts
10,400
Rich (BB code):
REFERENCE_BY_POINTER (18)
Arguments:
Arg1: ffff9188026c9f00, Object type of the object whose reference count is being lowered
Arg2: ffff91880ef86080, Object whose reference count is being lowered
Arg3: 0000000000000001, Reserved
Arg4: 0000000000000800, Reserved
    The reference count of an object is illegal for the current state of the object.
    Each time a driver uses a pointer to an object the driver calls a kernel routine
    to increment the reference count of the object. When the driver is done with the
    pointer the driver calls another kernel routine to decrement the reference count.
    Drivers must match calls to the increment and decrement routines. This bugcheck
    can occur because an object's reference count goes to zero while there are still
    open handles to the object, in which case the fourth parameter indicates the number
    of opened handles. It may also occur when the object's reference count drops below zero
    whether or not there are open handles to the object, and in that case the fourth parameter
    contains the actual value of the pointer references count.

Every object which is managed by the Object Manager has a reference count associated to it. This reference count is used by the Object Manager to ensure that objects which are still currently in use aren't prematurely deleted. The reference count is a sum of two parts: the handle count and the pointer count. The pointer count, as the name suggests, is the current number of pointers which are referring to the object in question. On the other hand, the handle count is the current number of open handles to the object.

The pointer count is exclusively used by kernel components; only they can use a pointer to refer to an object. However, handles can be opened by both user-mode and kernel-mode code. This something to bear in mind when debugging this bugcheck.

When a handle is opened or a pointer is set to refer to an object address, then the reference count is incremented by 1. Likewise, when the opposite is true, the reference count is decremented by 1. If we attempt deallocate an object whose reference count is still positive, then we will bugcheck as shown above. Additionally, if the reference count were to drop below 0 to any negative integer, then we will also bugcheck with the same error code.

Let's now examine the first two parameters: the object type and the address of the object itself. The first parameter contains a pointer to the object type structure, which contains metadata for that particular object type. In this case, we can see that our object was a thread object.

Rich (BB code):
2: kd> dt _OBJECT_TYPE ffff9188026c9f00
win32k!_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 ]

We can find the reference count for the particular object shown in the second parameter by using the !object command. As we can see, the reference count is 2048, since there is currently 2048 open handles for the object.

Rich (BB code):
2: kd> !object ffff91880ef86080
Object: ffff91880ef86080  Type: (ffff9188026c9f00) Thread
    ObjectHeader: ffff91880ef86050 (new version)
    HandleCount: 2048  PointerCount: 0

Upon investigating the call stack for the crash, we can see that a handle for a object was being closed. There is a number of important functions to understand here, and therefore we'll examine each one separately.

Rich (BB code):
2: kd> !stack -p
Call Stack : 6 frames
## Stack-Pointer    Return-Address   Call-Site
00 ffffa20a28a6f908 fffff80259416e66 nt!KeBugCheckEx+0
    Parameter[0] = (unknown)
    Parameter[1] = (unknown)
    Parameter[2] = (unknown)
    Parameter[3] = (unknown)
01 ffffa20a28a6f910 fffff802595f372e nt!ObfDereferenceObjectWithTag+20ebd6 (perf)
    Parameter[0] = (unknown)
    Parameter[1] = (unknown)
    Parameter[2] = (unknown)
    Parameter[3] = (unknown)
02 ffffa20a28a6f950 fffff802595f73ac nt!ObCloseHandleTableEntry+29e
    Parameter[0] = ffffa4870d1cf3c0 << _HANDLE_TABLE
    Parameter[1] = ffffa4870e0c3f10 << _HANDLE_TABLE_ENTRY
    Parameter[2] = ffff91880f7ba080 << _EPROCESS
    Parameter[3] = 00000000000007c4 << Handle value
03 ffffa20a28a6fa90 fffff802594086b8 nt!NtClose+ec (perf)
    Parameter[0] = 00000000000007c4 << Handle being closed
    Parameter[1] = (unknown)
    Parameter[2] = (unknown)
    Parameter[3] = (unknown)
04 ffffa20a28a6fb00 00000000772d1cfc nt!KiSystemServiceCopyEnd+28
    Parameter[0] = 00000000000007c4
    Parameter[1] = 0000000077368850
    Parameter[2] = 000000000000002b
    Parameter[3] = 00000000773538ec

The NtClose function takes one argument which is the value of the handle. The function will close the handle which has been passed to it and decrement the reference count for the associated object. This function may fail if the handle is kernel-mode only or is protected from being closed.

The second function is ObCloseHandleTableEntry, this is a Object Manager function is called by NtClose. The function looks up the corresponding handle, finds the handle table entry from the process' handle table and then attempts to close it. As we can see, the subsequent call is ObfDereferenceObjectWithTag. This, as the name suggests, decrements the reference count for the associated object.

Rich (BB code):
2: kd> !handle 7c4

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

07c4: free handle, Entry address ffffa4870e0c3f10, Next Entry ffffa4870e0c3d60

We can dump the handle which has been closed by using the !handle command. As we can see, the corresponding handle table entry points to a freed handle and the handle table which the handle belongs to is for the GameManagerService.exe process. All processes will have their own handle table and closing a handle occurs within the context of the owning process. This is why it would seem that the GameManagerService.exe process, however, this is necessarily the case and therefore I would recommend running Driver Verifier to be certain, or alternatively, setting object and handle tracing using GFlags and then checking the trace using !obtrace and !htrace respectively.

References:

Windows Internals 6th Edition - Part 1
 
Last edited:
I've recently come across an interesting example of this bugcheck, whereby the second parameter wasn't related to an actual object, rather it was for a field of an object which wasn't even managed by the Object Manager.

Rich (BB code):
REFERENCE_BY_POINTER (18)
Arguments:
Arg1: 0000000000000000, Object type of the object whose reference count is being lowered
Arg2: ffffcc8ff2292960, Object whose reference count is being lowered
Arg3: 0000000000000001, Reserved
Arg4: ffffcc8f00000128, Reserved

Let's use the !object command on the second parameter as we usually would.

Rich (BB code):
4: kd> !object ffffcc8ff2292960
ffffcc8ff2292960: Not a valid object (ObjectType invalid)

As we can see, the second parameter does not belong to an object managed by the Object Manager. We can figure out what the second parameter belongs to by using the !pool command.

Rich (BB code):
4: kd> !pool ffffcc8ff2292960
Pool page ffffcc8ff2292960 region is Nonpaged pool
 ffffcc8ff2292000 size:  250 previous size:    0  (Allocated)  ALPC
 ffffcc8ff2292260 size:  220 previous size:    0  (Allocated)  MmCi
 ffffcc8ff22924a0 size:  480 previous size:    0  (Allocated)  Via5
*ffffcc8ff2292940 size:  200 previous size:    0  (Allocated) *Irp 
        Pooltag Irp  : Io, IRP packets
 ffffcc8ff2292b60 size:  250 previous size:    0  (Allocated)  ALPC
 ffffcc8ff2292dc0 size:  210 previous size:    0  (Allocated)  NVRM

It would seem that the second parameter actually belongs to an IRP. Let's dump the IRP and see what it corresponds to. Remember each pool allocation begins with a pool header which is 0x10 bytes in size on x64 systems, therefore the actual base address of the IRP is 0x10 bytes ahead.

Rich (BB code):
4: kd> !irp ffffcc8ff2292940+0x10 1
Irp is active with 4 stacks 6 is current (= 0xffffcc8ff32b9ab0)
 No Mdl: System buffer=ffffcc8ff1b23f00: Thread ffffcc8ff350d080:  Irp is completed.  Pending has been returned
Flags = 00060040
ThreadListEntry.Flink = ffffcc8ff2292970
ThreadListEntry.Blink = ffffcc8ff2292970
IoStatus.Status = 00000102
IoStatus.Information = 000001f0
RequestorMode = 00000001
Cancel = 00
CancelIrql = 0
ApcEnvironment = 00
UserIosb = 57cc3ff0e8
UserEvent = ffffcc8ff0c9fe60
Overlay.AsynchronousParameters.UserApcRoutine = 00000000
Overlay.AsynchronousParameters.UserApcContext = 00000000
Overlay.AllocationSize = 00000000 - 00000000
CancelRoutine = 00000000  
UserBuffer = 24997dc8040
&Tail.Overlay.DeviceQueueEntry = ffffcc8ff22929c8
Tail.Overlay.Thread = ffffcc8ff350d080
Tail.Overlay.AuxiliaryBuffer = fffff8061a68dd70
Tail.Overlay.ListEntry.Flink = 00000000
Tail.Overlay.ListEntry.Blink = 00000000
Tail.Overlay.CurrentStackLocation = ffffcc8ff32b9ab0
Tail.Overlay.OriginalFileObject = 00000000
Tail.Apc = f0581712
Tail.CompletionKey = ffffcc8ff0581712
     cmd  flg cl Device   File     Completion-Context
 [N/A(0), N/A(0)]
            0  0 00000000 00000000 00000000-00000000   

            Args: 00000000 00000000 00000000 00000000
 [N/A(0), N/A(0)]
            0  0 00000000 00000000 00000000-00000000   

            Args: 00000000 00000000 00000000 00000000
 [N/A(0), N/A(0)]
            0  0 00000000 00000000 00000000-00000000   

            Args: 00000000 00000000 00000000 00000000
 [IRP_MJ_DEVICE_CONTROL(e), N/A(0)]
            0  0 ffffcc8fe05aaa80 00000000 00000000-00000000   
           \Driver\AFD
            Args: 00000000 00000000 0x0 00000000

So, it would seem that the IRP was part of a IOCTL request and was being sent to the AFD.sys driver, which is acts as a bridge between user-mode applications and the kernel networking stack. The second parameter is referring to the Flags field of the IRP object and we can verify that this is the case by using the dd command:

Rich (BB code):
4: kd> dd ffffcc8f`f2292960 L1
ffffcc8f`f2292960  00060040

If we examine the call stack for the thread, we'll be able to see where the system crashed.

Rich (BB code):
4: kd> knL
 # Child-SP          RetAddr           Call Site
00 fffff38a`f5d25638 fffff806`1a21762e nt!KeBugCheckEx
01 fffff38a`f5d25640 fffff806`1a00827e nt!ObfDereferenceObjectWithTag+0x20f39e << We crash here; attempt to dereference the Flags field
02 fffff38a`f5d25680 fffff806`1a08694e nt!HalPutDmaAdapter+0xe
03 fffff38a`f5d256b0 fffff806`1a00ec90 nt!IopCompleteRequest+0xace << Our completed I/O request
04 fffff38a`f5d25770 fffff806`1a00cb27 nt!KiDeliverApc+0x1b0
05 fffff38a`f5d25820 fffff806`1a00bd2f nt!KiSwapThread+0x827
06 fffff38a`f5d258d0 fffff806`1a00b5d3 nt!KiCommitThreadWait+0x14f
07 fffff38a`f5d25970 fffff806`1a3f7b51 nt!KeWaitForSingleObject+0x233 << User event from I/O request
08 fffff38a`f5d25a60 fffff806`1a3f7bfa nt!ObWaitForSingleObject+0x91
09 fffff38a`f5d25ac0 fffff806`1a208cb8 nt!NtWaitForSingleObject+0x6a
0a fffff38a`f5d25b00 00007ffe`c1e6cdf4 nt!KiSystemServiceCopyEnd+0x28
0b 00000057`cc3fef48 00000000`00000000 0x00007ffe`c1e6cdf4

By examining the call stack, you'll notice that we crash at nt!ObfDereferenceObjectWithTag and if we use !stack -p (available as part of CMKD.dll) to dump the parameters, you'll be able to see the Flags field address being used as an object pointer. Additionally, you'll also be able to see the default tag provided by the ObDereferenceObject API function.

Rich (BB code):
4: kd> !stack -p
Call Stack : 12 frames
## Stack-Pointer    Return-Address   Call-Site       
00 fffff38af5d25638 fffff8061a21762e nt!KeBugCheckEx+0 
    Parameter[0] = (unknown)       
    Parameter[1] = (unknown)       
    Parameter[2] = (unknown)       
    Parameter[3] = (unknown)       
01 fffff38af5d25640 fffff8061a00827e nt!ObfDereferenceObjectWithTag+20f39e (perf)
    Parameter[0] = ffffcc8ff2292960
    Parameter[1] = 00000000746c6644
    Parameter[2] = (unknown)       
    Parameter[3] = (unknown)       
02 fffff38af5d25680 fffff8061a08694e nt!HalPutDmaAdapter+e 
    Parameter[0] = ffffcc8ff2292960
    Parameter[1] = (unknown)       
    Parameter[2] = (unknown)       
    Parameter[3] = (unknown)

Rich (BB code):
4: kd> .formats 00000000746c6644
Evaluate expression:
  Hex:     00000000`746c6644
  Decimal: 1953261124
  Octal:   0000000000016433063104
  Binary:  00000000 00000000 00000000 00000000 01110100 01101100 01100110 01000100
  Chars:   ....tlfD
  Time:    Mon Nov 24 04:32:04 2031
  Float:   low 7.4918e+031 high 0
  Double:  9.65039e-315

Unfortunately, since the API call uses the default tag, it won't be worthwhile using object reference tracing, since we won't be able to differentiate between who is calling what. Besides, if the same call were to be made as shown above, then we would have crashed with another Stop 0x18. I'm not sure what exactly happened, however, my current thoughts are that the user event object shown in the IRP was meant to be referenced instead. Oddly enough, the user managed to resolve the issue by setting their voltage settings for their RAM to the value recommended by the manufacturer, as well as, installing all their motherboard drivers again.

Original Thread: [SOLVED] - Help | Random BSODs. FIX: CLEAN INSTALLATION; later, SET RAM voltages/frequencies like advised by the manufacturer.

References:

Object Reference Tracing with Tags - Windows drivers
 
Rich (BB code):
REFERENCE_BY_POINTER (18)
Arguments:
Arg1: 0000000000000000, Object type of the object whose reference count is being lowered
Arg2: ffffc78367ae4050, Object whose reference count is being lowered
Arg3: 0000000000000010, Reserved
Arg4: 0000000000000001, Reserved
    The reference count of an object is illegal for the current state of the object.
    Each time a driver uses a pointer to an object the driver calls a kernel routine
    to increment the reference count of the object. When the driver is done with the
    pointer the driver calls another kernel routine to decrement the reference count.
    Drivers must match calls to the increment and decrement routines. This bugcheck
    can occur because an object's reference count goes to zero while there are still
    open handles to the object, in which case the fourth parameter indicates the number
    of opened handles. It may also occur when the object's reference count drops below zero
    whether or not there are open handles to the object, and in that case the fourth parameter
    contains the actual value of the pointer references count.

Here's another interesting example of a Stop 0x18 which I thought would be worthwhile sharing. As usual we can dump the object in the second parameter using !object, but unfortunately, since this is a Minidump the command didn't work. Fortunately, I was able to infer the object type from the call stack - this is where things get interesting - and used the !devobj command to dump the object instead.

Rich (BB code):
1: kd> !devobj ffffc78367ae4050
Device object (ffffc78367ae4050) is for:
 Cannot read info offset from nt!ObpInfoMaskToOffset
 \Driver\DDDriver DriverObject ffffc783680827b0
Current Irp 00000000 RefCount 0 Type 00000022 Flags 00002040
SecurityDescriptor ffff800598be8d60 DevExt ffffc78367ae41a0 DevObjExt ffffc78367aec590
ExtensionFlags (0x00000012)  DOE_DELETE_PENDING, DOE_START_PENDING
Characteristics (0x00000100)  FILE_DEVICE_SECURE_OPEN
Device queue is not busy.

So, we know that we're dealing with a device object here. Let's dump the call stack using the !stack -p command and examine the call stack.

Rich (BB code):
1: kd> !stack -p
Call Stack : 13 frames
## Stack-Pointer    Return-Address   Call-Site     
00 ffffb409b3a774e8 fffff8001da14d34 nt!KeBugCheckEx+0
    Parameter[0] = (unknown)     
    Parameter[1] = (unknown)     
    Parameter[2] = (unknown)     
    Parameter[3] = (unknown)     
01 ffffb409b3a774f0 fffff8001d871886 nt!ObfReferenceObjectWithTag+17e2c4 (perf)
    Parameter[0] = ffffc78367ae4050
    Parameter[1] = 0000000069706e50
    Parameter[2] = (unknown)     
    Parameter[3] = (unknown)     
02 ffffb409b3a77530 fffff8001dc3a04b nt!IoGetAttachedDeviceReferenceWithTag+36
    Parameter[0] = (unknown)     
    Parameter[1] = 0000000069706e50
    Parameter[2] = (unknown)     
    Parameter[3] = (unknown)     
03 ffffb409b3a77560 fffff8001dd50c26 nt!IopSynchronousCall+3f
    Parameter[0] = ffffc783604e7390 << Device Object
    Parameter[1] = ffffb409b3a77600 << I/O Stack Location
    Parameter[2] = 00000000c00000bb << I/O Status
    Parameter[3] = 0000000000000000
04 ffffb409b3a775d0 fffff8001dd50b04 nt!PnpIrpQueryID+56
    Parameter[0] = ffffc783604e7390 << Device Object
    Parameter[1] = (unknown)     
    Parameter[2] = ffffb409b3a777a8
    Parameter[3] = (unknown)     
05 ffffb409b3a77660 fffff8001dd2495d nt!PnpQueryID+34
    Parameter[0] = ffffc783604e7580
    Parameter[1] = 0000000000000000
    Parameter[2] = ffffb409b3a777a8
    Parameter[3] = ffffb409b3a77790
06 ffffb409b3a776c0 fffff8001dd27220 nt!PiProcessNewDeviceNode+11d
    Parameter[0] = ffffc783604e7580 << Device Node
    Parameter[1] = (unknown)     
    Parameter[2] = (unknown)     
    Parameter[3] = (unknown)
[...]

Wait! The first parameter of the bugcheck infers that the reference count for our object was being lowered, however, we can see clearly see in the call stack that the object's reference count was being incremented by the call to nt!ObfReferenceObjectWithTag, what has happened here? Let's take a step back and examine each call stack frame in turn.

The nt!PiProcessNewDeviceNode indicates that we're setting up a new device node, the first parameter to this function is a pointer to the device node object in question. We can dump it using the !devnode command to verify that this is the case.

Rich (BB code):
1: kd> !devnode ffffc783604e7580
DevNode 0xffffc783604e7580 for PDO 0xffffc783604e7390
  Parent 0xffffc7836029a010   Sibling 0xffffc783604e7af0   Child 0000000000 
  InstancePath is "ROOT\SYSTEM\0001"
  State = DeviceNodeUninitialized (0x301)
  Previous State = DeviceNodeRemoved (0x312)
  StateHistory[12] = DeviceNodeRemoved (0x312)
  StateHistory[11] = DeviceNodeQueryRemoved (0x310)
  StateHistory[10] = DeviceNodeStarted (0x308)
  StateHistory[09] = DeviceNodeEnumerateCompletion (0x30d)
  StateHistory[08] = DeviceNodeEnumeratePending (0x30c)
  StateHistory[07] = DeviceNodeStarted (0x308)
  StateHistory[06] = DeviceNodeStartPostWork (0x307)
  StateHistory[05] = DeviceNodeStartCompletion (0x306)
  StateHistory[04] = DeviceNodeStartPending (0x305)
  StateHistory[03] = DeviceNodeResourcesAssigned (0x304)
  StateHistory[02] = DeviceNodeDriversAdded (0x303)
  StateHistory[01] = DeviceNodeInitialized (0x302)
  StateHistory[00] = DeviceNodeUninitialized (0x301)
  StateHistory[19] = Unknown State (0x0)
  StateHistory[18] = Unknown State (0x0)
  StateHistory[17] = Unknown State (0x0)
  StateHistory[16] = Unknown State (0x0)
  StateHistory[15] = Unknown State (0x0)
  StateHistory[14] = Unknown State (0x0)
  StateHistory[13] = Unknown State (0x0)
  Flags (0x00000031)  DNF_MADEUP, DNF_ENUMERATED,
                      DNF_IDS_QUERIED

We then send a PnP IRP to the physical device object associated to the device node. From what I can find, the nt!PnpIrpQueryID function is a wrapper which simply builds and sends an IRP_MN_QUERY_ID to the device object. Let's verify this by examining the I/O status block shown in the second parameter of the nt!IopSynchronousCall function.

Rich (BB code):
1: kd> dt _IO_STACK_LOCATION ffffb409b3a77600
nt!_IO_STACK_LOCATION
   +0x000 MajorFunction    : 0x1b '' << IRP_MJ_PNP
   +0x001 MinorFunction    : 0x13 '' << IRP_MN_QUERY_ID
   +0x002 Flags            : 0 ''
   +0x003 Control          : 0 ''
   +0x008 Parameters       : <anonymous-tag>
   +0x028 DeviceObject     : (null)
   +0x030 FileObject       : (null)
   +0x038 CompletionRoutine : (null)
   +0x040 Context          : (null)

You can look up the minor codes for a given PnP using the following page, and as we can see, the minor code of 0x13 corresponds to IRP_MN_QUERY_ID. This is what our wrapper function was sending earlier. This IRP can be used to enumerate the devices associated to a particular bus or to retrieve the unique device Id associated to a device. In either case, we can see a call gets made to the nt!IoGetAttachedDeviceReferenceWithTag function which returns a pointer to the highest attached driver in a given device stack. The function is also responsible for incrementing the reference count of the said device object.

The function takes a single parameter which is a pointer to a device object in which to find highest most device in the device stack from. We'll dump it using the usual command of !devobj.

Rich (BB code):
1: kd> !devobj ffffc783604e7390
Device object (ffffc783604e7390) is for:
 Cannot read info offset from nt!ObpInfoMaskToOffset
 \Driver\PnpManager DriverObject ffffc7836031fe50
Current Irp 00000000 RefCount 0 Type 00000004 Flags 00001040
SecurityDescriptor ffff800598be8d60 DevExt ffffc783604e74e0 DevObjExt ffffc783604e74e8 DevNode ffffc783604e7580
ExtensionFlags (0x00000010)  DOE_START_PENDING
Characteristics (0x00000180)  FILE_AUTOGENERATED_DEVICE_NAME, FILE_DEVICE_SECURE_OPEN
AttachedDevice (Upper) ffffc78367ae4050 \Driver\DDDriver
Device queue is not busy.

Notice the AttachedDevice field is set to the device object which caused the crash? We should now have a good understanding of the steps which have lead up to the crash and why our device object reference count was being incremented. However, we still don't particularly understand why it crashed and to do that, we need to dive into the assembly of the function nt!ObfReferenceObjectWithTag itself. The function uses the fast call calling convention which is only applicable on x86 and ignored on other architectures.

Rich (BB code):
1: kd> ub fffff800`1d871886 
nt!IoGetAttachedDeviceReferenceWithTag+0x19:
fffff800`1d871869 e892cc0400      call    nt!KeAcquireQueuedSpinLock (fffff800`1d8be500)
fffff800`1d87186e 488bcb          mov     rcx,rbx
fffff800`1d871871 408af8          mov     dil,al
fffff800`1d871874 e8d7f20100      call    nt!IoGetAttachedDevice (fffff800`1d890b50)
fffff800`1d871879 8bd6            mov     edx,esi << Tag value
fffff800`1d87187b 488bc8          mov     rcx,rax << Pointer to the device object
fffff800`1d87187e 488bd8          mov     rbx,rax
fffff800`1d871881 e8ea510200      call    nt!ObfReferenceObjectWithTag (fffff800`1d896a70)

Rich (BB code):
1: kd> u nt!ObfReferenceObjectWithTag
nt!ObfReferenceObjectWithTag:
fffff800`1d896a70 48895c2408      mov     qword ptr [rsp+8],rbx << Push the object pointer onto the stack
fffff800`1d896a75 4889742410      mov     qword ptr [rsp+10h],rsi << Push the tag value onto the stack
fffff800`1d896a7a 57              push    rdi
fffff800`1d896a7b 4883ec30        sub     rsp,30h << Adjust stack pointer to the object header
fffff800`1d896a7f 833d8a45a60000  cmp     dword ptr [nt!ObpTraceFlags (fffff800`1e2fb010)],0
fffff800`1d896a86 488bf1          mov     rsi,rcx
fffff800`1d896a89 bb01000000      mov     ebx,1
fffff800`1d896a8e 0f8570e21700    jne     nt!ObfReferenceObjectWithTag+0x17e294 (fffff800`1da14d04)

Rich (BB code):
1: kd> u fffff800`1da14d04
nt!ObfReferenceObjectWithTag+0x17e294:
fffff800`1da14d04 448bca          mov     r9d,edx
fffff800`1da14d07 448bc3          mov     r8d,ebx
fffff800`1da14d0a 0fb6d3          movzx   edx,bl
fffff800`1da14d0d 4883c1d0        add     rcx,0FFFFFFFFFFFFFFD0h
fffff800`1da14d11 e8c2ac1400      call    nt!ObpPushStackInfo (fffff800`1db5f9d8) << Check if the object is being traced
fffff800`1da14d16 90              nop
fffff800`1da14d17 e9781de8ff      jmp     nt!ObfReferenceObjectWithTag+0x24 (fffff800`1d896a94)
fffff800`1da14d1c 33d2            xor     edx,ed

Rich (BB code):
1: kd> u fffff800`1d896a94
nt!ObfReferenceObjectWithTag+0x24:
fffff800`1d896a94 f0480fc15ed0    lock xadd qword ptr [rsi-30h],rbx (1) << Perform an atomic exchange operation, this is the object header
fffff800`1d896a9a 48ffc3          inc     rbx (2)
fffff800`1d896a9d 4883fb01        cmp     rbx,1 (3)
fffff800`1d896aa1 0f8e75e21700    jle     nt!ObfReferenceObjectWithTag+0x17e2ac (fffff800`1da14d1c) (4)
fffff800`1d896aa7 488b742448      mov     rsi,qword ptr [rsp+48h]
fffff800`1d896aac 488bc3          mov     rax,rbx
fffff800`1d896aaf 488b5c2440      mov     rbx,qword ptr [rsp+40h]
fffff800`1d896ab4 4883c430        add     rsp,30h

The most important parts are listed from 1 to 4. The xadd instruction sums the two operands and then exchanges the destination register with the source. In this case, object header is set to the rbx register (1). Next, the pointer count is then incremented by 1; remember, the offset for the pointer count is 0x0 (2). We then compare the pointer count - which is now 1 - with the value of 1 (3). Since the pointer count is equal to 1, we then jump to another segment of code which sets up the bugcheck parameters and subsequently crashes the system.

Rich (BB code):
1: kd> u fffff800`1da14d1c
nt!ObfReferenceObjectWithTag+0x17e2ac:
fffff800`1da14d1c 33d2            xor     edx,edx
fffff800`1da14d1e 48895c2420      mov     qword ptr [rsp+20h],rbx
fffff800`1da14d23 41b910000000    mov     r9d,10h
fffff800`1da14d29 4c8bc6          mov     r8,rsi
fffff800`1da14d2c 8d4a18          lea     ecx,[rdx+18h]
fffff800`1da14d2f e87c24feff      call    nt!KeBugCheckEx (fffff800`1d9f71b0)

Now, I believe the system calls the bugcheck on the previously described condition since you can't reference an object which has been marked for deletion. This is evident in the object header itself. As we can see, the DeletedInline flag has been set, which indicates that this particular object has been queued for deletion by a worker thread.

Rich (BB code):
1: kd> dt _OBJECT_HEADER ffffc78367ae4050-0x30
nt!_OBJECT_HEADER
   +0x000 PointerCount     : 0n1
   +0x008 HandleCount      : 0n0
   +0x008 NextToFree       : (null)
   +0x010 Lock             : _EX_PUSH_LOCK
   +0x018 TypeIndex        : 0xb1 ''
   +0x019 TraceFlags       : 0 ''
   +0x019 DbgRefTrace      : 0y0
   +0x019 DbgTracePermanent : 0y0
   +0x01a InfoMask         : 0x2 ''
   +0x01b Flags            : 0x82 ''
   +0x01b NewObject        : 0y0
   +0x01b KernelObject     : 0y1
   +0x01b KernelOnlyAccess : 0y0
   +0x01b ExclusiveObject  : 0y0
   +0x01b PermanentObject  : 0y0
   +0x01b DefaultSecurityQuota : 0y0
   +0x01b SingleHandleEntry : 0y0
   +0x01b DeletedInline    : 0y1
   +0x01c Reserved         : 0xb886
   +0x020 ObjectCreateInfo : (null)
   +0x020 QuotaBlockCharged : (null)
   +0x028 SecurityDescriptor : (null)
   +0x030 Body             : _QUAD

References:

Plug and Play Minor IRPs - Windows drivers
Journey Into the Object Manager Executive Subsystem: Object Header and Object Type
IoGetAttachedDeviceReference function (ntifs.h) - Windows drivers
ReactOS: ntoskrnl/io/pnpmgr/pnpirp.c File Reference
WRK/obref.c at master ยท bigzz/WRK
https://github.com/AdaCore/gsh/blob/master/os/src/ddk/wdm.h
 
Last edited:
@x BlueRobot

RE: Post #3

Excellent analysis as always, Harry.

RE: 2nd code box - (first 4 lines) -
Rich (BB code):
1: kd> !devobj ffffc78367ae4050
Device object (ffffc78367ae4050) is for:
 Cannot read info offset from nt!ObpInfoMaskToOffset
 \Driver\DDDriver DriverObject ffffc783680827b0

4th line -
Rich (BB code):
 \Driver\DDDriver DriverObject ffffc783680827b0

Why would you not issue a !drvobj command on the memory address referenced on that line?

The first 3 lines tell us that the memory address tested does not contain a device -
Rich (BB code):
1: kd> !devobj ffffc78367ae4050
Device object (ffffc78367ae4050) is for:
 Cannot read info offset from nt!ObpInfoMaskToOffset

Thanks,

John
 
Last edited:
The first 3 lines tell us that the memory address tested does not contain a device -
I assume that you're referring to this message:

Cannot read info offset from nt!ObpInfoMaskToOffset
WinDbg isn't able to read the InfoMask field from the Object Header for some reason, most likely because I used a Minidump and not a Kernel Memory Dump. The nt!ObpInfoMaskToOffset variable is an array which stores the offset to the corresponding optional header.

From CodeMachine:

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 25possibilities based on the bits in OBJECT_HEADER->InfoMask.
Source: CodeMachine - Article - Windows Object Headers
 

Has Sysnative Forums helped you? Please consider donating to help us support the site!

Back
Top