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 ISYSNS::docfile::CIStorageBase::ResolveShort_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 ReadLongSector 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