繞過(guò)Windows 10的CFG機(jī)制
0x00 前言
本文是2016年7月一些研究的結(jié)果,但是直到現(xiàn)在才能發(fā)布。在6月,Theori發(fā)布了一篇博文關(guān)于IE的漏洞MS16-063,他們寫的利用是針對(duì)Windows7上IE 11版本,并且他們認(rèn)為因?yàn)閃indows 10 的CFG機(jī)制無(wú)法在Windows 10利用。本文描述了我如何在Windows 10利用,并繞過(guò)CFG,事實(shí)我還發(fā)現(xiàn)了另一種方法,會(huì)在將來(lái)發(fā)布。
0x01 理解CFG
控制流保護(hù)(CFG)是微軟在Windows 8.1 update 3和Windows 10中實(shí)現(xiàn)的緩解措施,用來(lái)保護(hù)匯編級(jí)別的非直接調(diào)用。趨勢(shì)已經(jīng)公布了Windows 10上面關(guān)于CFG的很好的分析文章。已經(jīng)有集中公布的繞過(guò)CFG的方法,但是這些方法主要的目標(biāo)是CFG實(shí)現(xiàn)算法,但是我想從這個(gè)功能的弱點(diǎn)入手。因?yàn)門heori在他們博文中寫的Windows 7上面的利用因?yàn)镃FG的存在無(wú)法有效工作,讓我們看下為什么并試圖繞過(guò)它。
來(lái)自Theori的利用代碼在Windows 10的IE中直接覆蓋了虛函數(shù)表。因此問(wèn)題是我們?nèi)绾卫萌我庾x寫來(lái)繞過(guò)CFG。根據(jù)趨勢(shì)的研究,CFG被函數(shù)LdrValidateUserCallTarget調(diào)用來(lái)驗(yàn)證一個(gè)函數(shù)的調(diào)用是否用了非直接調(diào)用:
加載到EDX中的指針是驗(yàn)證位圖的基本指針,在這種情況下是:
然后,被驗(yàn)證的函數(shù)將其地址加載到ECX中,如果kernel32!VirtualProtectStub作為示例,則在這種情況下的地址是:
然后地址右移8位,用于裝入保存該地址有效位的DWORD值,在這種情況下:
然后函數(shù)地址右移3位,并執(zhí)行位測(cè)試,這本質(zhì)上對(duì)位移地址進(jìn)行模數(shù)0x20操作,然后是驗(yàn)證位圖的DWORD中檢查的位,因此在這種情況下:
因此相關(guān)位在偏移0x14處:
這意味著它是可靠的。因此VirtualProtect的調(diào)用地址是可靠的,然而這沒(méi)有真的解決問(wèn)題,它的參數(shù)也必須由攻擊者提供。正常情況應(yīng)該是用ROP鏈,但是任何不是從函數(shù)開(kāi)始的字節(jié)都是無(wú)效的。因此解決方案是找到一個(gè)函數(shù)在被調(diào)用的地方的參數(shù)是可以控制的,并且函數(shù)的功能可以給攻擊者利用。這需要在利用中非常仔細(xì)。
0x02 在Windows 10中利用
在Theori提供的利用,代碼是通過(guò)stack pivot小配件覆蓋TypedArray的虛函數(shù)表,因此這個(gè)沒(méi)有其他可能了,研究TypedArray提供的函數(shù)是值得的,發(fā)現(xiàn)下面兩個(gè)函數(shù)比較有用:
他們的偏移是0x7c和0x188,他們能從javascript代碼中直接調(diào)用,并且HasItem有個(gè)可以控制的參數(shù),同時(shí)Subarray有兩個(gè)用戶可控制的參數(shù)。然而問(wèn)題是它們都不返回除布爾值之外的任何數(shù)據(jù)。此外,所選擇的函數(shù)必須采用相同數(shù)量的參數(shù),否則堆棧不平衡將會(huì)引發(fā)異常。我搜索的API應(yīng)該暴露一個(gè)指針能用來(lái)覆蓋返回地址,以便可以繞過(guò)CFG。
我定位的可用的API是RtlCaptureContext,由kernel32.dll、kernelbase.dll和ntdll.dll導(dǎo)出,這個(gè)API有一個(gè)指向CONTEXT結(jié)構(gòu)的參數(shù):
CONTEXT結(jié)構(gòu)儲(chǔ)存了轉(zhuǎn)儲(chǔ)的所有的寄存器(包括ESP),而且輸入值僅僅是一個(gè)緩沖區(qū)的指針??匆幌耇ypedArray對(duì)象的內(nèi)存布局:
第一個(gè)DWORD值是虛函數(shù)表指針,能夠被覆蓋創(chuàng)建一個(gè)假的虛函數(shù)表,在偏移0x7c處存儲(chǔ)API RtlCaptureContext的地址,同時(shí)偏移0x20是TypedArray指向的真實(shí)數(shù)據(jù)的指針:
因?yàn)樾孤┻@個(gè)緩沖區(qū)的地址也是可能的,它能提供RtlCaptureContext的參數(shù)。為了完成夾的虛函數(shù)表,不得不創(chuàng)建一個(gè)指針指向偏移0x7c處的ntdll!RtlCaptureContext,這意味著泄露了RtlCaptureContext的地址,繼而意味這泄露了ntdll.dll的地址。執(zhí)行此操作的默認(rèn)路徑是使用vtable的地址,它是一個(gè)指向jscript9.dll的指針:
從這個(gè)指針往回0x1000個(gè)字節(jié),搜索MZ頭,繼而查找到指向kernelbase.dll的導(dǎo)入表。做同樣的操作能偶獲得kernelbase.dll的基址,然后再獲得ntdll.dll的導(dǎo)入表指針并再次獲得其基址,然后從導(dǎo)出函數(shù)中找到RtlCaptureContext。盡管這個(gè)方法是可靠的但是有個(gè)缺陷,如果在系統(tǒng)中裝了EMET,將觸發(fā)來(lái)自jscript9.dll的代碼的崩潰,因?yàn)閺腜E頭或?qū)С霰碜x取數(shù)據(jù)不被允許,為了繞過(guò)EMET我使用了一個(gè)不同的技術(shù)。記住每一個(gè)非直接調(diào)用都被CFG保護(hù),因?yàn)閖script9.dll的函數(shù)被CFG保護(hù)了,所以不能調(diào)用直接指向ntdll的函數(shù)。一個(gè)在虛表中偏移0x10的函數(shù)如下:
用原始讀操作,指向ntdll.dll的指針能通過(guò)以下函數(shù)找到:
通過(guò)ntdll.dll的指針得到RtlCaptureContext的地址,不通過(guò)讀取導(dǎo)出表而是使用搜索特征和哈希找到并讀取。RtlCaptureContext看起來(lái)如下:
前0x30字節(jié)總是相同的并且很特殊,因此可以用哈希碰撞找到函數(shù)地址:
函數(shù)可以使用指向ntdll.dll的指針作為參數(shù)。
把上面的都整合到一起:

