Vulnerabilities > CVE-2016-5646 - Improper Restriction of Operations within the Bounds of a Memory Buffer vulnerability in Lexmark Perceptive Document Filters 11.2.0.1732
Attack vector
LOCAL Attack complexity
LOW Privileges required
NONE Confidentiality impact
HIGH Integrity impact
HIGH Availability impact
HIGH Summary
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.
Vulnerable Configurations
Part | Description | Count |
---|---|---|
Application | 1 |
Common Weakness Enumeration (CWE)
Common Attack Pattern Enumeration and Classification (CAPEC)
- Buffer Overflow via Environment Variables This attack pattern involves causing a buffer overflow through manipulation of environment variables. Once the attacker finds that they can modify an environment variable, they may try to overflow associated buffers. This attack leverages implicit trust often placed in environment variables.
- Overflow Buffers Buffer Overflow attacks target improper or missing bounds checking on buffer operations, typically triggered by input injected by an attacker. As a consequence, an attacker is able to write past the boundaries of allocated buffer regions in memory, causing a program crash or potentially redirection of execution as per the attackers' choice.
- Client-side Injection-induced Buffer Overflow This type of attack exploits a buffer overflow vulnerability in targeted client software through injection of malicious content from a custom-built hostile service.
- Filter Failure through Buffer Overflow In this attack, the idea is to cause an active filter to fail by causing an oversized transaction. An attacker may try to feed overly long input strings to the program in an attempt to overwhelm the filter (by causing a buffer overflow) and hoping that the filter does not fail securely (i.e. the user input is let into the system unfiltered).
- MIME Conversion An attacker exploits a weakness in the MIME conversion routine to cause a buffer overflow and gain control over the mail server machine. The MIME system is designed to allow various different information formats to be interpreted and sent via e-mail. Attack points exist when data are converted to MIME compatible format and back.
Seebug
bulletinFamily | exploit |
description | ### 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 ### 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 |
id | SSV:96682 |
last seen | 2017-11-19 |
modified | 2017-10-13 |
published | 2017-10-13 |
reporter | Root |
title | Lexmark Perceptive Document Filters CBFF Code Execution Vulnerability(CVE-2016-5646) |
Talos
id | TALOS-2016-0185 |
last seen | 2019-05-29 |
published | 2016-08-06 |
reporter | Talos Intelligence |
source | http://www.talosintelligence.com/vulnerability_reports/TALOS-2016-0185 |
title | Lexmark Perceptive Document Filters CBFF Code Execution Vulnerability |