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的除零異常程式碼中讀取,然後執行,這樣就很好理解了。
之後我們會通過這個猜想來實現我們的程式碼。