微软SMBv3远程代码执行漏洞分析(CVE-2020-0796)

0x01 漏洞描述

Microsoft通过Microsoft Server Message Block 3.1.1(SMBv3)协议处理某些请求的方式中存在一个远程执行代码漏洞,成功利用此漏洞的攻击者可以在目标SMB服务器或SMB客户端上执行代码。

安全更新

Windows 10 Version 1903 for 32-bit Systems                  Remote Code Execution   Critical    
Windows 10 Version 1903 for ARM64-based Systems             Remote Code Execution   Critical    
Windows 10 Version 1903 for x64-based Systems               Remote Code Execution   Critical    
Windows 10 Version 1909 for 32-bit Systems                  Remote Code Execution   Critical    
Windows 10 Version 1909 for ARM64-based Systems             Remote Code Execution   Critical    
Windows 10 Version 1909 for x64-based Systems               Remote Code Execution   Critical    
Windows Server, version 1903 (Server Core installation)     Remote Code Execution   Critical    
Windows Server, version 1909 (Server Core installation)     Remote Code Execution   Critical

解决方法

禁用SMBv3压缩。可以使用以下PowerShell命令禁用压缩功能,以阻止未经身份验证的攻击者利用SMBv3服务器的漏洞:
Set-ItemProperty -Path“ HKLM:\ SYSTEM \ CurrentControlSet \ Services \ LanmanServer \ Parameters” DisableCompression -Type DWORD -Value 1 -Force

0x02 静态分析

用IDA加载srv2.sys后分析,通过MS符号服务器加载srv2.sys符号文件,解析出一些函数:

  • Srv2DecompressMessageAsync
  • Srv2DecompressData
  • Smb2GetHonorCompressionAlgOrder
  • Smb2SelectCompressionAlgorithm
  • Smb2ValidateCompressionCapabilities

SMB v3中支持数据压缩,如果SMB Header中的ProtocolId是0xFC, ‘S’, ‘M’, ‘B’,说明数据是压缩的,这时smb会调用解压函数来处理,smb会调用srv2!Srv2ReceiveHandler函数接收smb数据包,并根据ProtocoII调用对应的处理函数。

