兩種異常(CPU異常、使用者模擬異常)的收集

OneTrainee發表於2020-04-24

 Windows核心分析索引目錄:https://www.cnblogs.com/onetrainee/p/11675224.html

兩種異常(CPU異常、使用者模擬異常)的收集

 文章的核心:異常收集的是什麼?(TrapFrame與ExceptionRecord);如何收集異常?(看文章)。

 

1. 異常的分類

  ① CPU指令異常 (比如除零異常) CPU執行檢測到;

  ② 使用者模擬異常 (throw 1)

  其在收集是存在不同,但在派發時和處理時是完全相同的,下面我們就來分析一下其存在的不同。

 

2. CPU指令異常 - 除零異常(分析)

  1)Trap00函式分析

    除零異常會引發中斷,其執行 Trap00 函式,該函式逆向分析如下,我們在三環進零環時,已經學習過一個_TrapFrame結構,其本質也是填寫該結構。

    注意,我們應該清楚,TrapFrame結構不只是三環進零環使用,而是隻要走IDT表都要使用該結構,其是通過TrapFrame怎麼返回,至於從三環還是零環進來這不重要。

    其分析情況如下圖,Trap00總結做已下事情:

    ① 儲存TrapFrame基本的資訊;

    ② 檢查DebugActive是否存在偵錯程式,若存在偵錯程式,則儲存Dr相關暫存器;

    ③ 判斷來自核心或三環,來確定其錯誤號(0xc0000094/0xc0000095);

    ④ 其最後有三次機會走CommonDispatchException函式。

    

  2) CommonDispatchException 異常記錄函式分析

    該函式就是生成ExceptionRecord這個結構體,填寫完成,然後呼叫nt!KiDispatchException進行下一步分析(三環的模擬異常直接在三環形成,然後拷貝到零環)。

    我們要知道ExceptionRecord的作用:儲存了異常碼異常屬性、異常記錄(連結串列,多次出現異常時記錄)異常發生地址異常有關引數

    其中該函式的重要一點:其ExceptionFlags置為0,我們之後關注KiDispatchException如何處理的。

    

 

3.使用者模擬異常分析

  像thorw 1 這種引發的是使用者模擬異常,我們下面就來分析一下如下程式碼:

#include "stdafx.h"
#include <windows.h>

