### 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
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)
```
### Timeline
* 2017-10-25 - Vendor Disclosure
* 2017-11-15 - Public Release
暂无评论