Threat Research

Microsoft Windows Remote Kernel Crash Vulnerability

By Honggang Ren | June 14, 2018

At the end of January 2018, the FortiGuard Labs team discovered a remote kernel crash vulnerability in Microsoft Windows and reported it to Microsoft by following Fortinet’s responsible disclosure process. On June 12, Microsoft released an advisory that contains the fix for this vulnerability and identifies it as CVE-2018-1040.

This kernel crash vulnerability exists in the Microsoft Windows Code Integrity Kernel Module “ci.dll”. All popular Windows versions are affected, including Windows 10, Windows 7, Windows 8.1, Windows Server 2008, Windows Server 2012, and Windows Server 2016.

The vulnerability I discovered can be triggered by remotely downloading a crafted .dll or .lib file on Windows from a website or SMB share. When using IE or Edge to download the file and save it, a Windows kernel pointer dereference to an invalid address is executed. As a result, a Windows Bugcheck (Kernel crash) occurs. On Windows 10, after the system is rebooted, a kernel crash occurs when users login it. This results in the Windows 10 kernel crashing in a loop.

In this blog, I want to share my detailed analysis of this vulnerability.

Analysis

To reproduce this remote kernel crash vulnerability, you can open IE or Edge on Windows 10, enter the url http://192.168.0.111/poc.dll (it can be any url where the PoC file is hosted), then choose “save” in the pop-up window. When the file poc.dll is saved, you can see that Windows 10 bugcheck (kernel crash) occurs as a result. For the kernel crash in Windows 10, the kernel crash continues to occur even if it’s rebooted, which results in Windows 10 not working any more. For users, the system may need to be re-installed.

Following is the call stack when the crash occurs.

Figure 1. Call stack when the crash occurs

From the above call stack output, we can see that the kernel crash occurs when calling the function “KERNELBASE!GetFileVersionInfoSizeExW”, which then calls the function “KERNELBASE!LoadLibraryExW”. Finally, it results in a complete kernel crash.

When IE/Edge downloads a .dll or .lib file and saves on disk, it will call the function "KERNELBASE!GetFileVersionInfoSizeExW" to retrieve the .dll/.lib version information. To get the .dll/.lib version information, it then calls the function “KERNELBASE!LoadLibraryExW” to load the .dll/.lib file with dwFlags equaling 0x22. Searching in Microsoft MSDN, we can see that dwFlags 0x22 is the combination of "LOAD_LIBRARY_AS_DATAFILE(0x00000002)" and "LOAD_LIBRARY_AS_IMAGE_RESOURCE(0x00000020)". So IE/Edge loads the downloaded .dll/.lib file as a resource .dll/.lib and data file to retrieve related info. Due to the crafted poc.dll, after the kernel crash in Windows 10 occurs, even rebooting can’t restore the system. This is because the .dll/.lib file in IE/Edge temporary directory is scanned when users login Windows.

The function LoadLibraryExW loads the crafted PoC file poc.dll. When it deals with SizeOfHeaders, it gets a crafted size of 0x06 (the correct size is 0x200). This results in an integer overflow when calculating the sha1 hash block size in the function CI!CipImageGetImageHash in CI.dll. The overflowed block size is 0xfffffeb6. With the overflowed block size 0xfffffe7a, which is obtained after calculation, calling the function CI!SymCryptSha1AppendBlocks—due to the overlarge crafted sha1 block size—results in a large loop and a kernel memory read access violation. As a result, a Windows bugcheck (kernel crash) occurs.

Figure 2. poc.dll containing crafted SizeOfHeaders

By reverse engineering and tracing, we can see that the call of the function _CipImageGetImageHash results in a sha1 block size integer overflow.

PAGE:85D15618 _CipImageGetImageHash@36 proc near      ; CODE XREF:

......

PAGE:85D1571F                 mov     edx, edi

PAGE:85D15721                 mov     ecx, [ebp+arg_4]

PAGE:85D15724                 call    _HashpHashBytes@12 ; HashpHashBytes(x,x,x)

PAGE:85D15729                 lea     edx, [esi+0A0h]

PAGE:85D1572F

