修改整理自 http://blog.csdn.net/magictong/article/details/7517630 侵刪
作者:magictong
時間:2012年3月16日星期五
摘要:主要介紹SafeSEH的基本原理和SafeSEH的繞過技術(shù)仁期,重點在原理介紹越庇。
關(guān)鍵詞:SafeSEH;繞過技術(shù);異常處理
前言
設(shè)計SafeSEH保護(hù)機制的目的,以為了防止那種攻擊者通過覆蓋堆棧上的異常處理函數(shù)句柄,從而控制程序執(zhí)行流程的攻擊。
自Windwos XP SP2之后,微軟就已經(jīng)引入了SafeSEH技術(shù)兑徘。不過由于SafeSEH需要編譯器在編譯PE文件時進(jìn)行特殊支持才能發(fā)揮作用,而xpsp2下的系統(tǒng)文件基本都是不支持SafeSEH的編譯器編譯的羡洛,因此在xpsp2下挂脑,SafeSEH還沒有發(fā)揮作用(VS2003及更高版本的編譯器中已經(jīng)開始支持)藕漱。
從Vista開始,由于系統(tǒng)PE文件基本都是由支持SafeSEH的編譯器編譯的崭闲,因此從Vista開始肋联,SafeSEH開始發(fā)揮他強大的作用,對于以前那種簡單的通過覆蓋異常處理句柄的漏洞利用技術(shù)刁俭,也就基本失效了橄仍。
SafeSEH的保護(hù)原理
SafeSEH的基本原理很簡單,即在調(diào)用異常處理函數(shù)之前牍戚,對要調(diào)用的異常處理函數(shù)進(jìn)行一系列的有效性校驗侮繁,如果發(fā)現(xiàn)異常處理函數(shù)不可靠(被覆蓋了,被篡改了)如孝,立即終止異常處理函數(shù)的調(diào)用宪哩。不過SafeSEH需要編譯器和系統(tǒng)雙重支持,缺少一個則保護(hù)能力基本就喪失了第晰。下面從兩個方面來闡述怎樣來實現(xiàn)SafeSEH锁孟。
(1)二進(jìn)制層面
首先我們先看看編譯器做了些什么事情(通過啟用鏈接選項/SafeSEH即可使編譯出來的二進(jìn)制文件具備SafeSEH功能,微軟VS2003及以后的編譯器已經(jīng)默認(rèn)支持)茁瘦。在編譯器生成二進(jìn)制IMAGE的時候品抽,把所有合法的SEH函數(shù)的地址解析出來,在IMAGE里生成一張合法的SEH函數(shù)表甜熔,用于異常處理時候進(jìn)行嚴(yán)格的匹配檢查圆恤。可以使用VC下面的dumpbin工具查看一個二進(jìn)制文件的config信息腔稀,這樣調(diào)用dumpbin /loadconfig file_all_path_filename盆昙。
輸出的可能是下面這樣(注:tttt.exe使用vs2005編譯):
Dump of file H:\Prj_N\tttt\Release\tttt.exe
File Type: EXECUTABLE IMAGE
Section contains the following load config:
00000048 size
0 time date stamp
0.00 Version
0 GlobalFlags Clear
0 GlobalFlags Set
0 Critical Section Default Timeout
0 Decommit Free Block Threshold
0 Decommit Total Free Threshold
00000000 Lock Prefix Table
0 Maximum Allocation Size
0 Virtual Memory Threshold
0 Process Heap Flags
0 Process Affinity Mask
0 CSD Version
0000 Reserved
00000000 Edit list
00403018 Security Cookie
00402360 Safe Exception Handler Table
1 Safe Exception Handler Count
Safe Exception Handler Table
Address
--------
004018A1 __except_handler4
Summary
1000 .data
1000 .rdata
1000 .rsrc
1000 .text
注意里面 Safe Exception Handler Table 部分,這就是該二進(jìn)制文件里面的SEH異常處理函數(shù)地址表烧颖。上面的輸出實際上涉及如下的一個結(jié)構(gòu)弱左,是保存在二進(jìn)制文件里面的一份配置表:
#include <windows.h>
extern DWORD_PTR __security_cookie; /* /GS security cookie */
/*
* The following two names are automatically created by the linker for any
* image that has the safe exception table present.
*/
extern PVOID __safe_se_handler_table[]; /* base of safe handler entry table */
extern BYTE __safe_se_handler_count; /* absolute symbol whose address is
the count of table entries */
typedef struct {
DWORD Size;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD GlobalFlagsClear;
DWORD GlobalFlagsSet;
DWORD CriticalSectionDefaultTimeout;
DWORD DeCommitFreeBlockThreshold;
DWORD DeCommitTotalFreeThreshold;
DWORD LockPrefixTable; // VA
DWORD MaximumAllocationSize;
DWORD VirtualMemoryThreshold;
DWORD ProcessHeapFlags;
DWORD ProcessAffinityMask;
WORD CSDVersion;
WORD Reserved1;
DWORD EditList; // VA
DWORD_PTR *SecurityCookie;
PVOID *SEHandlerTable;
DWORD SEHandlerCount;
} IMAGE_LOAD_CONFIG_DIRECTORY32_2;
const IMAGE_LOAD_CONFIG_DIRECTORY32_2 _load_config_used = {
sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_2),
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
&__security_cookie,
__safe_se_handler_table,
(DWORD)(DWORD_PTR) &__safe_se_handler_count
};
(2)系統(tǒng)層面
基本過程如下(XP SP2和VISTA一樣)窄陡。
加載準(zhǔn)備過程:
加載PE文件時炕淮,定位和讀出合法SEH函數(shù)表的地址(如果該IMAGE是不支持SafeSEH的,則這個SEH函數(shù)表的地址為0)跳夭,并使用共享內(nèi)存中的一個隨機數(shù)加密涂圆。將加密后的SEH函數(shù)表地址,IMAGE的開始地址币叹,IMAGE的長度润歉,合法SEH函數(shù)的個數(shù),作為一條記錄放入ntdll(ntdll模塊是進(jìn)行異常分發(fā)的模塊)的加載模塊數(shù)據(jù)內(nèi)存中颈抚。
異常發(fā)生后踩衩,異常處理過程如下(RtlDispatchException框架偽碼):
void RtlDispatchException(...)
{
if (exception record is not on the stack)
goto corruption;
if (handler is on the stack)
goto corruption;
if (RtlIsValidHandler(handler, process_flags) == FALSE)
goto corruption;
// execute handler
RtlpExecuteHandlerForException(handler, ...)
...
}
RtlDispatchException()這個函數(shù)的檢測主要分三步,首先檢查異常處理節(jié)點是否在棧上,如果不在棧上程序?qū)⒔K止異常處理驱富,其次檢查異常處理句柄是否在棧上锚赤,如果在棧上程序?qū)⒅巩惓L幚恚@兩個檢測可以防止那種在堆上偽造異常鏈和把shellcode放置在棧上的情況褐鸥。最后檢測handler的有效性线脚,這才是SafeSEH的重點。
下面看一下RtlIsValidHandler的偽碼(vista sp1):
BOOL RtlIsValidHandler(handler)
{
if (handler is in an image)
{
// 在加載模塊的進(jìn)程空間
if (image has the IMAGE_DLLCHARACTERISTICS_NO_SEH flag set)
return FALSE; // 該標(biāo)志設(shè)置叫榕,忽略異常處理浑侥,直接返回FALSE
if (image has a SafeSEH table) // 是否含有SEH表
if (handler found in the table)
return TRUE; // 異常處理handle在表中,返回TRUE
else
return FALSE; // 異常處理handle不在表中晰绎,返回FALSE
if (image is a .NET assembly with the ILonly flag set)
return FALSE; // .NET 返回FALSE
// fall through
}
if (handler is on a non-executable page)
{
// handle在不可執(zhí)行頁上面
if (ExecuteDispatchEnable bit set in the process flags)
return TRUE; // DEP關(guān)閉寓落,返回TRUE;否則拋出異常
else
raise ACCESS_VIOLATION; // enforce DEP even if we have no hardware NX
}
if (handler is not in an image)
{
// 在加載模塊內(nèi)存之外寒匙,并且是可執(zhí)行頁
if (ImageDispatchEnable bit set in the process flags)
return TRUE; // 允許在加載模塊內(nèi)存空間外執(zhí)行零如,返回驗證成功
else
return FALSE; // don't allow handlers outside of images
}
// everything else is allowed
return TRUE;
}
對上面的偽碼的理解,請看代碼注釋和流程圖:
偽碼里面的ExecuteDispatchEnable和ImageDispatchEnable位標(biāo)志是內(nèi)核KPROCESS結(jié)構(gòu)的一部分锄弱,這兩個位用來控制當(dāng)異常處理函數(shù)在不可以執(zhí)行內(nèi)存或者不在異常模塊的映像(IMAGE)內(nèi)時考蕾,是否執(zhí)行異常處理函數(shù)。這兩個位的值可以在運行時修改会宪,不過默認(rèn)情況下如果進(jìn)程的DEP被關(guān)閉肖卧,則這兩個位置1,如果進(jìn)程的DEP是開啟狀態(tài)掸鹅,則這兩個位被置0塞帐。
在進(jìn)程的DEP是開啟的情況,有兩種異常處理函數(shù)被異常分發(fā)器認(rèn)為是有效的:
- 異常處理函數(shù)在進(jìn)程映像的SafeSEH表中巍沙,并且沒有NO_SEH標(biāo)志葵姥。
- 異常處理函數(shù)在進(jìn)程映像的可執(zhí)行頁,并且沒有NO_SEH標(biāo)志句携,沒有SafeSEH表榔幸,沒有.NET的ILonly標(biāo)志。
在進(jìn)程的DEP關(guān)閉的情況下矮嫉,有三種情況異常處理函數(shù)被異常分發(fā)器認(rèn)為是有效的:
- 異常處理函數(shù)在進(jìn)程映像的SafeSEH表中削咆,并且沒有NO_SEH標(biāo)志。
- 異常處理函數(shù)在進(jìn)程映像的可執(zhí)行頁蠢笋,并且沒有NO_SEH標(biāo)志拨齐,沒有SafeSEH表,沒有.NET的ILonly標(biāo)志昨寞。
- 異常處理函數(shù)不在當(dāng)前進(jìn)程的映像里面瞻惋,但是不在當(dāng)前線程的堆棧上厦滤。
這里的偽碼是非常簡單的,還有很多值得探討的問題歼狼,譬如ntdll里面馁害,當(dāng)前異常處理句柄是怎么和SEH表進(jìn)行對比的等等,不過這暫時不列入討論蹂匹。
怎么關(guān)掉編譯器的SafeSEH支持
雖然我不知道你為什么要這么做碘菜,而且我覺得很瘋狂,但是方法還是有的限寞。以 VC6 為例忍啸,在編譯器的屬性框Liker|CommandLine的Additional options 加入/SAFESEH:NO即可,見下圖履植。
怎樣檢測一個PE文件是否啟用了SafeSEH
前面介紹過數(shù)據(jù)目錄里面的一個結(jié)構(gòu)(IMAGE_LOAD_CONFIG_DIRECTORY)计雌,該結(jié)構(gòu)的成員SEHandlerTable是指向合法SEH處理程序地址列表的指針,成員SEHandlerCount是數(shù)目玫霎。而IMAGE_LOAD_CONFIG_DIRECTORY這個結(jié)構(gòu)體只有/SAFESEH選項設(shè)置了才存在凿滤,因此,就可以根據(jù)它來判斷PE文件是否加了/SAFESEH鏈接選項庶近。這個結(jié)構(gòu)在PE中的偏移由PE附加頭IMAGE_DATA_DIRECTORY 數(shù)組的第11項指定翁脆。
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory
繞過方法簡介
(1)利用堆地址覆蓋SEH結(jié)構(gòu)繞過SafeSEH
上面講過,在禁用DEP的進(jìn)程中鼻种,異常分發(fā)器允許SEH handler位于除椃捶空間之外的非映像頁面。也就是說我們可以把shellcode放置在堆中叉钥,然后通過覆蓋SEH跳至堆空間以執(zhí)行shellcode罢缸,這樣即可繞過SafeSEH保護(hù)。
(2)利用沒有啟用SafeSEH保護(hù)的模塊繞過SafeSEH
在介紹原理時講過投队,在國內(nèi)枫疆,目前大部分的PC都是安裝的Windows XP,也就是說對于大部分Windows操作系統(tǒng)敷鸦,其系統(tǒng)模塊都沒有受到SafeSEH保護(hù)息楔,可以選用未開啟SafeSEH保護(hù)的模塊來利用,另外轧膘,現(xiàn)在還有很多VC6編譯的軟件钞螟,這些軟件本身和自帶的dll文件兔甘,都是可能沒有SafeSEH保護(hù)的谎碍。這時就可以使用它里面的指令作為跳板來繞過SafeSEH。
(3)利用加載模塊之外的地址繞過SafeSEH
同樣是根據(jù)SafeSEH的原理可知洞焙,對于加載模塊之外的地址蟆淀,SafeSEH同樣是不進(jìn)行有效性檢測的(當(dāng)然假設(shè)是DEP是關(guān)閉的拯啦,或者DEP已經(jīng)被繞過)。
注:繞過方法這里沒有細(xì)講熔任,原因是沒有找到很好的例子褒链,在《0day安全:軟件漏洞分析技術(shù)》上面有自己書籍作者自己寫的例子。以后這塊再詳說疑苔。
參考文獻(xiàn)
[1] Preventing the Exploitation of Structured Exception Handler (SEH) Overwrites with SEHOP
[2] SafeSEH筆記http://pstgroup.blogspot.com/2007/08/tipssafeseh.html
[3] /SAFESEH (Image has Safe Exception Handlers)
http://msdn.microsoft.com/en-us/library/9a89h429(VS.80).aspx
[4] 0day安全:軟件漏洞分析技術(shù)(第二版)
[5] Bypassing Browser Memory Protections
[6] pecoff_v8