int main(int argc, char* argv[])
{
    throw 11;
    
    return 0;
}

  9:        throw 11;
  00401028   mov         dword ptr [ebp-4],0Bh
  0040102F   push        offset __TI1H (00423580)
  00401034   lea         eax,[ebp-4]
  00401037   push        eax
  00401038   call        __CxxThrowException@8 (004011e0)

  1)CxxThrowException反彙編分析

    如下是CxxThrowException函式的反彙編,該函式需要兩個引數,一個是我們模擬的程式碼,另外一個是ExceptionList,我們下面詳細分析該段程式碼的執行流程:

    004011E0   push        ebp
    004011E1   mov         ebp,esp
    004011E3   sub         esp,20h
    004011E6   push        esi
    004011E7   push        edi
    004011E8   mov         ecx,8
    004011ED   mov         esi,offset string "The value of ESP was not properl"...+0E0h (00422118)
    004011F2   lea         edi,[ebp-20h]
    004011F5   rep movs    dword ptr [edi],dword ptr [esi]
    // 複製一段結構體
    004011F7   mov         eax,dword ptr [ebp+8]
    004011FA   mov         dword ptr [ebp-8],eax
    004011FD   mov         ecx,dword ptr [ebp+0Ch]
    00401200   mov         dword ptr [ebp-4],ecx
    // 傳入引數
    00401203   lea         edx,[ebp-0Ch]
    00401206   push        edx  // 引數指標
    00401207   mov         eax,dword ptr [ebp-10h]
    0040120A   push        eax // 引數個數
    0040120B   mov         ecx,dword ptr [ebp-1Ch]
    0040120E   push        ecx // 異常標誌位
    0040120F   mov         edx,dword ptr [ebp-20h]
    00401212   push        edx  // 異常碼
    00401213   call        dword ptr [__imp__RaiseException@16 (0042a154)]

    該程式碼所做的事情如下:

    ① 先從記憶體中拷貝一段0x20位元組的固定結構體到堆疊中;

    ② 將ExceptionList也拷貝到堆疊中(該結構體內部);

    ③ 傳入有關引數呼叫RaiseException函式。

    可能光看比較抽象,下面畫出其詳細的棧幀圖就知道了,注意,ThrowCode雖然從使用者程式碼傳入進來,但分析其函式並沒有用到,而是直接呼叫一段固定的異常碼。

    而&ThrowCode以及異常鏈被作為其引數儲存,這樣通過分析就可以輕易找到其ThrowCode值,其作為參考之後來處理SEH。

      

   2)kernel32!RaiseException函式分析

    前面分析過,其CxxThrowException函式呼叫該函式,該函式的本質就是生成一個ExceptionRecord結構體,

    我們之前分析過,如果是CPU指令異常,其在CommonDispatchException函式中生成該結構體;

    函式分析如下,該過程比較好理解,注意其觸發異常的地址標記為該地址,並不是ThrowCode的地址,這個是你要明確的,後續在將異常處理時這裡用到SEH異常,我們再繼續分析

    

   3)ntdll!RtlRaiseException函式分析

    該函式分析如下,其在三環進入零環前在堆疊中儲存了一個_CONTEXT結構體,我們之前在使用者APC分析過,返回三環時要在零環向三環堆疊中寫入一個CONTEXT用於儲存。

    來自使用者層的異常我們確定其必須返回,,則就進零環之前就直接在三環中預先儲存了一個_CONTEXT結構體,至於其如何使用,我們在異常的處理中會詳細分析到。

    這裡我們要關注CONTEXT幾個比較重要的點:eip與esp,因為其是程式的重要落腳點,發現其是該函式的返回地址與上一個函式的堆疊圖(本質就是kernel!RaiseException呼叫時的現場)。

    

   4)nt!RtlRaiseException函式分析分析

    上面我們經過分析ntdll!RtlRaiseException,發現其呼叫ZwRaiseException函式進核心,其對應nt!NtRaiseException函式,其分析如下圖

    該函式如下所示,其中值得注意的一點是:KThread.TrapFrame儲存著TrapFrame.ebx,進零環時,mov edx,esp,其esp儲存著call的返回地址,即ebx儲存著三環棧頂,也是三環的返回地址。

    這是這裡一個比較重要的細節,你是一定要明確的。

    

   5)nt!KiRaiseException函式分析

    我們之前在三環生成了ExceptionRecord與Context,但是我們要在零環使用,其如何使用的呢?

    分析了這個函式你就會明白,其在nt!KiRaiseException函式中呼叫完成的兩者的複製,之後將Context轉換為TrapFrame其KiDispatchException要使用。

    之前我們存在一個疑問,為什麼不能使用三環進零環的一個TrapFrame而非得從三環拿過來一個Context轉換?

    猜想:因為該TrapFrame必須是異常現場的TrapFrame,而CPU中斷直接呼叫IDT異常表很容易儲存,但是使用者模擬的必須是從三環到零環的,其TrapFrame是關於系統呼叫的。

      因此必須呼叫ntdll!RtlRaiseException三環儲存的Context,這是直接記錄異常現場的,這樣你就能很好理解其中的邏輯。                

      

4. 總結

  到此為止,我們分析過上面兩種異常的收集流程,其最終都會流向nt!KiDispatchException函式,下面一張圖簡單彙總,如果看詳情,直接回去看有關函式,都說的很詳細。

  我們要知道異常收集的是什麼-ExceptionRecord以及TrapFrame,怎麼收集的?按上面來分析即可。

  

 

 

5.通過異常來進行三環與零環通訊的思路

  對於異常,我們存在一種思路,即通過除零異常觸發,然後hook除零異常,接收到訊息,然後讀取全域性變數或者節區。

  我們將資料儲存在全域性變數或者存放在一個專門的PE節區中,我們在Hook的除零異常程式碼中讀取,然後執行,這樣就很好理解了。

  之後我們會通過這個猜想來實現我們的程式碼。

 

相關文章