Talos Vulnerability Report

TALOS-2016-0173

LexMark Perceptive Document Filters Bzip2 Convert Out of Bounds Write Vulnerability

August 6, 2016
CVE Number

CVE-2016-4336

Description

An exploitable out of bounds write exists in the Bzip2 parsing of the Perspective Document Filters conversion functionality. A crafted Bzip2 document can lead to a stack based buffer overflow causing an out of bounds write which under the right circumstance could potentially be leveraged by an attacker to gain arbitrary code execution.

Tested Versions

Lexmark Perceptive Document Filters

Product URLs

http://www.lexmark.com/en_us/partners/enterprise-software/technology-partners/oem-technologies/document-filters.html

CVSSv3 Score

7.3 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L

Details

This vulnerability is present in the Lexmark Document filter parsing which is used for big data, eDiscovery, DLP, email archival, content management, business intelligence and intelligent capture. This product is mainly used by MarkLogic for document conversions in there rendering. It can convert common formats such as Microsoft’s document formats into more useable and easily viewed formats. This converter also handles files inside of a compressed format for further use cases.

There is a vulnerability in the parsing and conversion of a Bzip2 document. A specially crafted Bzip2 file can lead to an out of bounds write and potentially to remote code execution.

The number of bits to be read in from the next sequence of parsing is directly controlled by the user as shown here:

```
compress_object = *(struct_v1 **)(a1 + 80);
result = getbits(a1, compress_object->dword4C);
v3 = result;
...
    result = 0LL;
  compress_object->counter = v3;
}
return result;
```

Running this with a debugger we can see the value assigned to our counter.

```
RAX: 0x0
RBX: 0x7ffff7f55010 --> 0x7ffff7f95186 --> 0x130883c184b0480c
RCX: 0x101
RDX: 0x101
RSI: 0x101
RDI: 0x6617c0 --> 0x0
RBP: 0x7ffff7f55010 --> 0x7ffff7f95186 --> 0x130883c184b0480c
RSP: 0x7ffffffedb40 --> 0x7e000000db
RIP: 0x7ffff496e0a5 (<next_code+373>:    mov    DWORD PTR [rbp+0x50],esi)
R8 : 0x7ffff7f55010 --> 0x7ffff7f95186 --> 0x130883c184b0480c
R9 : 0x0
R10: 0x22 ('"')
R11: 0x246
R12: 0x6617c0 --> 0x0
R13: 0x7ffff64f4ca0 --> 0x10100000101
R14: 0x661880 --> 0xff
R15: 0x6617c0 --> 0x0
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7ffff496e09d <next_code+365>:    sub    eax,0x1
   0x7ffff496e0a0 <next_code+368>:    mov    DWORD PTR [rbp+0x48],eax
   0x7ffff496e0a3 <next_code+371>:    xor    eax,eax
=> 0x7ffff496e0a5 <next_code+373>:    mov    DWORD PTR [rbp+0x50],esi
   0x7ffff496e0a8 <next_code+376>:    jmp    0x7ffff496dfc9 <next_code+153>
   0x7ffff496e0ad <next_code+381>:    mov    rax,QWORD PTR [rbp+0x30060]
   0x7ffff496e0b4 <next_code+388>:    mov    edx,DWORD PTR [rbp+0x54]
   0x7ffff496e0b7 <next_code+391>:    mov    BYTE PTR [rax],dl
```

Notice the value of ESI, 0x101, being written to RBP (compressed_object) + 0x50 which is the counter variable inside of the object. This becomes important for the next part of the vulnerability.

This read in counter value is then used in a loop that is effectively a memset of the compressed objects buffer. This loop writes null bytes into the buffer until the counter is less than 255. The problem arises in that the counter object never changes. The counter relies on multiple fields inside of the compressed object but none of these change the value of the counter causing an infinite loop.

```
while ( v3 > 255 )//v3 = 0x101
{
     v10 = compress_object->write_value; //NULL
     *v10 = compress_object->array[v3];
     v3 = *(_WORD *)&compress_object->array[2 * v3 + 0x10000];
     compress_object->array_index = v10 + 1;
}
```

As can be seen below the array access to change v3 always returns the current value of v3 causing this to turn into an infinite loop with the array continually being incremented.

