FortiGuard Labs Threat Research
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.
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.
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.