CVE: CVE-2020-0889

Tested Versions:

  • msexcl40.dll 4.0.9801.17

Product URL(s):

Description of the vulnerability

msexcl40.dll is a part of Microsoft Jet Excel. It is responsible for processing Excel files. When opening a craft .xls file, especially when the pExcelRecordBuffer is corrupt, this will cause an Out-of-Bounds write problem.

The crash occurs at msexcl40!WriteStringPool+0xa5:

0:000> r
eax=25c90000 ebx=256662ec ecx=00000000 edx=00000000 esi=00000000 edi=256662ec
eip=7ca9a905 esp=00f6ea8c ebp=00000000 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
msexcl40!WriteStringPool+0xa5:
7ca9a905 8910            mov     dword ptr [eax],edx  ds:002b:25c90000=????????
0:000> kb
 # ChildEBP RetAddr  Args to Child              
00 00f6eab0 7ca98066 256657ac 256600fc 256657ac msexcl40!WriteStringPool+0xa5
01 00f6ead4 7ca8b496 256657ac 00000000 00000000 msexcl40!ExcelMISave+0x86
02 00f6eaf0 7ca84f41 256657ac 00000000 00000000 msexcl40!ExcelCloseFile+0x26
03 00f6eb08 7ca7d996 25665744 00000000 252aaf20 msexcl40!WorkbookClose+0x31
04 00f6eb24 7c83b36a 00000000 00000001 00000000 msexcl40!WBDBCloseDatabase+0xd6
05 00f6eb3c 7c81cf4c 252aaf20 000000ff 00000000 msjet40!ErrDispCloseDatabase+0x4a
06 00f6eb50 7c77afb0 252aaf20 000000ff 00000000 msjet40!JetCloseDatabase+0x3c
07 00f6ebd4 7c5643e9 25cafef4 00000000 25cb3fb0 msjetoledb40!CDBSession::~CDBSession+0x220
08 00f6ebe8 7c5a67fb 25cafee8 7c562a99 45181ec9 oledb32!CACMDynamic<CACMAggregationWrapper>::CmFinalRelease+0x5c
09 00f6ebf0 7c562a99 45181ec9 27689fe0 7c562a40 oledb32!CSCM::FinalRelease+0xa
0a 00f6ec14 7c53dd1c 25cafee8 2767fee8 00f6ec9c oledb32!CSCMComPolyObject<CSCM>::Release+0x59
0b 00f6ec24 7c779255 25cafef4 f3aefcc7 2767fee8 oledb32!ATL::CComContainedObject<CACMAggregationWrapper>::Release+0x1c
0c 00f6ec6c 00297521 274e4fdc 00000000 0032ae58 msjetoledb40!CCommand::~CCommand+0x175

EAX should be ExcelRecordBuffer locates at msexcl40!pExcelRecordBuffer, but as we see, the value is corrupted

We will add a breakpoint when the msexcl40!pExcelRecordBuffer is changed, and then we found it in the function msexcl40!ExcelExtractString, the msexcl40!pExcelRecordBuffer is set to a corrupt value:

0:000> r
eax=00000400 ebx=00006161 ecx=00000000 edx=7cac2ce0 esi=00000200 edi=7cac28e0
eip=7ca8b7fc esp=00f6cdb8 ebp=00001908 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
msexcl40!ExcelExtractString+0xbc:
7ca8b7fc 5f              pop     edi
0:000> ub
msexcl40!ExcelExtractString+0xa8:
7ca8b7e8 668942fe        mov     word ptr [edx-2],ax
7ca8b7ec 8d4901          lea     ecx,[ecx+1]
7ca8b7ef 4b              dec     ebx
7ca8b7f0 75f0            jne     msexcl40!ExcelExtractString+0xa2 (7ca8b7e2)
7ca8b7f2 8d0436          lea     eax,[esi+esi]
7ca8b7f5 33c9            xor     ecx,ecx
7ca8b7f7 5b              pop     ebx
7ca8b7f8 66890c38        mov     word ptr [eax+edi],cx
0:000> kb5
 # ChildEBP RetAddr  Args to Child              
