CVE-2019-0808
一、 漏洞資訊
CVE-2019-0808是發生在win32k中的一個空指標解引用漏洞,根據網上的blog介紹該漏洞在win32k!xxxMNMouseMove中產生。
二、 測試環境及漏洞復現
測試環境
POC:https://github.com/ze0r/cve-2019-0808-poc
靶機: win10 x86 SP1
漏洞復現
三、 漏洞成因分析
補丁分析
透過bindiff對xxxMNMouseMove函式進行對比,發現win32k!xxxMNFindWindowFromPoint函式新增對選單視窗物件的校驗。
對win32k!xxxMNFindWindowFromPoint進行詳細分析時,可以看到xxxSendMessage成功獲取到選單視窗物件控制程式碼後,透過HMValidateHandleNoSecure獲取對應的選單視窗物件,隨後直接返回。
而在補丁檔案中,HMValidateHandleNoSecure成功獲取選單視窗物件後會進行一系列的安全檢查後(保證tagWndMenu、tagWnd、tagPopuMenu、tagMenu這四個物件均存在且不為NULL)才會返回。此時也猜測到這個漏洞產生的原因大機率是空指標解引用。
成因分析
在補丁分析中,我們猜測這是一個空指標解引用的洞,這裡結合POC去印證。下圖為執行POC後windbg抓到的cash現場,從堆疊中可以看到藍色畫面觸發點在win32!MNGetItem函式。
此時進行精準定位後,就是對ecx+0x20的poi操作發生錯誤。
進一步除錯後,我們發現ecx的值居然也為空!!!
結合IDA對ecx的值進行溯源, 發現其儲存的值是ptagMenu。
而ptagMenu的值是由選單彈出物件tagPopuMenu+0x14提供的。
最終,我們溯源完成後知道ptagPopuMenu的值正是由透過xxxMNFindWindowFromPoint函式中HMValidateHandleNoSecure返回的選單視窗物件tagMenuWnd+0xb0得到的。
至此,空指標解引用產生的原因已然明瞭。
四、 漏洞利用分析
漏洞利用分析部分是結合EXP,對其漏洞構造、提權利用進行詳細闡述,方式為Q-A模式。
問題一,如何構造空的選單物件(ptagPopupMenu->spmenu=NULL)
在漏洞成因過程中知道,tagMenu是由tagPopuMenu+0x14提供的,而tagPopuMenu是由tagMenuWnd+0xb0提供的。tagMenuWnd是在xxxMNFindWindowFromPoint在xxxSendMessage獲取的彈出選單控制程式碼後透過HMValidateHandleNoSecure得到的。
從windbg的堆疊可以看到漏洞函式xxxMNUpdateDraggingInfo是由xxxMNMouseMove呼叫的,另外在window中win32k!xxxMNMouseMove是選單視窗過程處理函式用來處理滑鼠在選單內移動的訊息,如果此時滑鼠由主彈出選單移動到了子彈出選單,那麼在win32k!xxxMNFindWindowFromPoint內部就會透過xxxSendMessage對子彈出選單傳送訊息來獲取子彈出選單的控制程式碼,用來在後續程式碼中對子彈出選單傳送訊息來完成繪製和滑鼠選中等效果。
因此在EXP對應會建立兩個彈出選單並將其設定成為非模擬態(原因是模擬態會導致其他其他選單發生阻塞,無法正常接收訊息)
與此同時去建立一個偽造選單視窗(tagfakeMenuwnd->spmenu==NULL),xxxSendMessage傳送訊息後替換其返回值(正常應該返回hMenuSub)。Ps:CSDN中也有定義lpClassName為#32768時,建立的是選單視窗。
彈出選單建立後,還需要一個視窗作為媒介來傳遞訊息。
TrackPopupMenuEx的作用是在桌面上顯示彈出選單。
彈出選單以及偽造視窗等都成功建立後,我們要想辦法將xxxSendMessage的返回值替換為hWndFakeMenu,這裡是透過SetWindowsHookEx和SetWinEventHook實現的。
下面就這兩處hook的原因及作用進行詳細介紹:
- SetWinEventHook
這裡是對EVENT_SYSTEM_MENUPOPUPSTART事件進行了hook,原因是在TrackPopupMenuEx的內部實現xxxTrackPopupMenuEx中對選單視窗(tagMenuWnd)、彈出選單物件(tagPopupMenu)以及tagMENUSTATE建立並初始化後,會透過xxxWindowEvent向使用者層傳送EVENT_SYSTEM_MENUPOPUPSTART事件訊息通知應用層的事件回撥。
當彈出選單成功顯示後,向使用者層傳送EVENT_SYSTEM_MENUPOPUPSTART事件。
註冊Event Hook後,首先在主彈出選單(hMenuRoot)繪製完成後第一次進入此事件回撥
並向其傳送WM_LBUTTONDOWN訊息即按下左鍵顯示出子彈出選單(hMenuSub)
子彈出選單的顯示是我們第二次進入事件回撥並向hMenuSub傳送WM_MOUSEMOVE訊息從而形成一個從主彈出選單到子彈出選單的拖拽動作。
拖拽動作成功實現後,EXP就會順利進行到xxxMNMouseMove函式中。
2.SetWindowsHookEx
使用者層成功捕捉到由xxxMNFindWindowFromPoint中的xxxSendMessage發來的WM_MN_FINDMENUWINDOWFROMPOINT訊息後透過SetWindowLongPtr將子彈出選單視窗對應的過程函式(DefWindowProc)更改為SubMenuProc。
SubMenuProc最終返回使用者態構造的視窗物件控制程式碼hWndFakeMenu作為xxxMNFindWindowFromPoint函式呼叫xxxSendMessage的返回值,最終得到一個
ptagPopupMenu->spmenu=NULL的空指標。
到這為止,已經成功實現了xxxMNFindWindowFromPoint函式呼叫xxxSendMessage後返回的是偽造的視窗物件。但是,這並不代表著已經能夠成功觸發空指標解引用的操作。原因是第二次觸發DisplayEventProc事件回撥後,EXP向子彈出選單視窗傳送WM_MOUSEMOVE訊息時就會在R0中呼叫一次xxxMNMouseMove。
此時,視窗控制程式碼雖然可以被替換但在成功替換後,xxxMNMouseMove往下執行時並不會去呼叫xxxMNUpdateDraggingInfo。
透過IDA可以看到在xxxMNMouseMove中檢查了tagMENUSTATE的fInDoDragDrop標誌位是否置位,才會進入xxxMNUpdateDraggingInfo函式。
DisplayEventProc事件回撥中雖然傳送WM_MOUSEMOVE能夠成功進入xxxMNFindWindowFromPoint的邏輯中,但是由於fInDoDragDrop沒有被置位,因此並不能觸發空指標解引用。
問題二,如何在xxxMNFindWindowFromPoint函式成功獲取偽造視窗控制程式碼成功後exp能夠順利進入xxxMNUpdateDraggingInfo的執行邏輯,觸發空指標解引用的操作
對於這個問題,網上大神的blog給出瞭解決方案。我們可以透過手動呼叫NtUserMNDragOver函式來將fInDoDragDrop置位。
最後還需控制視窗控制程式碼替換時機,設定一個全域性變數bOnDraging在事件回撥傳送xxxMNMouseMove訊息時xxxMNFindWindowFromPoint的xxxSendMessage返回正確的視窗控制程式碼。
在我們手動呼叫NtUserMNDragOver後進入xxxMNFindWindowFromPoint的xxxSendMessage時替換子選單視窗的視窗過程,因此我們的視窗過程回撥需要稍作修改,並在手動呼叫NtUserMNDragOver前將全域性變數bOnDrag置1:
至此,可以實現穩定的觸發漏洞。
問題三,如何構造任意地址覆蓋寫漏洞
在漏洞成因分析中我們知道,產生崩潰的點是在xxxMNUpdateDraggingInfo-> MNGetpItem中,具體是spmenu的空指標解引用。而對於此類漏洞Win7中使用者態可以透過ntdll!NtAllocateVirtualMemory函式分配零頁記憶體利用。分配零頁記憶體的原理比較簡單,不再詳述:
零頁記憶體分配成功後,MNGetpItem函式中的對於選單物件的取值操作並不會觸發異常,我們可以看到此時pMenu的地址為0x0, pMenu->cItems [0x00000020] 也是可控資料。
接著在下方的if判斷中uDraggingIndex < pMenu->cItems=0 不成立,導致返回pItem=0。我們需要控制程式流程走到紅框分支,返回可控資料
因此需要在[00000020]設定一個合適的值(這裡設定為0xffffffff)進入if分支。
返回值為uDraggingINdex*6C+[00000034]
uDraggingINdex的值可以透過wParam+0x10去獲取
[00000034]是我們可控的資料,因此MNGetpItem的返回值也是可控的。接著往下發現在xxxMNUpdateDraggingInfo-> xxxMNSetGapState存在位修改操作
想必看到這裡,我們知道此邏輯能夠實現任意地地址的有限修改。具體如下:
addressToWrite = ret+0x4 = uDraggingINdex*6C+[00000034]+0x4
上述表示式addressToWrite代表著準備改寫的任意地址,因此我們僅需設定[00000034]處的地址即可。
[00000034] = addressToWrite - uDraggingINdex*6C - 0x4
至此,我們就實現了指定地址值的有限修改功能。
問題四,如何實現本地提權利用(LPE)
透過分析exp知道,準備修改的地址是一個視窗物件tagWnd+0x90處的資料即cbwndExtra的值,cbwndExtra為此視窗擴充套件資料的大小,此處將其修改為0x4000000一個超長buf,目的是對其相鄰的視窗進行越界寫。
- 視窗物件噴射
首先申請100個視窗物件
透過pHmValidateHandle函式洩露視窗物件的地址並且以兩個視窗的地址差小於0x3fd00為判斷依據去尋找兩個相鄰的視窗物件。
成功找到後,將地址較高的視窗物件設定為primaryWindow,另外一個設定為secondaryWindow。
最後透過漏洞能力將primaryWindow的cbwndExtra修改為0x40000000。
2.越界寫,執行shellcode
xxxMNMouseMove執行完xxxMNUpdateDraggingInfo並且成功將primaryWindow物件的cbwndExtra欄位修改為0x4000000後,繼續向下執行最後會向子選單視窗傳送0x1E5訊息。
EXP成功攔截到發來的1E5訊息後,開始計算primaryWindow物件末尾到secondaryWindow物件的strName.buffer的差值(offset)
SetWindowLongA將secondaryWindowAddress的strName.buffer的地址修改為secondaryWindow視窗物件的0x16的地址。
接著用SetWindowTextA將bServerSideWindowProc置為1,從而可以在核心執行我們的視窗過程函式。
修改0x16偏移的原因如下圖所示:
最後向secondaryWindow視窗傳送WM_ENTERIDLE訊息執行shellcode(核心執行)。
secondaryWindow成功接收到WM_ENTERIDLE訊息後首先會透過cs暫存器的值去判斷當前執行上下文是否在核心態,之後開始執行shellcode,替換token。
Shellcode中除了必要的token替換,還清空了程式的Job物件指標,這是因為在Chrome的渲染程式中,即使shellcode替換了system程式的token,當前程式的token依然會繼承自Job物件,並且Job不允許Chrome渲染程式產生新程式,因此需要先清空當前程式的Job物件指標。
至此漏洞利用過程分析結束。