Talos Vulnerability Report

TALOS-2019-0814

NitroPDF jpeg2000 ssizDepth Remote Code Execution Vulnerability

October 9, 2019
CVE Number

CVE-2019-5045

Summary

A specifically crafted jpeg2000 file embedded in a PDF file can lead to a heap corruption when opening a PDF document in NitroPDF 12.12.1.522. 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.

Tested Versions

NitroPDF 12.12.1.522

Product URLs

https://www.gonitro.com/

CVSSv3 Score

8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

CWE

CWE-122: Heap Based Buffer Overflow

Details

A potential remote code execution vulnerability exists in the PDF parsing functionality of Nitro Pro. A specially crafted PDF file can cause a vulnerability resulting in potential code execution.

While parsing an embedded jpeg2000 file with an unusually large ssizDepth value in siz block of the code stream, an incorrectly sized allocation is made. Later, while further processing the file, improper checks lead to a write access of out of bounds memory on the heap. This can lead to following crash:

(a0e8.9a74): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Program Files\Nitro\Pro\12\npdf.dll - 
npdf!CosCryptGetVersion+0x8961:
00007fff`a27df4e1 418800          mov     byte ptr [r8],al ds:000001e0`0766f000=??
0:000> !heap -p -a r8
    address 000001e00766f000 found in
    _DPH_HEAP_ROOT @ 1e050631000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                             1e0068c2958:      1e00766a720             48d8 -      1e00766a000             6000
    00007fffdcc8f4bf ntdll!RtlDebugAllocateHeap+0x000000000000003f
    00007fffdcc3b530 ntdll!RtlpAllocateHeap+0x000000000008f760
    00007fffdcba9725 ntdll!RtlpAllocateHeapInternal+0x00000000000005e5
    00007fffc8a86a57 MSVCR120!malloc+0x000000000000005b [f:\dd\vctools\crt\crtw32\heap\malloc.c @ 92]
    00007fffc8a86967 MSVCR120!operator new+0x000000000000001f [f:\dd\vctools\crt\crtw32\heap\new.cpp @ 59]
    00007fffa27e090c npdf!CosCryptGetVersion+0x0000000000009d8c
    00007fffa27e1a77 npdf!CosCryptGetVersion+0x000000000000aef7
    00007fffa27e37be npdf!CosCryptGetVersion+0x000000000000cc3e
    00007fffa27e2fed npdf!CosCryptGetVersion+0x000000000000c46d
    00007fffa27e4cfa npdf!CosCryptGetVersion+0x000000000000e17a
    00007fffa27e4b9d npdf!CosCryptGetVersion+0x000000000000e01d
    00007fffa27e4912 npdf!CosCryptGetVersion+0x000000000000dd92
    00007fffa271c0b8 npdf!CosStreamSetStm+0x0000000000000468
    00007fffa271bb09 npdf!CosStreamOpenStm+0x0000000000000009
    00007fffa26787a5 npdf!PDTextIsSpaceBetween+0x00000000000fa305
    00007fffa25fb38d npdf!PDTextIsSpaceBetween+0x000000000007ceed
    00007fffa2685584 npdf!PDTextIsSpaceBetween+0x00000000001070e4
    00007fffa255ae10 npdf!init_npdf_optional_features+0x0000000000007be0
    00007fffa255d9d6 npdf!init_npdf_optional_features+0x000000000000a7a6

As can be seen from the above PageHeap output, we are accessing out of bounds memory. The size of the allocated chunk is 0x48d8. The size of the calculation happens in function sub_180420480, specifically in the following code:

.text:000000018042089D mov     rax, [rbp+20A0h+var_1900]
.text:00000001804208A4 mov     ecx, [rax+0Ch]
.text:00000001804208A7 imul    ecx, edx
.text:00000001804208AA imul    ecx, [rdi+8]
.text:00000001804208AE add     ecx, 7
.text:00000001804208B1 shr     ecx, 3
.text:00000001804208B4 mov     rax, [rbp+20A0h+var_10C0]
.text:00000001804208BB imul    ecx, [rax]
.text:00000001804208BE test    ecx, ecx
.text:00000001804208C0 jle     loc_180420

In the above code, the initial value in rax+0xc actually comes from sub_18048DF20, where it is calculated based on xsiz and xrsiz values from the siz block. The whole equation is roughly as follows:

buffer_size = (((((xsiz / xrsiz)) * (ssizdepth) * 3 ) + 7 ) >> 3 ) * 3

The above value is subsequently sign-extended before being used in an allocation function.

The relevant part in our sample testcase that triggers this vulnerability is as follows:

<siz>
    <lsiz>47</lsiz>
    <rsiz>ISO/IEC 15444-1</rsiz>
    <xsiz>16506</xsiz>
    <ysiz>148</ysiz>
    <xOsiz>0</xOsiz>
    <yOsiz>0</yOsiz>
    <xTsiz>2</xTsiz>
    <yTsiz>42</yTsiz>
    <xTOsiz>0</xTOsiz>
    <yTOsiz>0</yTOsiz>
    <numberOfTiles>33012</numberOfTiles>
    <csiz>3</csiz>
    <ssizSign>unsigned</ssizSign>
    <ssizDepth>74</ssizDepth>
    <xRsiz>74</xRsiz>
    <yRsiz>72</yRsiz>
    <ssizSign>unsigned</ssizSign>
    <ssizDepth>73</ssizDepth>
    <xRsiz>72</xRsiz>
    <yRsiz>72</yRsiz>
    <ssizSign>unsigned</ssizSign>
    <ssizDepth>73</ssizDepth>
    <xRsiz>72</xRsiz>
    <yRsiz>72</yRsiz>
</siz>

The actual overflow happens in function sub_18041F3D0 which has three nested loops guarded by different counters. Depending on the values, index into the allocated buffer can be incremented more than once per innermost loop and eventually leads to out of bounds access on the heap, overwriting adjacent memory.

By carefully controlling other image size values in the codestream, memory region of precise size can be allocated and by controlling the content of the image this overflow can be abused to overwrite adjacent heap memory which can ultimately lead to arbitrary code execution.

Timeline

2019-05-07 - Vendor disclosure
2019-07-02 - 60 day follow up
2019-07-29 - 2nd follow up (90 days approaching notice)
2019-08-06 - 3rd follow up
2019-08-07 - Vendor acknowledged & advised prior emails went to spam folder; Talos issued copy of report
2019-09-03 - Talos granted disclosure extension to 2019-09-10
2019-09-05 - Vendor advised issues will be addressed in a future release (timeline unknown)
2019-10-09 - Public Disclosure

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.