This June, FortiGuard Labs researcher Honggang Ren discovered a code execution vulnerability in the Windows JET Database Engine and reported it to Microsoft using the responsible disclosure process. On the patch Tuesday of September 2018, Microsoft released a Security Advisory that contains the fix for this vulnerability, identifying it as CVE-2018-8392.

The Microsoft JET Database Engine is a database engine on which several Microsoft products have been built. A database engine is the underlying component of a database, a collection of information stored on a computer in a systematic way.

The vulnerable DLL msexcl40.dll identified by FortiGuard Labs is a component of all supported Windows versions, from Windows 7 to Windows 10 and can be triggered with a crafted Excel file. When this PoC file is parsed by Excel as external data source created by a JET OLEDB data connection, a memory copy operation (memcpy function call) with an incorrect length is executed. As a result, the destination variable (pointer) is overwritten due to a buffer overflow caused by improper bounds checking. Then, during the next memcpy function call, the overwritten destination variable (destination buffer) results in an arbitrary memory address write.

In this blog we will share our detailed analysis of this vulnerability.

Analysis

To reproduce this vulnerability, we need to open the JET OLEDB external data source in Excel with the following parameters. The PoC file '03.xls' can be located in a local folder or SMB share. Once this is done, we can see that Excel crashes.

Figure 1. Reproducing the Vulnerability

The following is the call stack when the crash occurs.

Figure 2. Call Stack When Crash Occurs

From the above call stack output we can see that the crash occurs in the function 'msexcl40!memcpy', which is called by the function 'msexcl40!ExcelMIReadRecord'. The destination memory address sent to memcpy is 0x760061, which is an inaccessible address by any program.

Through reverse engineering and tracing we can see that the destination memory address is taken from the global variable _pExcelRecordBuffer. This global variable is located at .data:658B2CC0.

By further reverse engineering and tracing, we finally see that the call of the function ProcessWriteAccessRecord, and its child function _ExcelExtractString, results in the overwriting of the global variable _pExcelRecordBuffer due to the buffer overflow. In the _ExcelExtractString function, the real destination buffer of memcpy is the global variable _ExcelRecordTextBuffer, which has the address of 0x658B28C0. Due to the wrong memory copy size received from the crafted PoC file, the normal size 0x0C is changed to the wrong size of 0x100C. This results in the overwriting of the global variable _pExcelRecordBuffer, which is located at .data:658B2CC0.

The distance between the two adjacent variable addresses is only 0x400 bytes (0x658B2CC0 - 0x658B28C0 = 0x400). So, if the memory copy function is called with size 0x100C and destination address 0x658B28C0, the value of the key global variable _pExcelRecordBuffer is overwritten.

Following is the assembly code of _ExcelExtractString.

.text:6587B720 ; int __stdcall ExcelExtractString(int, int, void *Dst, int cchWideChar, LPCSTR lpMultiByteStr, int cbMultiByte)

.text:6587B720 _ExcelExtractString@24 proc near ; CODE XREF: DispatchLabelRecord+8A↑p

.text:6587B720 ; ExcelExtractBigString(x,x,x,x,x)+3C↑p ...

.text:6587B720

.text:6587B720 arg_0 = dword ptr 4

.text:6587B720 arg_4 = dword ptr 8

.text:6587B720 Dst = dword ptr 0Ch

.text:6587B720 cchWideChar = dword ptr 10h

.text:6587B720 lpMultiByteStr = dword ptr 14h --> this parameter is taken from the PoC file at offset 0x22A.

To clearly see what is in the overwritten buffer, we paste the buffer content as follows: (the buffer contents are all taken from the PoC file, but they are not continuous in the PoC file)

.text:6587B720 cbMultiByte = dword ptr 18h --> this parameter is from the PoC file at offset 0x228, and it is the memory copy size. Due to the crafted PoC, the copy size is wrongly set to 0x100C (the correct value should be only 0x0C). Due to an insufficient bounds check, this results in following buffer overflow.

.text:6587B720

.text:6587B720 cmp [esp+arg_0], 8

.text:6587B725 push esi ;

.text:6587B726 mov esi, [esp+4+Dst] ;

.text:6587B72A push edi

.text:6587B72B jge short loc_6587B74A ;

.text:6587B72D push [esp+8+cchWideChar] ; cchWideChar

.text:6587B731 mov eax, [esp+0Ch+arg_4]

.text:6587B735 push esi ; lpWideCharStr

.text:6587B736 push [esp+10h+cbMultiByte] ; cbMultiByte

.text:6587B73A push [esp+14h+lpMultiByteStr] ; lpMultiByteStr

.text:6587B73E push 0 ; dwFlags

.text:6587B740 cmp eax, 4B0h

.text:6587B745 jz short loc_6587B7BF

.text:6587B747 push eax

.text:6587B748 jmp short loc_6587B7C1

.text:6587B74A ; ---------------------------------------------------------------------------

.text:6587B74A

.text:6587B74A loc_6587B74A: ; CODE XREF: ExcelExtractString(x,x,x,x,x,x)+B↑j

.text:6587B74A mov ecx, [esp+8+lpMultiByteStr]

.text:6587B74E mov al, [ecx]

.text:6587B750 inc ecx

.text:6587B751 cmp al, 1

.text:6587B753 jnz short loc_6587B775 ;

.text:6587B755 mov edi, [esp+8+cbMultiByte]

.text:6587B759 lea eax, [edi+edi]

.text:6587B75C push eax ; Size

.text:6587B75D push ecx ; Src

.text:6587B75E push esi ; Dst

.text:6587B75F call _memcpy

.text:6587B764 add esp, 0Ch

.text:6587B767 lea eax, [edi+edi]

