Win10安全特性之执行流保护
騰訊電腦管家 · 2015/02/04 15:07
0x00 背景
微軟在2015年1月22日公布了windows10技術(shù)預(yù)覽版,Build號(hào):9926。電腦管家反病毒實(shí)驗(yàn)室第一時(shí)間對(duì)其引入的新安全特性進(jìn)行了深入分析。
眾所周知,漏洞利用過程中攻擊者若要執(zhí)行惡意代碼,需要破壞程序原有指令的的正常執(zhí)行。執(zhí)行流保護(hù)的作用就是在程序執(zhí)行的過程中檢測(cè)指令流的正常性,當(dāng)發(fā)生不符合預(yù)期的情況時(shí),及時(shí)進(jìn)行異常處理。業(yè)界針對(duì)執(zhí)行流保護(hù)已經(jīng)有一些相對(duì)成熟的技術(shù)方案,在微軟發(fā)布的windows10最新版本中,我們看到了這一防護(hù)思想的廣泛使用。
0x01 CFI
CFI即控制流完整性Control-Flow Integrity,主要是通過對(duì)二進(jìn)制可執(zhí)行文件的動(dòng)態(tài)改寫,以此為其增加額外的安全性保障。
這是Mihai Budiu介紹CFI技術(shù)時(shí)使用的例子。這里通過對(duì)二進(jìn)制可執(zhí)行文件的改寫,對(duì)jmp的目的地址前插入一個(gè)在改寫時(shí)約定好的校驗(yàn)ID,在jmp的時(shí)候看目的地址前的數(shù)據(jù)是不是我們約定好的校驗(yàn)ID,如果不是則進(jìn)入錯(cuò)誤處理流程。
同理在call 和 ret的時(shí)候也可以進(jìn)行改寫:
左半部分就是一個(gè)對(duì)call的改寫,右半部分是對(duì)ret的一個(gè)改寫,在call的目的地址和ret的返回地址之前插入校驗(yàn)ID,然后改寫的call 和ret中增加了對(duì)校驗(yàn)ID的檢查,如果不符合預(yù)期,進(jìn)入錯(cuò)誤處理流程,這個(gè)思路和上邊對(duì)jmp的處理是完全一樣的。
0x02 CFG
實(shí)現(xiàn)CFI需要在jmp、call 一個(gè)寄存器(或者使用寄存器間接尋址)的時(shí)候,目的地址有時(shí)必須通過動(dòng)態(tài)獲得,且改寫的開銷又很大,這些都給CFI的實(shí)際應(yīng)用造成了一定的困難。
微軟在最新的操作系統(tǒng)win10當(dāng)中,對(duì)基于執(zhí)行流防護(hù)的實(shí)際應(yīng)用中采用了CFG技術(shù)。CFG是Control Flow Guard的縮寫,就是控制流保護(hù),它是一種編譯器和操作系統(tǒng)相結(jié)合的防護(hù)手段,目的在于防止不可信的間接調(diào)用。
漏洞攻擊過程中,常見的利用手法是通過溢出覆蓋或者直接篡改某個(gè)寄存器的值,篡改間接調(diào)用的地址,進(jìn)而控制了程序的執(zhí)行流程。CFG通過在編譯和鏈接期間,記錄下所有的間接調(diào)用信息,并把他們記錄在最終的可執(zhí)行文件中,并且在所有的間接調(diào)用之前插入額外的校驗(yàn),當(dāng)間接調(diào)用的地址被篡改時(shí),會(huì)觸發(fā)一個(gè)異常,操作系統(tǒng)介入處理。
以win10 preview 9926中IE11的Spartan html解析模塊為例,看一下CFG的具體情況:
這里就是被編譯器插入的CFG校驗(yàn)函數(shù)
但是靜態(tài)情況下默認(rèn)的檢測(cè)函數(shù)是一個(gè)直接return的空函數(shù),是微軟在和我們開玩笑嗎?
通過動(dòng)態(tài)調(diào)試看一下
從上圖我們可以看出,實(shí)際運(yùn)行時(shí)的地址和我們通過IDA靜態(tài)看到的地址是不一樣的,這里就涉及到CFG和操作系統(tǒng)相關(guān)的那部分。支持CFG版本的操作系統(tǒng)加載器在加載支持CFG的模塊時(shí),會(huì)把這個(gè)地址替換成ntdll中的一個(gè)函數(shù)地址。不支持CFG版本的操作系統(tǒng)不用理會(huì)這個(gè)檢測(cè),程序執(zhí)行時(shí)直接retn。
這是ntdll中的檢測(cè)函數(shù)
原理是在進(jìn)入檢測(cè)函數(shù)之前,把即將call的寄存器值(或者是帶偏移的寄存器間接尋址)賦值給ecx,在檢測(cè)函數(shù)中通過編譯期間記錄的數(shù)據(jù),來校驗(yàn)這個(gè)值是否有效。
檢測(cè)過程如下:
首先從LdrSystemDllInitBlock+0x60處讀取一個(gè)位圖(bitmap),這個(gè)位圖表明了哪些函數(shù)地址是有效的,通過間接調(diào)用的函數(shù)地址的高3個(gè)字節(jié)作為一個(gè)索引,獲取該函數(shù)地址所在的位圖的一個(gè)DWORD值,一共32位,證明1位代表了8個(gè)字節(jié),但一般來說間接調(diào)用的函數(shù)地址都是0x10對(duì)齊的,因此一般奇數(shù)位是不使用的。
通過函數(shù)地址的高3個(gè)字節(jié)作為索引拿到了一個(gè)所在的位圖的DWORD值,然后檢查低1字節(jié)的0-3位是否為0,如果為0,證明函數(shù)是0x10對(duì)齊的,則用3-7bit共5個(gè)bit就作為這個(gè)DWORD值的索引,這樣通過一個(gè)函數(shù)地址就能找到位圖中所對(duì)應(yīng)的位了。如果置位了,表明函數(shù)地址有效,反之則會(huì)觸發(fā)異常。
這里有個(gè)有趣的東西,雖然使用test cl,0Fh檢測(cè)是否0x10對(duì)齊,如果對(duì)齊的話實(shí)際上用3-7位作為索引,也就是說第3位一定是0。但如果函數(shù)地址不是0x10對(duì)齊的話,則會(huì)對(duì)3-7位 or 1,然后再作為索引。這樣就有一個(gè)弊端,如果一個(gè)有效的間接調(diào)用的函數(shù)地址是8字節(jié)對(duì)齊的,那么其實(shí)是允許一個(gè)8字節(jié)的一個(gè)錯(cuò)位調(diào)用的,這樣可能導(dǎo)致的結(jié)果就是可能造成雖然通過了校驗(yàn),但是實(shí)際調(diào)用的地址并不是原始記錄的函數(shù)地址。
還有一點(diǎn),如果這時(shí)候漏洞觸發(fā)成功,間接調(diào)用的寄存器值已經(jīng)被攻擊者修改了,這時(shí)候從bitmap中取值的時(shí)候可能造成內(nèi)存訪問無效。請(qǐng)看LdrpValidateUserCallTargetBitMapCheck符
號(hào)處的這條指令:mov edx,dword ptr [edx+eax*4] edx是bitmap地址,eax是索引,但如果eax不可信了,這個(gè)很有可能,則會(huì)導(dǎo)致內(nèi)存訪問異常,并且這個(gè)函數(shù)并沒有異常處理。這是因?yàn)槲④洖榱诵士紤](畢竟這個(gè)校驗(yàn)函數(shù)的調(diào)用十分頻繁,一個(gè)開啟CFG的模塊可能會(huì)有上萬個(gè)調(diào)用處),微軟在ntdll! RtlDispatchException中對(duì)該地址發(fā)生的異常做了一個(gè)處理:
如果異常發(fā)生的地址命中LdrpValidateUserCallTargetBitMapCheck,則進(jìn)行一個(gè)單獨(dú)處理,RtlpHandleInvalidUserCallTarget會(huì)校驗(yàn)當(dāng)前進(jìn)程的DEP狀態(tài)和要間接調(diào)用的地址(ecx)的內(nèi)存屬性,如果當(dāng)前進(jìn)程關(guān)閉了DEP并且要間接調(diào)用的地址有可執(zhí)行屬性,則觸發(fā)CFG異常,否則通過修改pContext把EIP修正到ret返回處,并且表明異常已被處理。
最后再說下這個(gè)原始的bitmap,在系統(tǒng)初始化的時(shí)候,內(nèi)存管理器初始化中會(huì)創(chuàng)建一個(gè)Section(MiCfgBitMapSection32),這個(gè)Section在Win8.1上的大小是通過MmSystemRangeStart(32位下是0x80000000)計(jì)算的,前面提到過bitmap里面1位代表8字節(jié),計(jì)算完后正好是32MB
而在Win10上MiCfgBitMapSection32的大小有了變化,直接寫死成了0x3000000(48MB)
Section創(chuàng)建完成后在每個(gè)進(jìn)程啟動(dòng)的時(shí)候會(huì)映射進(jìn)去
(NtCreateUserProcess-> PspAllocateProcess-> MmInitializeProcessAddressSpace-> MiMapProcessExecutable-> MiCfgInitializeProcess) 復(fù)制代碼映射的時(shí)候作為shared view,除非某一個(gè)進(jìn)程修改了這片內(nèi)存。
在一個(gè)CFG模塊映射進(jìn)來的時(shí)候,重定位過程中會(huì)重新解析PE文件LOADCONFIG中的Guard Function Table以重新計(jì)算該模塊對(duì)應(yīng)的bitmap(MiParseImageCfgBits),最后更新到MiCfgBitMapSection32中去(MiUpdateCfgSystemWideBitmap)。
0x03 電腦管家XP防護(hù)的執(zhí)行流保護(hù)
早些年的漏洞攻擊代碼可以直接在棧空間或堆空間執(zhí)行指令,但近幾年,微軟操作系統(tǒng)在安全性方面逐漸加強(qiáng),DEP、ASLR等防護(hù)手段的應(yīng)用,使得攻擊者必須借助ROP等繞過手段來實(shí)現(xiàn)漏洞利用。在ROP利用中,棧交換指令Stack pivot必不可少。
針對(duì)ROP攻擊的防御長(zhǎng)久以來是漏洞防御的一個(gè)難題,因?yàn)镽OP指令在靜態(tài)層面分析與程序的正常指令流毫無差別,且運(yùn)行時(shí)也是在合法模塊內(nèi)執(zhí)行,因此極難防御。
管家漏洞防御團(tuán)隊(duì)針對(duì)ROP利用的特點(diǎn),從整個(gè)程序的執(zhí)行流層面進(jìn)行分析,研究出在動(dòng)態(tài)運(yùn)行時(shí)區(qū)分是合法指令流還是異常指令流的方法,其思想與CFI不謀而合。
下邊就是一個(gè)由于錯(cuò)位匯編形成的比較常用的棧交換指令
而實(shí)際正常的執(zhí)行流程是這樣的
以上是沒有開啟XP防護(hù)的情況
開啟電腦管家XP防護(hù)之后:
此時(shí)如果攻擊者依靠靜態(tài)分析時(shí)得到棧交換指令位置來執(zhí)行ROP攻擊的話,會(huì)被執(zhí)行流保護(hù)邏輯發(fā)現(xiàn)異常,后續(xù)攻擊則無法實(shí)現(xiàn)。
0x04 尾聲
CFG防護(hù)方法需要在編譯鏈接階段來完成準(zhǔn)備工作,同時(shí)需要操作系統(tǒng)的支持。CFI無需編譯時(shí)的幫助,且不僅能夠防御call調(diào)用,能夠?qū)θ繄?zhí)行流進(jìn)行保護(hù)。但CFI需要插入大量的檢測(cè)點(diǎn),并且在執(zhí)行過程中檢測(cè)的頻率極高,難免對(duì)程序執(zhí)行效率帶來影響。
電腦管家XP版的防御方法相比于前兩者,對(duì)性能的影響更小,但這種方法是針對(duì)舊版操作系統(tǒng)的緩解方案,通用性會(huì)打折扣。所以建議廣大windows用戶盡量升級(jí)到最新操作系統(tǒng),享受全面的安全保護(hù)。而由于某些原因無法升級(jí)的用戶也不必?fù)?dān)心,管家XP版會(huì)繼續(xù)提供最高的安全防護(hù)能力。
總結(jié)
以上是生活随笔為你收集整理的Win10安全特性之执行流保护的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java字符串表表容量_java –
- 下一篇: 利用Underscore求数组的交集、并