異常處理第二講,結構化異常(微軟未公開)
轉載請注明出處
講解之前,請熟悉WinDbg的使用,工具使用的博客鏈接: http://www.cnblogs.com/iBinary/p/7589722.html
一丶認識段寄存器FS的內容,以及作用
首先我們要先認識一下段寄存器FS的作用,和內容,
我們打開OD,隨便附加一個32位程序,看下段寄存器內容是什么
現在先介紹一下段寄存器吧
段寄存器,保存的是系統信息的一個表.而FS則是存的下標,在OD中,這個都是固定的
32位系統中,沒有分段的概念了. 在16位系統中,我們定位物理地址的時候
是段 * 16 + 偏移 = 20位地址 ,而32位,其實也是這樣做的,也是段+偏移的形式
只不過32系統擴展了,直接就可以尋址了,所以 CS DS ES SS 等段寄存器的值都是0了
0 + 偏移,那么現在就可以把0省略了.
FS中下標中存的什么,我們可以看下
使用WinDbg看,因為OD是不能看的,你跳轉過去是沒有顯示任何信息的
關於FS下標存儲的是什么,我們可以去看雪的論壇去看下具體存放的什么.
看下帖子內容,請點擊: https://bbs.pediy.com/thread-175833.htm
二丶從FS寄存器中,查看TEB線程內容,以及異常鏈表
我們為什么要知道TEB的內容
是這樣的,我們以前的篩選器異常,什么異常都會去處理的.但是我們覺着很不足,因為我們不知道具體的那個函數出現了異常,所以我們要對異常處理作進一步的升級
我們要知道那個函數出現了異常才可以.
那么怎么知道那個函數出現了異常哪,那么這就和FS里面的TEB里面的內容有關了.
TEB 也就是線程相關
我們使用WinDbg看下TEB的內容
我們看到了第一個框,WinDbg已經幫我們解釋出來了(如果解釋不出來,請看下自己的符號路徑是否下載了,具體設置在熟悉WinDbg的博客中有講解,以及現在的dt命令也有講解)
第一個框,存放的是異常信息,我們還可以DT 一下,進去看一下
第二個框,我們可以看到是和進程相關的.
那么第一個框我們先DT 一
可以看出,這個地方是存放異常的地方,那么我們現在再次進入后面的結構體
注意,后面這個結構體,是未公開的,也就是微軟不讓我們自己用的.但是使用WinDbg解析符號我們得到了,或者我們去MSDN上搜索一下,也是搜索不到了.這個都是通過逆向得來的
,那現在我們看下這個表,顯示的是異常信息表,我們DT 進去看
進去后
進去后,我們發現了,他是一個鏈表,next,指向了下一次的異常信息結構體
,而第二個就是一個函數指針,注意這個我們是可以查到的.打開VC6.0 把后面的結構體復制過去,然后使用VA 插件的GO功能,可以看到是什么結果
可以看出,這個結構體保存的是返回值信息,我們也可以去WinDbg中DT一下看下
因為是未公開的,所以只知道返回值是什么意思,
第一個是代表,我不處理,繼續執行(這個篩選器異常已經講過了)
第二個是我已經處理了.
看了上面介紹的怎么多,可能不知道什么意思
其實SHE(結構化異常) 就是使用內聯匯編,給每個函數注冊一個篩選器異常,然后每個函數都有自己的回調函數,而回調函數是第上面截圖的第二個參數Handler,這個是一個函數指針.
因為未公開的,所以不知道.
但是我們也可以找得到,還是在VC6.0中定義上面那個結構體,然后GO過去
我們在上面找到的只是返回值,但是在下面尋找的時候,我們發現,使用的上面typedef定義的結構體
用來定義這個Handler了.
關於注冊,關於注冊,我們下面細講,但是現在我們先熟悉一下段寄存器FS的使用
三丶熟悉段寄存器的使用,創建反調試程序
還記得我們上次,也就是第一次dt的時候,花了兩個框嗎,我們看到了一個PEB
PEB就是和進程相關的,不知道的我下方再次貼下圖
我們先進去看下他有什么好玩的
進去之后,看到這里有一個檢測Dbg調試的功能,那我們內聯匯編使用一下FS寄存器,寫一個調試檢測是否調試.
下面寫的代碼可能不懂,因為你必須去看看雪的那篇帖子,才知道FS中到底是什么
這里截圖一部分,我們大概要知道是什么.
看下以下代碼
#include "stdafx.h" #include <STDLIB.H> int main(int argc, char* argv[]) { char isDbg; __asm { mov eax,fs:[0x18] //找到teb的位置 mov eax,[eax + 0x30] //teb + 30找到PEB的位置,對其取內容得到第一個首地址 mov eax,[eax+0x2] //首地址 + 偏移找到debug的位置對其取內容 mov isDbg,al //求出是否在調試 } printf("%d\r\n",isDbg); system("pause"); }
,接着看下下面的圖片
首先介紹一下我這次些聯匯編是什么意思
mov eax,fs:[0x18] 對照看雪的部分截圖我得到了 TEB的位置,而剛才的TEB我們也dt看了以下
現在再看下
第二步,mov eax,[eax + 0x30]
從之句話中,我們得出了PEB指向的內容,也就是 DT _PEB ,得到第一個地址.
第三步: mov eax,[eax + 0x2]
這句話代表的意思則是,我要從 PEB的首地址 + 2個偏移 然后得出里面的內容是什么.
而我們看下PEB里面是什么
這個正是我們要取出來的判斷是否在調試的標志,而因為我們 eax + 0x2的出來的是它的地址,但是我們有對它取內容了,所以結果放在了eax當中,如果不同,可以自己調試一下看看.
現在因為他是UChar類型,也就是無符號類型,所以一個字節,會放在al當中,所以我們把al的值,給了變量了.
第四步:輸出我們變量的值是什么.
我們看下我們使用VC調試的時候輸出什么
首先調試起來
單步一下
結果輸出的是1
那么我們不調試,直接運行起來,看下結果是什么
結果是0,那么現在就好辦了,我們可以開辟個線程,然后判斷這個標志,如果為1,代表被調試了
那么我們就要讓軟件崩潰,開線程崩潰,為什么要崩潰,因為你讓軟件退出的話,會逆向的人,它會在ExitProcess的位置下段點,然后回溯,就可以找到你判斷標志位的原因,而現在你可以判斷標志位,然后如果為1我就開啟一個線程,而這個線程我隨便讓它訪問個錯誤的值,比如
給指針為NULL,然后再給NULL賦值,注意,只有當標志位1才開啟,不為1不開啟,這樣崩潰了,他就會以為C05,而不調試的時候,你軟件就是正常的.
當然上面的代碼我是通過TEB尋得PEB地址然后加了02偏移,你也可以直接寫
第30個下標就是PEB,
mov eax,fs:[0x30]
mov eax,[eax +0x2]
mov isdbg,al
一樣可以.
而且你也可以隱藏模塊,下方也有模塊的鏈表.我們也可以字節遍歷這個鏈表,找到自己的模塊,然后隱藏.
四丶SEH結構化異常處理詳解
上面我們說了很多,主要就是為了SHE結構化的講解,比如FS寄存器的使用,因為當你會使用的時候,我們為每一個函數注冊一個結構化異常處理就簡單明了了.
那么我們開始注冊一個異常處理
注冊的意思:
我們上面第二步已經把異常的處理的鏈表找出了了,我們也知道了第二個參數是函數指針.
既然我們每個函數都注冊一個異常處理,也就是要往這個鏈表中插入一個異常鏈
注意: 我們是往頭上插入
(注意,只能在VC6.0中使用,高版本會有別的方法)
第一,我們要想一個問題,既然我們要注冊一個結構化異常處理
下面且看我寫一下異常處理的代碼注冊的代碼;
#include "stdafx.h" #include <WINDOWS.H> #include <STDLIB.H> #include <WINNT.H> EXCEPTION_DISPOSITION __cdecl HANDLER1( struct _EXCEPTION_RECORD *ExceptionRecord, void * EstablisherFrame, struct _CONTEXT *ContextRecord, void * DispatcherContext) { MessageBox(NULL,"我處理了異常\r\n",NULL,NULL); return ExceptionContinueSearch; } void fun1() { __asm { push offset HANDLER1 push fs:[0] mov fs:[0],esp } char *p = NULL; *p = 1; __asm { pop fs:[0] add esp,4 ret } } int main(int argc, char* argv[]) { fun1(); system("pause"); }
請先看下上面的代碼
我們一開始的內聯匯編,是要先注冊
__asm { push 函數指針 push fs:[0] mov fs:[0],esp }
這句話什么意思,因為函數從右向左傳遞參數
請看下圖
我們首先取得了fs:[0] 也就是第一個異常鏈的位置
我們dt一下看看
0偏移是_NT_TIB
我們接着dt一下這個
發現了0偏移就是異常鏈表,是一個指針,所以我們可以直接push fs:[0]了
那么這句話什么意思,
我們使用OD調試一下我們的程序看下
單步調試一下看下什么結果
我們也要調到FS 的數據區位置
單步調試,跟着走,看下會有什么結果
首先是壓入的函數的地址,我們跳轉過去看下 ctrl + G
OD自動幫我們標出來了結構異常處理程序,
現在看下第二句,壓入FS地址的0的內容.也就是舊的異常鏈表
現在棧頂位置,然后重新賦值給FS:[0]的位置
現在,我們這三行的意思就是往fs[0]位置的異常鏈表的頭部插入一個鏈表
現在的FS:[0]的位置是我們當前的位置,那么調用的時候會調用我們當前注冊的HANDLE1的回調函數,當我們把這個鏈表注銷后,才會把以前的鏈表的位置換回去
如果不懂,看下面圖片:
如果真的不理解,那么內存布局多看幾遍,其實把FS:[0]位置改為我們的棧的位置,而以前的異常鏈表的位置我們已經保存了.
所以下面可以pop fs:[0] 把我們第一個棧頂的位置,也就是保存的以前的異常鏈表的位置,換回去了.
現在我們試下我們程序的正常運行
五丶C++ 中的try catch 語法的實現
我們學過C++的都知道,C++中有一個語法叫做try catch
也可以 throw 一個異常出來
只不過一個是主動拋異常,一個是被動的拋異常
現在假設,我們fun1 函數里面調用了fun2,(fun2也不注冊異常處理)
我們fun2出現了異常,但是我們不想處理怎么辦.
那么它會往上面一層尋找,那么上面一層,也就是我們注冊的fun1的異常處理的位置,會調用對應的fun1的回調函數
那么我們現在試一下.
而Fun2()
那么我們運行起來,看下信息框來了沒有.
發現來了,那么這個就是異常處理中的 throw的原理,會往上層查找.
課堂資料就是注冊SEH的幾行代碼,請根據博客編寫,自己手動敲下
博客園IBinary原創 博客連接:http://www.cnblogs.com/iBinary/
轉載請注明出處,謝謝