一、漏洞介紹

2020年3月10日,微軟在其官方SRC發佈了CVE-2020-0796的安全公告(ADV200005,MicrosoftGuidance for Disabling SMBv3 Compression),公告表示在Windows SMBv3版本的客戶端和服務端存在遠程代碼執行漏洞。同時指出該漏洞存在於MicroSoft Server Message Block 3.1.1協議處理特定請求包的功能中,攻擊者利用該漏洞可在目標SMB Server或者Client中執行任意代碼。

啓明星辰ADLab安全研究人員在對該漏洞進行研究的過程中發現目前流傳的一些漏洞分析存在某些問題,因此對該漏洞進行了深入的分析,並在Windows 10系統上進行了復現。

二、漏洞復現

採用Windows 10 1903版本進行復現。在漏洞利用後,驗證程序提權結束後創建了一個system權限的cmd shell,如圖1所示。

圖1 CVE-2020-0796本地提權

三、漏洞基本原理

CVE-2020-0796漏洞存在於受影響版本的Windows驅動srv2.sys中。Windows SMB v3.1.1 版本增加了對壓縮數據的支持。圖2所示爲帶壓縮數據的SMB數據報文的構成。

圖2 帶壓縮數據的SMB數據報文結構

根據微軟MS-SMB2協議文檔,SMBCompression Transform Header的結構如圖3所示。

ProtocolId:4字節,固定爲0x424D53FC
OriginalComressedSegmentSize:4字節,原始的未壓縮數據大小
CompressionAlgorithm:2字節,壓縮算法
Flags:2字節,詳見協議文檔
Offset/Length:根據Flags的取值爲Offset或者Length,Offset表示數據包中壓縮數據相對於當前結構的偏移

圖3 SMB Compression Transform Header數據結構

srv2.sys中處理SMBv3壓縮數據包的解壓函數Srv2DecompressData未嚴格校驗數據包中OriginalCompressedSegmentSize和Offset/Length字段的合法性。而這兩個字段影響了Srv2DecompressData中內存分配函數SrvNetAllocateBuffer的參數。如圖4所示的Srv2DecompressData函數反編譯代碼,SrvNetAllocateBuffer實際的參數爲OriginalCompressedSegmentSize+Offset。這兩個參數都直接來源於數據包中SMB Compression Transform Header中的字段,而函數並未判斷這兩個字段是否合法,就直接將其相加後作爲內存分配的參數(unsigned int類型)。

圖4 Srv2DecompressData函數的關鍵代碼

這裏,OriginalCompressedSegmentSize+Offset可能小於實際需要分配的內存大小,從而在後續調用解壓函數SmbCompressionDecompress過程中存在越界讀取或者寫入的風險。

四、提權利用過程

目前已公開的針對該漏洞的本地提權利用包含如下的主要過程:

(1)驗證程序首先創建到SMS server的會話連接(記爲session)。

(2)驗證程序獲取自身token數據結構中privilege成員在內核中的地址(記tokenAddr)。

(3)驗證程序通過session發送畸形壓縮數據(記爲evilData)給SMB server觸發漏洞。其中,evilData包含tokenAddr、權限數據、溢出佔位數據。

(4) SMS server收到evilData後觸發漏洞,並修改tokenAddr地址處的權限數據,從而提升驗證程序的權限。

(5)驗證程序獲取權限後對winlogon進行控制,來創建system用戶shell。

五、漏洞內存分配分析

首先,看一下已公開利用的evilData數據包的內容,如圖5所示。

圖5 提權poc發送的帶壓縮數據的SMB數據包

數據包的內容很簡單,其中幾個關鍵字段數據如下:

OriginalSize :0xffffffff
Offset:0×10
Real compressed data :13字節的壓縮數據,解壓後應爲1108字節’A’加8字節的token地址。
SMB3 raw data :實際上是由2個8字節的0x1FF2FFFFBC(總長0×10)加上0×13字節的壓縮數據組成

從上面的漏洞原理分析可知,漏洞成因是Srv2DecompressData函數對報文字段缺乏合法性判斷造成內存分配不當。在該漏洞數據包中,OriginalSize 是一個畸形值。OriginalSize+ Offset = 0xffffffff + 0×10 = 0xf 是一個很小的值,其將會傳遞給SrvNetAllocateBuffer進行調用,下面具體分析內存分配情況。SrvNetAllocateBuffer的反編譯代碼如圖6。

圖6 SrvNetAllocateBuffer內存分配過程

由於傳給SrvNetAllocateBuffer的參數爲0xf,根據SrvNetAllocateBuffer的處理流程可知,該請求內存將從SrvNetBufferLookasides表中分配。這裏需要注意的是,變量SrvDisableNetBufferLookAsideList跟註冊表項相關,系統默認狀態下SrvDisableNetBufferLookAsideList爲0。

圖7 SrvDisableNetBufferLookAsideList變量初始化過程

SrvNetBufferLookasides表通過函數SrvNetCreateBuffer初始化,實際SrvNetCreateBuffer循環調用了SrvNetBufferLookasideAllocate分配內存,調用SrvNetBufferLookasideAllocate的參數分別爲[‘0x1100’, ‘0x2100’, ‘0x4100’, ‘0x8100’, ‘0x10100’, ‘0x20100’, ‘0x40100’, ‘0x80100’, ‘0x100100’]。在這裏,內存分配參數爲0xf,對應的lookaside表爲0×1100大小的表項。

圖8 SrvNetCreateBuffer反編譯代碼