00 00f6cdbc 7ca8fe2c 00000008 000003a8 7cac28e0 msexcl40!ExcelExtractString+0xbc
01 00f6cde8 7ca8d1a9 256657ac 00f6ce40 7ca84c60 msexcl40!ProcessFormatRecord+0x9c
02 00f6cfb8 7ca8567f 256657ac 7cabc4e0 00000000 msexcl40!ExcelScanFile+0x549
03 00f6d5ec 7ca7eb7f 00f6dac8 00f6d650 00000112 msexcl40!WorkbookOpen+0x11f
04 00f6e0d4 7c827187 252aaf20 20a82ff4 00f6e3ec msexcl40!WBISAMOpenDatabase+0xb4f

0:000> dd msexcl40!pExcelRecordBuffer L1
7cac2ce0  25c90000

As we can see the msexcl40!pExcelRecordBuffer is set to 0x25c90000, the normal value should be 0x25c91cfc:

0:000> dd eax+edi L1
7cac2ce0  25c91cfc

The instruction mov word ptr [eax+edi],cx at 0x7ca8b7f8 causes this corrupt value. We can see that edi points to msexcl40!ExcelRecordTextBuffer and it is a normal value, then the eax is corrupt.

We analyzed the ExcelExtractString function and find that eax is related to the cbMultiByte parameter.

ExcelExtractString accepts 6 parameters:

ExcelExtractString (int, UINT CodePage, LPWSTR lpWideCharStr, int cchWideChar, LPCSTR lpMultiByteStr, int cbMultiByte)

0:000> dd esp
00f6cdc4  00000008 000003a8 7cac28e0 00000200
00f6cdd4  25c91d00 00001904 256657ac 00000000
00f6cde4  00f6cfb8 7cabc4e0 7ca8d1a9 256657ac

int =0x8
CodePage=0x000003a8
lpWideCharStr=0x7cac28e0
cchWideChar=0x00000200
lpMultiByteStr=0x25c91d00
cbMultiByte=0x00001904

The cbMultiByte is 0x00001904 in our case, and this value comes from the offset 0x11BC in our PoC file.

How is this value checked before use?

Let’s look at the code around msexcl40+0x1B7A1:

loc_1001B7A1:
mov     ecx, [esp+8+lpMultiByteStr]
mov     edx, [esp+8+cchWideChar]
mov     esi, edx
push    ebx
mov     bl, [ecx]
inc     ecx
cmp     eax, edx
cmovl   esi, eax
cmp     bl, 1
jnz     short loc_1001B7D6

In our case, cchWideChar is set to 0x200 at the parent call function. There is a comparison between cchWideChar and cbMultiByte with cmp eax, edx:

eax=00001904 ebx=00006100 ecx=25c91d01 edx=00000200 esi=00000200 edi=7cac28e0
eip=7ca8b7af esp=00f6cdb4 ebp=00001908 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
msexcl40!ExcelExtractString+0x6f:
7ca8b7af 3bc2            cmp     eax,edx
0:000> p
eax=00001904 ebx=00006100 ecx=25c91d01 edx=00000200 esi=00000200 edi=7cac28e0
eip=7ca8b7b1 esp=00f6cdb4 ebp=00001908 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
msexcl40!ExcelExtractString+0x71:
7ca8b7b1 0f4cf0          cmovl   esi,eax

As eax is greater than edx, esi won’t be changed. It will keep its original value with 0x200.

When the program executes to msexcl40!ExcelExtractString+0xb8:

0:000> g
Breakpoint 2 hit
eax=00000400 ebx=00006161 ecx=00000000 edx=7cac2ce0 esi=00000200 edi=7cac28e0
eip=7ca8b7f8 esp=00f6cdb8 ebp=00001908 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
msexcl40!ExcelExtractString+0xb8:
7ca8b7f8 66890c38        mov     word ptr [eax+edi],cx    ds:002b:7cac2ce0=1cfc

The offset(eax) is 0x400,and the global variable pExcelRecordBuffer is next to ExcelRecordTextBuffer

To conclude, the value at offset 0x11BC in the PoC will be treated as the cbMultiByte value in function ExcelExtractString. It is treated as Format Record Length, but the program failed to check it. The program will write to 0x400+ExcelRecordTextBuffer, corrupt the pExcelRecordBuffer.

Timeline:

  • 2019-12-04 Vendor disclosure
  • 2020-04-14 Vendor patched

Vendor Response

The vendor has acknowledged the issue and released an update to address it.

The vendor advisory can be found here: https://msrc.microsoft.com/update-guide/vulnerability/CVE-2020-0889.