對CFG漏洞緩解機制的分析研究
控制流保護(CFG)是Windows的一種安全機制,其目的是通過檢查間接調(diào)用的目標地址是否是有效的函數(shù)來減輕執(zhí)行流的重定向,我們可以用這個例子作分析。
0x01 CFG是怎樣工作的
通過這個例子,我們用MSVC編譯器編譯一個exe文件,看看在調(diào)用main()之前生成和執(zhí)行了什么代碼:
- call __scrt_get_dyn_tls_init_callback
- mov esi, eax
- ...
- mov esi, [esi]
- mov ecx, esi
- call ds:__guard_check_icall_fptr
- call esi
函數(shù) scrt_get_dyn_tls_init_callback得到一個指向TLS回調(diào)表的指針,以調(diào)用第一個條目,回調(diào)的函數(shù)受到CFG的保護,因此編譯器在執(zhí)行ESI中的目標地址之前添加代碼以檢查函數(shù)地址是否有效。
隨后執(zhí)行:
- __guard_check_icall_fptr dd offset _guard_check_icall_nop
- _guard_check_icall_nop proc near
- retn
- _guard_check_icall_nop endp
retn可以繞過,為什么? 這樣程序就可以在不支持CFG的舊操作系統(tǒng)版本中運行 。 在支持它的系統(tǒng)中 ,
_guard_check_icall_nop地址會被NT DLL中的Ldrp驗證用戶調(diào)用目標替換:
- ntdll!LdrpValidateUserCallTarget:
- mov edx,[ntdll!LdrSystemDllInitBlock+0xb0 (76fb82e8)]
- mov eax,ecx
- shr eax,8
- ntdll!LdrpValidateUserCallTargetBitMapCheck:
- mov edx,[edx+eax*4]
- mov eax,ecx
- shr eax,3
0x02 Bitmap簡介
對于CFG,他們在Load Config目錄中的PE中添加了一堆新字段 : Guard CF Check 函 數(shù) 指 針 , 該 指 針 指 向guard_check_icall_ptr,是要替換的函數(shù)地址和Guard CF函數(shù)表, 該表包含所有要設(shè)置為有效目標的函數(shù)的RVA,在加載PE時創(chuàng)建的Bitmap中。
驗證用戶調(diào)用目標的Ldrp從該第一指令中的LdrSystemDllInit塊+0xb0獲取Bitmap的地址。 Bitmap包含整個過程中每16個字節(jié)的“狀態(tài)”, 當加載PE時,表中的RVAs被轉(zhuǎn)換為偏移量, 然后相應(yīng)地設(shè)置該偏移量處的狀態(tài)。
0x03 傳送 Bitmap
我的想法是使用Guard CFFunction表填充具有選定狀態(tài)的Bitmap,并在其中重新生成我們的代碼,然后在入口點將其復(fù)制到Bitmap中并執(zhí)行它。 由于 Alex Ionescu 在WindowsInternals中的研究,我能夠找出一些以前的文檔:
假設(shè)我們代碼中的第一個字節(jié)是0x10(010000b),我們從Bitmap傳輸代碼的區(qū)域從0x402000(RVA:0x2000)開始,為了清晰起見,我們將使用相同的區(qū)域來處理假RVA。 要生成 0x10,我們只需要表中的1個條目:0x2020,跳過前32個字節(jié),使狀態(tài)設(shè)置為0000b,0x2020將下一個狀態(tài)設(shè)置為 01b,Bitmap變?yōu)?10000b?,F(xiàn)在要得到狀態(tài)11b,假設(shè)我們想要字節(jié)0x1D(011101b), 我們使用未對齊的RVA,表將變成:0x2000(設(shè)置為01b), 0x2012(設(shè)置為11b),0x2020(設(shè)置為01b)。
要獲得10b,我們需要使用帶有元數(shù)據(jù)的特殊類型的 RVA,但很簡單,我們將一個字節(jié)附加到用于生成10b的 RVA 中 。
元數(shù)據(jù)是一個標 志: IMAGE_GUARD_FLAG_FID_SUPPRESSED (1) 或
IMAGE_GUARD_FLAG_EXPORT_SUPPRESSED(2)。 所以我們說要生成0x86(10000110b),使用:0x2000與0x2(設(shè)置為10b),0x2010(設(shè)置為01b),0x2030與0x2(設(shè)置為10b)。
0x04 Bitmap 轉(zhuǎn)換
- mov esi, 0DEADh ;GuardCFCheckFunctionPointer points here
- mov esi, [esi + 2] ;get LdrSystemDllInitBlock+0xb0 address
- mov esi, [esi] ;get the Bitmap address
- mov eax, [ebx + 8] ;ebx=fs:[30h] at start time
- lea edi, [eax + xxxxxxxx] ;imagebase + buffer rva
- add ah, 20h ;imagebase + 0x2000
- shr eax, 8 ;shift-right 8 bits to make the offset
- lea esi, [esi + eax*4] ;esi=our code in the Bitmap
- mov ecx, xxxxxxxx ;size of code
- rep movsb
我們讓加載程序?qū)?DEADH替換為LDRP驗證用戶調(diào)用目標的地址,從中可以得到Bitmap的地址, 我們計算Bitmap(0x402000)中區(qū)域的偏移量,并從它復(fù)制再生代碼。
0x05 分析總結(jié)
那么,當檢測到無效地址時會發(fā)生什么呢? 程序被終止了。 因為大多數(shù)改變PE文件的工具或代碼不支持CFG:更改以在其他地方執(zhí)行代碼的任何地址,都必須在表中。 這會殺死許多病毒改變?nèi)肟邳c地址,或使用入口點Fuzzing(EPO)技術(shù)的效果。 但是,如果在PE中禁用CFG,可以用自己的地址替換Guard CF Check函數(shù)指針,以獲得EPO。
本文翻譯自:https://github.com/86hh/PagedOut2/blob/master/CFGTeleport.pdf如若轉(zhuǎn)載,請注明原文地址: