1. 程式人生 > 實用技巧 >第8章:Windows 下的異常處理——SafeSEH、SEHOP

第8章:Windows 下的異常處理——SafeSEH、SEHOP

異常處理程式的安全性
  SEH的結構是儲存在棧中的,而棧中資料的安全性有時無法得到保證,例如程式接受惡意輸入導致溢位攻擊時,棧中的 SEHandler可能被覆蓋為非法過程,從而執行攻擊者預設的功能程式碼。為了防止此類攻擊,微軟提供了 SafeSEH 機制和 SEHOP 機制,以阻止那些非法的 SEHandler 程式被執行。


1. SafeSEH機制

這是微軟從 Windows XP SP2 開始引人的一種安全機制,由作業系統和編譯器聯合提供,即由編譯器提供 SEH 基礎資料,由作業系統在產生異常時進行驗證。

(1) 編譯的工作

  從 Visual Studio .NET 2003 開始,在編譯PE檔案時加入了一個 SafeSEH

開關。如果編譯時打開了這個開關,那麼編譯器會在PE頭的 DllCharacteristics 中加人一個標誌,並在編譯階段提取所有異常處理程式的相對虛擬地址(RVA),將它放入一個表。這個表的位置是由PE頭部 IMACE_OPTIONAL_HEADER結構中資料目錄的第10項指定的(Load_Config Directory),相關定義如下。

目錄實際指向一個結構體:

  SEHandlerTable 是指向一個 SEH 處理函式 RVA 的表格,SEHandlerCount 是這個表格的項數,它指出了有幾個有效的 SEHandler。當 PE 被載入時, PE 的基址、大小、SEHandlerTable (表格的地址)、SEHandlerCount (長度)會儲存在 ntdll.dll

的一個表格中。當異常發生時,系統會根據每個 PE 的基址和大小檢查當前 SEHandler 處理函式屬於哪一個 PE 模組,然後取出相應的表格地址和長度。在載入時就已經取出,載入後 SEHandlerTable 和 SEHandlerCount 就沒有用處了,所以對它進行修改不會影響系統對 SEHandler 的驗證結果。

RtlIsValidHandler 函式:

  虛擬碼裡面的 ExecuteDispatchEnableImageDispatchEnable 位標誌是核心 KPROCESS 結構的一部分,用於控制當異常處理函式在不可執行記憶體或者不在異常模組的映像(IMAGE)內時是否執行異常處理函式。這兩個位的值可以在執行時修改。不過,在預設情況下,如果程序的 DEP

(Data Execution Prevention資料執行保護)處於關閉狀態,則這兩個位置1;如果程序的 DEP 處於開啟狀態,則這兩個位置0

以下的異常處理函式是有效的:

程序的DEP 開啟,異常處理函式在程序映像的 SafeSEH 表中,沒有 NO_SEH 標誌。

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

程序的DEP 關閉,異常處理函式在程序映像的 SafeSEH 表中,沒有 NO_SEH 標誌。

程序的DEP 關閉, 異常處理函式不在當前程序的映像裡,也不在當前執行緒的棧上。

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

  如果 SEHandler 處於動態申請的記憶體中,因為它不處於任何一個 PE Image 內,所以 SEH 是沒有任何限制的;否則,不在相應的表格中,會導致 SEH 部分的異常處理被中止,即跳過後面所有 SEH 節點的遍歷。在 Visual C++ 中使用的 C 異常處理 _try/_except (_finally) 及 C++ 異常處理 try/catch/finally 等SEH處理函式,都會被編譯器自動放入該表格。但如果使用 inline asm 對 fs:[0] 進行操作,設定 SEH 就是無效的。

2.SEHOP

  SEHOP 是微軟為了進一步增強 SEH 處理程式的安全性從 2009年開始在 Windows Server 2008SPO、Windows Vista SP1 和 Windows 7 及後續版本中加人的一種保護機制,它的全稱是“StructuredException Handling Overwrite Protection”( SEH 覆防寫機制),可作為 SEH 的擴充套件,用於檢測SEH是否被覆寫。

SEHOP的核心檢測主要包括如下兩點。
① 檢測 SEH鏈的完整性,即每一個節點都必須在棧中,並且都可以正常訪問。
② 檢測最後一個節點的異常處理函式是不是位於ntdll 中的ntdll!FinalExceptionHandler()。

  一般在使用 SEH 攻擊執行 Shellcode 時,通常是用"jmp 06 pop pop ret"命令來覆蓋 SEH 結構的,此時從當前SEH節點已經無法正確指向下一個 SEH 節點。只要SEH結構連結串列的完整性遭到了破壞,SEHOP就能檢測到異常,從而阻止Shellcode的執行,

  開啟 SEHOP 保護之後,在 SEH 鏈的最後增加了一個節點,此時與倒數第2個節點並不影響。執行到倒數第2個節點會執行 kernel32!UnhandledExceptionHandler 函式進行終結處理,此時最後一個節點的異常處理函式根本不會發揮作用,它只在對 SEHandler 進行驗證時起輔助作用。即使基於某些原因或者在某些特殊的執行緒中,執行到了最後一個節點的異常處理函式ntdll!FinalExceptionHandler,該函式的內部仍然會呼叫 kernel32.dll 中的 UEF 函式或 ntdll.dll 自己的 UEF 函式,所以這與頂層異常處理的過程並不矛盾。

  SEHOP 的保護是系統級的,將更難繞過,而且不需要修改原有的應用程式,所以對效能的影響基本可以忽略(因為只有在觸發異常時才會觸發SEHOP保護邏輯)。但是,該方法並不是萬無一失的,如果攻擊者能夠事先操縱棧中的資料,同樣可以偽造節點以保證整個 SEH 鏈有效,併到達最終的 Safe 節點。

