新聞中心
0x00 前言

創(chuàng)新互聯(lián)公司堅(jiān)持“要么做到,要么別承諾”的工作理念,服務(wù)領(lǐng)域包括:成都網(wǎng)站設(shè)計(jì)、網(wǎng)站制作、企業(yè)官網(wǎng)、英文網(wǎng)站、手機(jī)端網(wǎng)站、網(wǎng)站推廣等服務(wù),滿(mǎn)足客戶(hù)于互聯(lián)網(wǎng)時(shí)代的簡(jiǎn)陽(yáng)網(wǎng)站設(shè)計(jì)、移動(dòng)媒體設(shè)計(jì)的需求,幫助企業(yè)找到有效的互聯(lián)網(wǎng)解決方案。努力成為您成熟可靠的網(wǎng)絡(luò)建設(shè)合作伙伴!
隨著操作系統(tǒng)開(kāi)發(fā)人員一直在增強(qiáng)漏洞利用的緩解措施,微軟在Windows 10和Windows 8.1 Update 3中默認(rèn)啟用了一個(gè)新的機(jī)制。這個(gè)技術(shù)稱(chēng)作控制流保護(hù)(CFG)。
和其他利用緩解措施機(jī)制一樣,例如地址空間布局隨機(jī)化(ASLR),和數(shù)據(jù)執(zhí)行保護(hù)(DEP),它使得漏洞利用更加困難。毫無(wú)疑問(wèn),它將大大改變攻擊者的利用技術(shù)。就像ALSR導(dǎo)致了堆噴射技術(shù)的出現(xiàn),和DEP導(dǎo)致了ROP技術(shù)的出現(xiàn)。
為了研究這個(gè)特別的技術(shù),我使用了Windows 10 技術(shù)預(yù)覽版(build 6.4.9841)和使用Visual Studio 2015 預(yù)覽版編譯的測(cè)試程序。因?yàn)槟壳白钚掳娴腤indows 10 技術(shù)預(yù)覽版(build 10.0.9926)有了一點(diǎn)改變,我將指出不同之處。
為了完全實(shí)現(xiàn)CFG,編譯器和操作系統(tǒng)都必須支持它。作為系統(tǒng)層面的利用緩解措施,CFG的實(shí)現(xiàn)需要聯(lián)合編譯器、操作系統(tǒng)用戶(hù)層庫(kù)和內(nèi)核模塊。MSDN上面的一篇文章描述了支持CFG開(kāi)發(fā)者需要做的步驟。
微軟的CFG實(shí)現(xiàn)主要集中在間接調(diào)用保護(hù)上??紤]下面測(cè)試程序中的代碼:
圖1 - 測(cè)試程序的代碼
讓我們看下CFG沒(méi)有啟用時(shí)的代碼情況。
圖2 - 測(cè)試程序的匯編代碼
在上圖中,有一個(gè)間接調(diào)用。它的目標(biāo)地址不在編譯時(shí)決定,而是在運(yùn)行時(shí)決定。一個(gè)利用如下:
圖3 – 怎么濫用間接調(diào)用
微軟實(shí)現(xiàn)的CFG主要關(guān)注緩解間接調(diào)用和調(diào)用不可靠目標(biāo)的問(wèn)題(在利用中,這是shellcode的第一步)。
不可靠的目標(biāo)有明顯特征:在大部分情況下,它不是一個(gè)有效的函數(shù)起始地址。微軟的CFG實(shí)現(xiàn)是基于間接調(diào)用的目標(biāo)必須是一個(gè)可靠的函數(shù)的起始位置。啟用CFG后的匯編代碼是怎樣的?
圖4 – 啟用CFG后的匯編代碼
在間接調(diào)用之前,目標(biāo)地址傳給_guard_check_icall函數(shù),在其中實(shí)現(xiàn)CFG。在沒(méi)有CFG支持的Windows中,這個(gè)函數(shù)不做任何事。在Windows 10中,有了CFG的支持,它指向ntdll!LdrpValidateUserCallTarget函數(shù)。這個(gè)函數(shù)使用目標(biāo)地址作為參數(shù),并且做了以下事情:
1. 訪(fǎng)問(wèn)一個(gè)bitmap(稱(chēng)為CFGBitmap),其表示在進(jìn)程空間內(nèi)所有函數(shù)的起始位置。在進(jìn)程空間內(nèi)每8個(gè)字節(jié)的狀態(tài)對(duì)應(yīng)CFGBitmap中的一位。如果在每組8字節(jié)中有函數(shù)的起始地址,則在CFGBitmap中對(duì)應(yīng)的位設(shè)置為1;否則設(shè)置為0。下圖是CFGBitmap的一部分示例:
圖5 – CFGBitmap
2. 將目標(biāo)地址轉(zhuǎn)化為CFGBitmap中的一個(gè)位。讓我們以00b01030為例:
圖6 – 目標(biāo)地址
高位的3個(gè)字節(jié)(藍(lán)色圈中的24位)是CFGBitmap(單位是4字節(jié)/32位)的偏移。在這個(gè)例子中,高位的3個(gè)字節(jié)相當(dāng)于0xb010。因此,CFGBitmap中指向字節(jié)單元的指針是CFGBitmap的基址加上0xb010。
同時(shí),第四位到第八位(紅色圈中的)有值X。如果目標(biāo)地址以0x10對(duì)齊(目標(biāo)地址&0xf==0),則X為單位內(nèi)的位偏移值。如果目標(biāo)地址不以0x10對(duì)齊(目標(biāo)地址&0xf!=0),則X|0x1是位偏移值。
在這個(gè)例子中,目標(biāo)地址是0x00b01030。X的值為6。表達(dá)式0x00b01030&0xf值為0;這意味著位偏移也是6。
3. 我們看到第二步定義的位。如果位等于1,意味著間接調(diào)用的目標(biāo)是可靠的,因?yàn)樗且粋€(gè)函數(shù)的起始地址。如果這個(gè)位為0,意味著間接調(diào)用的目標(biāo)是不可靠的,因?yàn)樗皇且粋€(gè)函數(shù)的起始地址。如果間接調(diào)用目標(biāo)是可靠的,函數(shù)將不做任何事并且直接執(zhí)行。如果間接調(diào)用是不可靠的,將觸發(fā)異常阻止利用代碼運(yùn)行。
圖7 – CFGBitmap中的值
值X取自第4位到第8位(上面紅圈中5位)。如果目標(biāo)地址以0x10對(duì)齊(目標(biāo)地址&0xf==0),X是單元中的位偏移值。如果目標(biāo)地址不以0x10對(duì)齊(目標(biāo)地址&0xf!=0),X|0x1是位偏移值。在這個(gè)例子中,目標(biāo)地址是0x00b01030,X是6(圖6中紅色圈)。0x00b01030&0xf==0,因此位偏移是6。
在第二步中,位偏移是6。以圖7為例,第6位(紅圈)為1。意味著間接調(diào)用的目標(biāo)是一個(gè)可靠的函數(shù)地址。
現(xiàn)在,我們已經(jīng)有了CFG工作機(jī)制的基本認(rèn)識(shí)。但是這個(gè)技巧帶來(lái)了下面的問(wèn)題:
1. CFGBitmap的位信息來(lái)自哪里?
2. 何時(shí)且怎么生成CFGBitmap?
3. 系統(tǒng)怎么處理不可靠的間接調(diào)用觸發(fā)的異常?
0x01 深入CFG實(shí)現(xiàn)
我們能在PE文件(啟用CFG的VS2015編譯的)中發(fā)現(xiàn)另外的CFG信息。讓我們看下PE文件中的圖1的代碼。這個(gè)信息能用VS2015的dumpbin.exe轉(zhuǎn)儲(chǔ)出來(lái)。在PE文件的Load Config Table部分,我們能找到下面的內(nèi)容:
圖8 – PE信息
Guard CF address of check-function pointer:_guard_check_icall的地址(見(jiàn)圖4)。在Windows 10預(yù)覽版中,當(dāng)PE文件加載時(shí),_guard_check_icall將被修改并指向nt!LdrpValidateUserCallTarget。
Guard CF function table:函數(shù)的相對(duì)虛擬地址(RVA)列表的指針,其包含了程序的代碼。每個(gè)函數(shù)的RVA將轉(zhuǎn)化為CFGBitmap中的“1”位。換句話(huà)說(shuō),CFGBitmap的位信息來(lái)自Guard CF function table。
Guard CF function count:函數(shù)RVA的個(gè)數(shù)。
CF Instrumented:表明程序中啟用了CFG。
在這里,編譯器完成了CFG的整個(gè)工作。剩下的是OS的支持使CFG機(jī)制起作用。
1. 在OS引導(dǎo)階段,第一個(gè)CFG相關(guān)的函數(shù)是MiInitializeCfg。這個(gè)進(jìn)程是system。調(diào)用堆棧如下:
圖9 – 調(diào)用堆棧
MiInitializeCfg函數(shù)的前置工作是創(chuàng)建包含CFGBitmap的共享內(nèi)存。調(diào)用時(shí)間可以在NT內(nèi)核階段1內(nèi)存管理器初始化時(shí)找到(MmInitSystem)。如你所知,在NT內(nèi)核階段1的初始化期間,它調(diào)用MmInitSystem兩次。第一個(gè)MmInitSystem將進(jìn)入MiInitializeCfg。那么MiInitializeCfg做了什么?
圖10 – 函數(shù)的主要邏輯
步驟A:注冊(cè)表值來(lái)自HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\kernel: MitigationOptions
步驟B:MmEnableCfg是一個(gè)全局變量,它被用來(lái)表示系統(tǒng)是否啟用CFG功能
步驟C:MiCfgBitMapSection的DesiredAccess允許所有的權(quán)限;它的分配類(lèi)型是“reserve”。在build 10.0.9926和build 6.4.9841中共享內(nèi)存的大小是不同的。對(duì)于build 6.4.9841,它按用戶(hù)模式空間大小計(jì)算。表達(dá)式是size=User Mode Space Size>>6。(>>X:右移X位)。對(duì)于build 10.0.9926,這個(gè)大小是0x3000000。CFG bitmap能表示整個(gè)用戶(hù)模式空間。MiCfgBitMapSection是CFG實(shí)現(xiàn)的核心組件,因?yàn)樗挥脕?lái)包含CFGBitmap。
2. 獲得壓縮RVA列表信息的函數(shù)且保存到映像的Control_Area結(jié)構(gòu)。
PE映像第一次加載到系統(tǒng)。NT內(nèi)核將調(diào)用MiRelocateImage來(lái)重定位。MiRelocateImage將調(diào)用MiParseImageCfgBits。在函數(shù)MiParseImageCfgBits中,PE映像的壓縮的RVA列表被計(jì)算且存儲(chǔ)在PE映像節(jié)中的Control_Area數(shù)據(jù)結(jié)構(gòu)。在系統(tǒng)引導(dǎo)期間一個(gè)PE映像只發(fā)生一次。
當(dāng)PE再次加載進(jìn)進(jìn)程,NT內(nèi)核將調(diào)用MiRelocateImageAgain。因?yàn)樗膲嚎s的RVA列表已經(jīng)保存了(且不需要再次計(jì)算),MiRelocateImageAgain不需要調(diào)用MiParseImageCfgBits保存一些進(jìn)程的時(shí)間。MiParseImageCfgBits被用來(lái)計(jì)算壓縮的RVA列表以便在小的空間中保存RVA列表。微軟實(shí)現(xiàn)CFG有時(shí)間和空間的消耗。在MiRelocateImage中,它的CFG相關(guān)的部分被如下描述:
MiParseImageCfgBits被用來(lái)計(jì)算啟用CFG編譯的模塊的壓縮的RVA列表。在深入這個(gè)函數(shù)之前,我們將看一下這個(gè)函數(shù)調(diào)用的上下文。函數(shù)MiParseImageCfgBits將在MiRelocateImage函數(shù)中調(diào)用。
函數(shù)MiParseImageCfgBits有5個(gè)參數(shù):
a) 映像節(jié)的Control_Area結(jié)構(gòu)的指針
b) 映像文件內(nèi)容的指針
c) 映像大小
d) 包含PE可選頭結(jié)構(gòu)的指針
e) 輸出壓縮的CFG函數(shù)RVA列表的指針
MiParseImageCfgBits的主要工作如下:
a) 從映像的Load Config Table獲得函數(shù)RVA列表
b) 使用壓縮算法壓縮列表,以便在小空間保存列表
c) 創(chuàng)建壓縮的RVA列表作為輸出
3. 在CFGBitmap共享內(nèi)存對(duì)象被創(chuàng)建后,CFGBimap共享內(nèi)存對(duì)象被映射來(lái)作為兩種用途:
a) 用來(lái)寫(xiě)共享模塊(.DLL文件等)的bits。這個(gè)映射是臨時(shí)的;在bits寫(xiě)入完成后,映射將釋放。通過(guò)這個(gè)映射寫(xiě)入的bits信息是共享的,意味著它能被操作系統(tǒng)內(nèi)所有的進(jìn)程讀取。這個(gè)映射發(fā)生在MiUpdateCfgSystemWideBitmap函數(shù)中。調(diào)用堆棧如下:
圖11 – 調(diào)用堆棧
b) 用來(lái)寫(xiě)私有的bits和讀取校驗(yàn)間接調(diào)用的bits。通過(guò)這個(gè)映射寫(xiě)入的bits是私有的,意味著它只能被當(dāng)前進(jìn)程讀取。這個(gè)映射的生存周期與進(jìn)程的生命周期相同。這個(gè)映射發(fā)生子MiCfgInitializeProcess中,調(diào)用堆棧如下:
圖12 – 調(diào)用堆棧
基于調(diào)用堆棧,我們知道它在一個(gè)正在初始化的進(jìn)程中被映射。Build 10.0.9926和6.4.9841的映射大小是不一樣的。對(duì)于6.4.9841,大小是基于用戶(hù)模式空間大小計(jì)算的。表達(dá)式為size=User Mode Sapce Size>>6(>>X:右移X位)。對(duì)于10.0.9926,這個(gè)大小是0x3000000。映射的空間在進(jìn)程生命周期內(nèi)總是存在的。映射的基址和長(zhǎng)度將被保存在類(lèi)型為MI_CFG_BITMAP_INFO的結(jié)構(gòu)體中,且地址被修改了(在6.4.9841中,基址是0xC0802144。在10.0.9926中,是0xC080214C)。我稍后將討論怎么將私有的bits寫(xiě)入映射空間中。MI_CFG_BITMAP_INFO的結(jié)構(gòu)如下:
4. 一旦PE映像的RVA列表準(zhǔn)備好了且CFGBitmap節(jié)也映射了,就可以將RVA列表翻譯為CFGBitmap中的bits。
圖13 – 更新CFGBitmap的bits
在幾種不同的場(chǎng)景下這個(gè)過(guò)程不太一樣:
在ReloadImage/ReloadImageAgain,通過(guò) MiUpdateCfgSystemWideBitmap寫(xiě)入共享模塊(如dll)的bits
在進(jìn)程初始化階段寫(xiě)入私有模塊(如exe)的bits
寫(xiě)入VM(虛擬內(nèi)存)操作的bits
寫(xiě)入映像和數(shù)據(jù)段的映射的bits
在深入每個(gè)場(chǎng)景之前,我們需要搞清楚一些背景信息。在每個(gè)進(jìn)程中,包含CFGBitmap的空間被分為兩部分:共享和私有。
MiCfgBitMapSection是一個(gè)共享內(nèi)存對(duì)象,包含了CFGBitmap的共享的bitmap的內(nèi)容。它與每個(gè)進(jìn)程共享。當(dāng)它在自己的虛擬內(nèi)存空間中映射MiCfgBitMapSection時(shí),每個(gè)進(jìn)程看見(jiàn)的內(nèi)容都相同。共享模塊(dll等)的bitmap信息將通過(guò)3.a節(jié)描述的映射方法寫(xiě)入。
然而每個(gè)進(jìn)程需要CFGBitmap的一部分不是被所有進(jìn)程共享的。它需要私有寫(xiě)入一些模塊的bitmap信息到CFGBitmap中。這個(gè)私有的部分將不和所有的進(jìn)程共享。EXE模塊的bitmap信息使用3.b節(jié)描述的方法寫(xiě)入。下圖描述了一個(gè)通用的場(chǎng)景。
圖14 – 在MiCfgBitMapSection中的共享部分的bitmap內(nèi)容的3中過(guò)程和他們的私有節(jié)
a) 在ReloadImage/ReloadImageAgain中,通過(guò)MiUpdateCfgSystemWideBitmap寫(xiě)入共享模塊(dll等)的bits。
如第2節(jié)所見(jiàn),在得到壓縮的函數(shù)的RVA列表并將它保存到Control_Area結(jié)構(gòu)后(在build6.4.9841中: _Control_Area ->SeImageStub->[+4]->[+24h];在build10.0.9926中: _Control_Area ->SeImageStub->[+0]->[+24h]),它將調(diào)用MiSelectImageBase。這個(gè)函數(shù)是ASLR實(shí)現(xiàn)的核心。它返回最終選擇的基址。選擇的基地址對(duì)于寫(xiě)bit信息到CFGBitmap中非常重要。在得到基地址后,它將調(diào)用MiUpdateCfgSystemWideBitmap。
MiUpdateCfgSystemWideBitmap的主要任務(wù)是將壓縮的RVA列表翻譯為CFGBitmap中的“1”bit。通過(guò)這個(gè)函數(shù)寫(xiě)入的bitmap信息是共享的,且被操作系統(tǒng)所有的進(jìn)程共享。這個(gè)函數(shù)只針對(duì)共享模塊有效(dll文件等)。
MiUpdateCfgSystemWideBitmap有3個(gè)參數(shù):
指向Control_Area結(jié)構(gòu)的指針
映像的基址
指向壓縮的RVA列表的指針
MiUpdateCfgSystemWideBitmap的主要邏輯如下:
圖15 – MiUpdateCfgSystemWideBitmap的主要邏輯
在步驟B中,它映射CFGBitmap共享內(nèi)存到系統(tǒng)進(jìn)程空間中。它不映射所有共享內(nèi)存的全部大小。它轉(zhuǎn)化映像的基址為CFGBitmap的偏移,且使用轉(zhuǎn)化的結(jié)果作為映射的起始地址。轉(zhuǎn)為過(guò)程如下:
Bitmap的偏移=基地址>>6。按這個(gè)公式,映射大小是映像大小右移6位。這個(gè)方法在映像需要重定位(ReloadImageAgain函數(shù))的時(shí)候也會(huì)被使用。
b) 在進(jìn)程初始化階段寫(xiě)私有模塊(exe文件等)的bits。它將調(diào)用MiCommitVadCfgBits,其是一個(gè)派遣函數(shù)。你能使用圖13作為參考。它在確定的場(chǎng)景被調(diào)用。這個(gè)函數(shù)的前置工作是在VAD描述的空間寫(xiě)入bits。主要邏輯如下:
圖16 – MiMarkPrivateImageCfgBits函數(shù)處理寫(xiě)入私有模塊的bits
MiMarkPrivateImageCfgBits函數(shù)實(shí)現(xiàn)向CFG Bitmap中寫(xiě)入私有模塊(exe等)的bit信息。當(dāng)系統(tǒng)映射一個(gè)EXE的節(jié)或者啟動(dòng)一個(gè)進(jìn)程時(shí),這個(gè)函數(shù)被調(diào)用。
這個(gè)函數(shù)有2個(gè)參數(shù):
1) Cfg信息的全局變量地址
2) 映像空間的VAD
VAD是用來(lái)描述虛擬內(nèi)存空間范圍的一個(gè)結(jié)構(gòu)。
函數(shù)的前置工作是將輸入的VAD的相關(guān)的壓縮的RVA列表轉(zhuǎn)化為bitmap信息,且在CFGBitmap中寫(xiě)入bits。主要邏輯如下:
圖17 – MiMarkPrivateImageCfgBits的主要邏輯
在步驟A中,壓縮的RVA列表能從輸入的VAD關(guān)聯(lián)的Control_Area結(jié)構(gòu)中獲得,在MiRelocateImage中保存(參見(jiàn)第二節(jié))。
這個(gè)函數(shù)的主要步驟是步驟C。它實(shí)現(xiàn)私有寫(xiě)入映射的MiCfgBitMapSection32節(jié)(在3.b節(jié)有描述)。寫(xiě)入的私有的bits的映射是只讀的。向映射的空間寫(xiě)入bits怎么實(shí)現(xiàn)?關(guān)鍵步驟如下:
i. 獲得映射的空間地址的物理地址(PFN)
ii. 申請(qǐng)一個(gè)空的頁(yè)表入口(PTE)并使用上步獲得的物理地址填充PTE,新的PTE被映射到相同的物理頁(yè),其包含了映射的MiCfgBitMapSection32的虛擬地址。
iii. 復(fù)制結(jié)果緩沖區(qū)(圖12)到新的PTE。物理頁(yè)將包含結(jié)果緩沖區(qū)的內(nèi)容
iv. 釋放新的PTE
在上面步驟完成后,bitmap信息被拷貝到當(dāng)前進(jìn)程地址空間內(nèi)。但是不會(huì)影響MiCfgBitMapSection。換句話(huà)說(shuō),MiCfgBitMapSection不知道bitmap改變了。其他進(jìn)程也不會(huì)看到改變;新添加的bitmap信息對(duì)當(dāng)前進(jìn)程是私有的。
c) 寫(xiě)虛擬內(nèi)存操作的bits。如果一個(gè)進(jìn)程有虛擬內(nèi)存操作,它可能會(huì)影響CFGBitmap中的bitmap的bits狀態(tài)。從圖13的場(chǎng)景看,它將調(diào)用MiMarkPrivateImageCfgBits。函數(shù)的前置工作是復(fù)制“1”或“0”頁(yè)到CFGBitmap空間中。
i. 對(duì)于NtAllocVirtualMemory函數(shù)
如果一個(gè)進(jìn)程調(diào)用NtAllocVirtualMemory函數(shù)來(lái)分配具有可執(zhí)行屬性的虛擬內(nèi)存,NT內(nèi)核將設(shè)置CFGBitmap中相關(guān)的位為“1”。但是如果分配的內(nèi)存的保護(hù)屬性有 SEC_WRITECOMBINE,NT內(nèi)核將使用“0”設(shè)置bitmap。
ii. 對(duì)于MiProtectVirtualMemory函數(shù)
如果一個(gè)進(jìn)程調(diào)用MiProtectVirtualMemory來(lái)改變虛擬內(nèi)存的保護(hù)屬性為“可執(zhí)行”,NT內(nèi)核將設(shè)置CFGBitmap相關(guān)位為“1”。
d) 寫(xiě)映像和數(shù)據(jù)段映射的bits
i. 對(duì)于映像(dll,EXE等)節(jié)的映射,如果映像不是共享的,處理過(guò)長(zhǎng)如4.b節(jié)描述。如果是共享的,將由圖13中的MiMarkPrivateImageCfgBits函數(shù)處理。它遍歷映射空間中的每個(gè)頁(yè)且將頁(yè)地址轉(zhuǎn)化為CFGBitmap中的偏移。
i. 如果CFGBitmap中的偏移不被PrototypePTE支持,相關(guān)的bits信息將被拷貝到CFGBitmap空間中。
ii. 如果CFGBitmap中的偏移已經(jīng)有bitmap信息,CFGBitmap部分將改為只讀。
ii. 對(duì)于數(shù)據(jù)段的映射,處理與4.c.i相同。
5. 上面提到的步驟都發(fā)生在內(nèi)核模式下。但是對(duì)于用戶(hù)模式,CFGBitmap需要訪(fǎng)問(wèn)LdrpValidateUserCallTarget函數(shù),它在上一部分已經(jīng)描述了。用戶(hù)模式下怎么知道CFGBitmap映射的地址?當(dāng)創(chuàng)建一個(gè)進(jìn)程,NT內(nèi)核調(diào)用PspPrepareSystemDllInitBlock函數(shù)來(lái)寫(xiě)CFGBitmap映射的地址和全局變量的長(zhǎng)度,其數(shù)據(jù)結(jié)構(gòu)是PspSystemDllInitBlock結(jié)構(gòu)。PspSystemDllInitBlock是修正過(guò)的地址并且從用戶(hù)模式和內(nèi)核模式都能訪(fǎng)問(wèn)。
圖18 – 調(diào)用堆棧
用戶(hù)模式可以訪(fǎng)問(wèn)硬編碼的PspSystemDllInitBlock全局變量的CFGBitmap字段。
6. 在圖4中,_guard_check_icall函數(shù)指針將指向ntdll的LdrpValidateUserCallTarget。何時(shí)發(fā)生,如何發(fā)生?LdrpCfgProcessLoadConfig來(lái)完成這個(gè)工作。進(jìn)程創(chuàng)建過(guò)程將在用戶(hù)模式下調(diào)用LdrpCfgProcessLoadConfig。
圖19 – 在這個(gè)函數(shù)中,它將修改_guard_check_icall的值指向LdrpValidateUserCallTarget
7. 在所有的準(zhǔn)備都完成后,如果間接調(diào)用的目標(biāo)地址相關(guān)的位在CFGBitmap中不是“1”,將觸發(fā)CFG。進(jìn)程將采取行動(dòng)處理這個(gè)異常。處理函數(shù)是RtlpHandleInvalidUserCallTarget。這個(gè)函數(shù)使用間接調(diào)用的目標(biāo)為唯一的參數(shù)。函數(shù)的主要邏輯如下:
圖20 – RtlpHandleInvalidUserCallTarget的主要邏輯
函數(shù)的主要流程是校驗(yàn)DEP狀態(tài)和觸發(fā)int 29中斷,這個(gè)內(nèi)核中斷處理例程是KiRaiseSecurityCheckFailure。它的行為是結(jié)束進(jìn)程。
如果一個(gè)間接調(diào)用的目標(biāo)地址的CFGBitmap中的相關(guān)的位不能訪(fǎng)問(wèn)(如超出了CFGBitmap空間),意味著目標(biāo)地址是不可靠的。系統(tǒng)將拋出訪(fǎng)問(wèn)異常。當(dāng)這個(gè)異?;氐接脩?hù)模式的處理函數(shù)KiUserExceptionDispatcher時(shí),它將調(diào)用RTLDispatchException。在RTLDispatchException中,它將校驗(yàn)異常發(fā)生的地址。如果指令的地址能訪(fǎng)問(wèn)CFGBitmap,它將繼續(xù)調(diào)用RtlpHandleInvalidUserCallTarget。
8. 如果一個(gè)進(jìn)程需要自定義CFGBitmap,它能調(diào)用ntdll中的NtSetInformationVirtualMemory。在內(nèi)核中函數(shù)MiCfgMarkValidEntries實(shí)現(xiàn)了這個(gè)功能。MiCfgMarkValidEntries以一個(gè)緩沖區(qū)和長(zhǎng)度作為參數(shù)。緩沖區(qū)中的每個(gè)單位是8字節(jié)。頭四個(gè)字節(jié)是目標(biāo)地址,其想在CFGBitmap中設(shè)置相關(guān)的位,且后四個(gè)字節(jié)是設(shè)置“0”或“1”的標(biāo)志。MiCfgMarkValidEntries自定義的CFGBitmap只在當(dāng)前進(jìn)程能看見(jiàn)。
9. 如果一個(gè)攻擊者需要改變用戶(hù)模式下的CFGBitmap的內(nèi)容,是不可能的。因?yàn)镃FGBitmap被映射為只讀(在3.b節(jié)討論過(guò))。不管改內(nèi)存保護(hù)屬性還是向空間中寫(xiě)值都將失敗。
0x02 CFG的弱點(diǎn)
當(dāng)然,這個(gè)機(jī)制不是沒(méi)有弱點(diǎn)的。我們指出了一些弱點(diǎn)如下:
CFGBitmap空間地址存儲(chǔ)在修正過(guò)的地址中,其能被用戶(hù)模式代碼獲得。這在CFG實(shí)現(xiàn)中討論過(guò)。這是很重要的安全問(wèn)題,但是被簡(jiǎn)單的放過(guò)了。
如果主模塊沒(méi)有開(kāi)啟CFG,即使加載的啟用了CFG的模塊,進(jìn)程也不會(huì)受保護(hù)。
基于圖20,如果一個(gè)進(jìn)程的主模塊禁用了DEP(通過(guò)/NXCOMPAT:NO),能繞過(guò)CFG訪(fǎng)問(wèn)處理,即使間接調(diào)用的目標(biāo)地址是不可靠的。
在CFGBitmap中的每個(gè)bit在進(jìn)程空間中表示8個(gè)字節(jié)。因此如果一個(gè)不可靠的目標(biāo)地址少于8個(gè)字節(jié),CFG將認(rèn)為是可靠的。
如果目標(biāo)函數(shù)是動(dòng)態(tài)生成的(類(lèi)似JIT技術(shù)),CFG的實(shí)現(xiàn)不能保護(hù)。這是因?yàn)镹tAllocVirtualMemory將在CFGBitmap中為所有分配的可執(zhí)行的內(nèi)存空間設(shè)置為“1”(4.c.i描述)。通過(guò)MiCfgMarkValidEntries自定義的CFGBitmap解決這個(gè)問(wèn)題是可能的。
分享標(biāo)題:探索Windows10的CFG機(jī)制
文章位置:http://www.dlmjj.cn/article/cogjcsd.html


咨詢(xún)
建站咨詢(xún)
