CVE-2019-5048
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.
NitroPDF 12.12.1.522
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CWE-122: Heap Based Buffer Overflow
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.
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
Discovered by Aleksandar Nikolic of Cisco Talos.