從這偏移0x200包含了RtlCaptureContext的結(jié)果,看起來(lái)如下:

從上面可以清楚地看出堆棧指針已經(jīng)泄漏,現(xiàn)在找到一個(gè)能控制執(zhí)行的地址是個(gè)問(wèn)題??聪聴m旓@示:
那就是當(dāng)前函數(shù)的返回地址,這個(gè)地址位于與RtlCaptureContext信息中的偏移0x9C處的泄漏指針相距0x40字節(jié)的偏移處。運(yùn)氣好,這個(gè)偏移對(duì)于其他簡(jiǎn)單函數(shù)將是相同的,所以應(yīng)該可以寫入并使其覆蓋其自己的返回地址,從而繞過(guò)CFG。
利用補(bǔ)充如下:
運(yùn)行時(shí)顯示EIP控制:
而且,在偏移0x40和0x44的寫入現(xiàn)在位于棧頂,允許創(chuàng)建一個(gè)stack pivot和ROP鏈,一種方法是用POP EAX接著XCHG EAX,ESP。
0x03 微軟緩解措施
Microsoft已經(jīng)聲明,堆棧上的損壞返回地來(lái)繞過(guò)CFG是一個(gè)已知的設(shè)計(jì)限制,因此無(wú)法修復(fù)或領(lǐng)取任何種類的賞金,如下所示:
正如那個(gè)所說(shuō),微軟做了兩個(gè)事情來(lái)緩解這個(gè)技術(shù),首先在未來(lái)的Windows 10版本中,RFG將被實(shí)現(xiàn),阻止堆棧損壞以給予執(zhí)行控制的方式。另一個(gè)是在Windows 10的周年版發(fā)布中引入敏感的API的介紹,它僅保護(hù)微軟Edge,但是上述情況無(wú)法保護(hù),但是他能阻止微軟Edge中的RtlCaptureContext。Poc代碼可以在這找到:https://github.com/MortenSchenk/RtlCaptureContext-CFG-Bypass。