作者:SungLin@知道創宇404實驗室
時間:2020年4月2日
英文版本:http://www.bjnorthway.com/1165/
0x00 漏洞背景
2020年3月12日微軟確認在Windows 10最新版本中存在一個影響SMBv3協議的嚴重漏洞,并分配了CVE編號CVE-2020-0796,該漏洞可能允許攻擊者在SMB服務器或客戶端上遠程執行代碼,3月13日公布了可造成BSOD的poc,3月30日公布了可本地特權提升的poc, 這里我們來分析一下本地特權提升的poc。
0x01 漏洞利用原理
漏洞存在于在srv2.sys驅動中,由于SMB沒有正確處理壓縮的數據包,在解壓數據包的時候調用函數Srv2DecompressData處理壓縮數據時候,對壓縮數據頭部壓縮數據大小OriginalCompressedSegmentSize和其偏移Offset的沒有檢查其是否合法,導致其相加可分配較小的內存,后面調用SmbCompressionDecompress進行數據處理時候使用這片較小的內存可導致拷貝溢出或越界訪問,而在執行本地程序的時候,可通過獲取當前本地程序的token+0x40的偏移地址,通過發送壓縮數據給SMB服務器,之后此偏移地址在解壓縮數據時候拷貝的內核內存中,通過精心構造的內存布局在內核中修改token將權限提升。
0x02 獲取Token
我們先來分析下代碼,POC程序和smb建立連接后,首先會通過調用函數OpenProcessToken獲取本程序的Token,獲得的Token偏移地址將通過壓縮數據發送到SMB服務器中在內核驅動進行修改,而這個Token就是本進程的句柄的在內核中的偏移地址,Token是一種內核內存結構,用于描述進程的安全上下文,包含如進程令牌特權、登錄ID、會話ID、令牌類型之類的信息。

以下是我測試獲得的Token偏移地址:

0x03 壓縮數據
接下來poc會調用RtCompressBuffer來壓縮一段數據,通過發送這段壓縮數據到SMB服務器,SMB服務器將會在內核利用這個token偏移,而這段數據是'A'*0x1108+ (ktoken + 0x40)。

而經壓縮后的數據長度0x13,之后這段壓縮數據除去壓縮數據段頭部外,發送出去的壓縮數據前面將會連接兩個相同的值0x1FF2FF00BC,而這兩個值將會是提權的關鍵。


0x04 調試
我們先來進行調試,首先因為這里是整數溢出漏洞,在srv2!Srv2DecompressData函數這里將會因為加法0xffff ffff + 0x10 = 0xf導致整數溢出,并且進入srvnet!SrvNetAllocateBuffer分配一個較小的內存。

在進入了srvnet!SmbCompressionDecompress然后進入nt!RtlDecompressBufferEx2繼續進行解壓,最后進入函數nt!PoSetHiberRange,再開始進行解壓運算,通過OriginalSize= 0xffff ffff與剛開始整數溢出分配的UnCompressBuffer存儲數據的內存地址相加得一個遠遠大于限制范圍的地址,將會造成拷貝溢出。

但是我們最后需要復制的數據大小是0x1108,所以到底還是沒有溢出,因為真正分配的數據大小是0x1278,通過srvnet!SrvNetAllocateBuffer進入池內存分配的時候,最后進入srvnet!SrvNetAllocateBufferFromPool調用nt!ExAllocatePoolWithTag來分配池內存:

雖然拷貝沒有溢出,但是卻把這片內存的其他變量給覆蓋了,包括srv2!Srv2DecompressDatade的返回值, nt!ExAllocatePoolWithTag分配了一個結構體來存儲有關解壓的信息與數據,存儲解壓數據的偏移相對于UnCompressBuffer_address是固定的0x60,而返回值相對于UnCompressBuffer_address偏移是固定的0x1150,也就是說存儲UnCompressBuffer的地址相對于返回值的偏移是0x10f0,而存儲offset數據的地址是0x1168,相對于存儲解壓數據地址的偏移是0x1108。

有一個問題是為什么是固定的值,因為在這次傳入的OriginalSize= 0xffff ffff,offset=0x10,乘法整數溢出為0xf,而在srvnet! SrvNetAllocateBuffer中,對于傳入的大小0xf做了判斷,小于0x1100的時候將會傳入固定的值0x1100作為后面結構體空間的內存分配值進行相應運算。

然后回到解壓數據這里,需解壓數據的大小是0x13,解壓將會正常進行,拷貝了0x1108
個'A'后,將會把8字節大小token+0x40的偏移地址拷貝到'A'的后面。


解壓完并復制解壓數據到剛開始分配的地址后正常退出解壓函數,接著就會調用memcpy進行下一步的數據拷貝,關鍵的地方是現在rcx變成了剛開始傳入的本地程序的token+0x40的地址!!

回顧一下解壓縮后,內存數據的分布0x1100(‘A’)+Token=0x1108,然后再調用了srvnet!SrvNetAllocateBuffer函數后返回我們需要的內存地址,而v8的地址剛好是初始化內存偏移的0x10f0,所以v8+0x18=0x1108,拷貝的大小是可控的,為傳入的offset大小是0x10,最后調用memcpy將源地址就是壓縮數據0x1FF2FF00BC拷貝到目的地址是0xffff9b893fdc46f0(token+0x40)的后16字節將被覆蓋,成功修改Token的值。

0x05 提權
而覆蓋的值是兩個相同的0x1FF2FF00BC,為什么用兩個相同的值去覆蓋token+0x40的偏移呢,這就是在windows內核中操作Token提升權限的方法之一了,一般是兩種方法:

第一種方法是直接覆蓋Token,第二種方法是修改Token,這里采用的是修改Token。
在windbg中可運行kd> dt _token的命令查看其結構體:

所以修改_SEP_TOKEN_PRIVILEGES的值可以開啟禁用, 同時修改Present和Enabled為SYSTEM進程令牌具有的所有特權的值0x1FF2FF00BC,之后權限設置為:

這里順利在內核提升了權限,接下來通過注入常規的shellcode到windows進程winlogon.exe中執行任意代碼:

如下所示執行了彈計算器的動作:

參考鏈接:
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1164/
暫無評論