Talos Vulnerability Report

TALOS-2017-0284

AntennaHouse DMC HTMLFilter iBldDirInfo Code Execution Vulnerability

May 4, 2017
CVE Number

CVE-2017-2792

Summary

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.

Tested Versions

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

Product URLs

https://www.antennahouse.com/antenna1/

CVSSv3 Score

8.3 - CVSS:3.0/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H

Details

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:

  • there is still space for more 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.

Crash Information

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)

Timeline

2017-02-09 - Vendor Disclosure
2017-05-04 - Public Release

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos.