深入分析Win32k系統(tǒng)調(diào)用過濾機(jī)制
前言
Windows內(nèi)核漏洞的利用具有高風(fēng)險(xiǎn),經(jīng)常用于瀏覽器沙箱逃逸。許多年以來,發(fā)現(xiàn)的大多數(shù)漏洞都是來源于Win32k.sys驅(qū)動(dòng),它負(fù)責(zé)處理來自GDI32.DLL和user32.dll的調(diào)用。為了緩解這些漏洞,微軟在window10上實(shí)現(xiàn)了Win32系統(tǒng)調(diào)用過濾.總體思路是在進(jìn)程入口處嘗試阻止大量的發(fā)往win32.sys的系統(tǒng)調(diào)用,以便阻止未知的漏洞利用。我沒有找到實(shí)現(xiàn)的相關(guān)細(xì)節(jié),也不確定效果如何。我發(fā)現(xiàn)的唯一資料就是由Peter Hlavaty發(fā)表在 Rainbow Over the Windows的相關(guān)文章。
系統(tǒng)調(diào)用101
我理解過濾技術(shù)的第一個(gè)方法就是研究系統(tǒng)調(diào)用是如何執(zhí)行。分析是基于Windows 10周年更新的64位版本。通常,系統(tǒng)調(diào)用在gdi32.dll或者user32.dll的函數(shù)中被初始化,最終會(huì)在win32u.dll中調(diào)用真實(shí)的系統(tǒng)調(diào)用。但是,我們可以直接使用匯編來顯示系統(tǒng)調(diào)用,在以下的POC中,我查詢到系統(tǒng)調(diào)用號(hào)為0x119E的WIN32K函數(shù)是NtGdiDdDDICreateAllocation。所以我簡(jiǎn)單地創(chuàng)建下面的測(cè)試應(yīng)用程序:
下圖是NtGdiDdDDICreateAllocation的匯編代碼:
當(dāng)運(yùn)行syscall指令后,將轉(zhuǎn)入內(nèi)核模式執(zhí)行.真實(shí)的系統(tǒng)調(diào)用位于NT!KiSystemStartService。但是,由于有大量的系統(tǒng)調(diào)用,所以我們需要在調(diào)試器中設(shè)置一個(gè)條件斷點(diǎn):
運(yùn)行POC并開啟斷點(diǎn)
首先顯示的系統(tǒng)調(diào)用號(hào)就是我們提供的0x119E,其實(shí)參數(shù)1,2,3,4保存在寄存器RCX,RDX,R8和R9中。在IDA中查看相關(guān)代碼:
查閱代碼,我們發(fā)現(xiàn)一個(gè)有趣的問題:RBX寄存器的內(nèi)容是什么,這又是從何而來。嘗試引用KiSystemServiceStart,我們發(fā)現(xiàn) RBX在以下函數(shù)中被設(shè)置:
MOV RBX GS:188將Win32SyscallFilter.exe的內(nèi)核線程結(jié)構(gòu)載入RBX中,驗(yàn)證如下:
研究算法
接下來的問題是RBX + 0x78代表什么,事實(shí)證明,它代表一系列的標(biāo)志位。下圖引用的兩個(gè)標(biāo)志是GuiThread和RestrictedGuiThread,它們分別位于標(biāo)志的第6位和和19位。
在我們的例子中,標(biāo)志位的值如下:
由于線程不是一個(gè)GUI線程,所以會(huì)重定向的將它轉(zhuǎn)換成一個(gè)GUI線程,然后返回相同的指針。繼續(xù)執(zhí)行會(huì)發(fā)生:
Win32kSyscallFilter并沒有做任何事。但是接下來的檢查很有趣。RestrictedGuiThread標(biāo)志指明,如果啟用系統(tǒng)調(diào)用過濾,會(huì)在進(jìn)程級(jí)別上進(jìn)行檢測(cè):
因此,對(duì)于當(dāng)前的進(jìn)程和線程,系統(tǒng)調(diào)用過濾沒有啟用。查看進(jìn)一步執(zhí)行,將體現(xiàn)出這個(gè)標(biāo)志位的重要性:
如果開啟了系統(tǒng)調(diào)用過濾功能,KeServiceDescriptorTableFilter將取代KeServiceDescriptorTableShadow,如果沒有開啟過濾,則將使用KeServiceDescriptorTableShadow。接下來要觀察系統(tǒng)調(diào)用表的使用,如下圖所示:
在經(jīng)過運(yùn)算后,RDI包含系統(tǒng)調(diào)用數(shù)目。在WIN32K系統(tǒng)的情況下,它的值是0x20。所以,取決于系統(tǒng)調(diào)用過濾是否開啟,不同的表會(huì)被載入R10.這兩個(gè)選項(xiàng)是:
然后該表將轉(zhuǎn)入真實(shí)的函數(shù)調(diào)用:
跟隨以上的算法,我們?cè)谡{(diào)試器中找到:
所以這很顯然,系統(tǒng)調(diào)用號(hào)通過負(fù)偏移指向W32pServiceTable結(jié)構(gòu),然后指向真實(shí)的NtGdiDdDDICreateAllocation函數(shù)。這是非常好的,但是如果開啟了系統(tǒng)調(diào)用過濾,會(huì)有什么區(qū)別呢,這可以使用W32pServiceTableFilter來進(jìn)行驗(yàn)證:
我們看到在此之前并沒有什么區(qū)別,這是因?yàn)镹tGdiDdDDiCreateAllocation并不是過濾的API之一,如果我們選擇其他的系統(tǒng)調(diào)用,比如NtGdiDdDDiCreateAllocation,它的系統(tǒng)調(diào)用號(hào)是0x117E。我們基于是否啟用系統(tǒng)調(diào)用過濾來對(duì)比以下兩個(gè)輸出。
首先是未啟用系統(tǒng)調(diào)用過濾的:
然后是啟用系統(tǒng)調(diào)用過濾:
我們發(fā)現(xiàn),如果開啟了系統(tǒng)調(diào)用過濾功能,系統(tǒng)調(diào)用是不允許的另一個(gè)函數(shù)被調(diào)用的。該過濾函數(shù)驗(yàn)證是否啟用系統(tǒng)調(diào)用過濾并簡(jiǎn)單的結(jié)束系統(tǒng)調(diào)用。
利用的結(jié)果
現(xiàn)在我們了解了系統(tǒng)調(diào)用過濾是如何工作的,我們需要研究它是如何防止內(nèi)核漏洞利用的。首先要看看它保護(hù)的是什么進(jìn)程,到目前為止,僅僅是微軟Edge可以啟用這個(gè)功能,目前第三方程序沒有可以啟用它的接口。這意味著系統(tǒng)調(diào)用過濾僅僅關(guān)心Microsoft Edge漏洞并且僅限于內(nèi)核漏洞。下面我們可以看到MicrosoftEdgeCP.exe的進(jìn)程結(jié)構(gòu),并啟用了系統(tǒng)調(diào)用過濾:
回顧我早期文章,關(guān)于重新啟用tagWND對(duì)象作為讀寫原語的用法的,我想知道是否使用這個(gè)方法的任意系統(tǒng)調(diào)用都會(huì)被過濾,在那個(gè)方法中使用的內(nèi)核模式的函數(shù)是:
- NtUserCreateWindowEx
- NtUserDestroyWindow
- NtUserSetWindowLongPtr
- NtUserDefSetText
- NtUserInternalGetWindowText
- NtUserMessageCall
在win32k.sys中沒有stub_*方法的函數(shù)意味著不會(huì)被過濾。結(jié)論就是Win32K系統(tǒng)調(diào)用過濾不會(huì)阻止可能導(dǎo)致漏洞的系統(tǒng)調(diào)用,但是當(dāng)系統(tǒng)調(diào)用觸發(fā)一個(gè)漏洞時(shí),它一定會(huì)阻止,可能是write-that-wherer或者是一個(gè)緩沖區(qū)溢出。WIN32K系統(tǒng)調(diào)用過濾所提供的保護(hù)是巧妙的,但關(guān)鍵在于是否使用了系統(tǒng)調(diào)用來觸發(fā)漏洞。