CVE-2017-2792
An exploitable heap corruption vulnerability exists in the iBldDirInfo functionality of AntennaHouse DMC HTMLFilter used by MarkLogic 8.0-6. A specially crafted xls file can cause a heap corruption resulting in arbitrary code execution. An attacker can provide a malicious xls file to trigger this vulnerability.
AntennaHouse DMC HTMLFilter shipped with MarkLogic 8.0-6 fb1a22fa08c986ec3614284f4e912b0a /opt/MarkLogic/Converters/cvtofc/libdhf_rdoc.so 15b0acc464fba28335239f722a62037f /opt/MarkLogic/Converters/cvtofc/libdmc_comm.so 1eabb31236c675f9856a7d001b339334 /opt/MarkLogic/Converters/cvtofc/libdhf_rxls.so 1415cbc784f05db0e9db424636df581a /opt/MarkLogic/Converters/cvtofc/libdhf_comm.so 4ae366fbd4540dd4c750e6679eb63dd4 /opt/MarkLogic/Converters/cvtofc/libdmc_conf.so 81db1b55e18a0cb70a78410147f50b9c /opt/MarkLogic/Converters/cvtofc/libdhf_htmlif.so d716dd77c8e9ee88df435e74fad687e6 /opt/MarkLogic/Converters/cvtofc/libdhf_whtml.so e01d37392e2b2cea757a52ddb7873515 /opt/MarkLogic/Converters/cvtofc/convert
https://www.antennahouse.com/antenna1/
8.3 - CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
This vulnerability is present in the AntennaHouse DMC HTMLFilter which is used, among others, to convert xls files to (x)html form.
This product is mainly used by MarkLogic for xls document conversions as part of their web based document search and rendering engine.
A specially crafted XLS file can lead to heap corruption and ultimately to remote code execution.
Let’s investigate this vulnerability. After execution of the XLS to html converter with a malformed xls file as an input we can easily observe the following when using Valgrind:
icewall@ubuntu:~/bugs/cvtofc_86$ valgrind ./convert config_xls/
==43073== Memcheck, a memory error detector
==43073== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==43073== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
==43073== Command: ./convert config_xls/
==43073==
input=/home/icewall/bugs/cvtofc_86/config_xls/toconv.xls
output=/home/icewall/bugs/cvtofc_86/config_xls/conv.html
type=2
info.options='0'
==43073== Invalid write of size 1
==43073== at 0x42E012A: iBldDirInfo (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so)
==43073== by 0x42DEDDA: DMC_2OLEopen (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so)
==43073== by 0x42C0941: CommOLEOpen (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so)
==43073== by 0x42BB8C9: autoOLE (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so)
==43073== by 0x42BFD2C: detectFormat (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so)
==43073== by 0x42C0269: DMC_FileDtct (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so)
==43073== by 0x4038144: DHF_GetFileInfo_V1 (in /home/icewall/bugs/cvtofc_86/libdhf_htmlif.so)
==43073== by 0x80498B4: main (in /home/icewall/bugs/cvtofc_86/convert)
==43073== Address 0x4bedb90 is 0 bytes after a block of size 1,536 alloc'd
==43073== at 0x402A17C: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==43073== by 0x42DCCB1: DMC_malloc (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so)
==43073== by 0x42DFFAD: iBldDirInfo (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so)
==43073== by 0x42DEDDA: DMC_2OLEopen (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so)
==43073== by 0x42C0941: CommOLEOpen (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so)
==43073== by 0x42BB8C9: autoOLE (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so)
==43073== by 0x42BFD2C: detectFormat (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so)
==43073== by 0x42C0269: DMC_FileDtct (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so)
==43073== by 0x4038144: DHF_GetFileInfo_V1 (in /home/icewall/bugs/cvtofc_86/libdhf_htmlif.so)
==43073== by 0x80498B4: main (in /home/icewall/bugs/cvtofc_86/convert)
==43073==
==43073== Invalid write of size 1
==43073== at 0x42E0151: iBldDirInfo (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so)
==43073== by 0x42DEDDA: DMC_2OLEopen (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so)
==43073== by 0x42C0941: CommOLEOpen (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so)
==43073== by 0x42BB8C9: autoOLE (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so)
==43073== by 0x42BFD2C: detectFormat (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so)
==43073== by 0x42C0269: DMC_FileDtct (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so)
==43073== by 0x4038144: DHF_GetFileInfo_V1 (in /home/icewall/bugs/cvtofc_86/libdhf_htmlif.so)
==43073== by 0x80498B4: main (in /home/icewall/bugs/cvtofc_86/convert)
==43073== Address 0x4bedb91 is 1 bytes after a block of size 1,536 alloc'd
==43073== at 0x402A17C: malloc (in /usr/lib/valgrind/vgpreload_memcheck-x86-linux.so)
==43073== by 0x42DCCB1: DMC_malloc (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so)
==43073== by 0x42DFFAD: iBldDirInfo (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so)
==43073== by 0x42DEDDA: DMC_2OLEopen (in /home/icewall/bugs/cvtofc_86/libdmc_comm.so)
==43073== by 0x42C0941: CommOLEOpen (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so)
==43073== by 0x42BB8C9: autoOLE (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so)
==43073== by 0x42BFD2C: detectFormat (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so)
==43073== by 0x42C0269: DMC_FileDtct (in /home/icewall/bugs/cvtofc_86/libdmc_dtct.so)
==43073== by 0x4038144: DHF_GetFileInfo_V1 (in /home/icewall/bugs/cvtofc_86/libdhf_htmlif.so)
==43073== by 0x80498B4: main (in /home/icewall/bugs/cvtofc_86/convert)
We see that a heap-based buffer overflow occurs in the function iBldDirInfo
. The overflowed buffer is also allocated in this function.
Running our target with an instrumented heap we stop in the following place :
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0x93
EBX: 0xf7c1acac --> 0x5ebf4
ECX: 0xe04a0fa0 --> 0x84938493
EDX: 0x60 ('`')
ESI: 0x60 ('`')
EDI: 0x60 ('`')
EBP: 0xfffec748 --> 0xfffec788 --> 0xfffec7b8 --> 0xfffec9b8 --> 0xfffec9d8 --> 0xfffeca08 --> 0xfffeccc8 --> 0xffffd038 --> 0x0
ESP: 0xfffec640 --> 0xffffffff
EIP: 0xf7bc412a (mov BYTE PTR [edx+ecx*1],al)
EFLAGS: 0x10a06 (carry PARITY adjust zero sign trap INTERRUPT direction OVERFLOW)
[-------------------------------------code-------------------------------------]
0xf7bc411d: movsx edx,si
0xf7bc4120: shr ax,0x8
0xf7bc4124: mov ecx,DWORD PTR [ebp-0xcc]
=> 0xf7bc412a: mov BYTE PTR [edx+ecx*1],al
0xf7bc412d: inc esi
0xf7bc412e: movsx eax,si
0xf7bc4131: mov dl,BYTE PTR [ebp-0xfa]
0xf7bc4137: jmp 0xf7bc414b
[------------------------------------stack-------------------------------------]
0000| 0xfffec640 --> 0xffffffff
0004| 0xfffec644 --> 0xffffffff
0008| 0xfffec648 --> 0xffffffff
0012| 0xfffec64c --> 0x93840000
0016| 0xfffec650 --> 0xe057e340 --> 0x0
0020| 0xfffec654 --> 0xe057cde0 --> 0xe049e000 --> 0xaaaaaaaa
0024| 0xfffec658 --> 0xe049ee00 --> 0x1
0028| 0xfffec65c --> 0xe049f000 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0xf7bc412a in iBldDirInfo () from ./libdmc_comm.so
gdb-peda$ bt
#0 0xf7bc412a in iBldDirInfo () from ./libdmc_comm.so
#1 0xf7bc2ddb in DMC_2OLEopen () from ./libdmc_comm.so
#2 0xf7c25942 in CommOLEOpen () from ./libdmc_dtct.so
#3 0xf7c208ca in autoOLE () from ./libdmc_dtct.so
#4 0xf7c24d2d in detectFormat () from ./libdmc_dtct.so
#5 0xf7c2526a in DMC_FileDtct () from ./libdmc_dtct.so
#6 0xf7fc0145 in DHF_GetFileInfo_V1 () from ./libdhf_htmlif.so
#7 0x080498b5 in main ()
#8 0xf7d60af3 in __libc_start_main (main=0x8049730 <main>, argc=0x2, argv=0xffffd0d4, init=0x8049f70 <__libc_csu_init>, fini=0x8049f60 <__libc_csu_fini>, rtld_fini=0xf7feb160 <_dl_fini>,
stack_end=0xffffd0cc) at libc-start.c:287
#9 0x08048ad1 in _start ()
To figure out something more about the place where the out-of-bound write appears, we can review the pseudo code of the iBldDirInfo
function:
Line 1 signed int __cdecl iBldDirInfo(struct_a1 *a1)
Line 2 {
Line 3 (...)
Line 4
Line 5 v24 = 0;
Line 6 v18 = -2;
Line 7 v17 = 0;
Line 8 v1 = (unsigned int)(a1->dword2C * a1->dword38) >> 2;
Line 9 v21 = a1->dword38 / 128;
Line 10 v23 = 1;
Line 11 v2 = 0;
Line 12 for ( i = lGetSect(a1, a1->dwordC); ; i = lGetSect(a1, v19) )
Line 13 {
Line 14 v19 = i;
Line 15 if ( i == -1 )
Line 16 break;
Line 17 if ( i <= -2 )
Line 18 {
Line 19 v5 = v21 * v23;
Line 20 a1->dword48 = v5;
Line 21 v6 = DMC_malloc(96 * v5);
Line 22 a1->buffer = v6;
Line 23 if ( v6 )
Line 24 {
Line 25 v20 = a1->dwordC;
Line 26 if ( !DMC_FileSeek(a1->pfile0, (v20 << a1->word8) + 512, 0) )
Line 27 {
Line 28 a1->word52 = 0;
Line 29 elementIndex = 0;
Line 30 while ( DMC_FileRead(rawDirectoryEntry, 1u, 0x80u, a1->pfile0) && elementIndex < a1->dword48 )
Line 31 {
Line 32 directoryEntry = (a1->buffer + 96 * elementIndex);
Line 33 v7 = usGetShort(&v26);
Line 34 if ( v7 )
Line 35 {
Line 36 directoryEntry->unsigned20 = v7;
Line 37 ++a1->word52;
Line 38 }
Line 39 else
Line 40 {
Line 41 v24 = 1;
Line 42 }
Line 43 if ( v7 && !v24 )
Line 44 {
Line 45 memset(directoryEntry, 0, 0x20u);
Line 46 v8 = 0;
Line 47 index = 0;
Line 48 directoryEntry->unsigned20;
Line 49 while ( index <= 127 )
Line 50 {
Line 51 v10 = DMC_unicodetosjis(((rawDirectoryEntry[index + 1] << 8) | rawDirectoryEntry[index]));
Line 52 v15 = v10;
Line 53 if ( v10 )
Line 54 {
Line 55 v11 = v10 & 0xFF00;
Line 56 if ( v11 )
Line 57 directoryEntry->Name[v8++] = BYTE1(v11);
Line 58 v12 = v8;
Line 59 v13 = v15;
Line 60 }
Line 61 else
Line 62 {
Line 63 v12 = v8;
Line 64 v13 = rawDirectoryEntry[index];
Line 65 }
Line 66 directoryEntry->Name[v12] = v13;
Line 67 ++v8;
Line 68 index += 2;
Line 69(...)
The out of bound write occurs at line 57
. This code parses a Compound File Directory Entry
, doing name conversion from Unicode to SJIS (Shift Japanese Industrial Standards) and fills
dynamically allocated structure directoryEntry
with the converted name. According to the documentation Directory Name
should not be bigger than 64 bytes.
We see a check for that in the while loop at line 49
. The problem appears because instead of only increasing the counter index
by 2 each time in the while loop
, the developers also increase v8
by 2 when a
correct conversion took place from Unicode to SJIS. We see v8
being incremented at lines 57
and 67
. v8
as a Directory Name
string index should not exceed 64 and should definitely not exceed
96 as is expected at line 32
. As we can imagine, v8
can easly reach a value like 126 which occurs when:
Directory Entry
structures (see allocation at line 21
, v5
equals the amount of Directory Entries
), a write on buffer space dedicated for the next
Directory Entry
which will cause the current Directory Entry
name to be overwritten during the next Directory Entry
read.But the most dangerous scenario appears when the parsed Directory Entry
is the last one, writing outside of the Name
buffer will cause a heap based buffer overflow,
causing heap corruption which can lead to arbitrary code execution.
Program received signal SIGABRT, Aborted.
[----------------------------------registers-----------------------------------]
EAX: 0x0
EBX: 0xab33
ECX: 0xab33
EDX: 0x6
ESI: 0x68 ('h')
EDI: 0xf7f06000 --> 0x1aada8
EBP: 0xfffec698 --> 0x8095590 --> 0x84938493
ESP: 0xfffec3d4 --> 0xfffec698 --> 0x8095590 --> 0x84938493
EIP: 0xf7fdacd9 (pop ebp)
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0xf7fdacd3: mov ebp,esp
0xf7fdacd5: sysenter
0xf7fdacd7: int 0x80
=> 0xf7fdacd9: pop ebp
0xf7fdacda: pop edx
0xf7fdacdb: pop ecx
0xf7fdacdc: ret
0xf7fdacdd: and edi,edx
[------------------------------------stack-------------------------------------]
0000| 0xfffec3d4 --> 0xfffec698 --> 0x8095590 --> 0x84938493
0004| 0xfffec3d8 --> 0x6
0008| 0xfffec3dc --> 0xab33
0012| 0xfffec3e0 --> 0xf7d89687 (xchg ebx,edi)
0016| 0xfffec3e4 --> 0xf7f06000 --> 0x1aada8
0020| 0xfffec3e8 --> 0xfffec484 --> 0x270f
0024| 0xfffec3ec --> 0xf7d8cab3 (mov edx,DWORD PTR gs:0x8)
0028| 0xfffec3f0 --> 0x6
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGABRT
0xf7fdacd9 in ?? ()
gdb-peda$ exploitable
Description: Heap error
Short description: HeapError (15/29)
Hash: eeae24290f4714292e59633fa30e2480.ff49ca1e4902951bc44eee297b21de88
Exploitability Classification: EXPLOITABLE
Explanation: The target's backtrace indicates that libc has detected a heap error or that the target was executing a heap function when it stopped. This could be due to heap corruption, passing a bad pointer to a heap function such as free(), etc. Since heap errors might include buffer overflows, use-after-free situations, etc. they are generally considered exploitable.
Other tags: AbortSignal (27/29)
2017-02-09 - Vendor Disclosure
2017-05-04 - Public Release
Discovered by Marcin 'Icewall' Noga of Cisco Talos.