Talos Vulnerability Report

TALOS-2016-0185

Lexmark Perceptive Document Filters CBFF Code Execution Vulnerability

August 6, 2016
CVE Number

CVE-2016-5646

Description

An exploitable heap overflow vulnerability exists in the Compound Binary File Format (CBFF) parser functionality of Lexmark Perceptive Document Filters library. A specially crafted CBFF file can cause a code execution. An attacker can send a malformed file to trigger this vulnerability.

Tested Versions

Perceptive Document Filters 11.2.0.1732

Product URLs

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

CVSSv3 Score

7.8 - CVSS:3.0/AV:L/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 Lexmark Document filter parsing which is used for big data, eDiscovery, DLP, email archival, content management, business intelligence and intelligent capture services. This product is mainly used by MarkLogic for document conversions as part of their web based document search and rendering. It can convert common formats such as Microsoft’s document formats into more useable and easily viewed formats. There is a vulnerability in the parsing and conversion of a CBFF file. A specially crafted CBFF file can lead to an integer overflow and ultimately to remote code execution.

This is what it looks like when we get the library to parse a malformed CBFF file:

Name Value Start Size
struct StructuredStorageHeader stg     200h
BYTE _abSig[8]     8h
struct CLSID _clsid   8h 10h
USHORT _uMinorVersion 62 18h 2h
USHORT _uDllVersion 3 1Ah 2h
USHORT _uByteOrder 65534 1Ch 2h
USHORT _uSectorShift 241 1Eh 2h
USHORT _uMiniSectorShift 6 20h 2h
USHORT _usReserved 0 22h 2h
ULONG _ulReserved1 0 24h 4h
FSINDEX _csectDir 0 28h 4h
FSINDEX _csectFat 1 2Ch 4h
SECT _sectDirStart 16842754 30h 4h
DFSIGNATURE _signature 0 34h 4h
ULONG _ulMiniSectorCutoff 0 38h 4h
SECT _sectMiniFatStart 4294705151 3Ch 4h
FSINDEX _csectMiniFat 4294967295 40h 4h
SECT _sectDifStart 16777215 44h 4h
FSINDEX _csectDif 4278714368 48h 4h
SECT _sectFat[109]   4Ch 1B4h

and raw form of first 80 bytes

00000000  d0 cf 11 e0 a1 b1 1a e1  00 00 00 00 00 00 00 00  |................|
00000010  00 00 00 00 00 00 00 1b  3e 00 03 00 fe ff f1 00  |........>.......|
00000020  06 00 00 00 00 00 00 00  00 00 00 00 01 00 00 00  |................|
00000030  02 00 01 01 00 00 00 00  00 00 00 00 ff ff fb ff  |................|
00000040  ff ff ff ff ff ff ff 00  00 00 08 ff ff ff ff ff  |................|

monitoring for potential memory corruption we obtain the following result:

