CVE-2017-12110
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.
libxls 1.4 readxl package 1.0.0 for R (tested using Microsoft R 4.3.1)
http://libxls.sourceforge.net/
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-787: Out-of-bounds Write
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.
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
icewall@ubuntu:~/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)
2017-10-25 - Vendor Disclosure
2017-11-15 - Public Release
Discovered by Marcin 'Icewall' Noga of Cisco Talos.