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