FortiGuard Labs Threat Research

Microsoft JET Database Engine Code Execution Vulnerability

By Honggang Ren | September 14, 2018

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.

 

Download our latest Global Threat Landscape Report to find out more detail about recent threat landscape trends.

Sign up for our weekly FortiGuard Threat Brief or for our FortiGuard Threat Intelligence Service.