反調試——Windows異常-SEH
反調試——Windows異常-SEH
概念:
SEH:Structured Exception Handling
SEH是Windows默認的異常處理機制
如何使用
在程式碼中使用
__try
__except()//結構類型的語句
__except()小括弧裡面填寫表達式,表達式為真的時候執行裡面的內容
__try裡面包含的是可能觸發異常的語句,except裡面包含的是出現了異常後執行的操作。
例子:
int main()
{
__try
{
cout<<"hello,world"<<endl;
}
__except(1)
{
cout<<"異常"<<endl;
}
return 0;
}
異常的作業
1 便於查找錯誤
2 可以用在反調試裡面
異常處理機制
當我們在非調試狀態下運行一個程式,程式如果觸發了異常,會先判斷是否有異常處理器,如果存在則跳轉到異常處理函數去執行,如果不存在則退出程式
如果程式處於被調試狀態,觸發異常時,作業系統會先把異常拋給調試進程,也就是讓調試器來處理異常。可以看到的現象就是觸發了異常後,程式會暫停下來,也就是斷下來,也就是斷點的原理。當異常拋給調試器後,調試器可以選擇:
1 修改觸發異常的程式碼繼續執行(程式會停在觸發異常的程式碼處,導致異常的程式碼無法執行)
2 忽略異常交給SEH執行
也就是說Windows發生異常後的處理順序為:1、調試器處理。2、SEH處理 3、崩潰
剖析SEH
SEH結構:
typedef struct _EXCEPTION_REGISTRATION_RECORD {
struct _EXCEPTION_REGISTRATION_RECORD *Next;
PEXCEPTION_ROUTINE Handler;//異常處理器,異常處理函數
} EXCEPTION_REGISTRATION_RECORD;
SEH在程式中實際上是以鏈表形式存在,每一個next都指向下一個SEH,這個鏈表也叫SEH鏈(如圖):
每次添加一個異常就會添加該異常到異常鏈的頭結點的位置
觀察SEH是如何生成和使用的
生成兩個程式觀察,兩個程式的區別就是一個有異常SEH,一個沒有
//有異常的程式程式碼
#include<Windows.h>
#include<iostream>
int main()
{
__try
{
char* str = NULL;
str[0] = 'a';
}
__except(1)
{
printf("觸發異常了\n");
}
printf("Sna1lGo\n");
return 0;
}
//沒有異常的程式程式碼
#include<Windows.h>
#include<iostream>
int main()
{
char* str = NULL;
str[0] = 'a';
printf("Sna1lGo\n");
return 0;
}
分別生成後,都用od打開調試比對
然後都進入main函數
重點查看SEH的程式碼:
這四條指令就是SEH的關鍵程式碼,這一系列操作相當於創建了一個異常的結構體,然後添加到異常鏈的表頭裡面。
分析為什麼是這樣:
首先,大概畫一個堆棧圖:
先存進來了一個0x01293609,然後調用了fs:[0]這個東西,這個東西有點眼熟之前的TEP-PEB查找核心模組的時候有用過,但是這裡,可以將fs:[0]直接理解為SEH鏈的表頭。
這裡取出來fs:[0]給eax後又把eax入棧,然後將fs:[0]賦值為esp
可以理解為在棧中創建了一個SEH變數,然後第一個欄位存放了函數地址也就是0x01293690,next欄位指向了fs:[0]也就是SEH鏈的第一個節點,然後再把fs:[0]的值修改為該節點的首地址,也就是讓fs:[0]指向新的節點。(可能你的程式碼有點不一樣,但是稍微分析下,對於異常處理這裡也是一樣的)
下面進入異常處理函數的地址查看(這裡是0x01293690)
然後再運行會暫停下來,因為出現了異常,異常優先給調試器處理,但是調試器也是可以選擇處理異常的:
在od的選項中選擇調試選項,再選擇異常:
就可以設置要捕獲的異常類型了,選擇忽略掉一些異常後再遇到就不會中斷下來了。
也可以選擇插件的StrongOD:
在取消掉Skip Some Exceptions之後,OD就會捕獲所有異常了
SEH鏈存放的位置
經過上面的分析可以知道了,SEH存放的位置在fs:[0]這裡
作業系統如何使用SEH
創建異常後,作業系統會自動把異常添加到頭結點,然後把頭指針指向新的異常節點