BlueKeep 漏洞利用分析

酷酷的曉得哥發表於2019-09-20

作者:SungLin@知道創宇404實驗室
時間:2019年9月18日

原文連結:

0x00 通道的建立、連線與釋放

通道的資料包定義在MCS Connect Inittial PDU with GCC Conference Create Request中,在rdp連線過程如下圖所示:

通道建立資料包格式如下:

在MCS Connect Inittial中屬於Client Network Data資料段, MS_T120將會在連線一開始的時候透過函式 termdd!_IcaRegisterVcBin建立一個虛擬通道id是0x1f大小為0x18的結構體,之後就呼叫 termdd!IcaCreateChannel開始建立大小為0x8c的通道結構體之後將會與虛擬通道id是0x1f繫結,也就是這個結構體將會被我們利用

通道的定義欄位主要是名字加上配置,配置主要包括了優先順序等

在server對MCS Connect Inittial應答包,將會依次給出對應虛擬通道的id值:

在rdp核心中依次註冊的值對應應該是0、1、2、3, MS_T120通道將會透過我們傳送的使用者虛擬id為3的值再一次繫結,首先透過 termdd!_IcaFindVcBind找到了剛開始註冊的虛擬通道id是0x1f,如下所示:

但是在 termdd!_IcaBindChannel時,卻將我們自定義的id值為3與通道結構體再一次繫結在一起了,此通道結構體就是MS_T120

同時我們自己的使用者id將內部繫結的0x1f給覆蓋了

我們往通道MS_T120傳送資料主動釋放其分配的結構體,其傳入虛擬通道id值為3透過函式 termdd!IcaFindChannel在channeltable中查詢返回對應的通道結構體:

下圖為返回的MS_T120通道結構體,其中0xf77b4300為此通道可呼叫的函式指標陣列:

在這個函式指標陣列中主要存放了三個函式,其中對應了 termdd!IcaCloseChanneltermdd!IcaReadChanneltermdd!IcaWriteChannel

我們傳入釋放MS_T120通道的資料如下,位元組大小為0x12,主要資料對應了0x02

之後將會進入 nt! IofCompleteRequest函式,透過apc注入後,將會透過 nt! IopCompleteRequestnt!IopAbortRequest進行資料請求的響應,最終在 termdd!IcaDispatch完成我們傳送資料的的請求, _BYTE v2就是我們傳送的資料,所以我們傳送的資料0x02將會最終呼叫到IcaClose函式進入IcaCloseChannel函式,最後主動釋放掉了 MS_T120通道結構體


0x01 透過RDPDR通道進行資料佔位

我們先來了解下rdpdr通道,首先rdpdr通道是檔案系統虛擬通道擴充套件,該擴充套件在名為rdpdr的靜態虛擬通道上執行。目的是將訪問從伺服器重定向到客戶端檔案系統,其資料頭部將會主要是兩種標識和PacketId欄位組成:

在這裡我們剛好利用到了rdpde客戶端name響應的資料來進行池記憶體的佔位

在完全建立連線後,將會建立rdpdr通道的結構體

在window7中,在建立完成後接收到server的rdpdr請求後,透過傳送客戶端name響應資料,將會呼叫到 termdd! IcaChannelInputInternal中的ExAllocatePoolWithTag分配非分頁池記憶體,並且其長度是我們可以控制的,基本滿足了UAF利用的需求:

可是在windowsxp中,直接傳送client name request將會導致記憶體分配失敗,直接進入 termdd! _IcaCopyDataToUserBuffer,並且在Tao Yan and Jin Chen[1]一文中也提到了透過傳送client name request在觸發一定的條件後將會繞過 termdd!_IcaCopyDataToUserBuffer而進入ExAllocatePoolWithTag分配我們想要的非分頁記憶體,而打破條件如下:

我們先來看看最開始通道結構體的建立,我們可以發現從一開始建立通道結構體的時候,將會出現兩個標誌,而這兩個標誌是按照地址順序排列的,而在上面需要打破的條件中,只要channelstruct +0x108的地址存放的是同一個地址,迴圈就會被break

