Talos Vulnerability Report

TALOS-2019-0817

NitroPDF ICCBased Color Space Remote Code Execution Vulnerability

October 9, 2019
CVE Number

CVE-2019-5048

Summary

A specifically crafted PDF file can lead to a heap corruption when opened 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

An 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.

PDF documents can have different types of color spaces defined in them. One of the subtypes is ICCBased color space. The PDF specification states that ICCBased color space object has a required argument N which denotes a number of components in the color space profile. These values must be 1, 3 or 4.

While processing the ICCBased color space object, NitroPDF tries to parse the number of color spaces and the relevant code looks as follows:

v6 = CosArrayGet(a2, 1i64);
v7 = v6;
v8 = CosDictGet(v6, qword_180A2CEC8);
v9 = CosIntegerValue(v8);
*v3 = v9;
if ( (v9 - 1) & 0xFFFFFFFC || v9 == 2 )
{
  if ( !CosDictKnown(v7, qword_180A2CF28) )
  {
    *v3 = 0x6F;
    ASRaise_nocall(
      0x40100001,
      "Y:\\workspace\\release-pipeline\\NxPdfLib\\pdflib\\CNxPDEColorSpace.cpp",
      476i64,
      0i64);
  }

It parses the array to get the dictionary and then gets the integer value from the dictionary. It then checks if this dictionary value is not 1, 3 or 4 (if it’s two or any other value). If that check passes, it checks first if the value is indirect by checking if the dictionary is known. If it’s a direct value, to would complain and properly raise an exception, but not before setting the value in pointer v3 to 0x6f. Then it throws an exception which is mostly ignored and execution continues.

The value in v3 above was ultimately supposed to contain the number of components, but now contains the value 0x6f. While further processing the color space profile, this value is used to index into an array and ultimately leads to an out of bounds write on the heap which results in the following crash:

First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
npdf!PDTextIsSpaceBetween+0x7f3c:
00007fff`b1e263dc f20f1102        movsd   mmword ptr [rdx],xmm0 ds:000001d5`fc22d158=????????????????
0:000> !heap -p -a rdx
    address 000001d5fc22d158 found in
    _DPH_HEAP_ROOT @ 1d5c3fd1000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                             1d5fc3bb270:      1d5fc22cda0              260 -      1d5fc22c000             2000
    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]
    00007fffb1e1f3be npdf!PDTextIsSpaceBetween+0x0000000000000f1e
    00007fffb1e1a3ec npdf!init_npdf_optional_features+0x00000000000271bc
    00007fffb1e1a653 npdf!init_npdf_optional_features+0x0000000000027423
    00007fffb1fef2b7 npdf!PDOCMDsMakeContentVisible+0x00000000000013e7
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for NitroPDF.exe - 
    00007ff715bd0bb5 NitroPDF!CxIOFile::Write+0x000000000005ea25
    00007ff715bcfefe NitroPDF!CxIOFile::Write+0x000000000005dd6e
    00007ff715bd03b2 NitroPDF!CxIOFile::Write+0x000000000005e222
    00007ff715bc8871 NitroPDF!CxIOFile::Write+0x00000000000566e1
    00007ff715be44cb NitroPDF!CxIOFile::Write+0x000000000007233b
    00007fffb0c18eb0 mfc120u!CWnd::OnWndMsg+0x0000000000000414 [f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\wincore.cpp @ 2318]
    00007fffb0c18a68 mfc120u!CWnd::WindowProc+0x0000000000000038 [f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\wincore.cpp @ 2094]
    00007fffb0c16422 mfc120u!AfxCallWndProc+0x000000000000010e [f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\wincore.cpp @ 282]
    00007fffb0c167a4 mfc120u!AfxWndProc+0x0000000000000054 [f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\wincore.cpp @ 434]
    00007fffb0ad0a75 mfc120u!AfxWndProcBase+0x0000000000000051 [f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\afxstate.cpp @ 299]
    00007fffda036d41 USER32!UserCallWinProcCheckWow+0x00000000000002c1
    00007fffda036a1c USER32!DispatchClientMessage+0x000000000000009c
    00007fffda0404d3 USER32!_fnDWORD+0x0000000000000033
    00007fffdcc2e6b4 ntdll!KiUserCallbackDispatcherContinue+0x0000000000000000
    00007fffd9ac1144 win32u!NtUserGetMessage+0x0000000000000014
    00007fffda041b8b USER32!GetMessageW+0x000000000000002b
    00007fffb0c00f8b mfc120u!AfxInternalPumpMessage+0x0000000000000027 [f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\thrdcore.cpp @ 153]
    00007fffb0c0180e mfc120u!CWinThread::Run+0x000000000000006e [f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\thrdcore.cpp @ 634]
    00007ff715c2449d NitroPDF!CxFile::operator=+0x00000000000231cd
    00007fffb0c300de mfc120u!AfxWinMain+0x00000000000000a6 [f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\winmain.cpp @ 47]
    00007ff715dab7ee NitroPDF!CxImageJPG::Encode+0x0000000000144a2e
    00007fffdc9e3dc4 KERNEL32!BaseThreadInitThunk+0x0000000000000014
    00007fffdcc03691 ntdll!RtlUserThreadStart+0x0000000000000021

 
0:000> k 6
 # Child-SP          RetAddr           Call Site
