Talos Vulnerability Report

TALOS-2017-0462

libxls xls_appendSST Code Execution Vulnerability

November 15, 2017
CVE Number

CVE-2017-12110

Summary

An exploitable integer overflow vulnerability exists in the xls_appendSST function of libxls 1.4. A specially crafted XLS file can cause memory corruption resulting in remote code execution. An attacker can send a malicious XLS file to trigger this vulnerability.

Tested Versions

libxls 1.4 readxl package 1.0.0 for R (tested using Microsoft R 4.3.1)

Product URLs

http://libxls.sourceforge.net/

CVSSv3 Score

8.8 - CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H CVSSv3 Calculator: https://www.first.org/cvss/calculator/3.0

CWE

CWE-787: Out-of-bounds Write

Details

libxls is a C library supported on windows, mac, cygwin which can read Microsoft Excel File Format ( XLS ) files. The library is used by the readxl package that can be installed in the R programming language. Before we will discuss the location where the out-of-bounds write appears, first let's check code where space allocation for that array takes place:

Line 120    void xls_addSST(xlsWorkBook* pWB,SST* sst,DWORD size)
Line 121    {
Line 122        verbose("xls_addSST");
Line 123
Line 124        pWB->sst.continued=0;
Line 125        pWB->sst.lastln=0;
Line 126        pWB->sst.lastid=0;
Line 127        pWB->sst.lastrt=0;
Line 128        pWB->sst.lastsz=0;
Line 129
Line 130        pWB->sst.count = sst->num;
Line 131        pWB->sst.string =(struct str_sst_string *)calloc(pWB->sst.count, sizeof(struct str_sst_string));
Line 132        xls_appendSST(pWB,&sst->strings,size-8);
Line 133    }   

As you can see at line 131 space for pWB->sst.string array is allocated based on the pWB->sst.count value. This value is obtained at line 130 from sst->num. The sst structure passed as argument is fully controlled by attacker. By controlling the sst structure, we can force allocation for any size, for example 1. The malformed SST record is located at offset 0x088F:

088Fh: FC 00 20 20 E7 01 00 00 00 00 00 00 0F 00 00 43  ü.  ç..........C 
089Fh: 69 73 63 6F 54 61 6C 6F 73 20 20 20 20 20 03 00  iscoTalos     .. 
08AFh: 00 53 37 38 03 00 00 53 37 39 03 00 00 53 37 36  .S78...S79...S76 
08BFh: 04 00 00 53 31 30 33 04 00 00 53 31 30 32 03 00  ...S103...S102.. 
08CFh: 00 53 37 37 03 00 00 53 37 34 03 00 00 53 37 35  .S77...S74...S75 
08DFh: 03 00 00 53 32 39 03 00 00 53 32 38 03 00 00 53  ...S29...S28...S 
08EFh: 32 37 03 00 00 53 37 33 03 00 00 53 32 32 03 00  27...S73...S22.. 
08FFh: 00                                               .
(...)

The sst strcture looks as follows in memory:

p *sst
$2 = {
  num = 0x0,
  numofstr = 0x0,
  strings = 0xf
}

The num field is at offset 0x897.

With an sst structure where values are set like these above, the pWB->sst.string array will have space for just one element. Going further inside the xls_appendSST function. Each string entry in sst->strings, after optional conversion, will be assigned (its address exactly) to separate entry in pWB->sst.string array. These string entries look as follows:

[string_size](SHORT)[flags](BYTE)[string...]

example:

0F 00 00 43 69 73 63 6F 54 61 6C 6F  .......CiscoTalo 
73 20 20 20 20 20

We can observe this in the code below, where the ret variable at line 235 containing a "decoded" string address is assigned to the pWB->sst.string array entry at line 257. Notice that for each string, lastid field value is increased being used as a index in pWB->sst.string array.