我們傳送一個正常的rdpdr的name request資料包,頭部標識是0x7244和0x4e43

經過 termdd!_IcaCopyDataToUserBuffer之後,將會進入 nt!IofCompleteRequest,在響應請求後進入 rdpdr!DrSession::ReadCompletion,此函式處理邏輯如下,其將會遍歷一個連結串列,從連結串列中取出對應的vftable函式陣列

遍歷第一次取出第一張函式陣列

傳入我們傳送的資料後,透過函式陣列呼叫 rdpdr!DrSession::RecognizePacket進行讀取

判斷頭部標誌是否為(RDPDR_CTYP_CORE)0x7244

接著將會讀取函式vftable第二個地址,進行轉發

如下圖可以看到rdpdr的資料包處理邏輯

rdpdr經過一系列資料包處理後最終進入了我們關心的地方,將會傳入channelstruct透過呼叫 termdd! _IcaQueueReadChannelRequest進行標誌位的處理

最初rdpdr的channelstruct的標誌位如下

經過函式 termdd! _IcaQueueReadChannelRequest對此標誌的處理後變成如下,所以下一個資料依然會進入 termdd!_IcaCopyDataToUserBuffer,導致我們進行池噴射的失敗

回到rdpdr頭部處理函式 rdpdr!DrSession::RecognizePacket,我們發現在連結串列遍歷失敗後將會進行跳轉,最後將會進入讀取失敗處理函式 rdpdr!DrSession::ChannelIoFailed,然後直接return了

我們構造一個頭部異常的資料包傳送,頭部標誌我們構造的是0x7240,將會導致 rdpdr!DrSession::RecognizePacket判斷失敗,之後將會繼續遍歷連結串列依次再取出兩張函式陣列

最後兩個函式陣列依次呼叫 rdpdr!DrExchangeManager::RecognizePacketrdpdr!DrDeviceManager::RecognizePacket,都會判斷錯誤的頭部標誌0x7240,最後導致連結串列遍歷完後進行錯誤跳轉,直接繞過了 termdd! _IcaQueueReadChannelRequest對標誌位的修改,將會打破迴圈

最後我們連續構造多個錯誤的資料包後將會進入 ExAllocatePoolWithTag,分配到我們需要的非分頁記憶體!

0x02 win7 EXP 池噴射簡要分析

首先被釋放的MS_T120池大小包括是0x170,池的標誌是TSic

分析Win7 exp 可以知道資料佔位是用的rdpsnd通道,作者沒有采用rdpdr通道,應該也和噴射的穩定性有關,rdpsnd噴射是再建立完了rdpdr初始化後開始的,在free掉MS_T120結構體前,傳送了1044個資料包去申請0x170大小的池記憶體,這樣做可以說應該是為了防止之後被free掉的記憶體被其他程式佔用了,提高free後記憶體被我們佔用的生存機率

佔位被free的實際資料大小為0x128,利用的中轉地址是0xfffffa80ec000948

之後開始池噴射,將payload噴射到可以call [rax] == 0xfffffa80ec000948的地方,噴射的payload大小基本是0x400,總共噴射了200mb的資料大小,我們先來看下噴射前帶標誌TSic總共佔用池記憶體大小是58kib左右

BlueKeep 漏洞利用分析

BlueKeep 漏洞利用分析

噴射完後帶TSic標誌池記憶體大小大約就是201mb,池記憶體噴射基本是成功的,我的win7是sp1,總共記憶體大小是1GB,再噴射過程中也沒有其他干擾的,所以噴射很順利

BlueKeep 漏洞利用分析

BlueKeep 漏洞利用分析

圖中可以發現基本已經很穩定的0x400大小的池噴射payload,地址越高0x400大小的記憶體基本就很穩定了

最後斷開連線時候,被free的記憶體已經被我們噴射的0x128大小的資料給佔用了

執行call指令後穩定跳轉到了我們的payload,成功執行!

參考連結:
[0]  
[1]  
[2]


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69912109/viewspace-2657670/,如需轉載,請註明出處,否則將追究法律責任。

相關文章