CVE-2019-5046
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.
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.
While parsing an embedded jpeg2000 file with an unusually large xSiz
and yTsiz
, an integer overflow can happen while calculating the size of memory required for each pixel. Due to an integer overflow, an undersized chunk of memory is allocated and is then written to in a loop. Since the memory chunk is undersized, the process will write outside of its bounds and overwrite adjacent memory.
The memory allocation happens at npdf!CosCryptGetVersion+0x8a7c
via VirtualAlloc
. The size argument for VirtualAlloc
is calculated in the calling function:
Breakpoint 3 hit
npdf!CosCryptGetVersion+0x9c9d:
00007fff`b208081d 03c2 add eax,edx
0:000> t
npdf!CosCryptGetVersion+0x9c9f:
00007fff`b208081f c1f803 sar eax,3
0:000> t
npdf!CosCryptGetVersion+0x9ca2:
00007fff`b2080822 448bf8 mov r15d,eax
0:000> t
npdf!CosCryptGetVersion+0x9ca5:
00007fff`b2080825 488b8da0070000 mov rcx,qword ptr [rbp+7A0h] ss:0000007c`8b9f5ca0=0000007c8b9f5cb8
0:000> t
npdf!CosCryptGetVersion+0x9cac:
00007fff`b208082c 8b510c mov edx,dword ptr [rcx+0Ch] ds:0000007c`8b9f5cc4=02000000
0:000> ?edx
Evaluate expression: 0 = 00000000`00000000
0:000> t
npdf!CosCryptGetVersion+0x9caf:
00007fff`b208082f 488b8de00f0000 mov rcx,qword ptr [rbp+0FE0h] ss:0000007c`8b9f64e0=0000007c8b9f64f8
0:000> ?edx
Evaluate expression: 33554432 = 00000000`02000000 [1]
0:000> t
npdf!CosCryptGetVersion+0x9cb6:
00007fff`b2080836 0faf11 imul edx,dword ptr [rcx] ds:0000007c`8b9f64f8=0000002c
0:000> ?edx
Evaluate expression: 33554432 = 00000000`02000000 [2]
0:000> t
npdf!CosCryptGetVersion+0x9cb9:
00007fff`b2080839 0faf5708 imul edx,dword ptr [rdi+8] ds:0000007c`8b9f75e8=00000003
0:000> ?edx
Evaluate expression: 1476395008 = 00000000`58000000 [3]
0:000> t
npdf!CosCryptGetVersion+0x9cbd:
00007fff`b208083d 0fafd0 imul edx,eax
0:000> ?edx
Evaluate expression: 134217728 = 00000000`08000000 [4]
In the above output, we can see the size is being calculated. At [1], edx
first contains the value corresponding to xSiz
value from the sample that triggers this vulnerability. In this case , it is 0x02000000. At [2], this value will be multiplied by 0x2c which is the value of yTsiz
, a tile height value. At [3], an already large value is multiplied by 3 which leads to an integer overflow, as only edx
is used as a size parameter. If we continue execution to the VirtualAlloc call, we can see this being the case:
0:000> bp npdf!CosCryptGetVersion+0x8a7c
0:000> g
Breakpoint 4 hit
npdf!CosCryptGetVersion+0x8a7c:
00007fff`b207f5fc ff1596212f00 call qword ptr [npdf!nitro::digital_signature::signature_verifier::GetSignerCertificate+0x1754a8 (00007fff`b2371798)] ds:00007fff`b2371798={KERNEL32!VirtualAllocStub (00007fff`dc9e93c0)}
0:000> u
npdf!CosCryptGetVersion+0x8a7c:
00007fff`b207f5fc ff1596212f00 call qword ptr [npdf!nitro::digital_signature::signature_verifier::GetSignerCertificate+0x1754a8 (00007fff`b2371798)]
00007fff`b207f602 488bf0 mov rsi,rax
00007fff`b207f605 4885c0 test rax,rax
00007fff`b207f608 753e jne npdf!CosCryptGetVersion+0x8ac8 (00007fff`b207f648)
00007fff`b207f60a 488d05dfea3300 lea rax,[npdf!CAPFileSpecifier::`vftable'+0x49748 (00007fff`b23be0f0)]
00007fff`b207f611 4889442430 mov qword ptr [rsp+30h],rax
00007fff`b207f616 448d4601 lea r8d,[rsi+1]
00007fff`b207f61a 488d542430 lea rdx,[rsp+30h]
0:000> ?rdx
Evaluate expression: 134217728 = 00000000`08000000
0:000> p
npdf!CosCryptGetVersion+0x8a82:
00007fff`b207f602 488bf0 mov rsi,rax
0:000> ?rax
Evaluate expression: 1848504483840 = 000001ae`63740000 [5]
At [5] we see the pointer to allocated memory chunk in rax
.
If we continue the execution, the heap overflow happens in function at npdf!nitro::notifications::notification_manager::FindNotification+0x753c0
. Indeed, if we break at the beginning of this function, we see the above allocated buffer as first argument:
Breakpoint 5 hit
npdf!nitro::notifications::notification_manager::FindNotification+0x753c0:
00007fff`b2119ee0 48895c2408 mov qword ptr [rsp+8],rbx ss:0000007c`8b9f5250=000001ae61efaf28
0:000> ?rcx
Evaluate expression: 1848504483840 = 000001ae`63740000
This function basically writes byte by byte values for a single component. First time it is called, it starts right at the beginning of our chunk. Second time it starts at offset 1, doing the second component. Third time for offset two, doing the third component:
Breakpoint 5 hit
npdf!nitro::notifications::notification_manager::FindNotification+0x753c0:
00007fff`b2119ee0 48895c2408 mov qword ptr [rsp+8],rbx ss:0000007c`8b9f5250=000001ae61efaf28
0:000> ?rcx
Evaluate expression: 1848504483841 = 000001ae`63740001
0:000> g
Breakpoint 5 hit
npdf!nitro::notifications::notification_manager::FindNotification+0x753c0:
00007fff`b2119ee0 48895c2408 mov qword ptr [rsp+8],rbx ss:0000007c`8b9f5250=000001ae61efaf28
0:000> ?rcx
Evaluate expression: 1848504483842 = 000001ae`63740002
When it’s called once more, it’s using the second third of the chunk, starting at offset 0x06000000, which is still well within the allocated chunk. The actual heap overflow and memory corruption happens when the function is called the 7th time, this time with offset of 0x0c000000, which is now more than the initially allocated buffer, and actually points to a next memory chunk which in this case happens to be allocated via heap:
Breakpoint 5 hit
npdf!nitro::notifications::notification_manager::FindNotification+0x753c0:
00007fff`b2119ee0 48895c2408 mov qword ptr [rsp+8],rbx ss:0000007c`8b9f5250=000001ae61efaf28
0:000> ?rcx
Evaluate expression: 1848705810432 = 000001ae`6f740000
0:000> ?rcx-000001ae`63740002+2
Evaluate expression: 201326592 = 00000000`0c000000
0:000> !heap -p -a rcx
address 000001ae6f740000 found in
_DPH_HEAP_ROOT @ 1ae2d751000
in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize)
1ae62757000: 1ae6b750d80 438627f - 1ae6b750000 4388000
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]
00007fffb20eb7c1 npdf!nitro::notifications::notification_manager::FindNotification+0x0000000000046ca1
00007fffb2139cc1 npdf!nitro::notifications::notification_manager::FindNotification+0x00000000000951a1
00007fffb2139064 npdf!nitro::notifications::notification_manager::FindNotification+0x0000000000094544
00007fffb2139432 npdf!nitro::notifications::notification_manager::FindNotification+0x0000000000094912
00007fffb21183e2 npdf!nitro::notifications::notification_manager::FindNotification+0x00000000000738c2
00007fffb21193e7 npdf!nitro::notifications::notification_manager::FindNotification+0x00000000000748c7
00007fffb2081e88 npdf!CosCryptGetVersion+0x000000000000b308
00007fffb2080c4b npdf!CosCryptGetVersion+0x000000000000a0cb
00007fffb2081a77 npdf!CosCryptGetVersion+0x000000000000aef7
00007fffb20837be npdf!CosCryptGetVersion+0x000000000000cc3e
Since the first argument of the function already points outside the original buffer and into an adjacent one, memory corruption is already happening, but it will only crash once it reaches the end of this adjacent chunk:
(666c.62d8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
npdf!nitro::notifications::notification_manager::FindNotification+0x757db:
00007fff`b211a2fb 41880a mov byte ptr [r10],cl ds:000001ae`6fad7002=??
0:000> ?r10
Evaluate expression: 1848709574658 = 000001ae`6fad7002
0:000> dd r10
000001ae`6fad7002 ???????? ???????? ???????? ????????
000001ae`6fad7012 ???????? ???????? ???????? ????????
000001ae`6fad7022 ???????? ???????? ???????? ????????
000001ae`6fad7032 ???????? ???????? ???????? ????????
000001ae`6fad7042 ???????? ???????? ???????? ????????
000001ae`6fad7052 ???????? ???????? ???????? ????????
000001ae`6fad7062 ???????? ???????? ???????? ????????
000001ae`6fad7072 ???????? ???????? ???????? ????????
0:000> k 5
# Child-SP RetAddr Call Site
00 0000007c`8b9f5240 00007fff`b2119131 npdf!nitro::notifications::notification_manager::FindNotification+0x757db
01 0000007c`8b9f5250 00007fff`b211944a npdf!nitro::notifications::notification_manager::FindNotification+0x74611
02 0000007c`8b9f52c0 00007fff`b2081e88 npdf!nitro::notifications::notification_manager::FindNotification+0x7492a
03 0000007c`8b9f53a0 00007fff`b2080c4b npdf!CosCryptGetVersion+0xb308
04 0000007c`8b9f5400 00007fff`b2081a77 npdf!CosCryptGetVersion+0xa0cb
The crash eventually happens in the following code that loops:
.text:00000001804BA2F8 loc_1804BA2F8:
.text:00000001804BA2F8 dec r8d
.text:00000001804BA2FB mov [r10], cl
.text:00000001804BA2FE add rdx, 4
.text:00000001804BA302 add r10, r14
.text:00000001804BA305 test r8d, r8d
.text:00000001804BA308 jg short loc_1804BA2E1
In the above code, r10
holds the pointer into our overflown memory. Pointer in r10
is incremented by 3 each time the loop is executed, and the loop is guarded by value in r8d
which decrements by 1. Initial value of r8d
comes directly from xTsiz
value in the crashing testcase. This leads to a controlled out of bounds overwrite on the heap.
By carefully controlling parameters in siz
block, a precise integer overflow can be triggered allocating a memory region of precise size. 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.
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.