Line 135    void xls_appendSST(xlsWorkBook* pWB,BYTE* buf,DWORD size)
Line 136    {
Line 137        DWORD ln;   // String character count
Line 138        DWORD ofs;  // Current offset in SST buffer
Line 139        DWORD rt;   // Count of rich text formatting runs
Line 140        DWORD sz;   // Size of asian phonetic settings block
Line 141        BYTE flag;  // String flags
Line 142        BYTE* ret;

                (...)       

Line 163        else
Line 164        {
Line 165            ln=xlsShortVal(*(WORD_UA *)(buf+ofs));
Line 166            rt = 0;
Line 167            sz = 0;
Line 168
Line 169            ofs+=2;
Line 170        }

                (...)

Line 231        else
Line 232        {
Line 233            ln_toread = min((size-ofs), ln);
Line 234
Line 235            ret = utf8_decode((buf+ofs), ln_toread, pWB->charset);
Line 236
Line 237            ln  -= ln_toread;
Line 238            ofs +=ln_toread;
Line 239
Line 240            if (xls_debug) {
Line 241                printf("String8SST: %s(%u) \n",ret,ln);
Line 242            }
Line 243        }       

                (...)           

Line 250        if (  (ln_toread > 0)
Line 251            ||(!pWB->sst.continued) )
Line 252        {
Line 253            // Concat string if it's a continue, or add string in table
Line 254            if (!pWB->sst.continued)
Line 255            {
Line 256                pWB->sst.lastid++;
Line 257                pWB->sst.string[pWB->sst.lastid-1].str=ret;
Line 258            }           

In our case, the attempt to assign a second string to that array will cause an out-of-bounds write, resulting in memory corruption and potential code execution.

Crash Information

Microsoft R crash

(96c.954): Access violation - code c0000005 (!!! second chance !!!)
*** ERROR: Symbol file could not be found.  Defaulted to export symbols for C:\Users\Icewall\Documents\R\win-library\3.4\readxl\libs\x64\readxl.dll - 
readxl!xls_appendSST+0xa4:
00000000`6534a974 4c890cc2        mov     qword ptr [rdx+rax*8],r9 ds:00000000`30b49000=????????????????
0:000> r
rax=0000000000000002 rbx=000000000000001e rcx=0000000000000000
rdx=0000000030b48ff0 rsi=0000000031619f70 rdi=0000000000000000
rip=000000006534a974 rsp=000000000440ba00 rbp=0000000000000000
 r8=0000000000000000  r9=0000000030b4eff0 r10=000000000000001b
r11=0000000030b4eff0 r12=0000000000002018 r13=0000000000000000
r14=0000000000000003 r15=0000000000000003
iopl=0         nv up ei pl zr na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010244
readxl!xls_appendSST+0xa4:
00000000`6534a974 4c890cc2        mov     qword ptr [rdx+rax*8],r9 ds:00000000`30b49000=????????????????
0:000> kb 5
 # RetAddr           : Args to Child                                                           : Call Site
00 00000000`6534bdf9 : 00000000`31796ff0 00000000`31798fe8 00000000`3161bfb0 00000000`65349fe7 : readxl!xls_appendSST+0xa4
01 00000000`6534cc96 : 00000000`31619f70 00000000`00000006 00000000`00000000 00000000`0440bd68 : readxl!xls_parseWorkBook+0x3d9
02 00000000`6538025b : 00000000`0000003e 00000000`0440bc10 00000000`106f0788 00007ffe`155d9a00 : readxl!xls_open+0x136
03 00000000`65344d9d : 00000000`00000001 00000000`6cbfdb68 000cc587`59d3b80a 00000000`106f0788 : readxl!ZN11XlsWorkBookC1ERKSs+0x11b
04 00000000`65341efa : 00000000`00000000 00000000`0440be80 00000000`0440c700 00000000`6c87662c : readxl!Z10xls_sheetsSs+0x1d

0:000> lmv m readxl
Browse full module list
start             end                 module name
00000000`65340000 00000000`6543f000   readxl     (export symbols)       C:\Users\Icewall\Documents\R\win-library\3.4\readxl\libs\x64\readxl.dll
    Loaded symbol image file: C:\Users\Icewall\Documents\R\win-library\3.4\readxl\libs\x64\readxl.dll
    Image path: C:\Users\Icewall\Documents\R\win-library\3.4\readxl\libs\x64\readxl.dll
    Image name: readxl.dll
    Browse all global symbols  functions  data
    Timestamp:        Wed Aug 30 18:38:23 2017 (59A6E9FF)
    CheckSum:         001018F6
    ImageSize:        000FF000
    Translations:     0000.04b0 0000.04e4 0409.04b0 0409.04e4


Linux version   

[email protected]:~/bugs/libxls-1.4.0/build/bin$ valgrind ./xls2csv test/poc.xls        
==37508== Memcheck, a memory error detector
==37508== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==37508== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==37508== Command: ./xls2csv test/poc.xls
==37508== 
==37508== Invalid write of size 8
==37508==    at 0x4E4207E: xls_appendSST (xls.c:257)
==37508==    by 0x4E41D25: xls_addSST (xls.c:132)
==37508==    by 0x4E43B12: xls_parseWorkBook (xls.c:781)
==37508==    by 0x4E44F7B: xls_open (xls.c:1272)
==37508==    by 0x400E80: main (xls2csv.c:108)
==37508==  Address 0x54607d0 is 0 bytes after a block of size 0 alloc'd
==37508==    at 0x4C2FB55: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==37508==    by 0x4E41CFD: xls_addSST (xls.c:131)
==37508==    by 0x4E43B12: xls_parseWorkBook (xls.c:781)
==37508==    by 0x4E44F7B: xls_open (xls.c:1272)

Timeline

2017-10-25 - Vendor Disclosure
2017-11-15 - Public Release

Credit

Discovered by Marcin 'Icewall' Noga of Cisco Talos.