```
==29826== Memcheck, a memory error detector
==29826== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==29826== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==29826== Command: ./convert config/
==29826==
==29826== Invalid write of size 8
==29826==    at 0x4C2F5F3: memcpy@GLIBC_2.2.5 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==29826==    by 0x68953E4: ISYS_NS::CBufferedReader::Read(void*, unsigned int) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSshared.so)
==29826==    by 0x689B8A7: ISYS_NS::CIGRStreamStream::Read(void*, unsigned int) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSshared.so)
==29826==    by 0x685DF0A: ISYS_NS::docfile::CIStorageBase::Read_Long_Sector(unsigned int, void*, bool, unsigned int*) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSshared.so)
==29826==    by 0x685EF0D: ISYS_NS::docfile::CIStorageBase::Resolve_Short_Sectors() (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSshared.so)
==29826==    by 0x6862CA4: ISYS_NS::docfile::CIStorageBase::CIStorageBase(ISYS_NS::CStream*, bool) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSshared.so)
==29826==    by 0x6864D35: ISYS_NS::docfile::StgOpenStorage(ISYS_NS::CStream*, bool, IStorage**, unsigned int) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSshared.so)
==29826==    by 0x8647628: ISYS_NS::OpenStorageFromStream(ISYS_NS::CStream*, bool, IStorage**) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSreaders.so)
==29826==    by 0x864DE85: ISYS_NS::CISYSReaderFactoryBase::FindFactoryFor(std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > const&, ISYS_NS::CStream*, std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > const&, int*, int) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSreaders.so)
==29826==    by 0x877822B: ISYS_NS::exports::IGR_Get_Stream_Type(IGR_Stream*, int*, int*, Error_Control_Block*) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSreaders.so)
==29826==    by 0x4E3E137: IGR_Get_File_Type (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYS11df.so)
==29826==    by 0x40A035: processFile(std::string, std::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> >&) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/convert)
==29826==  Address 0x8b6d690 is 0 bytes after a block of size 0 alloc'd
==29826==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==29826==    by 0x685EEA9: ISYS_NS::docfile::CIStorageBase::Resolve_Short_Sectors() (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSshared.so)
==29826==    by 0x6862CA4: ISYS_NS::docfile::CIStorageBase::CIStorageBase(ISYS_NS::CStream*, bool) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSshared.so)
==29826==    by 0x6864D35: ISYS_NS::docfile::StgOpenStorage(ISYS_NS::CStream*, bool, IStorage**, unsigned int) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSshared.so)
==29826==    by 0x8647628: ISYS_NS::OpenStorageFromStream(ISYS_NS::CStream*, bool, IStorage**) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSreaders.so)
==29826==    by 0x864DE85: ISYS_NS::CISYSReaderFactoryBase::FindFactoryFor(std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > const&, ISYS_NS::CStream*, std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > const&, int*, int) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSreaders.so)
==29826==    by 0x877822B: ISYS_NS::exports::IGR_Get_Stream_Type(IGR_Stream*, int*, int*, Error_Control_Block*) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSreaders.so)
==29826==    by 0x4E3E137: IGR_Get_File_Type (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYS11df.so)
==29826==    by 0x40A035: processFile(std::string, std::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> >&) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/convert)
==29826==    by 0x40B79F: main (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/convert)
==29826==

Thread 1: status = VgTs_Runnable
==29826==    at 0x4C2F63B: memcpy@GLIBC_2.2.5 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==29826==    by 0x68953E4: ISYS_NS::CBufferedReader::Read(void*, unsigned int) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSshared.so)
==29826==    by 0x689B8A7: ISYS_NS::CIGRStreamStream::Read(void*, unsigned int) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSshared.so)
==29826==    by 0x685DF0A: ISYS_NS::docfile::CIStorageBase::Read_Long_Sector(unsigned int, void*, bool, unsigned int*) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSshared.so)
==29826==    by 0x685EF0D: ISYS_NS::docfile::CIStorageBase::Resolve_Short_Sectors() (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSshared.so)
==29826==    by 0x6862CA4: ISYS_NS::docfile::CIStorageBase::CIStorageBase(ISYS_NS::CStream*, bool) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSshared.so)
==29826==    by 0x6864D35: ISYS_NS::docfile::StgOpenStorage(ISYS_NS::CStream*, bool, IStorage**, unsigned int) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSshared.so)
==29826==    by 0x8647628: ISYS_NS::OpenStorageFromStream(ISYS_NS::CStream*, bool, IStorage**) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSreaders.so)
==29826==    by 0x864DE85: ISYS_NS::CISYSReaderFactoryBase::FindFactoryFor(std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > const&, ISYS_NS::CStream*, std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > const&, int*, int) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSreaders.so)
==29826==    by 0x877822B: ISYS_NS::exports::IGR_Get_Stream_Type(IGR_Stream*, int*, int*, Error_Control_Block*) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSreaders.so)
==29826==    by 0x4E3E137: IGR_Get_File_Type (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYS11df.so)
==29826==    by 0x40A035: processFile(std::string, std::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> >&) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/convert)
==29826==    by 0x40B79F: main (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/convert)
```

as we can see in lines:

```
==29826==  Address 0x8b6d690 is 0 bytes after a block of size 0 alloc'd
==29826==    at 0x4C2AB80: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==29826==    by 0x685EEA9: ISYS_NS::docfile::CIStorageBase::Resolve_Short_Sectors()
```

had place allocation of heap buffer with size 0 and next

```
==29826== Invalid write of size 8
==29826==    at 0x4C2F5F3: memcpy
```

at least 8 bytes are copied to this buffer with memcpy causing heap corruption.

Having information about the call stack is interesting. Let’s investigate the ISYS_NS::docfile::CIStorageBase::Resolve_Short_Sectors method in context of what values of our corrupted file have been used to calculate the buffer size and have triggered memcpy.

```
Line 1 	_DWORD *__fastcall ISYS_NS::docfile::CIStorageBase::Resolve_Short_Sectors(struct_this *this)
Line 2 	{
Line 3 	  struct_this *v1; // rbp@1
Line 4 	  void *_buff; // rax@3
Line 5 	  unsigned int __sectMiniFatStart; // ebx@3
Line 6 	  unsigned int index; // er12@5
Line 7 	  unsigned int v5; // eax@9
Line 8 	  bool v6; // cf@9
Line 9 	  bool v7; // zf@9
Line 10	  _DWORD *result; // rax@11
Line 11	  _DWORD *v9; // rbx@11
Line 12	  unsigned int v10; // er12@15
Line 13	  int v11; // edx@16
Line 14	  unsigned int v12; // edi@18
Line 15	  _DWORD *v13; // r13@18
Line 16	  unsigned int v14; // ebx@19
Line 17	  unsigned int v15; // er14@21
Line 18	  unsigned int v16; // er12@22
Line 19	  void *v17; // rax@27
Line 20	  _QWORD *v18; // rax@31
Line 21
Line 22	  if ( this->_csectMiniFat > 0x10000u )
Line 23		this->_csectMiniFat = 0x10000;
Line 24	  _buff = malloc((unsigned int)(this->dword214 * this->_csectMiniFat));
Line 25	  __sectMiniFatStart = this->_sectMiniFatStart;
Line 26	  this->buff = _buff;
Line 27	  if ( __sectMiniFatStart <= 0xFFFFFFF9 && this->_csectMiniFat )
Line 28	  {
Line 29		index = 0;
Line 30		do
Line 31		{
Line 32		  if ( !(unsigned __int8)ISYS_NS::docfile::CIStorageBase::Read_Long_Sector(
Line 33								   (ISYS_NS::docfile::CIStorageBase *)this,
Line 34								   __sectMiniFatStart,
Line 35								   (char *)this->buff + this->dword214 * index,
Line 36								   1,
Line 37								   0LL) )
```

We can observe in line 24 the malloc size argument is a result of multiplication of _csectMiniFat and dword214. There is no check to make sure no integer overflow has occurred before passing this result to malloc. Let we see what the values look like just before imul instruction is called

```
[----------------------------------registers-----------------------------------]
RDI: 0x10000
RBP: 0x20a5f50
[-------------------------------------code-------------------------------------]
   0x7f746c41be94 <_ZN7ISYS_NS7docfile13CIStorageBase21Resolve_Short_SectorsEv+20>:	mov    DWORD PTR [rdi+0x54],0x10000
   0x7f746c41be9b <_ZN7ISYS_NS7docfile13CIStorageBase21Resolve_Short_SectorsEv+27>:	mov    edi,DWORD PTR [rbp+0x54]
=> 0x7f746c41be9e <_ZN7ISYS_NS7docfile13CIStorageBase21Resolve_Short_SectorsEv+30>:	imul   edi,DWORD PTR [rbp+0x214]
   0x7f746c41bea5 <_ZN7ISYS_NS7docfile13CIStorageBase21Resolve_Short_SectorsEv+37>:	call   0x7f746c404d78 <malloc@plt>

gdb-peda$ x /xw $rbp+0x214
0x20a6164:	0x00020000

gdb-peda$ p $rdi*0x00020000
$4 = 0x200000000
```

ok, let execute imul instruction:

```
[----------------------------------registers-----------------------------------]
RDI: 0x0
[-------------------------------------code-------------------------------------]
   0x7f53cb719e94 <_ZN7ISYS_NS7docfile13CIStorageBase21Resolve_Short_SectorsEv+20>:	mov    DWORD PTR [rdi+0x54],0x10000
   0x7f53cb719e9b <_ZN7ISYS_NS7docfile13CIStorageBase21Resolve_Short_SectorsEv+27>:	mov    edi,DWORD PTR [rbp+0x54]
   0x7f53cb719e9e <_ZN7ISYS_NS7docfile13CIStorageBase21Resolve_Short_SectorsEv+30>:	imul   edi,DWORD PTR [rbp+0x214]
=> 0x7f53cb719ea5 <_ZN7ISYS_NS7docfile13CIStorageBase21Resolve_Short_SectorsEv+37>:	call   0x7f53cb702d78 <malloc@plt>
```