向量化異常處理(Vectored Exception Handling)

這是在 Win XP 以上新增的一種異常處理機制。

  向量化異常處理的基本理念與 SEH 相似,也是註冊一個回撥函式,當發生異常時會被系統的異常處理過程呼叫。可以通過 API 函式 AddVectoredExceptionHandler 註冊 VEH 回撥函式,其原型如下。

WINBASEAPI PVOID WINAPI AddVectoredExceptionHandler(
    ULONG FirstHandler,
    PVECTORED_EXCEPTION_HANDLER VectoredHandler //回撥函式地址
);

//回撥函式原型
LONG CALLBACK VectoredHandler(
        PEXCEPTION_POINTERS ExceptionInfo
};
註冊函式和VEH函式原型

注意註冊函式的第一個元素,用於指明函式呼叫順序,0 代表最末尾,大於 0 的將置於前端。 VEH 回撥函式需要在程式退出前自己完成解除安裝工作,使用 API 函式

引數為前面註冊函式給的返回值。

  VEH 用到的引數和頂層異常處理函式用到的引數是一樣的。VEH 合理的返回值只有兩個:EXCEPTION_CONTINUE _EXECUTION (0xfffffffff)和 EXCEPTION_CONTINUE_SEARCH (0x0),其意義與 SEH 回撥函式的意義相同。

VEH 和 SEH 的區別

註冊機制不同。SEH 的相關資訊主要儲存在棧中,而且後註冊的回撥函式總是處於 SEH 鏈的前端,也就是說,當異常發生時,異常總是由內層回撥函式優先處理,只有在內層回撥函式不處理異常時,外層回撥函式才有機會獲得控制權。而 VEH 不同,它的相關資訊儲存在獨立的連結串列中(實際儲存在 ntdll 中),在註冊 VEH 時可以指定回撥函式是位於 VEH 連結串列的前端還是尾部,這就避免了我們希望在 SEH 中獲得優先處理權卻常常不能如願的問題。

優先順序不同。VEH 優先於SEH 被呼叫,這對某些需要先於 SEH 取得異常處理權的特殊程式來說非常重要。如果 VEH 表明自己處理了異常,那麼 SEH 將沒有機會再處理該異常。作用範圍不同。SEH 機制是基於執行緒的,也就是說,同一程序內的 A 執行緒無法捕獲和處理 B 執行緒產生的異常,並且對特定的 SEH 處理程式來說,它的作用範圍更是侷限在安裝它的那個函式內部(除了頂層異常處理這個特殊的全域性回撥函式)。而 VEH 在整個程序範圍內都是有效的,它可以捕獲和處理所有執行緒產生的異常。
VEH 不需要棧展開。由於 SEH 的註冊和使用依賴於函式呼叫的棧幀,在呼叫 SEH 回撥函式時會涉及棧展開的問題,這樣 SEH 就有2次被呼叫的機會。因為 VEH 的實現不依賴棧所以在呼叫 VEH 回撥函式前不需要進行棧展開,它只有1次被呼叫的機會。

VEH ——> VCH

PVOID WINAPI AddvectoredcontinueHandler(
__in    ULONG FirstHandler,
__in    PVECTORED_EXCEPTION_HANDLER VectoredHandler
);
ULONG WINAPI RemoveVectoredContinueHandlert(
__in    PVOID Handler
);
VCH 註冊與解除安裝

值得注意的是 VCH 回撥函式的呼叫時機。分析 ntdIl!RtIDispatchException 的程式碼可知,它會在兩種情況下被呼叫。
① 在 SEH 機制無法正常執行的情況下(例如,相關資料結構被破壞,未通過 SafeSEH 或 SEHOP 驗證),SEH 分發將被跳過。
② 當 SEH 回撥函式能夠返回 ntdll!RtIDispatchException 函式時。因為 SEH 處理程式具有特殊性,在執行 SEH 回撥函式的時候,不僅有可能直接跳轉到其他位置執行(例如EXCEPTION_EXECUTE_HANDLER 的情況,會跳到 Try 塊的結束處),也有可能不再返回(例如 kernel32!UnhandledExceptionFilter 進行終結處理),所以,只有當 SEH 回撥函式返回了 ExceptionContinueExecution,或者 UEF 函式修復了異常,或 UEF 函式因為偵錯程式的存在其終結處理被跳過的情況下,SEH 回撥函式才會返回 ntdll!RtIDispatchException 函式,此時才有機會執行VCH回撥函式。