00 0000007e`f51fecd0 00007fff`b1e23e32 npdf!PDTextIsSpaceBetween+0x7f3c
01 0000007e`f51fed10 00007fff`b1e16b6a npdf!PDTextIsSpaceBetween+0x5992
02 0000007e`f51fedc0 00007fff`b1e1a46b npdf!init_npdf_optional_features+0x2393a
03 0000007e`f51feea0 00007fff`b1e1a653 npdf!init_npdf_optional_features+0x2723b
04 0000007e`f51fef50 00007fff`b1fef2b7 npdf!init_npdf_optional_features+0x27423
05 0000007e`f51ff0b0 00007ff7`15bd0bb5 npdf!PDOCMDsMakeContentVisible+0x13e7

From the crash context and PageHeap output, we can see that the crash happens while writing past the bounds of a heap chunk with size 0x260. If we step back to the calling function, we can see the following:

Breakpoint 3 hit
npdf!PDTextIsSpaceBetween+0x598d:
00007fff`b1e23e2d e83e250000      call    npdf!PDTextIsSpaceBetween+0x7ed0 (00007fff`b1e26370)
0:000> ?rcx
Evaluate expression: 1925229497728 = 000001c0`409ecd80
0:000> !heap -p -a rcx
    address 000001c0409ecd80 found in
    _DPH_HEAP_ROOT @ 1c021301000
    in busy allocation (  DPH_HEAP_BLOCK:         UserAddr         UserSize -         VirtAddr         VirtSize)
                             1c0596ef4e0:      1c0409ecd80              280 -      1c0409ec000             2000
          ? npdf!CAPFileSpecifier::`vftable'+1df20
    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]
    00007fffb1fef261 npdf!PDOCMDsMakeContentVisible+0x0000000000001391
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for NitroPDF.exe - 
    00007ff715bd0bb5 NitroPDF!CxIOFile::Write+0x000000000005ea25
    00007ff715bcfefe NitroPDF!CxIOFile::Write+0x000000000005dd6e
    00007ff715bd03b2 NitroPDF!CxIOFile::Write+0x000000000005e222
    00007ff715bc8871 NitroPDF!CxIOFile::Write+0x00000000000566e1
    00007ff715be44cb NitroPDF!CxIOFile::Write+0x000000000007233b
    00007fffb0c18eb0 mfc120u!CWnd::OnWndMsg+0x0000000000000414 [f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\wincore.cpp @ 2318]
    00007fffb0c18a68 mfc120u!CWnd::WindowProc+0x0000000000000038 [f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\wincore.cpp @ 2094]
    00007fffb0c16422 mfc120u!AfxCallWndProc+0x000000000000010e [f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\wincore.cpp @ 282]
    00007fffb0c167a4 mfc120u!AfxWndProc+0x0000000000000054 [f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\wincore.cpp @ 434]
    00007fffb0ad0a75 mfc120u!AfxWndProcBase+0x0000000000000051 [f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\afxstate.cpp @ 299]
    00007fffda036d41 USER32!UserCallWinProcCheckWow+0x00000000000002c1
    00007fffda036a1c USER32!DispatchClientMessage+0x000000000000009c
    00007fffda0404d3 USER32!_fnDWORD+0x0000000000000033
    00007fffdcc2e6b4 ntdll!KiUserCallbackDispatcherContinue+0x0000000000000000
    00007fffd9ac1144 win32u!NtUserGetMessage+0x0000000000000014
    00007fffda041b8b USER32!GetMessageW+0x000000000000002b
    00007fffb0c00f8b mfc120u!AfxInternalPumpMessage+0x0000000000000027 [f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\thrdcore.cpp @ 153]
    00007fffb0c0180e mfc120u!CWinThread::Run+0x000000000000006e [f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\thrdcore.cpp @ 634]
    00007ff715c2449d NitroPDF!CxFile::operator=+0x00000000000231cd
    00007fffb0c300de mfc120u!AfxWinMain+0x00000000000000a6 [f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\winmain.cpp @ 47]
    00007ff715dab7ee NitroPDF!CxImageJPG::Encode+0x0000000000144a2e
    00007fffdc9e3dc4 KERNEL32!BaseThreadInitThunk+0x0000000000000014
    00007fffdcc03691 ntdll!RtlUserThreadStart+0x0000000000000021

 
0:000> ?rdx
Evaluate expression: 1925402554712 = 000001c0`4aef7158
0:000> dd rdx
000001c0`4aef7158  ???????? ???????? ???????? ????????
000001c0`4aef7168  ???????? ???????? ???????? ????????
000001c0`4aef7178  ???????? ???????? ???????? ????????
000001c0`4aef7188  ???????? ???????? ???????? ????????
000001c0`4aef7198  ???????? ???????? ???????? ????????
000001c0`4aef71a8  ???????? ???????? ???????? ????????
000001c0`4aef71b8  ???????? ???????? ???????? ????????
000001c0`4aef71c8  ???????? ???????? ???????? ????????

We can see that before a call to the crashing function is made, the second argument is already pointing past the bounds of the buffer into invalid memory. The value in rdx is actually calculated from the start of the 0x260 sized chunk to which an offset is added. This piece of code is called in a loop like so:

v20 = v29 - 1;
for ( i = v29 - 1 < 0; !i; i = v20 < 0 )
{
  if ( v2 )
    v21 = v20 + 0x14i64;
  else
    v21 = v20 + 9i64;
  if ( !sub_1801C6370(v3, (*(v3 + 35) + 8 * v21)) )
    return 0;
  --v20;
}

Function sub_1801C6370 is where the crash is happening. The initial value in v29 is the same invalid “number of components” 0x6f value mentioned previously. This value then has 9 added to it and is then used as index into the array. Array elements are 8 bytes long, meaning that the index is multiplied by 8, which easily overflows the 0x260 buffer size leading to out of bounds memory overwrite and heap corruption.

By carefully controlling the memory contents adjacent the overflown chunk, and by having specific values for the color space profile stream 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.