.text:6587B76A xor ecx, ecx

.text:6587B76C mov [eax+esi], cx

.text:6587B770 pop edi

.text:6587B771 pop esi

.text:6587B772 retn 18h

.text:6587B775 ; ---------------------------------------------------------------------------

.text:6587B775

.text:6587B775 loc_6587B775: ; CODE XREF: ExcelExtractString(x,x,x,x,x,x)+33↑j

.text:6587B775 test al, al

.text:6587B777 jnz short loc_6587B7A5

.text:6587B779 mov edi, [esp+8+cbMultiByte] ; --> this is the crafted memory copy size edi=0x100C

.text:6587B779 ; 0:000> db

.text:6587B779 ; 01499c7e 68 61 76 65 20 65 6e 6f-75 67 68 20 74 69 6d 65 have enough time

.text:6587B779 ; 01499c8e 20 77 69 74 68 20 79 6f-75 72 20 66 61 6d 69 6c with your famil

.text:6587B779 ; 01499c9e 79 2e 2a 00 00 79 6f 75-20 61 63 68 69 65 76 65 y.*..you achieve

.text:6587B779 ; '

.text:6587B77D mov edx, esi

.text:6587B77F test edi, edi

.text:6587B781 jle short loc_6587B7C9

.text:6587B783 push ebx

.text:6587B784 mov ebx, edi

.text:6587B786

.text:6587B786 loc_6587B786: ; CODE XREF: ExcelExtractString(x,x,x,x,x,x)+74↓j

.text:6587B786 movzx eax, byte ptr [ecx]

.text:6587B789 lea edx, [edx+2]

.text:6587B78C mov [edx-2], ax --> this is where the key global variable pExcelRecordBuffer is overwritten. Its value is then changed to 0x760061.

.text:6587B790 lea ecx, [ecx+1]

.text:6587B793 dec ebx

.text:6587B794 jnz short loc_6587B786

...

The following is the disassembly code at the final crash point caused by the key global variable being overwritten:

.text:65887D40 ; __stdcall ExcelMIReadRecord(x, x, x)

.text:65887D40 _ExcelMIReadRecord@12 proc near ; CODE XREF: ExcelReadTotalRecord(x,x,x)+33↑p

.text:65887D40

.text:65887D40 arg_0 = dword ptr 4

.text:65887D40 arg_8 = dword ptr 0Ch

.text:65887D40

.text:65887D40 mov eax, [esp+arg_0]

.text:65887D44 push esi

.text:65887D45 push edi ;

.text:65887D46 mov edi, [eax+28h]

.text:65887D49 mov eax, [edi+8]

.text:65887D4C movsx esi, word ptr [eax+6]

.text:65887D50 cmp esi, _g_cbCurrentSize

.text:65887D56 jbe short loc_65887D85 ------> jump here

.text:65887D58 push esi ; Size

.text:65887D59 push _pExcelRecordBuffer ; Src

.text:65887D5F call _MemReAllocate@8 ; MemReAllocate(x,x)

.text:65887D64 mov edx, eax

.text:65887D66 mov _pExcelRecordBuffer, edx

.text:65887D6C test edx, edx

.text:65887D6E jnz short loc_65887D7D

.text:65887D70 pop edi

.text:65887D71 mov _g_cbCurrentSize, eax

.text:65887D76 lea eax, [edx-2]

.text:65887D79 pop esi

.text:65887D7A retn 0Ch

.text:65887D7D ; ---------------------------------------------------------------------------

.text:65887D7D

.text:65887D7D loc_65887D7D: ; CODE XREF: ExcelMIReadRecord(x,x,x)+2E↑j

.text:65887D7D mov _g_cbCurrentSize, esi

.text:65887D83 jmp short loc_65887D8B

.text:65887D85 ; ---------------------------------------------------------------------------

.text:65887D85

.text:65887D85 loc_65887D85: ; CODE XREF: ExcelMIReadRecord(x,x,x)+16↑j

.text:65887D85 mov edx, _pExcelRecordBuffer --> this is the value of the overwritten global variable, with the arbitrary address assigned to edx

.text:65887D8B

.text:65887D8B loc_65887D8B: ; CODE XREF: ExcelMIReadRecord(x,x,x)+43↑j

.text:65887D8B mov ecx, [edi+8]

.text:65887D8E movsx eax, word ptr [ecx+6]

.text:65887D92 push eax ; Size

.text:65887D93 lea eax, [ecx+8]

.text:65887D96 push eax ; Src

.text:65887D97 push edx ; Dst

.text:65887D98 call _memcpy ; ----> this is where memcpy with an arbitrary destination address occurs

.text:65887D9D mov ecx, [esp+14h+arg_8]

From the above analysis, we can see that the root cause of this vulnerability is the malformed size value 0x100C, which is located in the PoC file at offset 0x228. The proper value should be 0x0C. Due to insufficient bounds check, the malformed size value results in the overwriting of a key neighbor global variable in the memcpy function call. Then, in next memcpy function call, the overwritten global variable with an arbitrary value is used in memory copy as the destination address, which results in a code execution condition. Successful exploitation of this vulnerability could lead to remote code execution.

Solution

All users of the vulnerable Microsoft Windows are encouraged to upgrade to the latest Windows version.

Additionally, organizations that have deployed Fortinet IPS solutions are already protected from this vulnerability with the signature MS.JET.Database.Engine.Remote.Code.Execution.

Attachments

  • Original document
  • Permalink

Disclaimer

Fortinet Inc. published this content on 14 September 2018 and is solely responsible for the information contained herein. Distributed by Public, unedited and unaltered, on 14 September 2018 16:42:02 UTC