您好,登錄后才能下訂單哦!
這篇文章給大家介紹win32k.sys中怎么實現漏洞挖掘,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
4月1日,以色列安全研究員Gil Dabah在博客上發布了一篇關于win32k漏洞研究文章,描述了如何通過內核對象的Destroy函數和win32k user-mode callback緩解措施的特性來尋找UAF漏洞的新思路。
為此,啟明星辰ADLab對win32k相關內核機制進行研究分析,并對這類漏洞的挖掘思路進行詳細解讀分析。
由于設計原因,win32k驅動需要處理很多用戶層的回調,這些回調給win32k模塊的安全帶來了非常大的隱患,并在過去10年時間貢獻了大量的漏洞。
為了便于漏洞描述,以如下偽代碼進行舉例分析。
上述代碼執行效果如下圖所示,用戶層執行的某函數通過syscall傳入內核層,當內核層代碼執行到somecallback這一句時,用戶層可以在用戶定義的callback函數中獲得代碼執行的機會,如果用戶在callback函數調用了DestroyWindow函數銷毀窗口p,內核層的相應銷毀代碼將會被執行,p的相應內存被釋放,回調執行完畢,NtUserSysCall函數繼續執行,當執行到xxxSetWindowStyle(p)一句時,由于p的內存已經被釋放從而導致UAF漏洞的產生。
為了防止上述問題的產生,微軟在對象中引入了一個引用計數(對象+0x8處),對象分配時引用計數為1,當執行對象的Destroy函數時引用計數減1,當引用計數為0時對象會被真正釋放。微軟通過鎖的概念為對象添加和減少引用計數,在win32k中為對象管理引用計數的鎖有兩種分別是臨時鎖(相應函數為ThreadLock/ ThreadUnlock)和永久鎖(相應函數為HMAssignmentLock/ HMAssignmentUnlock)。經過加固之后代碼表現為如下形式:
通過上述代碼,可以保證即使callback被執行,p在xxxSetWindowStyle函數執行的時候也不會被釋放。
上一節提到了對象的引用計數,如果對象的引用計數為正,即使執行對象的destroy函數,對象沒有真正被釋放,仍舊存留在內存中,這種對象被微軟開發者稱為僵尸(Zombie)對象。一旦僵尸對象的引用計數減少到0它將會消失,但是在此之前它仍舊存在內存中,只是用戶層無法訪問該對象。
同時為了防止僵尸對象繼續存留在內存中,鎖的釋放函數(ThreadUnlock/ HMAssignmentUnlock)一般會包含對象的釋放環節。
對象的Destroy函數還有一個特性就是在釋放對象的同時,Destroy函數也會釋放對象的子資源,其過程可以簡要描述如下。
DestroyWindow在第一次調用時釋放子資源,一旦窗口不再被引用,句柄管理器就會再次完全銷毀它,一般情況下,第二次銷毀Destroy函數不會在去處理子資源,因為第一次已經釋放了所有的子資源。
但是事情往往不是這么簡單,事實上即使是一個已經調用過相應Destroy函數釋放的僵尸對象,仍然有機會對其本身進行一些更改(回調之后內核代碼仍會對對象進行一些操作),我們把這種情況叫做Zombie Reload,當該僵尸對象由于引用計數為0而被真正釋放時,之前的更改操作將會給內核帶來一些隱患。
對于如下代碼片段:
我們在用戶層回調中對pwnd執行了Destroy函數,然后通過InternalSetTimer為之設置了一個計時器,當ThreadUnlock將pwnd真正釋放的時候,計時器也將被釋放,那么接下來對計時器的操作將會導致UAF漏洞的產生。
上一節我們討論了對象的引用計數和鎖給對象帶來的新的安全隱患,但是真正的挑戰在于我們如何確定一段代碼中存在漏洞,關鍵點是確保在unlock函數中釋放的對象在運行到有問題的代碼時其引用計數應該為1,只有這樣我們才能在用戶層回調調用其Destroy函數,并通過unlock函數將這個對象真正釋放掉(上鎖的時候會做+1處理),這也是我們接下來需要討論的。下面我們通過一個案例來分析漏洞挖掘思路。
下圖是xxxMnOpenHierarchy函數的代碼片段。
圖中通過xxxCreateWindowEx可以獲得一個返回用戶層執行callback函數的機會,xxxCreateWindowEx創建的窗口將作為父窗口*(structtagWND **)(**v3 + 8)(上圖紅框)的子窗口,如果我們可以通過ThreadUnlock釋放父窗口,那么子窗口v32也會被釋放,所以當后續的safe_cast_fnid_to_PMENUWND函數將v32作為參數執行時就會產生問題,值得注意的是通過回調釋放v32是行不通的,如果這樣xxxCreateWindowEx將會返回0,無法通過if判斷。
這里的問題就在于如何保證父窗口在ThreadUnlock函數執行的時候引用計數為1,因為要執行xxxMnOpenHierarchy函數需要將父窗口關聯到一個menu窗口上,此時父窗口和menu窗口將會被一個永久鎖鎖住,下面我們介紹如何繞過永久鎖。
首先我們創建了g_hMenuOwner和g_hNewOwner兩個窗口,其中g_hMenuOwner的菜單句柄為hMenu,它也是g_hNewOwner的所有者。
在上述創建過程中內核通過LockPopuMenu函數分別為hMenu和g_hMenuOwner添加了永久鎖,為了達成釋放目的,這個永久鎖需要被繞過。
此時鎖和所有者的關系是這樣的:
接下來我們通過SetWindowsHookEx給窗口添加了WH_CBT鉤子,并讓窗口進入消息循環中。
SendMessage操作為g_hMenuOwner添加一個臨時鎖,由于后續的所有攻擊都是在message的回調中進行,所以對于g_hMenuOwner來說這個臨時鎖是無法釋放的,如果想要構造一個漏洞利用環境首先需要用一些方法來繞過它。
現在的情況變成了下圖所示:
當消息為HCBT_CREATEWND時,我們第一次到達xxxMNOpenHierarchy函數內部的xxxCreateWindowEx。
這里可以通過定義關于HCBT_CREATEWND消息的處理得到執行用戶層回調代碼的機會,這一步的主要目的是為了獲取Menu的Wnd。
當接收到的消息為WM_ENTERIDLE時,我們在窗口的消息回調中通過PostMessage下發消息。
發送消息后,驅動程序來到了xxxMNKeyDown函數內部調用xxxSendMessage處。
通過WM_NEXTMENU消息的回調函數開始為LPARAM賦值,賦值操作是為了修改hMenu的Owner,這樣就可以將Owner的臨時鎖繞過。
此時內核會接到銷毀menu的消息,通過用戶層的回調函數返回1阻止menu的銷毀。
xxxMNKeyDown函數通過UnlockPopupMenu將g_hMenuOwner身上的永久鎖被去掉。
取而代之的是g_hNewOwner加上了一個鎖,hMenu的Owner也從g_hMenuOwner變成了g_hNewOwner。
這時,鎖的關系變成了:
接下來程序第二次進入到xxxMNOpenHierarchy函數并通過xxxSendMessage發送了消息。
此時通過設置WM_INITMENUPOPUP回調來獲得用戶層執行的機會,WM_INITMENUPOPUP回調函數通過SetWindowsHookEx函數設置了一個新的hook,目的是為了在xxxMnOpenHierarchy函數創建子窗口的時候獲得用戶層執行權限。
xxxMnOpenHierarchy函數繼續向下執行,再次來到xxxCreateWindowEx處。
xxxCreateWindowEx調用了剛剛設置的回調函數childMenuHookProc。
在回調函數childMenuHookProc中,SendMessage發送了WM_NEXTMENU消息,通過該定義該消息的回調函數再次修改參數LPARAM,這是為了去掉g_hNewOwner身上的永久鎖。
Menu的Owner關系再次被改變,xxxMNKeyDown通過函數UnlockPopMenu去掉g_hNewOwner身上的永久鎖。并將這個鎖重新加在了g_hMenuOwner上。
這個時候,所有的鎖都已經轉移到了g_hMenuOwner身上,而由于WH_CBT鉤子已經被移除,menu將被棄用,g_hNewOwner將把新創建的窗口link到自己身上。這個時候情況變成了下面的樣子,g_hNewOwner身上已經沒有需要繞過的鎖了。
接著childMenuHookProc通過SetWindowsHookEx函數又一次設置了回調函數并通過SetWindowLongPtr函數來調用它,回調函數銷毀了g_hNewOwner和xxxCreateWindowEx生成的新窗口。
xxxCreateWindowEx返回的值為ffff871b80239130,這就是xxxCreateWindowEx創建的子窗口。
接下來就可以通過ThreadUnlock來銷毀g_hNewOwner和其新創建的子窗口來得到一個UAF漏洞。
關于win32k.sys中怎么實現漏洞挖掘就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。