作者:晏子霜
原文鏈接:http://www.whsgwl.net/blog/CVE-2018-8453_0.html
0x00: Windows10 1703 X64 無補丁

0x01: 漏洞細節分析
如何構造一個觸發BSOD的Poc呢,根據網上現存的分析報告我們得到了一個這樣觸發BSOD的思路.
- 創建兩個窗口,一個父窗口,一個滾動條子控件
- Hook
PEB->KernelCallbackTable中的fnDword(),xxxClientAllocWindowClassExtraBytes()函數指針的指向,讓其指向我們自定義的處理函數. - 在
fnDword()函數內釋放父類窗口 - 在
xxxClientAllocWindowClassExtraBytes()函數內調用NtUserSetWindowFNID()函數,并創建新的滾動條控件,使用SetCapture()函數修改滾動條捕獲窗口 - 在
fnDword()函數內判斷發送的Message是否為0x70,如果是,則向新創建的滾動條控件發送0x1F號消息 - 向 滾動條子控件(Scroll)發送WM_LBUTTONDOWN消息即可觸發BSOD
雖然這樣確實可以觸發BSOD,但是我們根本不知道為什么這樣會導致BSOD(Double Free) 下面是本人關于CVE-2018-8453的分析報告
下面是本人關于 CVE-2018-8453的分析報告
觸發BSOD的Poc中,完成了1 - 5的準備工作之后,便向滾動條子控件發送了一個WM_LBUTTONDOWN消息

我們知道,向滾動條子控件(Scroll)發送WM_LBUTTONDOWN,消息時,會調用到win32kfull!xxxSBTrackInit()函數,該函數主要是來實現滾動條跟隨鼠標移動的,該函數首先會創建一個0x80字節大小的Session Pool,用來保存 tagSBTrack結構 接著將創建好tagSBTRACK結構的指針,寫入到tagTHREADINFO.tagSBTRACK處,在Windows10 1703 X64中,該結構的偏移地址為tagTHREADINFO+0x278

需要注意的是Lock(&pSBTrack->spwndSBNotify, pwnd->spwndParent),讓滾動條子控件引用父類窗口,也就是我們創建的父窗口建立引用+1,此處很重要(PS:當時寫Poc時創建滾動條子控件時,屬性忘記設置WS_CHILD,導致滾動條窗口的父窗口非創建的父窗口導致無法利用漏洞)

win32kfull!xxxSBTrackInit函數最后會調用win32kfull!xxxSBTrackLoop函數,來進行消息循環,消息循環函數win32kfull!xxxDispatchMessage會使用fnDWORD函數回調R3,這時我們就知道為什么要Hook fnDWORD函數了.
在fnDWORD函數里判斷是否是滾動條窗口發送的回調,調用DestroyWindow()函數釋放主窗口.

DestoryWindow()函數會調用win32kfull!xxxFreeWindow()函數來釋放窗口,但是該函數經常被調用,我們可以使用條件斷點來判斷是否是我們要釋放的窗口
Ba e1 win32kfull!xxxFreeWindow ".if( poi(rcx) == 釋放窗口的句柄 ){}.else{g}"

此時在rcx+0x52處下內存寫入斷點 rcx+0x52 處為tagwnd.FNID,也就是導致漏洞的主角(也不能這樣說,本質問題還是Kernel CallBack).窗口擴展空間必須要在創建窗口類時設置窗口類的大小,在Poc中我設置為 wndclass.cbWndExtra = 0x8
觸發xxxClientAllocWindowClassExtraBytes函數回調后,如何判斷是主窗口調用的該函數呢,這里我使用了SetWindowLongA(Window, 0, (ULONG)Window);將主窗口句柄保存在窗口擴展中.

MSG中,保存了窗口擴展類的地址,里面保存了設置的父窗口指針,通過這個來判斷是否為父窗口調用的xxxClientAllocWindowClassExtraBytes函數.
此時,我們創建一個新的滾動條窗口,不設置父類句柄,以及子類屬性,并設置正在釋放窗口Window的FNID為 0x2A1,本來Window的FNID為0x8000,調用NtUserSetWindowFNID后為0x82A1,接著設置新的捕獲窗口.
此時父窗口雖然已經調用DestroyWindow釋放了,但是由于滾動條子窗口Scroll還對父窗口有引用,所以并未釋放,最后win32kfull!xxxSBTrackLoop函數結束后,對pSBTrack->spwndSBNotify和鏈接的主窗口解引用,由于是最后一處引用,調用HMAssignmentUnlock()函數時會判斷被綁定(Win32 Object)結構的cLockObj結構是否為1,如果為1代表只有一個引用,修改指針內容后后便立刻調用函數釋放該結構,此處釋放的函數為(Win32kfull!DestoryWindow)

問題就在這里,由于釋放窗口要調用win32kfull!xxxFreeWindow函數,而FNID為釋放窗口的Flag屬性,被修改為0x82A1后,會再次調用fnDWORD函數回調R3,并發送為0x70的Message.

判斷FNID的內容,來決定是否調用fnDWORD

可以看到此處調用fnDWORD,并且發送的Message為0x70

此時,我們向新創建的滾動條控件發送Message為0x1F的消息,最終會調用到win32kfull!xxxEndScroll函數


通過在win32kfull!SetCapture設置的捕獲窗口,可繞過其驗證,直入到釋放tagSBTrack結構

此時線程內的tagSBTrack結構已經被釋放了,接著回到win32kfull!xxxSBTrackInit執行代碼

因為tagSBTrack結構已經在win32kfull!xxxEndScroll函數被釋放了,但是win32kfull!xxxSBTrackInit函數并不知道,再次釋放該內存導致Double Free!
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1135/
暫無評論