1. 程式人生 > >SafeSEH原理及繞過技術淺析

SafeSEH原理及繞過技術淺析

摘要:主要介紹SafeSEH的基本原理和SafeSEH的繞過技術,重點在原理介紹。
**關鍵詞:**SafeSEH;繞過技術;異常處理

前言

設計SafeSEH保護機制的目的,以為了防止那種攻擊者通過覆蓋堆疊上的異常處理函式控制代碼,從而控制程式執行流程的攻擊。

自Windwos XP SP2之後,微軟就已經引入了SafeSEH技術。不過由於SafeSEH需要編譯器在編譯PE檔案時進行特殊支援才能發揮作用,而xpsp2下的系統檔案基本都是不支援SafeSEH的編譯器編譯的,因此在xpsp2下,SafeSEH還沒有發揮作用(VS2003及更高版本的編譯器中已經開始支援)。

從Vista開始,由於系統PE檔案基本都是由支援SafeSEH的編譯器編譯的,因此從Vista開始,SafeSEH開始發揮他強大的作用,對於以前那種簡單的通過覆蓋異常處理控制代碼的漏洞利用技術,也就基本失效了。

SafeSEH的保護原理

SafeSEH的基本原理很簡單,即在呼叫異常處理函式之前,對要呼叫的異常處理函式進行一系列的有效性校驗,如果發現異常處理函式不可靠(被覆蓋了,被篡改了),立即終止異常處理函式的呼叫。不過SafeSEH需要編譯器和系統雙重支援,缺少一個則保護能力基本就喪失了。下面從兩個方面來闡述怎樣來實現SafeSEH。

(1)二進位制層面

首先我們先看看編譯器做了些什麼事情(通過啟用連結選項/SafeSEH即可使編譯出來的二進位制檔案具備SafeSEH功能,微軟VS2003及以後的編譯器已經預設支援)。在編譯器生成二進位制IMAGE的時候,把所有合法的SEH函式的地址解析出來,在IMAGE裡生成一張合法的SEH函式表,用於異常處理時候進行嚴格的匹配檢查。可以使用VC下面的dumpbin工具檢視一個二進位制檔案的config資訊,這樣呼叫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 部分,這就是該二進位制檔案裡面的SEH異常處理函式地址表。上面的輸出實際上涉及如下的一個結構,是儲存在二進位制檔案裡面的一份配置表:

#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)系統層面

基本過程如下(XP SP2和VISTA一樣)。

載入準備過程:

載入PE檔案時,定位和讀出合法SEH函式表的地址(如果該IMAGE是不支援SafeSEH的,則這個SEH函式表的地址為0),並使用共享記憶體中的一個隨機數加密。將加密後的SEH函式表地址,IMAGE的開始地址,IMAGE的長度,合法SEH函式的個數,作為一條記錄放入ntdll(ntdll模組是進行異常分發的模組)的載入模組資料記憶體中。