```
[----------------------------------registers-----------------------------------]
RAX: 0x7ffff7f8507a --> 0x0
RBX: 0x7ffff7f55010 --> 0x7ffff7f95186 --> 0x130883c184b0480c
RCX: 0x101
RDX: 0x0
RSI: 0x101
RDI: 0x6617c0 --> 0x0
RBP: 0x7ffff7f55010 --> 0x7ffff7f95186 --> 0x130883c184b0480c
RSP: 0x7ffffffedb40 --> 0x7e000000db
RIP: 0x7ffff496e03e (<next_code+270>:    mov    rax,QWORD PTR [rbp+0x30060])
R8 : 0x7ffff7f55010 --> 0x7ffff7f95186 --> 0x130883c184b0480c
R9 : 0x0
R10: 0x22 ('"')
R11: 0x246
R12: 0x6617c0 --> 0x0
R13: 0x7ffff64f4ca0 --> 0x10100000101
R14: 0x661880 --> 0xff
R15: 0x6617c0 --> 0x0
EFLAGS: 0x293 (CARRY parity ADJUST zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7ffff496e02f <next_code+255>:    mov    QWORD PTR [rbp+0x30060],rax
   0x7ffff496e036 <next_code+262>:    cmp    edx,0xff
   0x7ffff496e03c <next_code+268>:    jg     0x7ffff496e012 <next_code+226>
=> 0x7ffff496e03e <next_code+270>:    mov    rax,QWORD PTR [rbp+0x30060]
   0x7ffff496e045 <next_code+277>:    mov    DWORD PTR [rbp+0x54],edx
   0x7ffff496e048 <next_code+280>:    mov    BYTE PTR [rax],dl
   0x7ffff496e04a <next_code+282>:    add    rax,0x1
   0x7ffff496e04e <next_code+286>:    mov    QWORD PTR [rbp+0x30060],rax
```

First loop through notice RDX = 0 which will be written into the stack buffer. Going a few steps further we can see where RDX gets reassigned.

```
RAX: 0x7ffff7f85079 --> 0x0
RBX: 0x7ffff7f55010 --> 0x7ffff7f95187 --> 0x2a130883c184b048
RCX: 0x101
RDX: 0x101
RSI: 0x102
RDI: 0x6617c0 --> 0x0
RBP: 0x7ffff7f55010 --> 0x7ffff7f95187 --> 0x2a130883c184b048
RSP: 0x7ffffffedb40 --> 0x7e000000db
RIP: 0x7ffff496e02b (<next_code+251>:    add    rax,0x1)
R8 : 0x7ffff7f55010 --> 0x7ffff7f95187 --> 0x2a130883c184b048
R9 : 0x0
R10: 0x22 ('"')
R11: 0x246
R12: 0x6617c0 --> 0x0
R13: 0x7ffff64f4ca0 --> 0x10100000101
R14: 0x661880 --> 0xff000000ff
R15: 0x6617c0 --> 0x0
EFLAGS: 0x212 (carry parity ADJUST zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7ffff496e01c <next_code+236>:    movzx  edx,BYTE PTR [rcx+rbp*1+0x5c]
   0x7ffff496e021 <next_code+241>:    mov    BYTE PTR [rax],dl
   0x7ffff496e023 <next_code+243>:    movzx  edx,WORD PTR [rbp+rcx*2+0x1005c]
=> 0x7ffff496e02b <next_code+251>:    add    rax,0x1
   0x7ffff496e02f <next_code+255>:    mov    QWORD PTR [rbp+0x30060],rax
```

Directly after RDX gets assigned the value from the compressed objects array, we note that RDX still is equal to 0x101. This is going to cause an issue due to the fact that the stack buffer continuously gets incremented and there is no way for the loop to exit. The array continues to get incremented until it runs into unmapped memory which then throws the out of bounds memory access exception.

```
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x7ffff7fe0000
RBX: 0x7ffff7f55010 --> 0x7ffff7f95187 --> 0x0
RCX: 0x101
RDX: 0x0
RSI: 0x102
RDI: 0x6617c0 --> 0x0
RBP: 0x7ffff7f55010 --> 0x7ffff7f95187 --> 0x0
RSP: 0x7ffffffedb40 --> 0x7e000000db
RIP: 0x7ffff496e021 (<next_code+241>:    mov    BYTE PTR [rax],dl)
R8 : 0x7ffff7f55010 --> 0x7ffff7f95187 --> 0x0
R9 : 0x0
R10: 0x22 ('"')
R11: 0x246
R12: 0x6617c0 --> 0x0
R13: 0x7ffff64f4ca0 --> 0x10100000101
R14: 0x661880 --> 0xff000000ff
R15: 0x6617c0 --> 0x0
EFLAGS: 0x10212 (carry parity ADJUST zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x7ffff496e012 <next_code+226>:    movsxd rcx,edx
   0x7ffff496e015 <next_code+229>:    mov    rax,QWORD PTR [rbp+0x30060]
   0x7ffff496e01c <next_code+236>:    movzx  edx,BYTE PTR [rcx+rbp*1+0x5c]
=> 0x7ffff496e021 <next_code+241>:    mov    BYTE PTR [rax],dl
   0x7ffff496e023 <next_code+243>:    movzx  edx,WORD PTR [rbp+rcx*2+0x1005c]
   0x7ffff496e02b <next_code+251>:    add    rax,0x1
   0x7ffff496e02f <next_code+255>:    mov    QWORD PTR [rbp+0x30060],rax
   0x7ffff496e036 <next_code+262>:    cmp    edx,0xff
```