like we expected, an integer overflow occurred and malloc is called with argument size equal 0.

Before we go further to see where the heap is corrupted and what size value is used in memcpy let’s try to figure out where: [rbp+0x214] == dword214 == 0x00020000 is initialized.

```
RBP points on our file content :
gdb-peda$ hexdump $rbp 64
0x010dcf50 : 30 c5 31 cd 53 7f 00 00 e0 72 48 9d fc 7f 00 00   0.1.S....rH.....
0x010dcf60 : 00 00 00 00 d0 cf 11 e0 a1 b1 1a e1 00 00 00 00   ................
0x010dcf70 : 00 00 00 00 00 00 00 00 00 00 00 1b 3e 00 03 00   ............>...
0x010dcf80 : fe ff f1 00 06 00 00 00 00 00 00 00 00 00 00 00   ................
```

with 20 additional bytes at the beginning. Checking the location of allocation for this buffer:

```
>>> print allocations['0x010dcf50']["stack"]
#0  __GI___libc_malloc (bytes=0x7ffec96d6080) at malloc.c:2876
#1  0x00007f25d52eddad in operator new(unsigned long) () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#2  0x00007f25d252ad24 in ISYS_NS::docfile::StgOpenStorage(ISYS_NS::CStream*, bool, IStorage**, unsigned int) () from ./libISYSshared.so
#3  0x00007f25d1e98629 in ISYS_NS::OpenStorageFromStream(ISYS_NS::CStream*, bool, IStorage**) () from ./libISYSreaders.so
#4  0x00007f25d1e9ee86 in ISYS_NS::CISYSReaderFactoryBase::FindFactoryFor(std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > const&, ISYS_NS::CStream*, std::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> > const&, int*, int) () from ./libISYSreaders.so
#5  0x00007f25d1fc922c in ISYS_NS::exports::IGR_Get_Stream_Type(IGR_Stream*, int*, int*, Error_Control_Block*) () from ./libISYSreaders.so
#6  0x00007f25d579e138 in IGR_Get_File_Type () from ./libISYS11df.so
#7  0x000000000040a036 in processFile(std::string, std::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> >&) ()
#8  0x000000000040b7a0 in main ()
#9  0x00007f25d4174ec5 in __libc_start_main (main=0x40b0b0 <main>, argc=0x2, argv=0x7ffec96e0ac8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7ffec96e0ab8) at libc-start.c:287
#10 0x00000000004089e9 in _start ()
>>> print allocations['0x010dcf50']["size"]
712 ( 0x2c8 )
```

we see that this buffer has a size of 712 bytes and has been allocated in the ISYS_NS::docfile::StgOpenStorage method. Somewhere around there we should try to find the initialization of the field at offset +0x214.

```
Line 1 	signed __int64 __fastcall ISYS_NS::docfile::StgOpenStorage(ISYS_NS::docfile *this, ISYS_NS::CStream *a2, signed __int64 *a3, IStorage **a4)
Line 2 	{
Line 3 	  signed __int64 *v4; // r14@1
Line 4 	  ISYS_NS::docfile::CIStorageBase *v5; // rbx@1
Line 5 	  ISYS_NS::docfile::CIStorageBase *v6; // r13@1
Line 6 	  signed __int64 result; // rax@2
Line 7 	  __int64 v8; // rbx@4
Line 8 	  signed __int64 v9; // rdi@4
Line 9
Line 10	  v4 = a3;
Line 11	  *a3 = 0LL;
Line 12	  (*(void (__fastcall **)(ISYS_NS::docfile *, _QWORD, _QWORD, IStorage **))(*(_QWORD *)this + 40LL))(this, 0LL, 0LL, a4);
Line 13	  v5 = (ISYS_NS::docfile::CIStorageBase *)operator new(0x2C8uLL);
Line 14	  ISYS_NS::docfile::CIStorageBase::CIStorageBase(v5);
```

