原文:https://www.fortinet.com/blog/threat-research/microsoft-windows-remote-kernel-crash-vulnerability.html?utm_source=social

概述


在2018年1月底,我们在Microsoft Windows系统中发现了一个远程内核崩溃漏洞,并根据认真负责的披露流程向微软报告了这个安全问题。6月12日,微软发布了一份包含该漏洞修复程序的公告,并将该漏洞标识为CVE-2018-1040。

实际上,这个内核崩溃漏洞位于Microsoft Windows代码完整性内核模块“ci.dll”中。并且,目前所有流行的Windows版本都受该漏洞影响,这些版本包括Windows 10、Windows 7、Windows 8.1、Windows Server 2008、Windows Server 2012和Windows Server 2016。

这个漏洞可以通过从网站或SMB共享远程下载精心制作的.dll或.lib文件来触发。使用IE或Edge浏览器下载并保存漏洞触发文件时,会执行一个让Windows内核指针取消引用无效地址的操作,从而导致Windows Bugcheck(内核崩溃)。对于Windows 10来说,系统重新启动后,用户登录时又会发生内核崩溃,从而进入一个死循环。

在本文中,我们将同读者一道对这个漏洞进行详细的分析。

漏洞分析


为了重现该远程内核崩溃漏洞,可以在Windows 10上打开IE或Edge浏览器,并在地址栏输入http://192.168.0.111/poc.dll(可以是任何托管PoC文件的URL),然后在弹出的窗口中选择“保存”。当保存文件poc.dll时,就会出现Windows 10蓝屏死现象(内核崩溃)。对于Windows 10中来说,如果内核发生崩溃,即使重新启动,内核还会继续崩溃,从而导致Windows 10机器无法正常工作。对于用户来说,如果遇到这种情况,那只好重装系统了。

以下是发生崩溃时的调用堆栈情况。

图1. 发生崩溃时的调用堆栈

从上面的调用堆栈输出可以看到,内核崩溃出现在调用函数“KERNELBASE!GetFileVersionInfoSizeExW”的过程中,然后它会调用“KERNELBASE!LoadLibraryExW”函数。最后,导致内核彻底崩溃。

当IE/Edge下载.dll或.lib文件并将其保存到磁盘上时,将调用函数“KERNELBASE!GetFileVersionInfoSizeExW”来获取.dll/.lib文件的版本信息。为了获取.dll/.lib文件的版本信息,它会调用函数“KERNELBASE!LoadLibraryExW”来加载.dll/.lib文件,这时dwFlags等于0x22。如果考察Microsoft的MSDN文档,我们可以看到dwFlags 0x22是“LOAD_LIBRARY_AS_DATAFILE(0x00000002)”和“LOAD_LIBRARY_AS_IMAGE_RESOURCE(0x00000020)”的组合。因此,IE/Edge会将下载的.dll/.lib文件作为一个资源.dll/.lib和数据文件进行加载来检索相关信息。对于我们进行制作的这个poc.dll来说,在Windows 10发生内核崩溃后,即使重新启动也无法恢复系统,这是因为用户登录Windows时,会扫描IE/Edge临时目录中的.dll/.lib文件。

函数LoadLibraryExW会加载我们精心构造的PoC文件,即poc.dll。当它处理SizeOfHeaders时,得到的大小为0x06,这是我们精心构造的一个值,实际上正确的大小是0x200。这样,在计算CI.dll文件中的CI!CipImageGetImageHash函数中的sha1散列块大小时,会导致整数溢出。溢出的块大小为0xfffffeb6。当溢出块大小为0xfffffe7a(这是经过计算后获得的)时,由于精心制作的sha1块大小过大,所以调用函数CI!SymCryptSha1AppendBlocks会导致大循环和内核内存读取访问冲突,致使系统内核崩溃。

图2.包含精心制作的SizeOfHeaders的poc.dll

通过逆向工程和跟踪,我们可以看到,对函数_CipImageGetImageHash的调用导致sha1块大小整数溢出。

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

在以下函数中,执行的边界检查不够充分:

.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

使用溢出的sha1块大小后,最终会调用以下函数:

.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

通过上面的分析我们可以看到,远程内核崩溃的根本原因是LoadLibraryEx函数无法将精心构造的.dll/.lib文件正确解析为资源和数据文件。如果poc.dll包含精心设计的SizeOfHeaders值0x06(正确的值应为0x200),并且该值位于PoC文件中偏移量为0x104处时,就会发生整数溢出。

我们精心设计的大小值,会导致系统算出错误的sha1块大小(一个负值)。由于边界检查不够充分,所以sha1计算函数将进入一个非常大的循环中,导致与内存读取访问发生冲突。最后,导致系统蓝屏(内核崩溃)

解决方案


建议所有受该漏洞影响的Microsoft Windows用户升级到最新的Windows版本或应用最新的修补程序。

源链接

Hacking more

...