May has been a busy month for vulnerabilities in the world's most popular desktop operating system. Hackers have made headlines with massive infections by WannaCry ransomware, which exploits an SMB security flaw and the ETERNALBLUE tool. Shortly prior, on May 9, Microsoft fixed CVE-2017-0263, which had made it possible for attackers to gain maximum system privileges on PCs running Windows 10, Windows 8.1, Windows 7, Windows Server 2008, Windows Server 2012, and Windows Server 2016.
Vulnerability CVE-2017-0263 had been used already in phishing messages. The emails contained an exploit that first entered the system by taking advantage of incorrect handling of EPS files by Microsoft Office (CVE-2017-0262) and then, once on the inside, leveraged CVE-2017-0263 to get full administrator rights. Two years ago we looked at a similar vulnerability in Windows, and here we will see how the new CVE-2017-0263 opens the way to "pwning" remote workstations and servers.
In a word, this is a use-after-free vulnerability (CWE-416)—when context menu windows were closed and the memory occupied by the menu was freed up, the pointer to the freed-up memory was not zeroed out. As a result, the pointer could be reused.
The below discussion covers the process of window handling in the win32k.sys driver and how this process makes it possible to exploit the vulnerability.
Context menusEvery Windows user is familiar with context menus. These are the menus that drop down when we right-click.
The appearance of this menu and how it is displayed are completely up to the developer of each application. WinAPI provides developers with the TrackPopupMenuEx function, which causes a context menu to appear with the specified parameters at the specified location on the screen.
The state of the context menu is stored in the kernel in the variable win32k!gMenuState, which is a win32k!tagMENUSTATE structure:
0: kd> dt win32k!tagMenuState
+0x000 pGlobalPopupMenu : Ptr32 tagPOPUPMENU
+0x004 flags : Int4B
+0x008 ptMouseLast : tagPOINT
+0x010 mnFocus : Int4B
+0x014 cmdLast : Int4B
+0x018 ptiMenuStateOwner : Ptr32 tagTHREADINFO
+0x01c dwLockCount : Uint4B
+0x020 pmnsPrev : Ptr32 tagMENUSTATE
+0x024 ptButtonDown : tagPOINT
+0x02c uButtonDownHitArea: Uint4B
+0x030 uButtonDownIndex : Uint4B
+0x034 vkButtonDown : Int4B
+0x038 uDraggingHitArea : Uint4B
+0x03c uDraggingIndex : Uint4B
+0x040 uDraggingFlags : Uint4B
+0x044 hdcWndAni : Ptr32 HDC__
+0x048 dwAniStartTime : Uint4B
+0x04c ixAni : Int4B
+0x050 iyAni : Int4B
+0x054 cxAni : Int4B
+0x058 cyAni : Int4B
+0x05c hbmAni : Ptr32 HBITMAP__
+0x060 hdcAni : Ptr32 HDC__
Note that all of the call stacks and structures presented here are taken from Windows 7 x86. The 32-bit OS version is used for convenience: arguments for most functions are stored on the stack and there is no WoW64 layer, which during system calls switches to a 64-bit stack due to which 32-bit stack frames are lost when the call stack is printed. A full list of vulnerable operating systems is given on the Microsoft website.
The win32k!tagMENUSTATE structure stores, for example, such information as: the clicked region of the screen, number of the most recent menu command, and pointers to the windows that were clicked or selected for drag-and-drop. The list of context menu windows is stored in the first field, pGlobalPopupMenu, which is of the type win32k!tagPOPUPMENU:
0: kd> dt win32k!tagPopupMenu
+0x000 flags : Int4B
+0x004 spwndNotify : Ptr32 tagWND
+0x008 spwndPopupMenu : Ptr32 tagWND
+0x00c spwndNextPopup : Ptr32 tagWND
+0x010 spwndPrevPopup : Ptr32 tagWND
+0x014 spmenu : Ptr32 tagMENU
+0x018 spmenuAlternate : Ptr32 tagMENU
+0x01c spwndActivePopup : Ptr32 tagWND
+0x020 ppopupmenuRoot : Ptr32 tagPOPUPMENU
+0x024 ppmDelayedFree : Ptr32 tagPOPUPMENU
+0x028 posSelectedItem : Uint4B
+0x02c posDropped : Uint4B
+0x030 ppmlockFree : Ptr32 tagPOPUPMENU
In both structures we have highlighted the fields of interest, which will be used below to describe the exploitation process.
The variable win32k!gMenuState is initialized when a context menu is created, during the previously mentioned TrackPopupMenuEx function. Initialization occurs when win32k!xxxMNAllocMenuState is called:
1: kd> k
# ChildEBP RetAddr
00 95f29b38 81fe3ca6 win32k!xxxMNAllocMenuState+0x7c
01 95f29ba0 81fe410f win32k!xxxTrackPopupMenuEx+0x27f
02 95f29c14 82892db6 win32k!NtUserTrackPopupMenuEx+0xc3
03 95f29c14 77666c74 nt!KiSystemServicePostCall
04 0131fd58 7758480e ntdll!KiFastSystemCallRet
05 0131fd5c 100015b3 user32!NtUserTrackPopupMenuEx+0xc
06 0131fd84 7756c4b7 q_Main_Window_Class_wndproc (call TrackPopupMenuEx)
And when the context menu is no longer needed—for example, the user selected a menu item or clicked outside of the menu—the function win32k!xxxMNEndMenuState is called and frees up the state of the menu:
1: kd> k
# ChildEBP RetAddr
00 a0fb7ab0 82014f68 win32k!xxxMNEndMenuState
01 a0fb7b20 81fe39f5 win32k!xxxRealMenuWindowProc+0xd46
02 a0fb7b54 81f5c134 win32k!xxxMenuWindowProc+0xfd
03 a0fb7b94 81f1bb74 win32k!xxxSendMessageTimeout+0x1ac
04 a0fb7bbc 81f289c8 win32k!xxxWrapSendMessage+0x1c
05 a0fb7bd8 81f5e149 win32k!NtUserfnNCDESTROY+0x27
06 a0fb7c10 82892db6 win32k!NtUserMessageCall+0xcf
07 a0fb7c10 77666c74 nt!KiSystemServicePostCall
08 013cfd90 77564f21 ntdll!KiFastSystemCallRet
09 013cfd94 77560908 user32!NtUserMessageCall+0xc
0a 013cfdd0 77565552 user32!SendMessageWorker+0x546
0b 013cfdf0 100014e4 user32!SendMessageW+0x7c
0c 013cfe08 775630bc q_win_event_hook (call SendMessageW(MN_DODRAGDROP))
Important here is that the gMenuState.pGlobalPopupMenu field is updated only during initialization in the xxxMNAllocMenuState function—it is not zeroed out when the structure is destroyed.
This function is the star of our story. Its handful of lines harbor the vulnerability.
xxxMNEndMenuState starts with deinitialization and freeing of information related to the context menu. The MNFreePopup function—to which we will return in the following section—is called. The main task of MNFreePopup is to decrement reference counters for windows related to the particular context menu. When the reference count falls to zero, this decrementing can cause the window to be destroyed.
Then the xxxMNEndMenuState function checks the fMenuWindowRef flag of the pGlobalPopupMenu field to see if any references remain to the main window of the context menu. This flag is cleared upon destruction of the window contained in the spwndPopupMenu field of the context menu:
3: kd> k
# ChildEBP RetAddr
00 95fffa5c 81f287da win32k!xxxFreeWindow+0x847
01 95fffab0 81f71252 win32k!xxxDestroyWindow+0x532
02 95fffabc 81f7122c win32k!HMDestroyUnlockedObject+0x1b
03 95fffac8 81f70c4a win32k!HMUnlockObjectInternal+0x30
04 95fffad4 81f6e1fc win32k!HMUnlockObject+0x13
05 95fffadc 81fea664 win32k!HMAssignmentUnlock+0xf
06 95fffaec 81fea885 win32k!MNFreePopup+0x7d
07 95fffb14 8202c3d6 win32k!xxxMNEndMenuState+0x40
.text:BF89082E and ecx, 7FFFFFFFh ; ~fMenuWindowRef
.text:BF890834 mov [eax+tagPOPUPMENU.flags], ecx
As seen above, the flag is discarded and therefore the memory occupied by the pGlobalPopupMenu field is freed up, but the pointer itself is not zeroed out. This causes a dangling pointer, which under certain circumstances can be reused.
Immediately after the context menu memory is freed up, the execution flow deletes the references stored in the context menu state structure that relate to clicked windows (uButtonDownHitArea field) when the menu was active or were selected for drag-and-drop (uDraggingHitArea field).
A window object in the kernel is described by a tagWND structure. There we describe the concept of kernel callbacks, which will be needed here as well. The number of active references to a window is stored in the cLockObj field of the tagWND structure.
Deleting references to a window, as shown in the previous section, can cause the window itself to be destroyed. Before the window is destroyed, a WM_NCDESTROY change-of-window-state message is sent to the window.
This means that while xxxMNEndMenuState is running, control can be transferred to user application code—specifically, to the window procedure of the window being destroyed. This happens when no references remain to a window whose pointer is stored in the gMenuState.uButtonDownHitArea field.
2: kd> k
# ChildEBP RetAddr
0138fc34 7756c4b7 q_new_SysShadow_window_proc
0138fc60 77565f6f USER32!InternalCallWinProc+0x23
0138fcd8 77564ede USER32!UserCallWinProcCheckWow+0xe0
0138fd34 7755b28f USER32!DispatchClientMessage+0xcf
0138fd64 77666bae USER32!__fnNCDESTROY+0x26
0138fd90 77564f21 ntdll!KiUserCallbackDispatcher+0x2e
95fe38f8 81f56d86 nt!KeUserModeCallback
95fe3940 81f5c157 win32k!xxxSendMessageToClient+0x175
95fe398c 81f5c206 win32k!xxxSendMessageTimeout+0x1cf
95fe39b4 81f2839c win32k!xxxSendMessage+0x28
95fe3a10 81f2fb00 win32k!xxxDestroyWindow+0xf4
95fe3a24 81f302ee win32k!xxxRemoveShadow+0x3e
95fe3a64 81f287da win32k!xxxFreeWindow+0x2ff
95fe3ab8 81f71252 win32k!xxxDestroyWindow+0x532
95fe3ac4 81f7122c win32k!HMDestroyUnlockedObject+0x1b
95fe3ad0 81f70c4a win32k!HMUnlockObjectInternal+0x30
95fe3adc 81f6e1fc win32k!HMUnlockObject+0x13
95fe3ae4 81fe4162 win32k!HMAssignmentUnlock+0xf
95fe3aec 81fea8c3 win32k!UnlockMFMWFPWindow+0x18
95fe3b14 8202c3d6 win32k!xxxMNEndMenuState+0x7e
For example, in the call stack shown above, the WM_NCDESTROY message is handled by the window procedure for the SysShadow window class. Windows of this class are designed to provide shadowing and are usually destroyed together with the windows for which they are shadowing.
Now let's see the most interesting part of how this window message is handled, in the form that was found in the malware sample taken from a .docx phishing attachment:
When the attacker takes control, the first matter of business is to occupy the now-free memory that was just occupied by gMenuState.pGlobalPopupMenu, in order to reuse this pointer later. Attempting to allocate the indicated memory block, the exploit performs a large number of SetClassLongW calls, thus setting a specially formed menu name for window classes that have been specially created for this purpose:
2: kd> k
# ChildEBP RetAddr
00 9f74bafc 81f240d2 win32k!memcpy+0x33
01 9f74bb3c 81edadb1 win32k!AllocateUnicodeString+0x6b
02 9f74bb9c 81edb146 win32k!xxxSetClassData+0x1d1
03 9f74bbb8 81edb088 win32k!xxxSetClassLong+0x39
04 9f74bc1c 82892db6 win32k!NtUserSetClassLong+0xc8
05 9f74bc1c 77666c74 nt!KiSystemServicePostCall
06 0136fac0 7755658b ntdll!KiFastSystemCallRet
07 0136fac4 775565bf user32!NtUserSetClassLong+0xc
08 0136fafc 10001a52 user32!SetClassLongW+0x5e
09 0136fc34 7756c4b7 q_new_SysShadow_window_proc (call SetClassLongW)
After the memory is occupied, the next stage begins. The exploit accesses the NtUserMNDragLeave system procedure, which performs a nested call of the xxxMNEndMenuState function. Clearing of the gMenuState structure starts again:
2: kd> k
# ChildEBP RetAddr
00 9f74bbf0 8202c3d6 win32k!xxxMNEndMenuState
01 9f74bc04 8202c40e win32k!xxxUnlockMenuStateInternal+0x2e
02 9f74bc14 82015672 win32k!xxxUnlockAndEndMenuState+0xf
03 9f74bc24 82001728 win32k!xxxMNDragLeave+0x45
04 9f74bc2c 82892db6 win32k!NtUserMNDragLeave+0xd
05 9f74bc2c 100010a9 nt!KiSystemServicePostCall
06 0136fafc 10001a84 q_exec_int2e (int 2Eh)
07 0136fc34 7756c4b7 q_new_SysShadow_window_proc (call q_exec_int2e)
As described in the previous section, the procedure starts by deinitializing the pGlobalPopupMenu field; this process is performed by the MNFreePopup call, which decrements the reference counters for windows contained in various fields of tagPOPUPMENU. After the prior step, the content of this structure is now controlled by the attacker. So when the described chain of actions is performed, the attacker gets a decrement primitive to an arbitrary kernel address.
In this exploit, an address is inserted in the tagPOPUPMENU.spwndPrevPopup field and the primitive is used to decrement the field for flags of one of the windows, causing that window to be marked with the flag bServerSideProc, which means that its window procedure is run in the kernel.
As the code shows, immediately after returning from NtUserMNDragLeave, a message is sent to the window by a SendMessage call, causing arbitrary kernel code execution. At this stage, the attacker usually steals a system process token to obtain system privileges. Indeed, this is what happened in the exploit here.
What are the salient points of the exploit? The most common cause of vulnerabilities in the win32k.sys library is access to callbacks in user space when any kernel structures are in an intermediate stage when a transaction is changing them. Setting the bServerSideProc flag for a window is also a popular method for kernel code execution. In addition, the most convenient method to leverage kernel code execution for privilege escalation is to copy a reference to a system token.
In that sense, the exploit looks rather mundane. At the same time many of the nuances have been simplified or purposefully omitted from this discussion.
For example, we did not dwell on the exact appearance of the context menu or menu-related actions that cause the necessary state of the flags and fields of the win32k!gMenuState variable during execution of the xxxMNEndMenuState procedure. Left unmentioned was the fact that the menu names set during SetClassLong calls should, on the one hand, be a Unicode string with no null characters but, on the other hand, be a legitimate tagPOPUPMENU structure. This also means that the address of the window in the kernel (to which the decrement field will refer) must not contain any wchar_t null characters. These are just a few examples from a rather long list.
As for the update that fixes the vulnerability, a quick glance shows that the buffer addressed by the gMenuState.pGlobalPopupMenu field is now freed closer to the end of the xxxMNEndMenuState function, much later after the MNFreePopup and UnlockMFMWPWindow calls, and is accompanied by zeroing-out of the pointer. Thus the patch addresses two causes whose simultaneous presence caused the vulnerability to occur.