PAGE:85D1572F loc_85D1572F:                           ; CODE XREF: CipImageGetImageHash(x,x,x,x,x,x,x,x,x)+CF↑j

PAGE:85D1572F                 mov     edi, [ebp+arg_10]

PAGE:85D15732                 mov     eax, [edi+54h]  ; ----->  here [edi+54h] is obtained from poc.dll at offset 0x104, its value is 0x06.

PAGE:85D15735                 sub     eax, edx        ; ----->  here edx=83560150

PAGE:85D15737                 add     eax, [ebp+BaseAddress]  ----> here [ebp+BaseAddress]=83560000

PAGE:85D1573A                 push    eax             ;  ---------> So, after the above calculation, eax occurs integer subtraction overflow, result in eax=fffffeb6

PAGE:85D1573B                 mov     ecx, [ebp+arg_4]

PAGE:85D1573E                 call    _HashpHashBytes@12 ------> the function call chain finally results in a kernel crash

PAGE:85D15743                 mov     esi, [edi+54h]  ;

PAGE:85D15746                 mov     [ebp+var_30], esi

In following function, an insufficient bounds check is performed:

.text:85D0368C @SymCryptHashAppendInternal@16 proc near

.text:85D0368C                                         ; CODE XREF: SymCryptSha1Append(x,x,x)+10↑p

.text:85D0368C                                         ; SymCryptMd5Append(x,x,x)+10↑p

.text:85D0368C

.text:85D0368C var_18          = dword ptr -18h

.text:85D0368C var_14          = dword ptr -14h

.text:85D0368C var_10          = dword ptr -10h

.text:85D0368C var_C           = dword ptr -0Ch

.text:85D0368C var_8           = dword ptr -8

.text:85D0368C var_4           = dword ptr -4

.text:85D0368C Src             = dword ptr  8

.text:85D0368C MaxCount        = dword ptr  0Ch

.text:85D0368C

.text:85D0368C                 mov     edi, edi

.text:85D0368E                 push    ebp        

.text:85D0368F                 mov     ebp, esp     .

......

85D0372D                 mov     ecx, [ebp+var_8]

.text:85D03730                 mov     edx, [ebp+var_18]

.text:85D03733                 jmp     short loc_85D0373B

.text:85D03735 ; ---------------------------------------------------------------------------

.text:85D03735

.text:85D03735 loc_85D03735:                           ; CODE XREF: SymCryptHashAppendInternal(x,x,x,x)+46↑j

.text:85D03735                                         ; SymCryptHashAppendInternal(x,x,x,x)+52↑j

.text:85D03735                 mov     ecx, [ebp+Src]

.text:85D03738                 mov     [ebp+var_8], ecx

.text:85D0373B

.text:85D0373B loc_85D0373B:                           ; CODE XREF: SymCryptHashAppendInternal(x,x,x,x)+A7↑j

.text:85D0373B                 cmp     esi, [edx+18h]  ; ----> here [edx+18h] equals 40h, esi equals fffffe7a, due to unsigned integer comparison, the crafted block size is not found

.text:85D0373E                 jb      short loc_85D03769

.text:85D03740                 mov     edi, [edx+1Ch]

.text:85D03743                 lea     eax, [ebp+var_C]

.text:85D03746                 push    eax

.text:85D03747                 push    esi

.text:85D03748                 mov     esi, [edx+0Ch]

.text:85D0374B                 add     edi, ebx

.text:85D0374D                 mov     ecx, esi

.text:85D0374F                 call    ds:___guard_check_icall_fptr ; _guard_check_icall_nop(x)

.text:85D03755                 mov     edx, [ebp+var_8]

.text:85D03758                 mov     ecx, edi

.text:85D0375A                 call    esi       

With the overflowed sha1 block size, it finally calls the following function:    

.text:85D01060 @SymCryptSha1AppendBlocks@16 proc near  ; CODE XREF: SymCryptSha1Result(x,x)+40↑p

......

.text:85D010A4                 mov     eax, [ebp+arg_0] -----> here eax gets the overflowed sha1 block size= 0xfffffe7a

.text:85D010A7                 mov     [esp+0D0h+var_B4], edi