Line 13 presents the allocation of our buffer and next there is call to the CIStorageBase constructor. Reviewing the code of the CIStorageBase constructor we see:

```
Line 1 void __fastcall ISYS_NS::docfile::CIStorageBase::CIStorageBase(ISYS_NS::docfile::CIStorageBase *this, ISYS_NS::CStream *a2, char a3)
Line 2 {
Line 3 (...)
Line 4 .text:00000000001D4709                 call    ISYS_NS::docfile::CIStorageBase::Read_Header(void)
Line 5 .text:00000000001D470E                 test    al, al
Line 6 .text:00000000001D4710                 jz      short loc_1D473D
Line 7 .text:00000000001D4712                 movsx   ecx, word ptr [rbp+32h]
Line 8 .text:00000000001D4716                 mov     eax, 1
Line 9 .text:00000000001D471B                 mov     edx, eax
Line 10.text:00000000001D471D                 shl     edx, cl
Line 11.text:00000000001D471F                 mov     ecx, edx
Line 12.text:00000000001D4721                 mov     [rbp+214h], edx
```

so the first header from the file is read (512 bytes) and next we see the WORD at offset +0x32 which is in our file field:

```
+0x32 - 20 = 0x1e -> _uSectorShift
gdb-peda$ x /xh $rbp+0x32
0x10dcf82:	0x00f1
```

is used as a shift operator parameter and the result from this operation is stored in value +0x214. We can presents it as a pseudo code in the following way:

```
this->dword214 = 1 << this->_uSectorShift;
```

Now we know both integers values are used in a multiplication, and the result is later used in malloc.

Going back to situation where malloc was called with 0 parameter we can analyze details related with memcpy which cause heap corruption. At information's presented by valgrind we see that the heap corruption take place in Read_Long_Sector method:

```
==29826== Invalid write of size 8
==29826==    at 0x4C2F5F3: memcpy@GLIBC_2.2.5 (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==29826==    by 0x68953E4: ISYS_NS::CBufferedReader::Read(void*, unsigned int) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSshared.so)
==29826==    by 0x689B8A7: ISYS_NS::CIGRStreamStream::Read(void*, unsigned int) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSshared.so)
==29826==    by 0x685DF0A: ISYS_NS::docfile::CIStorageBase::Read_Long_Sector(unsigned int, void*, bool, unsigned int*) (in /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSshared.so)
```

This method is called just after the call to malloc with the following parameters:

```
Line 24	  _buff = malloc((unsigned int)(this->dword214 * this->_csectMiniFat));
Line 25	  __sectMiniFatStart = this->_sectMiniFatStart;
Line 26	  this->buff = _buff;
Line 27	  if ( __sectMiniFatStart <= 0xFFFFFFF9 && this->_csectMiniFat )
Line 28	  {
Line 29		index = 0;
Line 30		do
Line 31		{
Line 32		  if ( !(unsigned __int8)ISYS_NS::docfile::CIStorageBase::Read_Long_Sector(
Line 33								   (ISYS_NS::docfile::CIStorageBase *)this,
Line 34								   __sectMiniFatStart,
Line 35								   (char *)this->buff + this->dword214 * index,
Line 36								   1,
Line 37								   0LL) )
```

Coming just after malloc function let us set bp on ISYS_NS::CBufferedReader::Read and step to the line where memcpy is called.

We end up in the following situation:

```
(gdb) i r
rax            0x215	533
rbx            0x215	533
rcx            0x7ffff729a3a0	140737340089248
rdx            0x215	533
rsi            0x63bcf0	6536432
rdi            0x63ae90	6532752
rbp            0x63a890	0x63a890

=> 0x7fdc388ea3e0 <_ZN7ISYS_NS15CBufferedReader4ReadEPvj+192>:	call   0x7fdc388a0fc8 <memcpy@plt>
   0x7fdc388ea3e5 <_ZN7ISYS_NS15CBufferedReader4ReadEPvj+197>:	jmp    0x7fdc388ea369 <_ZN7ISYS_NS15CBufferedReader4ReadEPvj+73>
   0x7fdc388ea3e7 <_ZN7ISYS_NS15CBufferedReader4ReadEPvj+199>:	mov    rax,QWORD PTR [rbp+0x18]
   0x7fdc388ea3eb <_ZN7ISYS_NS15CBufferedReader4ReadEPvj+203>:	mov    r14d,0x2
   0x7fdc388ea3f1 <_ZN7ISYS_NS15CBufferedReader4ReadEPvj+209>:	mov    r12d,0x2
Guessed arguments:
arg[0]: 0x63ae90 --> 0x7fdc3a8a07c8 --> 0x7fdc3a8a07b8 --> 0x17c3210 --> 0x0
arg[1]: 0x63bcf0 --> 0xe11ab1a1e011cfd0
arg[2]: 0x215

where the parameters are obviously:
dst->arg0 contains a 0 size buffer:

(gdb) heap /b $rdi
    [In-use]
    [Address] 0x63ae90
    [Size]    40
    [Offset]  +0

normal behavior of modern malloc implementation.
arg1 src is content of our file
(gdb) x /32xb 0x63bcf0
0x63bcf0:	0xd0	0xcf	0x11	0xe0	0xa1	0xb1	0x1a	0xe1
0x63bcf8:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0x63bd00:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x1b
0x63bd08:	0x3e	0x00	0x03	0x00	0xfe	0xff	0xf1	0x00
```

Where does the 3rd argument (size = 0x215) come from? In ISYS_NS::CBufferedReader::Read method there is a check which checks if the value passed as the size of the buffer to copy (which in our case was __sectMiniFatStart) is bigger than file size. If so, the file size value is used in a memcpy. You can see this in the following pseudo code:

```
__int64 __fastcall ISYS_NS::CBufferedReader::Read(ISYS_NS::CBufferedReader *this, void *dest, size_t n)
{
(...)
      fileSize = *((unsigned __int64 *)this + 4) - v7;
      if ( _n <= fileSize )
        fileSize = _n;
    (...)
        else
        {
          v5 = fileSize;
          v6 = fileSize;
          memcpy(_ptr, (const void *)(*((_QWORD *)this + 3) + v7), fileSize);
        }

Before we call memcpy let us check the consistency of the heap:

(gdb) heap
    Tuning params & stats:
        mmap_threshold=131072
        pagesize=4096
        n_mmaps=2
        n_mmaps_max=65536
        total mmap regions created=2
        mmapped_mem=270336
        sbrk_base=0x626000
    Main arena (0x7ffff6941760) owns regions:
        [0x626010 - 0x647000] Total 131KB in-use 1289(81KB) free 5(40KB)
    mmap-ed large memory blocks:
        [0x7ffff7fab010 - 0x7ffff7fcc000] Total 131KB in-use 1(131KB) free 0(0)
        [0x7ffff7fd5010 - 0x7ffff7ff6000] Total 131KB in-use 1(131KB) free 0(0)

    There are 1 arenas and 2 mmap-ed memory blocks Total 385KB
    Total 1291 blocks in-use of 345KB
    Total 5 blocks free of 40KB

ok, execute memcpy call:

(gdb) ni
0x00007ffff498b3e5 in ISYS_NS::CBufferedReader::Read(void*, unsigned int) () from /home/icewall/bugs/cvtisys/clean_copy/cvtisys/libISYSshared.so
(gdb) heap
    Tuning params & stats:
        mmap_threshold=131072
        pagesize=4096
        n_mmaps=2
        n_mmaps_max=65536
        total mmap regions created=2
        mmapped_mem=270336
        sbrk_base=0x626000
    Main arena (0x7ffff6941760) owns regions:
        [0x626010 - 0x647000] Total 131KBFailed to walk arena. The chunk at 0x63aeb0 may be corrupted. Its size tag is 0x100000000

    mmap-ed large memory blocks:
        [0x7ffff7fab010 - 0x7ffff7fcc000] Total 131KB in-use 1(131KB) free 0(0)
        [0x7ffff7fd5010 - 0x7ffff7ff6000] Total 131KB in-use 1(131KB) free 0(0)

1 Errors encountered while walking the heap!
[Error] Failed to walk heap
```

As we can see the heap gets corrupted.

Timeline

2016-06-14 - Initial Vendor Contact
2016-08-06 - Public Release
Credit

Discovered by Marcin “Icewall” Noga of Cisco Talos