Crash Information

```
EXCEPTION_FAULTING_ADDRESS:0x007ffff7fe0000
EXCEPTION_CODE:0xb
FAULTING_INSTRUCTION:mov    BYTE PTR [rax],dl
MAJOR_HASH:b0aaec1471bbd86d7e818f1ebd111c6f
MINOR_HASH:b74fe16d6a33042541ff3552757b4e72
STACK_DEPTH:14
STACK_FRAME:/home/t/Downloads/cvtisys/cvtisys/libISYSshared.so!next_code+0x0
STACK_FRAME:/home/t/Downloads/cvtisys/cvtisys/libISYSshared.so!compress_filter_read+0x0
STACK_FRAME:/home/t/Downloads/cvtisys/cvtisys/libISYSshared.so!__archive_read_filter_ahead+0x0
STACK_FRAME:/home/t/Downloads/cvtisys/cvtisys/libISYSshared.so!bzip2_reader_bid+0x0
STACK_FRAME:/home/t/Downloads/cvtisys/cvtisys/libISYSshared.so!archive_read_open1+0x0
STACK_FRAME:/home/t/Downloads/cvtisys/cvtisys/libISYSreaders.so!ISYS_NS CLibArchiveReader openArchive()+0x0
STACK_FRAME:/home/t/Downloads/cvtisys/cvtisys/libISYSreaders.so!ISYS_NS CLibArchiveReader LoadDocument2(ISYS_NS CISYSSource const*, ISYS_NS tag_ReaderContext*)+0x0
STACK_FRAME:/home/t/Downloads/cvtisys/cvtisys/libISYSreaders.so!ISYS_NS CISYSReader Prepare(ISYS_NS CStream*, ISYS_NS CISYSSource const*, ISYS_NS tag_ReaderContext*)+0x0
STACK_FRAME:/home/t/Downloads/cvtisys/cvtisys/libISYSreaders.so!ISYS_NS exports IGR_Open_File_FromStream(wchar_t const*, wchar_t const*, ISYS_NS CStream*, bool, ISYS_NS exports Ext_Open_Options*, int, wchar_t const*, int*, int*, void**, int*, int, Error_Control_Block*)+0x0
STACK_FRAME:/home/t/Downloads/cvtisys/cvtisys/libISYSreaders.so!ISYS_NS exports IGR_Open_Stream_Ex(IGR_Stream*, int, unsigned short const*, int*, int*, void**, Error_Control_Block*)+0x0
STACK_FRAME:/home/t/Downloads/cvtisys/cvtisys/libISYS11df.so!IGR_Open_Stream_Ex+0x0
STACK_FRAME:/home/t/Downloads/cvtisys/cvtisys/convert!processStream(IGR_Stream*, long long&, ToXHTML&, std basic_ostringstream<char, std char_traits<char>, std allocator<char> >&)+0x0
STACK_FRAME:/home/t/Downloads/cvtisys/cvtisys/convert!processFile(std string, std basic_ostringstream<char, std char_traits<char>, std allocator<char> >&)+0x0
STACK_FRAME:/home/t/Downloads/cvtisys/cvtisys/convert!main+0x0
INSTRUCTION_ADDRESS:0x007ffff496e021
INVOKING_STACK_FRAME:0
DESCRIPTION:Access violation on destination operand
SHORT_DESCRIPTION:DestAv (8/22)
OTHER_RULES:AccessViolation (21/22)
CLASSIFICATION:EXPLOITABLE
EXPLANATION:The target crashed on an access violation at an address matching the destination operand of the instruction. This likely indicates a write access violation, which means the attacker may control the write address and/or value.
```

Exploit Proof-of-Concept

Ensure library path is setup correctly and run the convert application with the trigger passed in as the first argument.

Timeline

2016-05-19 - Vendor Disclosure
2016-08-06 - Public Release
Credit

Discovered by Marcin Noga and Tyler Bohan of Cisco Talos.