微軟在北京時間2015年1月22日,公布了其新一代操作系統Windows 10的新技術預覽版,并在隨后的北京時間1月24日向公眾開放了該預覽版本的下載(Build:9926)。在這個新的Windows10預覽版中,內核版本直接從6.4提升到了10.0,同時也帶來了一些新的產品形態和用戶體驗上的諸多改進。
作為國際頂尖的安全廠商,360除了在第一時間為使用Windows 10新預覽版的用戶提供安全防護,也在同時關注新操作系統中引入的新安全特性。
微軟從Windows 8開始,就為其新的操作系統不斷引入了很多新的安全特性,包括零頁禁用、高熵隨機化、執行流保護(Control Flow Guard , CFG)、管理模式執行保護(Superior Mode Execution Prevention, SMEP)等等,對Windows平臺上的應用和內核漏洞利用明顯地提高了門檻。這次Windows 10的新技術預覽版又為操作系統加入了什么樣新的保護呢?
在拿到內核并進行分析之后,首先進入筆者視線的就是本文將要介紹的,Windows 10預覽版中對于字體的安全防護的兩項增強。
這里要提到的是,本文在完成前(北京時間1月24日早上9點~11點左右),國外的一些安全研究人員,包括CrowdStrike公司的Alex Ionescu(@aionescu)和Google公司的Kostya Kortchinsky(@crypt0ad),也都同時發現并在Twitter上提到了這兩項增強相關的關鍵字。
這里本文將帶讀者第一時間深入地分析Windows 10字體防護增強的實現和策略,了解其中的來龍去脈。
內核字體引擎安全問題
由于諸多歷史原因,Windows的內核模式字體引擎長期以來是Windows操作系統內核中典型的復雜程度高而代碼安全質量并不高的組件。出于字體引擎性能的考慮,微軟也不得不將其一直放在內核模式。因此,一旦Windows內核中的字體引擎出現安全漏洞,漏洞攻擊代碼就可以在用戶瀏覽網頁或文檔中的遠程字體文件時,直接在Windows內核中執行,可以完全繞過幾乎所有的安全防護機制,殺傷力相當大。
360的安全研究人員王宇對于內核字體引擎的安全歷史和安全漏洞進行了深入的研究。他將一些技術成果公開在國際安全會議Syscan360 2012(http://www.syscan360.org/achive_2012_speech.html)和Blackhat2014(https://www.blackhat.com/us-14/briefings.html#understanding-tocttou-in-the-windows-kernel-font-scaler-engine)上。對相關技術有興趣的讀者可以深入地學習一下。
歷史上微軟修復的內核字體漏洞并不少,但最出名的,也是打破了字體漏洞神秘感的,可能就要數2011年被安全廠商披露的感染伊朗等國家的震網二代”Duqu”病毒。
該病毒是利用Windows內核字體引擎中的一處漏洞(CVE-2011-3402),將利用該漏洞的、命名為嗜血法醫Dexter的字體文件嵌入到word文檔中,當用戶打開word文檔、觸發字體加載,惡意代碼就直接進入內核模式運行。
這應該是首次被公開披露的字體漏洞用于真實的APT攻擊的案例。其后,該漏洞還被一些Exploit Kit改造為通過網頁瀏覽器進行攻擊的版本,開始大范圍的傳播和攻擊。
在今年的10月,國外安全公司Fireeye也披露了他們新發現的被用于真實的APT攻擊的字體漏洞:(CVE-2014-4148)(https://www.fireeye.com/blog/threat-research/2014/10/two-targeted-attacks-two-new-zero-days.html)。這個漏洞攻擊巧妙地利用字體引擎中一處整數符號問題,實現在瀏覽嵌入了字體的文檔或網頁時,直接從內核模式執行惡意代碼,繞過系統中的安全防護機制,安裝惡意程序。
無論是白帽子安全研究人員還是可能具備國家級背景的高級黑客,針對Windows字體引擎安全問題的深入挖掘,都將這一攻擊面的危險狀況更多地暴露出來。微軟在修復漏洞的同時,也在計劃著一些更通用的解決辦法。
在2014年的8月補丁日,微軟在針對字體引擎內核漏洞的補丁中,就引入了一項針對字體緩存機制的安全優化,來緩和通過字體緩存進行的某些字體漏洞攻擊手法。這次Windows 10新預覽版中的這兩項改進,則是微軟針對字體漏洞攻擊緩和的另一次嘗試。
Windows 10字體安全防護
下面筆者就來詳細介紹下這次引入的改進。
第一項增強:非系統字體禁用策略
從Windows 8開始,微軟引入了一個新的API: SetProcessMitigationPolicy。該API通過最終調用NtSetInformationProcess(ProcessMitigationPolicy),設置進程EPROCESS對象中Flags(Flags2,Flags3)域中的某些位,來打開/關閉進程粒度的一些漏洞緩和機制的開關。
這些緩和機制包括強制DEP/ALSR、零頁內存禁用、句柄強制檢查等。除了通過這個API,相關的開關也可以通過父進程繼承、StartupInfo(Process Thread Attributes)、IEFO和全局緩和選項等方式來控制。
本次引入的一個新的緩和選項被稱為ProcessFontDisablePolicy。在對進程設置這個緩和策略后,表現在EPROCESS->Flags3.DisableNonSystemFonts這個標記上,同時還有AuditNonSystemFontLoading作為輔助選項。
這個選項的功能顧名思義,就是針對進程禁止加載非系統的字體,AuditNonSystemFontLoading則是審計記錄非系統字體的加載。
那么系統是如何實現該機制的呢?這里我們就要看看內核字體引擎了。在Windows 10中,內核字體引擎主要位于Win32k內核驅動的完整版本: win32full.sys中。
桌面內核引擎win32k提供了多個系統調用接口允許用戶模式加載字體到內核字體引擎中,包括NtGdiAddFontResourceEx,NtGdiAddFontMemResourceEx等等。在內核字體引擎內部根據不同的場景,主要是使用以下四個函數來實現字體文件的加載處理的:
1 PUBLIC_PFTOBJ::bLoadFonts
2 PUBLIC_PFTOBJ::hLoadMemFonts
3 PUBLIC_PFTOBJ::bLoadRemoteFonts
4 DEVICE_PFTOBJ::bLoadFonts
這四個函數是內核字體引擎在不同場景下加載字體文件的關鍵函數。在這些函數中,內核將分配(或直接使用)關鍵的字體文件視圖對象(FontFileView),并調用vLoadFontFileView函數來加載字體文件視圖對象,將其描述的字體文件數據映射到內核內存中,并進行解析和處理,以供后面渲染使用。
本次Windows 10新預覽版就在這些函數中調用vLoadFontFileView加載字體文件視圖對象之前,進行檢查。
01 …
02 FontLoadingOptions = GetCurrentProcessFontLoadingOption();
03
04 if ( FontLoadingOptions == 2 )
05 {
06 NonSystemFontPath = GetFirstNonSystemFontPath(FontFilePathNames, FontFileCount);
07 if ( NonSystemFontPath )
08 {
09 LogFontLoadAttempt(FONT_LOAD_NORMAL, NonSystemFontPath, TRUE);
10 return FALSE;
11 }
12 }
13 else if ( FontLoadingOptions == 1 )
14 {
15 NonSystemFontPath = GetFirstNonSystemFontPath(FontFilePathNames, FontFileCount);
16 if ( NonSystemFontPath )
17 LogFontLoadAttempt(FONT_LOAD_NORMAL, NonSystemFontPath, FALSE); // log only
18 }
19
20 …
這里我們可以看到代碼首先通過GetCurrentProcessFontLoadingOption調用獲取字體加載緩和的選項。
該函數實際是通過NtQueryInformationProcess(ProcessMitigationPolicy)獲得_PROCESS_MITIGATION_FONT_DISABLE_POLICY結構。
在結構中,bit0為DisableNonSystemFonts,bit1為AuditNonSystemFontLoading,接著此結構被轉換成下面三個選項值:
0: 不審計、不禁用
1:審計、不禁用
2:審計并禁用
然后,系統會調用GetFirstNonSystemFontPath函數解析要加載的字體文件列表(可能是多個文件)。該函數分別通過IoCreateFile得到這些文件的文件句柄,再通過句柄得到文件對象的完整路徑,最后同系統字體目錄(%Systemroot%Fonts)進行對比。
如果待加載的字體列表中存在非系統字體目錄下的字體文件,那么系統會首先調用LogFontAttempt函數,通過ETW機制記錄到日志中。
該函數的第一個參數為觸發字體加載的類型:
1 0: LoadPublicFonts
2 1: LoadMemFonts
3 2: LoadRemoteFonts
4 3: LoadDeviceFonts
第二個參數是觸發的字體路徑。
第三個參數是是否要對該字體拒絕加載。
接著,系統會根據進程緩和選項的設置,決定對于這種情況,是僅審計記錄,還是要拒絕這個字體的加載。
對于調用時不含有直接的字體文件路徑的LoadMemFonts/LoadRemoteFonts/LoadDeviceFonts的情況,內核將不會判斷字體路徑,如果在緩和選項設置了禁止非系統字體加載,則會禁用這些接口在所有情況下的字體加載并進行審計。
從微軟公開的Windows 10技術預覽版來看,這項防護開關并沒有對一些關鍵應用如Internet Explorer、內置的PDF瀏覽器等默認開啟。
從目前的功能設計看來,該功能更像是為特定的企業用戶提供的防止高級攻擊的安全措施。尤其是針對字體加載的審計功能,可以幫助IT管理人員快速定位可能的高級威脅攻擊。
值得一提的是,在360的XP盾甲的2.0中,我們就引入了同Windows10本次更新加入的字體禁止策略類似的機制。通過XP盾甲的隔離引擎機制,我們將系統中的字體同沙箱隔離環境中的字體隔離開來,使得沙箱隔離環境中的字體無法加載到內核中,而隔離環境之外的字體在隔離環境內則不受影響,達到了和Windows10這項防護類似的漏洞防御效果。
在XP盾甲4.0的產品中,我們則引入了更強的內核字體引擎鎖定機制,針對內核字體引擎的攻擊防護強度又上了一個臺階。360一年前設計的XP盾甲防護機制同微軟公布的新一代操作系統中的新防護措施的不謀而合,也從側面印證了360在漏洞防護研究方面的探索。
第二項增強:隔離的用戶模式字體引擎
在Windows 10的新技術預覽版中,針對內核字體引擎的另一項引人注意的重大改進是“用戶模式字體驅動”(User Mode Font Driver,UMFD)機制。
就像前面我們說到的,位于內核模式的字體引擎的一旦被突破,攻擊者通過精巧的數據構造可以直接從遠程獲得內核代碼執行的能力,危險極大。
同時,由于字體引擎在內核中運行,因此針對字體引擎的修改也必須非常謹慎,稍有不慎就可能引發大面積的藍屏或應用程序異常。
關于修改內核字體引擎帶來的副作用,眼前就有典型的案例:在今年8月的補丁日中,微軟的KB2982791補丁為了修復字體引擎中的安全漏洞,修改了內核字體緩存引擎。結果,這一改動中存在的兼容問題不幸引發了大面積的用戶Windows系統啟動即藍屏的問題。該問題遭到了大量用戶和媒體的指責,微軟不得不宣布將該補丁撤回重發。
在這個Windows 10技術預覽版中,微軟首次建立了用戶模式和內核模式的字體引擎交互機制,將內核字體引擎中的多個字體驅動引擎移植到用戶模式,使得字體引擎能夠部分在隔離的用戶模式進程中運行,同時也保留了內核模式字體引擎驅動的機制,盡量將可能出現安全漏洞的渲染引擎放到用戶模式,同時使用一些內核預先加載、內核/用戶模式交互的方式,減少這種模式對于性能的損耗。
在這套機制應用后,如果相關的字體引擎代碼出現安全漏洞,攻擊者只能控制被隔離的用戶模式字體驅動宿主,無法通過字體漏洞直接控制操作系統內核。而修復相關漏洞的成本和可能的風險,也將大大降低。
這里筆者將主要介紹這套新的UMFD機制的運作原理和一些關鍵細節。限于時間和篇幅的原因,關于UMFD很多具體實現、實際性能狀況、實際漏洞隔離效果等,這里無法詳盡地覆蓋到,感興趣的讀者可以去深入逆向、分析和測試相關的代碼和功能來發現更多的內容,也可以在本Blog同筆者討論。
在前面一節中,我們介紹了,*::Load*Fonts系列函數將使用vLoadFontFileView加載字體文件視圖對象,而這節要介紹的增強里的一個關鍵點就在vLoadFontFileView函數中,在Windows10 build 9926中,該函數的偽代碼如下:
01 void vLoadFontFileView(…)
02 {
03 *(pchecksum + 4) = 0;
04 *pchecksum = 0;
05
06 if ( gbNetworkFontsLoaded && gbAttemptedEnableEUDC && gbFntCacheClosed )
07 {
08 UmfdLoadFontFileView(
09 FontPathName,
10 pfileview,
11 filecount,
12 pview,
13 countview,
14 pdesignvector,
15 hff,
16 devlist);
17 }
18 else
19 {
20 KmfdLoadFontFileView(
21 FontPathName,
22 cwc,
23 pfileview,
24 filecount,
25 pview,
26 countview,
27 pdesignvector,
28 countdv,
29 hff,
30 devlist,
31 pchecksum);
32 }
33 }
對比上個公開發布的Windows 10技術預覽版(Build 9879)我們可以看到,vLoadFontFileView根據多個開關進行判斷,分別執行到UmfdLoadFontFileView和KmfdLoadFontFileView這兩種情況。
而我們仔細觀察下KmfdLoadFontFileView就可以發現,它的代碼和9879中原有的vLoadFontFileView的代碼是非常相似的。
因此我們可以推測:在Build9926中,微軟針對gbNetworkFontsLoaded (有遠程字體加載) + gbAttemptedEnableEUDC(曾調用過NtGdiEnableEUDC啟用EUDC) + gbFntCacheClosed(系統啟動字體已經加載到緩存中)的情況下, 就會進入名為UmfdLoadFontFileView的分支來進行用戶模式字體加載,否則就使用KmfdLoadFontFileView,即原有的vLoadFontFileView邏輯來加載字體。
根據我們的推測,UmfdLoadFontFileView應該就是用戶模式字體驅動(UMFD)的字體加載函數。如何證實這點呢?該函數又是如何同用戶模式的字體驅動引擎交互的呢?
我們繼續進入UmfdLoadFontFileView函數,看看他的實現。
在該函數中,我們可以看到它會調用UmfdHostLifeTimeManager::EnsureUmfdHost,該函數是用戶模式的字體引擎宿主的生命周期管理函數,用于確保用戶模式的字體引擎宿主客戶端存在。
我們查看這個函數,可以發現該函數的關鍵點是:如果Winlogon進程存在,那么使用PostWinlogonMessage發送一條1033的消息,并等待UmfdHostLifeTimeManager::s_WinlogonCallbackEvent事件觸發。
PostWinlogonMessage函數實現在win32kbase.sys中,實際是調用msrpc.sys導出的相關LPC/RPC函數給winlogon.exe的LPC接口發送內核模式LPC消息。
winlogon.exe則會在WMsgKMessageHandler函數中接收這個消息。這個1033號消息實際的作用,就是去加載用戶模式的字體驅動客戶端fontdrvhost.exe。
在收到1033號消息后,Winlogon會使用LaunchUmfdHostWithRestrictedToken函數,通過WTSQueryUserToken獲取當前登錄的用戶token句柄。
接著,它使用SetTokenInformation(TokenIntegrityLevel)將token的完整性級別設置為Low。
最后,它通過CreateProcessAsUser,使用這個受限的token來創建fontdrvhost.exe(默認位于%systemroot%system32下)進程。
接下來的一個關鍵步驟是通知UMFD的驅動部分,在UMFD的內核部分引擎中注冊fontdrvhost.exe這個用戶模式客戶程序。
這里使用的是gdi32!NamedEscape函數,該函數實際上是通過調用NtGdiExtEscape來實現同win32k內核驅動通訊的。
NtGdiExtEscape過去被win32k內核部分用于同gdi32進行一些內核/用戶模式交互,在新的Windows10預覽版中,該函數被umfd使用來進行umfd的相關內核交互,是UMFD的內核/用戶模式交互基礎。
在創建了fontdrvhost進程后,Winlogon通過NamedEscape->NtGdiExtEscape將創建的進程PID發送給內核。
在新的NtGdiExtEscape中,內核檢測到發起該調用的進程是winlogon進程,就識別傳入的參數,通過UmfdHostLifeTimeManager::InitializeUmfdAndRegisterHost獲得該進程的進程對象,將其注冊為UMFD的宿主客戶端,并設置我們前面提到的UmfdHostLifeTimeManager::s_WinlogonCallbackEvent事件,來通知UMFD客戶端已經準備完畢。
在注冊為UMFD的宿主客戶端后,fontdrvhost.exe就可以通過NamedEscape->NtGdiExtEscape同內核模式進行通訊。在這個系統調用的實現里,會識別如果是UMFD的宿主進程進行的調用,則進入一個UMFD的專用通訊函數:UmfdDispatchEscape。
在UmfdDispatchEscape中,內核提供了一些用戶模式字體驅動客戶端所需要的內核-用戶模式數據交互、文件映射與訪問接口等,這個后面我們會提到。
上面我們說到的是UMFD用戶模式客戶端的創建以及它同內核模式部分通訊的過程。
接下來,我們回到剛才說的vLoadFontFileView函數過程。上面我們說到在注冊用戶模式字體宿主進程后,內核會置UmfdHostLifeTimeManager::s_WinlogonCallbackEvent事件來通知UMFD用戶模式部分已經開始工作并使得字體加載過程繼續下去。
那么接下來,內核就開始進行實際的用戶模式字體加載,通過調用PDEVOBJ::LoadFontFile,實際上內核會Attach到CSRSS進程上,并調用UMFD的字體加載接口函數:UmfdLoadFontFile來完成字體加載。
UmfdLoadFontFile是UMFD的接口列表UmfdDDIs中用于處理UMFD字體實際加載工作的接口函數。它的核心功能是通UmfdClientSendAndWaitForCompletion函數將UMFD請求插入UMFD工作隊列中,并通知UMFD的用戶模式部分處理隊列中的數據。
除了UmfdLoadFontFile外,UmfdDDIs這個接口列表中還包含了同內核模式字體接口一一對應的諸如UmfdQueryFontData、UmfdQueryFontTree、UmfdGetTrueTypeFile等字體相關的重要接口。
當對應的內核模式接口被調用時,實際也是由這些接口函數使用UmfdClientSendAndWaitForCompletion發送請求到UMFD用戶模式處理隊列中,通知用戶模式客戶端來完成實際的字體工作的。
用戶模式字體驅動客戶端宿主fontdrvhost.exe啟動后,則會建立一個ServerRequestLoop線程,該線程通過NamedEscape(NtGdiExtEscape)(Dispatch id = 0 )來同內核交互.
當NameEscape傳遞的Dispatch id = 0 時,實際內核執行的對應是UmfdEscSendCompleteWaitReceive函數。該函數同UmfdClientSendAndWaitForCompletion是成對的,用于“接收”通過UmfdClientSendAndWaitForCompletion“發送”到隊列的請求數據。
在宿主從內核獲得字體操作請求數據返回用戶模式后,用戶模式字體驅動宿主再使用DispatchRequest函數分發到內核的字體處理請求到各個用戶模式字體驅動接口中。
這些用戶模式字體驅動目前也都是編譯在fontdrvhost.exe這個進程的代碼中的,主要有ttf字體驅動、bmf字體驅動和vtf字體驅動等相關的處理代碼。
在處理內核字體請求時,DispatchRequest會根據請求數據中指定的設備和指定的請求類型(QueryFontData,LoadFontFile…)來選擇不同的字體驅動處理例程進行最終的字體處理過程。
值得一提的是,在用戶模式字體引擎中,包括針對字體虛擬機的指令實現部分(itrp_*函數),也是在用戶模式中實現的。
最近幾年被曝光用于真實攻擊的內核模式字體漏洞,都是在特定的itrp_*函數處理數據中發生的安全問題,也都需要依賴itrp_*函數來操縱內核數據來實現最終的漏洞攻擊。這項新的增強將這些函數隔離在低完整性級別的受限用戶模式進程中,顯然極大地提高了這類漏洞的攻擊難度和利用門檻。
簡單總結來說,UMFD這套機制實際上就是通過在內核并行純內核模式字體引擎和需交互的用戶模式字體引擎兩套系統。在系統啟動過程中使用性能更好的內核模式加載不會有安全問題的、已注冊的系統字體文件。
在系統啟動完成并開始加載非本地的字體文件時,就開啟用戶模式字體引擎。通過Winlogon控制的用戶模式字體引擎客戶端fontdrvhost.exe,利用NtGdiExtEscape同內核進行交互,為內核的UMFD部分實現字體文件的解析、渲染工作,并給內核模式返回需要的接口。
兩套接口并行、對外保持一致的數據交互,在不影響功能、消耗非常見情況下的較少性能的前提下,實現了字體數據解析和渲染的用戶模式+受限權限隔離。
即使字體解析和渲染過程中存在安全漏洞,也被限制在受限的用戶模式進程中,需要進一步利用其他安全漏洞才能最終完成攻擊,無法再直接獲得內核的最高權限,使得這類型漏洞的風險大大降低,也使限制、發現這類漏洞攻擊更容易和更具備可能性。
小結
本文所介紹的非系統字體禁用和用戶模式字體驅動機制,都是微軟在內核模式字體引擎漏洞攻擊方面的緩和措施,分別通過安全策略、安全隔離的思想,提高內核模式字體引擎漏洞的利用難度,降低了相關漏洞的安全風險。在特定的環境下,甚至可能完全杜絕遠程字體漏洞的攻擊。這個安全措施稱得上是繼默認開啟CFG后,Windows10又一可圈可點的新安全特性。
如同CFG在Windows10中默認開啟后,逐步開放到Windows8.1 Update3的更新上,希望這個機制也能盡早磨練成熟,升級到后續的操作系統中,為更多用戶提供更好的安全保障。