SrvNetBufferLookasideAllocate函數實際是調用SrvNetAllocateBufferFromPool來分配內存,如圖9所示。

圖9 SrvNetBufferLookasideAllocate反編譯代碼

在函數SrvNetAllocateBufferFromPool中,對於用戶請求的內存分配大小,內部通過ExAllocatePoolWithTag函數分配的內存實際要大於請求值(多出部分用於存儲部分內存相關數據結構)。以請求分配0×1100大小爲例,經過一系列判斷後,最後分配的內存大小allocate_size= 0×1100 + E8 + 2*(MmSizeOfMdl + 8)。

圖10 SrvNetAllocateBufferFromPool函數部分反編譯代碼

內存分配完畢之後,SrvNetAllocateBufferFromPool函數還對分配的內存進行了一系列初始化操作,最後返回了一個內存信息結構體指針作爲函數的返回值。

圖11 SrvNetAllocateBufferFromPool初始化內存數據

這裏需要注意如下的數據關係:SrvNetAllocateBufferFromPool函數返回值return_buffer指向一個內存數據結構,該內存數據結構起始地址同實際分配內存(函數ExAllocatePoolWithTag分配的內存)起始地址的的偏移爲0×1150;return_buffer+0×18位置指向了實際分配內存起始地址偏移0×50位置處,而最終return_buffer會作爲函數SrvNetAllocateBuffer的返回值。其內存佈局關係如圖12。

圖12 SrvNetAllocateBuffer(0xf)返回的內存數據佈局

六、漏洞內存破壞分析

回到漏洞解壓函數Srv2DecompressData,在進行內存分配之後,Srv2DecompressData調用函數SmbCompressionDecompress開始解壓被壓縮的數據。其函數邏輯如圖13所示。

圖13 Srv2DecompressData解壓壓縮數據

實際上,該函數調用了Windows庫函數RtlDecompressBufferEx2來實現解壓,根據RtlDecompressBufferEx2的函數原型來對應分析SmbCompressionDecompress函數的各個參數。

SmbCompressionDecompress(CompressAlgo,//壓縮算法
Compressed_buf,//指向數據包中的壓縮數據
Compressed_size,//數據包中壓縮數據大小,計算得到
UnCompressedBuf,//解壓後的數據存儲地址,*(alloc_buffer+0×18)+0×10
UnCompressedSize,//壓縮數據原始大小,源於數據包OriginalCompressedSegmentSize
FinalUnCompressedSize)//最終解壓後數據大小

從反編譯代碼可以看出,函數SmbCompressionDecompress中保存解壓後數據的地址爲*(alloc_buffer+0×18)+0×10的位置,根據內存分配過程分析,alloc_buffer + 0×18指向了實際內存分配起始位置偏移0×50處,所以拷貝目的地址爲實際內存分配起始地址偏移0×60位置處。

在解壓過程中,壓縮數據解壓後將存儲到這個地址指向的內存中。根據evilData數據的構造過程,解壓後的數據爲佔坑數據和tokenAddr。拷貝到該處地址後,tokenAddr將覆蓋原內存數據結構中alloc_buffer+0×18處的數據。也就是解壓縮函數SmbCompressionDecompress返回後,alloc_buffer+0×18將指向驗證程序的tokenAddr內核地址。拷貝過程如圖14所示:

圖14 解壓拷貝過程

圖15 解壓完成後內存佈局

繼續看Srv2DecompressData的後續處理流程,解壓成功後,函數判斷offset的結果不爲0。不爲0則進行內存移動,內存拷貝的參數如下:

memmove(*(alloc_buffer+0x18),SMB_payload,offset)

此時alloc_buffer+0×18已經指向驗證程序的tokenAddr內核地址,而SMB_payload此時指向evilData中的權限數據,offset則爲0×10。因此,這個內存移動完成後,權限數據將寫入tokenAddr處。這意味着,SMS Server成功修改了驗證程序的權限,從而實現了驗證程序的提權!

還有一個細節需要注意,在解壓時,Srv2DecompressData函數會判斷實際的解壓後數據大小FinalUnCompressedSize是否和數據包中原始數據大小OriginalCompressedSegmentSize一致,如圖16所示。

圖16 Srv2DecompressData檢查壓縮數據大小

按理來說實際解壓後的數據大小爲0×1100,不等於數據包中的原始壓縮數據大小0xffffffff,這裏應該進入到後面內存釋放的流程。然而,實際上在函數SmbCompressionDecompress中,調用RtlDecompressBufferEx2成功後會直接將OriginalCompressedSegmentSize賦值給FinalUnCompressedSize。這也是該漏洞關於任意地址寫入成功的關鍵之一。

圖17 SmbCompressionDecompres賦值FinalUnCompressedSize

七、漏洞修復建議

CVE-2020-0796是內存破壞漏洞,精心利用可導致遠程代碼執行,同時網絡上已經出現該漏洞的本地提權利用代碼。在此,建議受影響版本Windows用戶及時根據微軟官方漏洞防護公告對該漏洞進行防護。

八、參考鏈接

1. https://fortiguard.com/encyclopedia/ips/48773

2. https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/ADV200005

3. https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2020-0796

4. https://www.catalog.update.microsoft.com/Search.aspx?q=KB4551762

5. https://github.com/danigargu/CVE-2020-0796

6. https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/5606ad47-5ee0-437a-817e-70c366052962

7. https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntifs/nf-ntifs-rtldecompressbufferex2

*本文作者:ADLab,轉載請註明來自FreeBuf.COM

相關文章