The Vulnerability
The vulnerability resides in
xxxTrackPopupMenuEx, this function is responsible for
displaying shortcut menus and tracking user selections. During this
process it will try to get a reference to the GlobalMenuState
object via a call to xxxMNAllocMenuState, if the object is in use, for example: when another pop-up menu is already active,
this function will try to create a new instance.
If xxxMNAllocMenuState fails it
will return False but it will also set the pGlobalMenuState thread global variable
to NULL. The caller verifies the
return value, and in case of failure it will try to do some cleanup in
order to fail gracefully.
During this cleanup the xxxEndMenuState procedure
is called. This function's main responsibility
is to free and unlock all the resources acquired and saved for the
MenuState object, but it does not check that the pGlobalMenuState
variable is not NULL before using it. As a result a bunch of kernel operations are performed on a kernel object whose address is zero and thus potentially controlled from userland memory on platforms that allow it.
Triggering the vulnerability is relatively easy by just creating and displaying two popup instances and
exhausting GDI objects for the current session, as explained by Endgame. However, actually getting code execution is not
trivial.
Exploitation
Usually a NULL dereference
vulnerability in the kernel can be exploited by mapping memory at address zero in userland memory (when allowed by the OS),
creating a fake object inside of this null page and then triggering the vulnerability in the kernel from the current process context of your exploit which has the null page mapped with attacker controlled data. With some luck we get a function pointer of some sort called from our controlled object data and we achieve code execution with Kernel privileges (e.g. this was the case of MS11-054). As such, NULL dereference vulnerabilities have for many years provided a simple and straightforward route to kernel exploitation and privilege escalation in scenarios where you are allowed to map at zero.
Unfortunately in the case of CVE-2013-3881 life is not that simple, even on platforms that allow the null page to be allocated.
When xxxTrackPopupMenuEx
calls xxxMNAllocMenuState
and fails, it will directly jump to destroy the (non-existant)
MenuState object, and after some function calls, it will inevitably try to free the memory. This means that it does not matter if we
create a perfectly valid object at region zero. At some point
before xxxEndMenuState returns, a call to
ExFreePoolWithTag(0x0, tag) will be made. This call will
produce a system crash as it tries to access the pool headers which are normally located just before
the poolAddress which in this case is at address 0. Thus the kernel tries to fetch at 0-minus something which is unallocated and/or uncontrolled memory and we trigger a BSOD.
This means the only viable exploitation option is to try and get
code execution before this Free occurs.
Situational Awareness
At this point we try to
understand the entire behavior of xxxEndMenuState, and all of
the structures and objects being manipulated before we trigger any fatal crash. The main structure we have to deal with is the one that is being read from address zero, which is referenced from the pGlobalMenuState variable:
win32k!tagMENUSTATE
+0x000
pGlobalPopupMenu : Ptr32 tagPOPUPMENU
+0x004
fMenuStarted : Pos 0, 1 Bit
+0x004
fIsSysMenu : Pos 1, 1 Bit
+0x004
fInsideMenuLoop : Pos 2, 1 Bit
+0x004
fButtonDown : Pos 3, 1 Bit
+0x004
fInEndMenu : Pos 4, 1 Bit
+0x004
fUnderline : Pos 5, 1 Bit
+0x004
fButtonAlwaysDown : Pos 6, 1 Bit
+0x004
fDragging : Pos 7, 1 Bit
+0x004
fModelessMenu : Pos 8, 1 Bit
+0x004
fInCallHandleMenuMessages : Pos 9, 1 Bit
+0x004
fDragAndDrop : Pos 10, 1 Bit
+0x004
fAutoDismiss : Pos 11, 1 Bit
+0x004
fAboutToAutoDismiss : Pos 12, 1 Bit
+0x004
fIgnoreButtonUp : Pos 13, 1 Bit
+0x004
fMouseOffMenu : Pos 14, 1 Bit
+0x004
fInDoDragDrop : Pos 15, 1 Bit
+0x004
fActiveNoForeground : Pos 16, 1 Bit
+0x004
fNotifyByPos : Pos 17, 1 Bit
+0x004
fSetCapture : Pos 18, 1 Bit
+0x004
iAniDropDir : Pos 19, 5 Bits
+0x008
ptMouseLast : tagPOINT
+0x010
mnFocus : Int4B
+0x014
cmdLast : Int4B
+0x018
ptiMenuStateOwner : Ptr32 tagTHREADINFO
+0x01c
dwLockCount : Uint4B
+0x020
pmnsPrev : Ptr32 tagMENUSTATE
+0x024
ptButtonDown : tagPOINT
+0x02c
uButtonDownHitArea : Uint4B
+0x030
uButtonDownIndex : Uint4B
+0x034
vkButtonDown : Int4B
+0x038
uDraggingHitArea : Uint4B
+0x03c
uDraggingIndex : Uint4B
+0x040
uDraggingFlags : Uint4B
+0x044
hdcWndAni : Ptr32 HDC__
+0x048
dwAniStartTime : Uint4B
+0x04c
ixAni : Int4B
+0x050
iyAni : Int4B
+0x054
cxAni : Int4B
+0x058
cyAni : Int4B
+0x05c
hbmAni : Ptr32 HBITMAP__
+0x060
hdcAni : Ptr32 HDC__
This is the main object which
xxxEndMenuState will deal with, it will perform a couple of actions using the object and finally attempts to free it with the call to ExFreePoolWithTag. The interaction with the object that occurs prior to the free are the ones we have to analyze deeply as they are our only
hope in getting code execution before the imminent crash.
xxxEndMenuState is a destructor, and as such it will first call the destructor of all the
objects contained inside the main object before actually freeing their associated allocated memory, for example:
_MNFreePopup(pGlobalMenuState->pGlobalPopupMenu)
_UnlockMFMWFPWindow(pGlobalMenuState->uButtonDownHitArea)
_UnlockMFMWFPWindow(pGlobalMenuState->uDraggingHitArea)
_MNDestroyAnimationBitmap(pGlobalMenuState->hbmAni)
The _MNFreePopup call is very
interesting, as PopupMenu objects contain several WND objects and these have Handle references. This is relevant because if this
WND object has its lock count equal to one when MNFreePopup
is called, at some point it will try to destroy the object that the
Handle is referencing. These objects are global to a user
session. This means that we can force the deletion of any object within the current windows session, or at the very least decrement its reference
count.
win32k!tagPOPUPMENU
+0x000
fIsMenuBar : Pos 0, 1 Bit
+0x000
fHasMenuBar : Pos 1, 1 Bit
+0x000
fIsSysMenu : Pos 2, 1 Bit
+0x000
fIsTrackPopup : Pos 3, 1 Bit
+0x000
fDroppedLeft : Pos 4, 1 Bit
+0x000
fHierarchyDropped : Pos 5, 1 Bit
+0x000
fRightButton : Pos 6, 1 Bit
+0x000
fToggle : Pos 7, 1 Bit
+0x000
fSynchronous : Pos 8, 1 Bit
+0x000
fFirstClick : Pos 9, 1 Bit
+0x000
fDropNextPopup : Pos 10, 1 Bit
+0x000
fNoNotify : Pos 11, 1 Bit
+0x000
fAboutToHide : Pos 12, 1 Bit
+0x000
fShowTimer : Pos 13, 1 Bit
+0x000
fHideTimer : Pos 14, 1 Bit
+0x000
fDestroyed : Pos 15, 1 Bit
+0x000
fDelayedFree : Pos 16, 1 Bit
+0x000
fFlushDelayedFree : Pos 17, 1 Bit
+0x000
fFreed : Pos 18, 1 Bit
+0x000
fInCancel : Pos 19, 1 Bit
+0x000
fTrackMouseEvent : Pos 20, 1 Bit
+0x000
fSendUninit : Pos 21, 1 Bit
+0x000
fRtoL : Pos 22, 1 Bit
+0x000
iDropDir : Pos 23, 5 Bits
+0x000
fUseMonitorRect : Pos 28, 1 Bit
+0x004
spwndNotify : Ptr32 tagWND
+0x008
spwndPopupMenu : Ptr32 tagWND
+0x00c
spwndNextPopup : Ptr32 tagWND
+0x010
spwndPrevPopup : Ptr32 tagWND
+0x014
spmenu : Ptr32 tagMENU
+0x018
spmenuAlternate : Ptr32 tagMENU
+0x01c
spwndActivePopup : Ptr32 tagWND
+0x020
ppopupmenuRoot : Ptr32 tagPOPUPMENU
+0x024
ppmDelayedFree : Ptr32 tagPOPUPMENU
+0x028
posSelectedItem : Uint4B
+0x02c
posDropped : Uint4B
…...
In order to understand why this is so
useful, let's analyze what happens when a WND object is destroyed:
pWND
__stdcall HMUnlockObject(pWND pWndObject)
{
pWND
result = pWndObject;
pWndObject->cLockObj--;
if
(!pWndObject->cLockObj)
result
= HMUnlockObjectInternal(pWndObject);
return
result;
}
The first thing done is a decrement of the cLockObj counter, and if the counter is then zero the function
HMUnlockObjectInternal is called.
pWND
__stdcall HMUnlockObjectInternal( pWND pWndObject)
{
pWND
result;
char
v2;
result
= pWndObject;
unsigned
int entryIndex;
pHandleEntry
entry;
entryIndex
= pWndObject->handle & 0xFFFF;
entry
= gSharedInfo.aheList + gSharedInfo.HeEntrySize * entryIndex
if
( entry->bFlags & HANDLEF_DESTROY )
{
if
( !(entry->bFlags & HANDLEF_INDESTROY) )
{
HMDestroyUnlockedObject(entry);
result
= 0;
}
}
return
result;
}
Once it knows that the reference
count has reached zero, it has to actually destroy the object. For this task it gets the handle value and applies a mask in
order to get the index of the HandleEntry into the handle table.
Then it validates some state flags, and
calls HMDestroyUnlockedObject.
The HandleEntry contains information
about the object type and state. This information will be used to
select between the different destructor functions.
int
__stdcall HMDestroyUnlockedObject(pHandleEntry handleEntry)
{
int
index;
index
= 0xC * handleEntry->bType
handleEntry->bFlags
|= HANDLEF_INDESTROY;
return
(gahti[v2])(handleEntry->phead);
}
The handle type information table
(gahti) holds properties specific to each object type, as well as
their Destroy functions. So this function will use the bType value
from the handleEntry in order to determine which Destroy function to
call.
At this point it is important to
remember that we have full control over the MenuState object, and
that means we can create and fully control its inner PopupMenu
object, and in turn the WND objects inside this PopupMenu. This
implies that we have control over the handle value in the WND object.
Another important fact is that entry
zero on the gahti table is always zero, and it represents the FREE
object type.
So our strategy in order to get code
execution here is to, by some means, create an object whose
HandleEntry in the HandleEntry table has a bType = 0x0, and
bFlags = 0x1. If we can manage to do so we can then create a fake WND
object with a handle that makes reference to this object of
bType=0x0. When the
HMDestroyUnlockedObject is called it will end up in a call
gahti[0x0]. As the first element in gahti table is zero, this ends up as a "call 0". In other words we can force a path that will execute our controlled data at address zero.
What we need
We need to create a user object of bType=FREE (0x0) and bFlags= HANDLEF_DESTROY
(0x1).
This is not
possible directly, so we first focus on getting an object with
the bFlag value equal to 0x1. For this purpose we create a Menu
object, set it to a window, and then Destroy it. The internal reference count for the object did not reach zero because it is still
being accessed by the window object, so it is not actually deleted but instead flagged as HANDLEF_DESTROY on the HandleEntry. This means the bFlag
will equal to 0x1.
The bType value
is directly associated to the Object Type. In the case of a menu
object the value is 0x2 and there is no way of creating an object of
type 0x0. So we focus on what ways we have to alter this
value using some of the functions being called before destroying the
WND object.
As you can
probably remember from the PopupMenu structure shown before, it
contains several WND objects, and one of the first actions performed
when HMUnlockObject(pWnd) is called is to decrement the lockCount.
So we simply set-up two fake WND objects in such a way that the lockCount
field will be pointing to the HandleEntry->bType field. When
each of those fake WND objects is destroyed it will actually perform a
“dec” operation over the bType of our menu object, thus decrementing it from 0x2 to 0x0. We now have a bFlag of 0x1 and a bType of 0x0.
Using this
little trick we are able to create a User object with the needed
values on the HandleEntry.
Summary
First we will
create a MenuObject and force it to be flagged as HANDLEF_DESTROY.
Then we will
trigger the vulnerability, where
xxxEndMenuState will get a reference
to the menuState structure from a global thread pointer, and its value will be zero. So we map this address and create a fake
MenuState structure at zero.
XxxEndMenuState
will call FreePopup(..) on a popup object instance we created, and
will in turn try to destroy its internal objects. Three of
these objects will be fake WND objects which we also create. The first two will
serve the purpose of decrementing the bType value of our menu
object, and the third one will trigger a HMDestroyUnlockedObject
on this same object. This will result on code execution being
redirected to address 0x0 as previously discussed.
We have to remember that when we redirect execution to address 0, this memory also servers as a MenuState
object. In particular the first field is a pointer to the
PopupMenu object that we need to use. So what we do is to choose the
address of this popup menu object in such a way that the least significant bytes of the address also represent a valid X86 jump opcode (e.g. 0x04eb translates to eb 04 in little endian memory ordering which represents a jump 4).
Finish him!
Once we achieve execution at ring 0 we patch the Enabled
field on the _SEP_TOKEN_PRIVILEGES structure from the MOSDEF callback
process in order to enable all the privileges for the process. We fix up the
HandleEntry we modified before, and restore the stack in order to
return after the PoolFree thus skipping the BSOD.
Once all of this is done we return to user-land, but now our
MOSDEF process has all the privileges, this allows us to for example
migrate to LSASS and get System privileges.
-- Matias Soler