1. 程式人生 > >棧溢出筆記1.9 認識SEH

棧溢出筆記1.9 認識SEH

關閉 ont tps 字符 查看 lis vs2008 windows -a

從本節開始,我們就要研究一些略微高級點的話題了,如同在1.2節中看到的,Windows中為抵抗棧溢出做了非常多保護性的檢查工作,編譯的程序默認開啟了這些保護。

假設我們不能繞過這些保護。那麽我們的Shellcode也就是一個玩具而已,什麽都做不了。

我們從SEH(結構化異常處理)開始。

這篇文章講SEH簡潔易懂:http://www.securitysift.com/windows-exploit-development-part-6-seh-exploits/
因此。本文的前面部分就直接對其進行翻譯了,後面動手的部分再結合自己的樣例進行,由於動手實踐還是用自己寫的代碼好。

(1)什麽是結構化異常處理?
Windows下的硬件和軟件異常統一採用結構化異常處理(SEH)機制。異常處理結構通常包含在一個try/except或try/catch代碼塊中。

例如以下:

/*****************************************************************************/
__try {
    // 受保護的代碼區域
    ...
}
__except (exception filter) {
    // 異常處理代碼
    ...
}
/*****************************************************************************/

含義非常easy,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;
}
/*****************************************************************************/

最初暫停的點:
技術分享
圖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