__int64 __fastcall Srv2ReceiveHandler(__int64 a1, void *Src, __int64 a3, unsigned int a4, unsigned int *a5, char *Srca, struct _SLIST_ENTRY *a7, _QWORD *a8)
{
  ......
  Size = a4;
  v8 = a4;
  v73 = 0;
  v9 = (char *)Src;
  v10 = 2;
  v11 = -1i64;
  *a8 = 0i64;
  if ( WPP_GLOBAL_Control != (PDEVICE_OBJECT)&WPP_GLOBAL_Control
    && HIDWORD(WPP_GLOBAL_Control->Timer) & 0x200
    && BYTE1(WPP_GLOBAL_Control->Timer) >= 2u )
  {
    v70 = Src;
    WPP_SF_DqDq(WPP_GLOBAL_Control->AttachedDevice);
  }
  if ( v8 < 4 )
    goto LABEL_15;
  switch ( *(_DWORD *)Srca )
  {
    case 0x424D53FE:
      if ( v8 < 0x40 || *((_WORD *)Srca + 6) >= 0x13u )
        goto LABEL_15;
      goto LABEL_21;
    case 0x424D53FC:
      v12 = v8 < 0x10;
      break;
    case 0x424D53FD:
      v12 = v8 < 0x34;
      break;
    default:
      if ( *(_DWORD *)Srca != 1112364031 || v8 < 0x21 || Srca[4] != 114 )
        goto LABEL_15;
LABEL_21:
      v15 = __readgsdword(0x1A4u);
      v16 = *(_DWORD *)qword_1C0046110 - 1;
      if ( (unsigned int)v15 + 1 < *(_DWORD *)qword_1C0046110 )
        v16 = v15 + 1;
      v17 = v16;
      v18 = *((_QWORD *)qword_1C0046110 + 4);
      v19 = *(_QWORD *)(v18 + 8 * v17);
      if ( !*(_BYTE *)(v19 + 112) )
        PplpLazyInitializeLookasideList(qword_1C0046110, *(_QWORD *)(v18 + 8 * v17));
      ++*(_DWORD *)(v19 + 20);
      v20 = ExpInterlockedPopEntrySList((PSLIST_HEADER)v19);
     }

产生整数溢出漏洞的代码在srv2!Srv2DecompressData函数中:

__int64 __fastcall Srv2DecompressData(__int64 _smb_packet)
{
  __int64 smb_packet; // rdi
  __int64 _header; // rax
  SMB_V2_COMPRESSION_TRANSFORM_HEADER v3; // xmm0
  AAA smb_header_compress; // xmm0_8
  unsigned int CompressionAlgorithm; // ebp
  __int64 __alloc_buffer; // rax
  __int64 __allocated_buffer; // rbx
  int PayloadSize; // eax
  SMB_V2_COMPRESSION_TRANSFORM_HEADER Header; // [rsp+30h] [rbp-28h]
  int UncompressedSize; // [rsp+60h] [rbp+8h]

  UncompressedSize = 0;
  smb_packet = _smb_packet;
  _header = *(_QWORD *)(_smb_packet + 0xF0);

  // 检测数据包长度,_header + 0x24是数据包长度
  if ( *(_DWORD *)(_header + 0x24) < sizeof(SMB_V2_COMPRESSION_TRANSFORM_HEADER) )
    return 0xC000090Bi64;

  //_header + 0x18 是客户端传进来的Buffer
  v3 = *(SMB_V2_COMPRESSION_TRANSFORM_HEADER *)*(_QWORD *)(_header + 0x18);
  Header = v3;

  //检查使用的压缩算法是否与NEGOTIATE_PACKET序列中协商的算法相同
  *(__m128i *)&smb_header_compress.Algo = _mm_srli_si128(
                                            (__m128i)v3,
                                            offsetof(SMB_V2_COMPRESSION_TRANSFORM_HEADER, CompressionAlgorithm));
  CompressionAlgorithm = *(_DWORD *)(*(_QWORD *)(*(_QWORD *)(_smb_packet + 80) + 496i64) + 140i64);
  if ( CompressionAlgorithm != (unsigned __int16)smb_header_compress.Algo )
    return 0xC00000BBi64;

  // 这里没有检查相加的值,导致整数溢出,分配了一个较小的 __alloc_buffer
  __alloc_buffer = SrvNetAllocateBuffer(
        (unsigned int)(
            Header.OriginalCompressedSegmentSize + smb_header_compress.OffsetOrLength),
        0i64
  );

  __allocated_buffer = __alloc_buffer;
  if ( !__alloc_buffer )
    return 0xC000009Ai64;

  // 在新分配的缓冲区中解压缩数据,并检查未压缩的大小是否等于Header.OriginalCompressedSegmentSize中填写的大小
  if ( (int)SmbCompressionDecompress(
              CompressionAlgorithm,
              (BYTE *)(*(_QWORD *)(*(_QWORD *)(smb_packet + 240) + 24i64) + (unsigned int)Header.OffsetOrLength
                                                                          + 0x10i64),
              *(_DWORD *)(*(_QWORD *)(smb_packet + 240) + 36i64) - Header.OffsetOrLength - 0x10,
              (BYTE *)((unsigned int)Header.OffsetOrLength + *(_QWORD *)(__alloc_buffer + 0x18)),
              Header.OriginalCompressedSegmentSize,
              &UncompressedSize) < 0
    || (PayloadSize = UncompressedSize, UncompressedSize != Header.OriginalCompressedSegmentSize) )
  {
    SrvNetFreeBuffer(__allocated_buffer);
    return 0xC000090Bi64;
  }

  // Copy optional payload
  if ( Header.OffsetOrLength )
  {
    memmove(
      *(void **)(__allocated_buffer + 0x18),
      (const void *)(*(_QWORD *)(*(_QWORD *)(smb_packet + 240) + 24i64) + 0x10i64),
      (unsigned int)Header.OffsetOrLength);
    PayloadSize = UncompressedSize;
  }


  *(_DWORD *)(__allocated_buffer + 36) = Header.OffsetOrLength + PayloadSize;
  Srv2ReplaceReceiveBuffer(smb_packet, __allocated_buffer);
  return 0i64;
}

Srv2DecompressData函数主要有三个功能:分配缓冲区,解压缩数据,复制payload。

以下资料中有Smb数据包的详细信息:

  1. https://interopevents.blob.core.windows.net/uploads/PDFs/2019/Redmond/Talpey-SMB3doc-19H1-DevDays%20Redmond%202019.pdf
  2. https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/5606ad47-5ee0-437a-817e-70c366052962
  3. https://patchwork.kernel.org/patch/11014449/https://patchwork.kernel.org/patch/11014449/)

漏洞点就在下面没有检查相加的值,导致出现整数溢出,SrvNetAllocateBuffer分配了一个较小的 __alloc_buffer:

__alloc_buffer  =  SrvNetAllocateBuffer(
  (unsigned  int )(Header.OriginalCompressedSegmentSize  +  smb_header_compress.OffsetOrLength),
  0 i64 
);

OriginalCompressedSegmentSizeOffsetOrLength这两个字段是可控的,第一个字段描述未压缩数据的大小,第二个字段用于连接压缩的smb数据包(请参阅[MS-SMB2]规范2.2.42):

1584104848084

OriginalCompressedSegmentSizeOffsetOrLength是32位的值,srv2!Srv2DecompressData函数会分配新的缓冲区,在整数溢出之前将数据添加到buffer中。

对应的汇编代码如下:

00000001C0017EB2  movq    rcx, xmm0
...
00000001C0017EC8  mov     rax, qword ptr [rsp+58h+Header.ProtocolId]
00000001C0017ECD  xor     edx, edx
00000001C0017ECF  shr     rax, 20h ;  OriginalCompressedSegmentSize
00000001C0017ED3  shr     rcx, 20h ;  OffsetOrLength
00000001C0017ED7  add     ecx, eax
00000001C0017ED9  call    cs:__imp_SrvNetAllocateBuffer

0x03 动态分析

存在漏洞的系统是Windows 10 1903或1909 Windows,触发漏洞需要SMB v3.1.1完整压缩功能的SMB客户端。

https://github.com/microsoft/WindowsProtocolTestSuites/

上面项目是中有很多Windows协议数据包,比如SMB服务器/客户端,RDP服务器/客户端,Kerberos服务器,SMBD服务器等的完整实现,通过此项目创建了一个数据包,可以编写一个类似PoC的数据包用于动态调试。

.\WindowsProtocolTestSuites\ProtoSDK\MS-SMB2\Common\Smb2Compression.cs

发送Smb2Compression.cs数据包后可以用进行windbg调试,整数溢出上下文如下:

Breakpoint 3 hit
srv2!Srv2DecompressData+0x6f:
fffff800`50ad7ecf 48c1e820        shr     rax,20h
kd> p
srv2!Srv2DecompressData+0x73:
fffff800`50ad7ed3 48c1e920        shr     rcx,20h
kd> 
srv2!Srv2DecompressData+0x77:
fffff800`50ad7ed7 03c8            add     ecx,eax
kd> r eax
eax=7e
kd> r ecx
ecx=ffffffff
kd> p
srv2!Srv2DecompressData+0x79:
fffff800`50ad7ed9 4c8b15489a0200  mov     r10,qword ptr [srv2!_imp_SrvNetAllocateBuffer (fffff800`50b01928)]
kd> r ecx
ecx=7d
kd> p
srv2!Srv2DecompressData+0x80:
fffff800`50ad7ee0 e8fbe29704      call    srvnet!SrvNetAllocateBuffer (fffff800`554561e0)
kd> p
srv2!Srv2DecompressData+0x85:
fffff800`50ad7ee5 488bd8          mov     rbx,rax
kd> g
KDTARGET: Refreshing KD connection

*** Fatal System Error: 0x00000050
                       (0xFFFF8483C09E7E2F,0x0000000000000000,0xFFFFF80051A0E750,0x0000000000000002)

A fatal system error has occurred.
Debugger entered on first try; Bugcheck callbacks have not been invoked.

A fatal system error has occurred.

For analysis of this file, run !analyze -v
nt!DbgBreakPointWithStatus:
fffff800`51a79580 cc              int     3
kd> !analyze -v
Connected to Windows 10 18362 x64 target at (Wed Mar 11 18:06:55.585 2020 (UTC + 1:00)), ptr64 TRUE
*******************************************************************************
*                                                                             *
*                        Bugcheck Analysis                                    *
*                                                                             *
*******************************************************************************

PAGE_FAULT_IN_NONPAGED_AREA (50)
Invalid system memory was referenced.  This cannot be protected by try-except.
Typically the address is just plain bad or it is pointing at freed memory.
Arguments:
Arg1: ffff8483c09e7e2f, memory referenced.
Arg2: 0000000000000000, value 0 = read operation, 1 = write operation.
Arg3: fffff80051a0e750, If non-zero, the instruction address which referenced the bad memory
  address.
Arg4: 0000000000000002, (reserved)

Debugging Details:
------------------

READ_ADDRESS:  ffff8483c09e7e2f Nonpaged pool

TRAP_FRAME:  fffff105d6992c00 -- (.trap 0xfffff105d6992c00)
NOTE: The trap frame does not contain all registers.
Some register values may be zeroed or incorrect.
rax=fffff80051a0e700 rbx=0000000000000000 rcx=ffff8483bccd204f
rdx=ffff8483bccd204f rsi=0000000000000000 rdi=0000000000000000
rip=fffff80051a0e750 rsp=fffff105d6992d98 rbp=ffff8483bccd204f
 r8=ffff8483c09e7e2f  r9=0000000000000078 r10=ffff8483bccd1f6d
r11=ffff8483c09e7ea7 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl zr na po nc
nt!RtlDecompressBufferXpressLz+0x50:
fffff800`51a0e750 418b08          mov     ecx,dword ptr [r8] ds:ffff8483`c09e7e2f=????????
Resetting default scope

STACK_TEXT:  
fffff105`d69921b8 fffff800`51b5b492 : nt!DbgBreakPointWithStatus
fffff105`d69921c0 fffff800`51b5ab82 : nt!KiBugCheckDebugBreak+0x12
fffff105`d6992220 fffff800`51a71917 : nt!KeBugCheck2+0x952
fffff105`d6992920 fffff800`51ab5b0a : nt!KeBugCheckEx+0x107
fffff105`d6992960 fffff800`5197e1df : nt!MiSystemFault+0x18fafa
fffff105`d6992a60 fffff800`51a7f69a : nt!MmAccessFault+0x34f
fffff105`d6992c00 fffff800`51a0e750 : nt!KiPageFault+0x35a
fffff105`d6992d98 fffff800`5191c666 : nt!RtlDecompressBufferXpressLz+0x50
fffff105`d6992db0 fffff800`5546e0bd : nt!RtlDecompressBufferEx2+0x66
fffff105`d6992e00 fffff800`50ad7f41 : srvnet!SmbCompressionDecompress+0xdd
fffff105`d6992e70 fffff800`50ad699e : srv2!Srv2DecompressData+0xe1
fffff105`d6992ed0 fffff800`50b19a7f : srv2!Srv2DecompressMessageAsync+0x1e
fffff105`d6992f00 fffff800`51a7504e : srv2!RfspThreadPoolNodeWorkerProcessWorkItems+0x13f
fffff105`d6992f80 fffff800`51a7500c : nt!KxSwitchKernelStackCallout+0x2e
fffff105`d52cf8f0 fffff800`5197545e : nt!KiSwitchKernelStackContinue
fffff105`d52cf910 fffff800`5197525c : nt!KiExpandKernelStackAndCalloutOnStackSegment+0x18e
fffff105`d52cf9b0 fffff800`519750d3 : nt!KiExpandKernelStackAndCalloutSwitchStack+0xdc
fffff105`d52cfa20 fffff800`5197508d : nt!KeExpandKernelStackAndCalloutInternal+0x33
fffff105`d52cfa90 fffff800`50b197d7 : nt!KeExpandKernelStackAndCalloutEx+0x1d
fffff105`d52cfad0 fffff800`51fc54a7 : srv2!RfspThreadPoolNodeWorkerRun+0x117
fffff105`d52cfb30 fffff800`519e5925 : nt!IopThreadStart+0x37
fffff105`d52cfb90 fffff800`51a78d5a : nt!PspSystemThreadStartup+0x55
fffff105`d52cfbe0 00000000`00000000 : nt!KiStartSystemThread+0x2a

0x04 补丁分析

3月11日ADV200005漏洞泄漏后,微软在2020年3月12日发布了补丁程序(KB4551762),该补丁程序修复了分配大小中的整数溢出:

修复前代码:

__alloc_buffer  =  SrvNetAllocateBuffer(
  (unsigned  int )(Header.OriginalCompressedSegmentSize  +  smb_header_compress.OffsetOrLength),
  0 i64 
);

修复后代码:

unsigned int _v_allocation_size = 0;

  if (!NT_SUCCESS(RtlUlongAdd(Header.OriginalCompressedSegmentSize, smb_header_compress.OffsetOrLength, &_v_allocation_size)))
  {
    SEND_SOME_ETW_EVENT_FOR_TELEMETRY_AND_CATCHING_BAD_GUYS(&wpp_guid);
    goto ON_ERROR;
  }

  if (_v_allocation_size > another_smb_size_i_guess)
  {
    SEND_SOME_ETW_EVENT_FOR_TELEMETRY_AND_CATCHING_BAD_GUYS(&wpp_guid);
    goto ON_ERROR;
  }

  __alloc_buffer = SrvNetAllocateBuffer(
    _v_allocation_size,
    0i64
  );
  if ( !__alloc_buffer )
    return 0xC000009A;

  if (!NT_SUCCESS(RtlULongSub(_v_allocation_size, smb_header_compress.OffsetOrLength, &_v_uncompressed_size)))
  {
    SEND_SOME_ETW_EVENT_FOR_TELEMETRY_AND_CATCHING_BAD_GUYS(&wpp_guid);
    goto ON_ERROR;
  }

  if (!NT_SUCCESS(SmbCompressionDecompress(
              AlgoId,
              (BYTE *)(*(_QWORD *)(*(_QWORD *)(smb_packet + 240) + 24i64) + (unsigned int)Header.OffsetOrLength
                                                                          + 0x10i64),
              _v_uncompressed_size,
              Size.m128i_u32[3] + *(_QWORD *)(v10 + 24),
              Header.OriginalCompressedSegmentSize,
              &UncompressedSize)) < 0
    || (PayloadSize = UncompressedSize, UncompressedSize != Header.OriginalCompressedSegmentSize) )

RtlUlongAddRtlULongSub运行时做了整数溢出和下溢检查。

0x05 分析总结

此漏洞除了主动发送攻击SMB数据包外,由于上层应用程序对SMB协议的支持,导致很多主流的应用如Office文档、IE浏览器、文件夹(Desktop.ini)等都会受到漏洞影响。

此漏洞在大量主机上尚未修复,漏洞利用需要做到内核池溢出,必须在带有KALSR的Windows 10上远程利用,用户应该尽量暂时禁用SMB服务,避免在野利用攻击。


   转载规则


《微软SMBv3远程代码执行漏洞分析(CVE-2020-0796)》 0xbird 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
Windows RDP 协议堆溢出漏洞分析(CVE-2019-1181 & CVE-2019-1182) Windows RDP 协议堆溢出漏洞分析(CVE-2019-1181 & CVE-2019-1182)
2019年8月,微软公布了一系列RDP漏洞的补丁更新,其中两个是可蠕虫的RDP漏洞,从Windows 7到Windows 10,漏洞CVE-2019-1181和CVE-2019-1182会影响这之间的所有操作系统。 RDP客户端和服务器中都
2020-04-02
下一篇 
pppd中缓冲区溢出漏洞调试分析(CVE-2020-8597) pppd中缓冲区溢出漏洞调试分析(CVE-2020-8597)
0x01 漏洞描述此漏洞编号为CVE-2020-8597,未经身份验证的攻击者可以利用此漏洞在受影响的系统上远程执行任意代码并对其进行完全控制。 攻击者所需要做的就是向受攻击的ppp客户端或服务器发送未经请求的格式错误的EAP数据包。 此
2019-12-02
  目录