異常發生後,異常處理過程如下(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()這個函式的檢測主要分三步,首先檢查異常處理節點是否在棧上,如果不在棧上程式將終止異常處理,其次檢查異常處理控制代碼是否在棧上,如果在棧上程式將止異常處理,這兩個檢測可以防止那種在堆上偽造異常鏈和把shellcode放置在棧上的情況。最後檢測handler的有效性,這才是SafeSEH的重點。

下面看一下RtlIsValidHandler的偽碼(vista sp1):

BOOL RtlIsValidHandler(handler)

{

if (handler is in an image)

{

         // 在載入模組的程序空間

if (image has the IMAGE_DLLCHARACTERISTICS_NO_SEH flag set)

return FALSE; // 該標誌設定,忽略異常處理,直接返回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在不可執行頁上面

if (ExecuteDispatchEnable bit set in the process flags)

return TRUE; // DEP關閉,返回TRUE;否則丟擲異常

else

raise ACCESS_VIOLATION; // enforce DEP even if we have no hardware NX

}



if (handler is not in an image)

{

         // 在載入模組記憶體之外,並且是可執行頁

if (ImageDispatchEnable bit set in the process flags)

return TRUE; // 允許在載入模組記憶體空間外執行,返回驗證成功

else

return FALSE; // don't allow handlers outside of images

}



// everything else is allowed

return TRUE;

}

對上面的偽碼的理解,請看程式碼註釋和流程圖:
這裡寫圖片描述

偽碼裡面的ExecuteDispatchEnable和ImageDispatchEnable位標誌是核心KPROCESS結構的一部分,這兩個位用來控制當異常處理函式在不可以執行記憶體或者不在異常模組的映像(IMAGE)內時,是否執行異常處理函式。這兩個位的值可以在執行時修改,不過預設情況下如果程序的DEP被關閉,則這兩個位置1,如果程序的DEP是開啟狀態,則這兩個位被置0。

在程序的DEP是開啟的情況,有兩種異常處理函式被異常分發器認為是有效的:

  • 異常處理函式在程序映像的SafeSEH表中,並且沒有NO_SEH標誌。
  • 異常處理函式在程序映像的可執行頁,並且沒有NO_SEH標誌,沒有SafeSEH表,沒有.NET的ILonly標誌。

在程序的DEP關閉的情況下,有三種情況異常處理函式被異常分發器認為是有效的:

  • 異常處理函式在程序映像的SafeSEH表中,並且沒有NO_SEH標誌。
  • 異常處理函式在程序映像的可執行頁,並且沒有NO_SEH標誌,沒有SafeSEH表,沒有.NET的ILonly標誌。
  • 異常處理函式不在當前程序的映像裡面,但是不在當前執行緒的堆疊上。

這裡的偽碼是非常簡單的,還有很多值得探討的問題,譬如ntdll裡面,當前異常處理控制代碼是怎麼和SEH表進行對比的等等,不過這暫時不列入討論。

怎麼關掉編譯器的SafeSEH支援

雖然我不知道你為什麼要這麼做,而且我覺得很瘋狂,但是方法還是有的。以 VC6 為例,在編譯器的屬性框Liker|CommandLine的Additional options 加入/SAFESEH:NO即可,見下圖。
這裡寫圖片描述

怎樣檢測一個PE檔案是否啟用了SafeSEH

前面介紹過資料目錄裡面的一個結構(IMAGE_LOAD_CONFIG_DIRECTORY),該結構的成員SEHandlerTable是指向合法SEH處理程式地址列表的指標,成員SEHandlerCount是數目。而IMAGE_LOAD_CONFIG_DIRECTORY這個結構體只有/SAFESEH選項設定了才存在,因此,就可以根據它來判斷PE檔案是否加了/SAFESEH連結選項。這個結構在PE中的偏移由PE附加頭IMAGE_DATA_DIRECTORY 陣列的第11項指定。

#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG  10  // Load Configuration Directory

繞過方法簡介

(1)利用堆地址覆蓋SEH結構繞過SafeSEH

上面講過,在禁用DEP的程序中,異常分發器允許SEH handler位於除棧空間之外的非映像頁面。也就是說我們可以把shellcode放置在堆中,然後通過覆蓋SEH跳至堆空間以執行shellcode,這樣即可繞過SafeSEH保護。

(2)利用沒有啟用SafeSEH保護的模組繞過SafeSEH

在介紹原理時講過,在國內,目前大部分的PC都是安裝的Windows XP,也就是說對於大部分Windows作業系統,其系統模組都沒有受到SafeSEH保護,可以選用未開啟SafeSEH保護的模組來利用,另外,現在還有很多VC6編譯的軟體,這些軟體本身和自帶的dll檔案,都是可能沒有SafeSEH保護的。這時就可以使用它裡面的指令作為跳板來繞過SafeSEH。

(3)利用載入模組之外的地址繞過SafeSEH

同樣是根據SafeSEH的原理可知,對於載入模組之外的地址,SafeSEH同樣是不進行有效性檢測的(當然假設是DEP是關閉的,或者DEP已經被繞過)。

注:繞過方法這裡沒有細講,原因是沒有找到很好的例子,在《0day安全:軟體漏洞分析技術》上面有自己書籍作者自己寫的例子。以後這塊再詳說。

參考文獻

[1] Preventing the Exploitation of Structured Exception Handler (SEH) Overwrites with SEHOP

[3] /SAFESEH (Image has Safe Exception Handlers)

[4] 0day安全:軟體漏洞分析技術(第二版)

[5] Bypassing Browser Memory Protections

[6] pecoff_v8