CVE-2016-5646
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.
Perceptive Document Filters 11.2.0.1732
http://www.lexmark.com/en_us/partners/enterprise-software/technology-partners/oem-technologies/document-filters.html
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
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.
2016-06-14 - Initial Vendor Contact
2016-08-06 - Public Release
Discovered by Marcin “Icewall” Noga of Cisco Talos