SMB远程代码执行漏洞(CVE-2020-0796)分析验证及加固
Posted a66666
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SMB远程代码执行漏洞(CVE-2020-0796)分析验证及加固相关的知识,希望对你有一定的参考价值。
这几天有点忙,CVE-2020-0796出来了,没静下心来关注一下,显得太不尊重这个漏洞了,今天周末,关注一下,水一篇。
一、漏洞描述
漏洞公告显示,SMB 3.1.1协议中处理压缩消息时,对其中数据没有经过安全检查,直接使用会引发内存破坏漏洞,可能被攻击者利用远程执行任意代码。攻击者利用该漏洞无须权限即可实现远程代码执行,受黑客攻击的目标系统只需开机在线即可能被入侵。
二、漏洞危害等级
高
三、影响版本
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)
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)
四、漏洞原理
漏洞发生在srv2.sys中,由于SMB没有正确处理压缩的数据包,在解压数据包的时候使用客户端传过来的长度进行解压时,并没有检查长度是否合法.最终导致整数溢出。
SMB v3中支持数据压缩,如果SMB Header中的ProtocolId为0x424D53FC也就是0xFC, ‘S‘, ‘M‘, ‘B‘.那么就说明数据是压缩的,这时smb会调用压缩解压处理的函数.
首先SMB会调用srv2!Srv2ReceiveHandler函数接收smb数据包,并根据ProtocoIId设置对应的处理函数。
__int64 __fastcall Srv2ReceiveHandler(__int64 a1, void *Src, __int64 a3, unsigned int a4, unsigned int *a5, char *Srca, struct _SLIST_ENTRY *a7, _QWORD *a8)
{
...
//
// 这里判断头部ProtocolId
//
if ( **((_DWORD **)&v20[15].Next[1].Next + 1) == ‘BMSxFC‘ )
{
if ( KeGetCurrentIrql() > 1u )
{
v20[14].Next = (_SLIST_ENTRY *)v11;
v20[2].Next = (_SLIST_ENTRY *)Srv2DecompressMessageAsync;
v43 = HIDWORD(v20->Next) == 5;
*((_DWORD *)&v20[3].Next + 2) = 0;
if ( v43 )
{
LOBYTE(v71) = 1;
LOBYTE(v35) = 1;
SRV2_PERF_ENTER_EX(&v20[32].Next + 1, v35, 307i64, "Srv2PostToThreadPool", (_DWORD)v71);
}
v44 = *((_QWORD *)&v20[3].Next[8].Next + 1);
v45 = *(_QWORD *)(v44 + 8i64 * KeGetCurrentNodeNumber() + 8);
if ( !ExpInterlockedPushEntrySList((PSLIST_HEADER)(v45 + 16), v20 + 1) && *(_WORD *)(v45 + 66) )
RfspThreadPoolNodeWakeIdleWorker(v45);
goto LABEL_168;
}
}
}
1
__int64 __fastcall Srv2ReceiveHandler(__int64 a1, void *Src, __int64 a3, unsigned int a4, unsigned int *a5, char *Srca, struct _SLIST_ENTRY *a7, _QWORD *a8)
2
{
3
4
//
5
// 这里判断头部ProtocolId
6
//
7
if ( **((_DWORD **)&v20[15].Next[1].Next + 1) == ‘BMSxFC‘ )
8
{
9
if ( KeGetCurrentIrql() > 1u )
10
{
11
v20[14].Next = (_SLIST_ENTRY *)v11;
12
v20[2].Next = (_SLIST_ENTRY *)Srv2DecompressMessageAsync;
13
v43 = HIDWORD(v20->Next) == 5;
14
*((_DWORD *)&v20[3].Next + 2) = 0;
15
if ( v43 )
16
{
17
LOBYTE(v71) = 1;
18
LOBYTE(v35) = 1;
19
SRV2_PERF_ENTER_EX(&v20[32].Next + 1, v35, 307i64, "Srv2PostToThreadPool", (_DWORD)v71);
20
}
21
v44 = *((_QWORD *)&v20[3].Next[8].Next + 1);
22
v45 = *(_QWORD *)(v44 + 8i64 * KeGetCurrentNodeNumber() + 8);
23
if ( !ExpInterlockedPushEntrySList((PSLIST_HEADER)(v45 + 16), v20 + 1) && *(_WORD *)(v45 + 66) )
24
RfspThreadPoolNodeWakeIdleWorker(v45);
25
goto LABEL_168;
26
}
27
}
28
}
产生整数溢出漏洞的代码如下:
__int64 __fastcall Srv2DecompressData(__int64 pData)
{
__int64 v2; // rax
COMPRESSION_TRANSFORM_HEADER Header; // xmm0 MAPDST
__m128i v4; // xmm0
unsigned int CompressionAlgorithm; // ebp
__int64 UnComparessBuffer; // rax MAPDST
int v9; // eax
int v11; // [rsp+60h] [rbp+8h]
v11 = 0;
v2 = *(_QWORD *)(pData + 0xF0);
if ( *(_DWORD *)(v2 + 0x24) < 0x10u ) // 这里判断数据包长度的最小值
return 0xC000090Bi64;
Header = *(COMPRESSION_TRANSFORM_HEADER *)*(_QWORD *)(v2 + 0x18);// [v2+0x18]中为客户端传进来的Buffer
// [v2+0x24]为数据包长度
v4 = _mm_srli_si128((__m128i)Header, 8);
CompressionAlgorithm = *(_DWORD *)(*(_QWORD *)(*(_QWORD *)(pData + 0x50) + 0x1F0i64) + 0x8Ci64);
if ( CompressionAlgorithm != v4.m128i_u16[0] )
return 0xC00000BBi64;
UnCompressBuffer = SrvNetAllocateBuffer((unsigned int)(Header.OriginalCompressedSegmentSize + v4.m128i_i32[1]), 0i64);// OriginalCompressedSegmentSize + CompressedSegmentSize,这里没有检查相加的值,导致整数溢出,分配一个较小的UnCompressBuffer
if ( !UnComparessBuffer )
return 0xC000009Ai64;
if ( (int)SmbCompressionDecompress(
CompressionAlgorithm, // CompressionAlgorithm
*(_QWORD *)(*(_QWORD *)(pData + 0xF0) + 0x18i64) + (unsigned int)Header.Length + 0x10i64,// CompressedBuffer
(unsigned int)(*(_DWORD *)(*(_QWORD *)(pData + 0xF0) + 0x24i64) - Header.Length - 0x10),// CompressedBufferSize
(unsigned int)Header.Length + *(_QWORD *)(UnComparessBuffer + 0x18),// UncompressedBuffer,会传入SmbCompressionDecompress函数进行Decompress处理。
Header.OriginalCompressedSegmentSize,
&v11) < 0
|| (v9 = v11, v11 != Header.OriginalCompressedSegmentSize) )
{
SrvNetFreeBuffer(UnComparessBuffer);
return 0xC000090Bi64;
}
if ( Header.Length )
{
memmove(
*(void **)(UnComparessBuffer + 24),
(const void *)(*(_QWORD *)(*(_QWORD *)(pData + 240) + 24i64) + 16i64),
(unsigned int)Header.Length);
v9 = v11;
}
*(_DWORD *)(UnComparessBuffer + 36) = Header.Length + v9;
Srv2ReplaceReceiveBuffer(pData, UnComparessBuffer);
return 0i64;
}
1
__int64 __fastcall Srv2DecompressData(__int64 pData)
2
{
3
__int64 v2; // rax
4
COMPRESSION_TRANSFORM_HEADER Header; // xmm0 MAPDST
5
__m128i v4; // xmm0
6
unsigned int CompressionAlgorithm; // ebp
7
__int64 UnComparessBuffer; // rax MAPDST
8
int v9; // eax
9
int v11; // [rsp+60h] [rbp+8h]
10
v11 = 0;
11
v2 = *(_QWORD *)(pData + 0xF0);
12
if ( *(_DWORD *)(v2 + 0x24) < 0x10u ) // 这里判断数据包长度的最小值
13
return 0xC000090Bi64;
14
Header = *(COMPRESSION_TRANSFORM_HEADER *)*(_QWORD *)(v2 + 0x18);// [v2+0x18]中为客户端传进来的Buffer
15
// [v2+0x24]为数据包长度
16
v4 = _mm_srli_si128((__m128i)Header, 8);
17
CompressionAlgorithm = *(_DWORD *)(*(_QWORD *)(*(_QWORD *)(pData + 0x50) + 0x1F0i64) + 0x8Ci64);
18
if ( CompressionAlgorithm != v4.m128i_u16[0] )
19
return 0xC00000BBi64;
20
UnCompressBuffer = SrvNetAllocateBuffer((unsigned int)(Header.OriginalCompressedSegmentSize + v4.m128i_i32[1]), 0i64);// OriginalCompressedSegmentSize + CompressedSegmentSize,这里没有检查相加的值,导致整数溢出,分配一个较小的UnCompressBuffer
21
if ( !UnComparessBuffer )
22
return 0xC000009Ai64;
23
if ( (int)SmbCompressionDecompress(
24
CompressionAlgorithm, // CompressionAlgorithm
25
*(_QWORD *)(*(_QWORD *)(pData + 0xF0) + 0x18i64) + (unsigned int)Header.Length + 0x10i64,// CompressedBuffer
26
(unsigned int)(*(_DWORD *)(*(_QWORD *)(pData + 0xF0) + 0x24i64) - Header.Length - 0x10),// CompressedBufferSize
27
(unsigned int)Header.Length + *(_QWORD *)(UnComparessBuffer + 0x18),// UncompressedBuffer,会传入SmbCompressionDecompress函数进行Decompress处理。
28
Header.OriginalCompressedSegmentSize,
29
&v11) < 0
30
|| (v9 = v11, v11 != Header.OriginalCompressedSegmentSize) )
31
{
32
SrvNetFreeBuffer(UnComparessBuffer);
33
return 0xC000090Bi64;
34
}
35
if ( Header.Length )
36
{
37
memmove(
38
*(void **)(UnComparessBuffer + 24),
39
(const void *)(*(_QWORD *)(*(_QWORD *)(pData + 240) + 24i64) + 16i64),
40
(unsigned int)Header.Length);
41
v9 = v11;
42
}
43
*(_DWORD *)(UnComparessBuffer + 36) = Header.Length + v9;
44
Srv2ReplaceReceiveBuffer(pData, UnComparessBuffer);
45
return 0i64;
46
}
五、漏洞检测
已经很多的验证脚本,整体的思路都是验证回包中的特定位置是否包含十六进制的x11x03或x02x00这两个关键字。
在存在漏洞的SMB版本的通信回包如下:
python3版POC
import socket
import struct
import sys
from netaddr import IPNetwork
pkt = b‘x00x00x00xc0xfeSMB@x00x00x00x00x00x00x00x00x00x1fx00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00$x00x08x00x01x00x00x00x7fx00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00xx00x00x00x02x00x00x00x02x02x10x02"x02$x02x00x03x02x03x10x03x11x03x00x00x00x00x01x00&x00x00x00x00x00x01x00 x00x01x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x03x00
x00x00x00x00x00x01x00x00x00x01x00x00x00x01x00x00x00x00x00x00x00‘
subnet = sys.argv[1]
for ip in IPNetwork(subnet):
sock = socket.socket(socket.AF_INET)
sock.settimeout(3)
try:
sock.connect(( str(ip), 445 ))
except:
sock.close()
continue
sock.send(pkt)
nb, = struct.unpack(">I", sock.recv(4))
res = sock.recv(nb)
if res[68:70] != b"x11x03" or res[70:72] != b"x02x00":
print(f"{ip} Not vulnerable.")
else:
print(f"{ip} Vulnerable")
1
import socket
2
import struct
3
import sys
4
from netaddr import IPNetwork
5
6
pkt = b‘x00x00x00xc0xfeSMB@x00x00x00x00x00x00x00x00x00x1fx00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00$x00x08x00x01x00x00x00x7fx00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00xx00x00x00x02x00x00x00x02x02x10x02"x02$x02x00x03x02x03x10x03x11x03x00x00x00x00x01x00&x00x00x00x00x00x01x00 x00x01x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x03x00
x00x00x00x00x00x01x00x00x00x01x00x00x00x01x00x00x00x00x00x00x00‘
7
8
subnet = sys.argv[1]
9
10
for ip in IPNetwork(subnet):
11
12
sock = socket.socket(socket.AF_INET)
13
sock.settimeout(3)
14
15
try:
16
sock.connect(( str(ip), 445 ))
17
except:
18
sock.close()
19
continue
20
21
sock.send(pkt)
22
23
nb, = struct.unpack(">I", sock.recv(4))
24
res = sock.recv(nb)
25
if res[68:70] != b"x11x03" or res[70:72] != b"x02x00":
26
print(f"{ip} Not vulnerable.")
27
else:
28
print(f"{ip} Vulnerable")
也可以使用nmap的脚本进行验证,依托nmap的强大框架,更方便。
local smb = require "smb"
local stdnse = require "stdnse"
local nmap = require "nmap"
description = [[
smb-protocols script modified to apply check for CVE-2020-0796 by psc4re.
Attempts to list the supported protocols and dialects of a SMB server.
Packet check based on https://github.com/ollypwn/SMBGhost/
The script attempts to initiate a connection using the dialects:
* NT LM 0.12 (SMBv1)
* 2.02 (SMBv2)
* 2.10 (SMBv2)
* 3.00 (SMBv3)
* 3.02 (SMBv3)
* 3.11 (SMBv3)
Additionally if SMBv1 is found enabled, it will mark it as insecure. This
script is the successor to the (removed) smbv2-enabled script.
]]
---
-- @usage nmap -p445 --script smb-protocols <target>
-- @usage nmap -p139 --script smb-protocols <target>
--
-- @output
-- | smb-protocols:
-- | dialects:
-- | NT LM 0.12 (SMBv1) [dangerous, but default]
-- | 2.02
-- | 2.10
-- | 3.00
-- | 3.02
-- |_ 3.11 (SMBv3.11) compression algorithm - Vulnerable to CVE-2020-0796 SMBGhost
--
-- @xmloutput
-- <table key="dialects">
-- <elem>NT LM 0.12 (SMBv1) [dangerous, but default]</elem>
-- <elem>2.02</elem>
-- <elem>2.10</elem>
-- <elem>3.00</elem>
-- <elem>3.02</elem>
-- <elem>3.11 (SMBv3.11) [Potentially Vulnerable to CVE-2020-0796 Coronablue]</elem>
-- </table>
---
author = "Paulino Calderon (Modified by Psc4re)"
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
categories = {"safe", "discovery"}
hostrule = function(host)
return smb.get_port(host) ~= nil
end
action = function(host,port)
local status, supported_dialects, overrides
local output = stdnse.output_table()
overrides = {}
status, supported_dialects = smb.list_dialects(host, overrides)
if status then
for i, v in pairs(supported_dialects) do -- Mark SMBv1 as insecure
if v == "NT LM 0.12" then
supported_dialects[i] = v .. " (SMBv1) [dangerous, but default]"
end
if v == "3.11" then
local msg
local response
local compresionalg
local comp
msg = ‘x00x00x00xc0xfeSMB@x00x00x00x00x00x00x00x00x00x1fx00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00$x00x08x00x01x00x00x00x7fx00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00xx00x00x00x02x00x00x00x02x02x10x02"x02$x02x00x03x02x03x10x03x11x03x00x00x00x00x01x00&x00x00x00x00x00x01x00 x00x01x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x03x00
x00x00x00x00x00x01x00x00x00x01x00x00x00x01x00x00x00x00x00x00x00‘
local socket = nmap.new_socket()
socket:set_timeout(3000)
socket:connect(host.ip,445)
socket:send(msg)
response,data = socket:receive()
compressionalg= string.sub(data,-2)
if compressionalg == "x01x00" then
comp = "LZNT1 compression algorithm - Vulnerable to CVE-2020-0796 SMBGhost"
elseif compressionalg == "x02x00" then
comp ="LZ77 compression algorithm - Vulnerable to CVE-2020-0796 SMBGhost"
elseif compressionalg == "x00x00" then
comp ="No Compression Not Vulnerable"
elseif compressionalg == "x03x00" then
comp="LZ77+Huffman compression algorithm - Vulnerable to CVE-2020-0796 SMBGhost"
end
supported_dialects[i] = v .." " .. comp
end
end
output.dialects = supported_dialects
end
if #output.dialects>0 then
return output
else
stdnse.debug1("No dialects were accepted")
if nmap.verbosity()>1 then
return "No dialects accepted. Something may be blocking the responses"
end
end
end
1
local smb = require "smb"
2
local stdnse = require "stdnse"
3
local nmap = require "nmap"
4
5
description = [[
6
smb-protocols script modified to apply check for CVE-2020-0796 by psc4re.
7
Attempts to list the supported protocols and dialects of a SMB server.
8
Packet check based on https://github.com/ollypwn/SMBGhost/
9
The script attempts to initiate a connection using the dialects:
10
* NT LM 0.12 (SMBv1)
11
* 2.02 (SMBv2)
12
* 2.10 (SMBv2)
13
* 3.00 (SMBv3)
14
* 3.02 (SMBv3)
15
* 3.11 (SMBv3)
16
Additionally if SMBv1 is found enabled, it will mark it as insecure. This
17
script is the successor to the (removed) smbv2-enabled script.
18
]]
19
20
---
21
-- @usage nmap -p445 --script smb-protocols <target>
22
-- @usage nmap -p139 --script smb-protocols <target>
23
--
24
-- @output
25
-- | smb-protocols:
26
-- | dialects:
27
-- | NT LM 0.12 (SMBv1) [dangerous, but default]
28
-- | 2.02
29
-- | 2.10
30
-- | 3.00
31
-- | 3.02
32
-- |_ 3.11 (SMBv3.11) compression algorithm - Vulnerable to CVE-2020-0796 SMBGhost
33
--
34
-- @xmloutput
35
-- <table key="dialects">
36
-- <elem>NT LM 0.12 (SMBv1) [dangerous, but default]</elem>
37
-- <elem>2.02</elem>
38
-- <elem>2.10</elem>
39
-- <elem>3.00</elem>
40
-- <elem>3.02</elem>
41
-- <elem>3.11 (SMBv3.11) [Potentially Vulnerable to CVE-2020-0796 Coronablue]</elem>
42
-- </table>
43
---
44
45
author = "Paulino Calderon (Modified by Psc4re)"
46
license = "Same as Nmap--See https://nmap.org/book/man-legal.html"
47
categories = {"safe", "discovery"}
48
49
hostrule = function(host)
50
return smb.get_port(host) ~= nil
51
end
52
53
action = function(host,port)
54
local status, supported_dialects, overrides
55
local output = stdnse.output_table()
56
overrides = {}
57
status, supported_dialects = smb.list_dialects(host, overrides)
58
if status then
59
for i, v in pairs(supported_dialects) do -- Mark SMBv1 as insecure
60
if v == "NT LM 0.12" then
61
supported_dialects[i] = v .. " (SMBv1) [dangerous, but default]"
62
end
63
if v == "3.11" then
64
local msg
65
local response
66
local compresionalg
67
local comp
68
msg = ‘x00x00x00xc0xfeSMB@x00x00x00x00x00x00x00x00x00x1fx00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00$x00x08x00x01x00x00x00x7fx00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00xx00x00x00x02x00x00x00x02x02x10x02"x02$x02x00x03x02x03x10x03x11x03x00x00x00x00x01x00&x00x00x00x00x00x01x00 x00x01x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x00x03x00
x00x00x00x00x00x01x00x00x00x01x00x00x00x01x00x00x00x00x00x00x00‘
69
local socket = nmap.new_socket()
70
socket:set_timeout(3000)
71
socket:connect(host.ip,445)
72
socket:send(msg)
73
response,data = socket:receive()
74
compressionalg= string.sub(data,-2)
75
if compressionalg == "x01x00" then
76
comp = "LZNT1 compression algorithm - Vulnerable to CVE-2020-0796 SMBGhost"
77
elseif compressionalg == "x02x00" then
78
comp ="LZ77 compression algorithm - Vulnerable to CVE-2020-0796 SMBGhost"
79
elseif compressionalg == "x00x00" then
80
comp ="No Compression Not Vulnerable"
81
elseif compressionalg == "x03x00" then
82
comp="LZ77+Huffman compression algorithm - Vulnerable to CVE-2020-0796 SMBGhost"
83
end
84
supported_dialects[i] = v .." " .. comp
85
end
86
end
87
output.dialects = supported_dialects
88
end
89
90
if #output.dialects>0 then
91
return output
92
else
93
stdnse.debug1("No dialects were accepted")
94
if nmap.verbosity()>1 then
95
return "No dialects accepted. Something may be blocking the responses"
96
end
97
end
98
end
六、漏洞加固
1. 更新,完成补丁的安装。
操作步骤:设置->更新和安全->Windows更新,点击“检查更新”。
2.微软给出了临时的应对办法:
运行regedit.exe,打开注册表编辑器,在HKLMSYSTEMCurrentControlSetServicesLanmanServerParameters建立一个名为DisableCompression的DWORD,值为1,禁止SMB的压缩功能。
3.对SMB通信445端口进行封禁。
补丁链接
参考链接
以上是关于SMB远程代码执行漏洞(CVE-2020-0796)分析验证及加固的主要内容,如果未能解决你的问题,请参考以下文章
CVE-2020-0796永恒之黑复现POC EXP以及修复方案
CVE-2020-0796永恒之黑复现POC EXP以及修复方案
永恒之黑(CVE-2020-0796 微软SMBv3协议远程代码执行漏洞)