.text:85D010AB                 mov     [esp+0D0h+var_C4], ecx

.text:85D010AF                 cmp     eax, 40h

.text:85D010B2                 jb      loc_85D02507

.text:85D010B8                 mov     [esp+0D0h+var_58], ecx

.text:85D010BC                 mov     ecx, [esp+0D0h+var_C0]

.text:85D010C0                 mov     [esp+0D0h+var_54], ecx

.text:85D010C4                 lea     ecx, [edx+8]    ;

.text:85D010C7                 shr     eax, 6    -------> the overflowed block size is used as the following loop function counter

.text:85D010CA                 mov     [esp+0D0h+var_60], esi

.text:85D010CE                 mov     [esp+0D0h+var_5C], edi

.text:85D010D2                 mov     [esp+0D0h+var_68], ecx ;

.text:85D010D6                 mov     [esp+0D0h+var_50], eax -----> here is the loop counter

......

.text:85D01359                 ror     edx, 2

.text:85D0135C                 mov     ecx, [ecx+28h]

.text:85D0135F                 bswap   ecx

.text:85D01361                 mov     [esp+0D0h+var_6C], ecx

.text:85D01365                 mov     ecx, eax

.text:85D01367                 rol     ecx, 5

.text:85D0136A                 mov     eax, edi

.text:85D0136C                 add     ecx, [esp+0D0h+var_6C]

.text:85D01370                 xor     eax, edx

.text:85D01372                 and     eax, [esp+0D0h+var_C0]

.text:85D01376                 xor     eax, edi

.text:85D01378                 add     edi, 5A827999h

.text:85D0137E                 add     eax, ecx

.text:85D01380                 mov     ecx, [esp+0D0h+var_68]

.text:85D01384                 add     eax, esi

.text:85D01386                 mov     esi, [esp+0D0h+var_C0]

.text:85D0138A                 mov     [esp+0D0h+var_84], eax

.text:85D0138E                 ror     esi, 2

.text:85D01391                 mov     ecx, [ecx+2Ch]  ----> after a large loop call, here it results in a read access violation, so the bugcheck (kernel crash) occurs 

.text:85D01394                 bswap   ecx

.text:85D01396                 mov     [esp+0D0h+var_9C], ecx

.......

.text:85D024DD                 mov     ecx, [esp+0D0h+var_68]

.text:85D024E1                 mov     [esp+0D0h+var_54], eax

.text:85D024E5                 add     ecx, 40h  ----> memory access pointer increases 0x40 in each loop

.text:85D024E8                 mov     [esp+0D0h+var_C0], eax

.text:85D024EC                 mov     eax, [ebp+arg_0]

.text:85D024EF                 sub     eax, 40h

.text:85D024F2                 mov     [esp+0D0h+var_68], ecx

.text:85D024F6                 sub     [esp+0D0h+var_50], 1  ------> here the loop counter decreases by 1, not equaling 0, to continue the loop. Due to the overflowed large sha1 block size, here a large loop is executed.

.text:85D024FE                 mov     [ebp+arg_0], eax

.text:85D02501                 jnz     loc_85D010DD

.text:85D02507

From the above analysis, we can see that the root cause of the remote kernel crash is that the function LoadLibraryEx fails to correctly parse a crafted .dll/.lib file as resource and data file. When the poc.dll includes a crafted SizeOfHeaders value 0x06 (the correct value should be 0x200) which is located at offset 0x104 in the PoC file, an integer overflow occurs.

The crafted size value results in a wrong sha1 block size being calculated (it becomes a negative value). Due to an insufficient bounds check, the sha1 calculation function enters a large loop, which results in a memory read access violation. Finally, the system bugcheck (kernel crash) occurs

Solution

All users of vulnerable Microsoft Windows are encouraged to upgrade to the latest Windows version or apply the latest patches. Additionally, organizations that have deployed Fortinet IPS solutions are already protected from this vulnerability with the following signature:

MS.Windows.Code.Integrity.Module.DoS

 



Check out our latest Quarterly Threat Landscape Report for more details about recent threats.

Sign up for our weekly FortiGuard intel briefs or for our FortiGuard Threat Intelligence Service.