栈溢出笔记1.9 认识SEH
從本節開始,我們就要研究一些稍微高級點的話題了,如同在1.2節中看到的,Windows中為抵抗棧溢出做了很多保護性的檢查工作,編譯的程序默認開啟了這些保護。如果我們不能繞過這些保護,那么我們的Shellcode也就是一個玩具而已,什么都做不了。
我們從SEH(結構化異常處理)開始。
這篇文章講SEH簡潔易懂:http://www.securitysift.com/windows-exploit-development-part-6-seh-exploits/?
因此,本文的前面部分就直接對其進行翻譯了,后面動手的部分再結合自己的例子進行,因為動手實踐還是用自己寫的代碼好。
(1)什么是結構化異常處理??
Windows下的硬件和軟件異常統一采用結構化異常處理(SEH)機制。異常處理結構通常包含在一個try/except或try/catch代碼塊中。如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
含義很簡單,try保護的代碼一定會執行,在發生指定的錯誤/異常之后,就執行except中的代碼,進行異常處理。異常處理器(exception filter)就是告訴操作系統對指定的錯誤/異常執行什么操作。
異常處理器(exception filter)可能由應用程序實現(通過__try/__except結構),或者使用系統自帶的。由于錯誤的種類很多(除0,越界等),對應的異常處理器也有很多。
所有種類的異常處理器,包括應用程序實現和操作系統實現的,都由Windows系統通過一些數據結構和函數進行統一管理。
(2)SEH的主要組成?
每個異常處理器都對應一個EXCEPTION_REGISTRATION_RECORD結構,該結構如下:?
?
這些異常處理器的EXCEPTION_REGISTRATION_RECORD結構連接在一起,組成一個SEH鏈表。EXCEPTION_REGISTRATION_RECORD結構中的第一個成員Next指向SEH鏈表中的下一個成員,因此,你可以通過Next來遍歷SEH鏈。EXCEPTION_REGISTRATION_RECORD結構中的第二個成員Handler為一個異常處理函數的函數指針,該異常處理函數定義如下:?
?
函數的第一個參數指向一個_EXCEPTION_RECORD結構。該結構保存了某個異常的相關信息,包括異常碼,異常發生的地址,參數的個數等,如下:?
?
_except_handler異常處理函數使用該結構中的信息(還有ContextRecord 參數中的寄存器信息)來判斷該異常能否被SEH鏈中的某個異常處理器處理。EstablisherFrame 參數也很重要,后面會說到。
_except_handler異常處理函數返回EXCEPTION_DISPOSITION,如果為ExceptionContinueExecution,表示該異常是否已經被成功處理,如果為ExceptionContinueSearch,表示當前異常處理器無法處理該異常,異常移交給SEH鏈中的下一個異常處理器。
那么,異常處理機制是如何使用這些結構和函數來進行異常處理的呢?當一個異常發生的時候,操作系統從SEH鏈頭部開始,檢查第一個_EXCEPTION_REGISTRATION_RECORD(即異常處理器)的異常處理函數,看它能否處理該異常(通過ExceptionRecord 和ContextRecord參數)。如果不能,則移動到下一個_EXCEPTION_REGISTRATION_RECORD,繼續檢查,直到找到合適的異常處理器。Windows在SEH鏈的末尾放置了一個默認的通用異常處理器,保證異常肯定能被處理。如果使用默認的異常處理器處理,你通常會看到“程序遇到了一個問題,需要關閉…”之類的信息。
每個線程有它自己的SEH鏈。操作系統通過TEB中的ExceptionList成員定位SEH鏈的起始地址,TEB位于FS:[0]。下面為SEH鏈的一個示意圖(圖中簡化了_EXCEPTION_REGISTRATION_RECORD結構):?
?
圖47 Windows SEH鏈
上圖不是SEH機制的全部,但是足夠你理解基本的原理。現在,我們用一個示例來看一看SEH機制。
好了,翻譯到此為止,但是我后面所寫的內容基本也就是原文的內容,只是我換了自己的示例,這樣便于實際操作,基本上也就相當于翻譯。我們找出1.2節中的example_2(具有棧溢出漏洞的那個程序),來看看它的SEH是什么樣的。在Immunity Debugger中選擇如下菜單:?
?
圖48 在Immunity Debugger查看SEH鏈
即可查看SEH鏈。我們看一看example_2的SEH鏈:?
?
圖49 example_2的SEH鏈
SEH的try/except或try/catch代碼塊實際上是宏定義的一段代碼,將我們自己的代碼包裹起來,因此,我們可以從當前線程的棧上來找到SEH鏈,對照上面的地址,找到它:?
?
圖50 棧上的SEH鏈
對照前面講述的EXCEPTION_REGISTRATION_RECORD結構,Next成員為鏈中的下一個異常處理器地址,為0xFFFFFFFF表示已經結尾,即最后的一個默認異常處理器。0x7c839ac0為該默認異常處理器的異常處理函數地址。
回看example_2的代碼,我們并沒有定義自己的異常處理塊(try/except或try/catch),因此,程序自帶一個默認異常處理器。前面說到,每個線程都有一個異常處理鏈,而線程是動態變化的,隨著指令流的進行,執行不同的代碼塊,調用函數等。那么,程序執行起來又是什么樣子的呢?
為了回答上面的問題,我們再來看一看。這個程序有輸入字符串的操作(gets),因此,我們讓程序運行,到達等待輸入的時刻,然后再來看SEH鏈:?
?
圖51 暫停于gets時刻的SEH鏈
好大一串。其中有系統的,有VS2008的,還有一個我們“自己”的,最后才是系統默認的。這些異常都是用來干嘛的?現在,我們把斷點設在調用gets函數之后:?
?
圖52
在看此時的SEH鏈:?
?
圖53
看來,剛剛我們應該是看錯了位置。我們前面是在gets函數等待輸入的時候看的,也就是說停在了gets函數內部,而gets函數由編譯器實現,因此,它內部包裝有自己的異常處理,這就是圖51中為什么我們看到了那么多系統和編譯器提供的異常處理函數。看來,SEH鏈是在動態變化的,進入了包裝有異常處理的代碼,就會在SEH鏈中添加異常處理器,退出其代碼塊之后,又會從SEH鏈中刪除異常處理器。這就是為什么說SEH鏈是與線程對應的。但是,既然我們自己沒有定義異常處理,這里為什么還多出來一個?這個后面再說。
接下來,我們給example_2的程序包裝一個異常處理塊,然后再看看SEH鏈的樣子:
/*****************************************************************************/ // example_10: 演示SEH鏈 #include <Windows.h> #include <stdio.h>void get_print() { char str[11]; __try { gets(str); printf("%s\n", str); } __except(EXCEPTION_EXECUTE_HANDLER) { // } }int main() { get_print(); return 0; } /*****************************************************************************/- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
最初暫停的點:?
?
圖54
最初還是只有一個SEH鏈。同樣在調用gets之后的語句暫停:?
?
圖55 example_10的SEH鏈
和圖53對比,SEH鏈中多了一個節點,因為我們自己添加了一個異常處理塊。現在還有一個疑問,多出來的那個是什么?按照SEH鏈的原理,局部的應該位于前面,因此,第一個是我們自己定義的,那第二個是哪里來的呢?(注意不要根據地址來和圖53比較進行判斷,現在已經是一個不同的程序了)它的異常處理函數地址為0x0041104B,明顯位于本模塊中。我們把斷點設置調用 get_print()之前,也就是main函數中,來看:?
?
圖56
這個時候,第二個異常處理器就已經出現了,因此,這個異常處理器是main函數的,VC++實現main函數的時候也包裝了一個異常處理塊。你可以自己去找到是何時設置的。
我們來看看兩個異常處理函數的地址,分別為0x411046和0x41104B:?
?
圖56
?
圖57
第一個指向MSVCR90D.dll中的_except_handler3,第二個最終指向MSVCR90D.dll中的_except_handler4_common。這是VC++對SEH的實現,并非使用原生的SEH,要理解這個_except_handler3和_except_handler4_common,你需要這篇文章:https://www.microsoft.com/msj/0197/exception/exception.aspx。這篇經典的文章有中文翻譯。
本節先到這里,下一節繼續。
總結
以上是生活随笔為你收集整理的栈溢出笔记1.9 认识SEH的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Centos 6.5下的OPENJDK卸
- 下一篇: (计算机组成原理)第三章存储系统-第七节