異常處理第二講,結構化異常(微軟未公開)


            異常處理第二講,結構化異常(微軟未公開)

 轉載請注明出處

講解之前,請熟悉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/

轉載請注明出處,謝謝

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM