讀書筆記|Windows 除錯原理學習|持續更新

Ox9A82發表於2016-03-29

關於除錯方面的學習筆記,主要來源於《軟體除錯》的讀書筆記和夢織未來論壇的視訊教程

1.偵錯程式使用一個死迴圈監聽除錯資訊。

DebugActiveProcess(PID);
while(TRUE) { DEBUG_EVENT MyDebugInfo; WaitForDebugEvent(MyDebugInfo,INFINITE);//阻塞 switch (MyDebugInfo.dwDebugEventCode) { case CREATE_THREAD_DEBUG_EVENT: break; } }

 

 

 

2.什麼是除錯資訊,程式建立、終止,載入模組都是除錯資訊。dwDebugEventCode說明了除錯資訊的種類。

dwDebugEventCode

Type: DWORD

The code that identifies the type of debugging event. This member can be one of the following values.

ValueMeaning
CREATE_PROCESS_DEBUG_EVENT
3

Reports a create-process debugging event. The value of u.CreateProcessInfo specifies a CREATE_PROCESS_DEBUG_INFO structure.

CREATE_THREAD_DEBUG_EVENT
2

Reports a create-thread debugging event. The value of u.CreateThread specifies a CREATE_THREAD_DEBUG_INFO structure.

EXCEPTION_DEBUG_EVENT
1

Reports an exception debugging event. The value of u.Exception specifies an EXCEPTION_DEBUG_INFO structure.

EXIT_PROCESS_DEBUG_EVENT
5

Reports an exit-process debugging event. The value of u.ExitProcess specifies an EXIT_PROCESS_DEBUG_INFO structure.

EXIT_THREAD_DEBUG_EVENT
4

Reports an exit-thread debugging event. The value of u.ExitThread specifies an EXIT_THREAD_DEBUG_INFO structure.

LOAD_DLL_DEBUG_EVENT
6

Reports a load-dynamic-link-library (DLL) debugging event. The value of u.LoadDll specifies a LOAD_DLL_DEBUG_INFO structure.

OUTPUT_DEBUG_STRING_EVENT
8

Reports an output-debugging-string debugging event. The value of u.DebugString specifies an OUTPUT_DEBUG_STRING_INFO structure.

RIP_EVENT
9

Reports a RIP-debugging event (system debugging error). The value of u.RipInfo specifies a RIP_INFO structure.

UNLOAD_DLL_DEBUG_EVENT
7

Reports an unload-DLL debugging event. The value of u.UnloadDll specifies an UNLOAD_DLL_DEBUG_INFO structure.

 

 

 

3.DEBUG_EVENT中使用共用體來儲存具體資料

typedef struct _DEBUG_EVENT {
  DWORD dwDebugEventCode;
  DWORD dwProcessId;
  DWORD dwThreadId;
  union {
    EXCEPTION_DEBUG_INFO      Exception;
    CREATE_THREAD_DEBUG_INFO  CreateThread;
    CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
    EXIT_THREAD_DEBUG_INFO    ExitThread;
    EXIT_PROCESS_DEBUG_INFO   ExitProcess;
    LOAD_DLL_DEBUG_INFO       LoadDll;
    UNLOAD_DLL_DEBUG_INFO     UnloadDll;
    OUTPUT_DEBUG_STRING_INFO  DebugString;
    RIP_INFO                  RipInfo;
  } u;
} DEBUG_EVENT, *LPDEBUG_EVENT;

 

 

 

4.一但附加程式,Windows系統會傳送除錯資訊。包括已經建立過的程式和載入過的模組資訊也會傳送

 

 

5.使用者層除錯函式的實現

 

6.除錯原理概述

Windows除錯系統使用事件驅動,這一點與窗體是很相似的。

WaitForDebugEvent是用來等待除錯事件的,偵錯程式處理除錯事件時,被除錯程式會掛起,所以偵錯程式處理完畢後要呼叫ContinueDebugEvent來使掛起的被除錯程式繼續執行。

在系統核心中除錯事件的資料結構是DBGKM_APIMSG,而NTDLL也就是原生應用層使用的是DBGUI_WAIT_STATE_CHANGE,而應用層的偵錯程式是使用DEBUG_EVENT。所以要進行一些結構的轉換。

在核心建立執行緒的函式執行過程中會呼叫一個DbgkCreateThread函式,這個DbgkCreateThread函式會檢查新建執行緒的程式是否正在被除錯(通過檢查DebugPort的值是否為空)然後會傳送除錯訊息。

注意windows核心函式會根據DebugPort是否為空來判斷程式是否處於被除錯情況。

核心態下的除錯結構DBGKM_APIMSG

 1 typedef struct _DBGKM_APIMSG
 2   {
 3           PORT_MESSAGE h;                            // LPC埠訊息結構,XP之前使用
 4           DBGKM_APINUMBER ApiNumber;                // 訊息型別
 5           ULONG ReturnedStatus;                    // 偵錯程式的回覆狀態
 6           union                                    // 具體描述訊息的共用體,真正的資訊在這裡面
 7           {
 8               DBGKM_EXCEPTION Exception;            // 異常
 9               DBGKM_CREATE_THREAD CreateThread;   // 建立執行緒
10              DBGKM_CREATE_PROCESS CreateProcess;    // 建立程式
11              DBGKM_EXIT_THREAD ExitThread;        // 執行緒退出
12              DBGKM_EXIT_PROCESS ExitProcess;        // 程式退出
13              DBGKM_LOAD_DLL LoadDll;                // 對映DLl
14              DBGKM_UNLOAD_DLL UnloadDll;            // 反對映Dll
15          };
16  } DBGKM_MSG, *PDBGKM_MSG;
17 
18 複製程式碼


NTSTATUS
DbgkpSendApiMessage(
    IN OUT PDBGKM_APIMSG ApiMsg,
    IN BOOLEAN SuspendProcess
    );

 

除錯系統使用DbgkpSuspendProcess和DbgkpResumeProcess這兩個函式來控制被除錯程式。

DbgkpSuspendProcess會凍結被除錯程式中除了呼叫執行緒之外的所有執行緒,執行這個函式後被除錯程式中就只有這個發生除錯資訊的執行緒還活動著。接著會執行實際的傳送訊息的函式,

即DbgkpQueueMessage。

windows除錯子系統處於CSRSS會話管理器中,除錯子系統是以核心物件DebugObject為核心的。

除錯物件

來自wrk1.2
typedef struct _DEBUG_OBJECT {
    KEVENT EventsPresent;
    FAST_MUTEX Mutex;
    LIST_ENTRY EventList;
    ULONG Flags;
} DEBUG_OBJECT, *PDEBUG_OBJECT

EventPresent事件物件是用來同步偵錯程式程式和被除錯程式的。函式WaitForDebugEvent等待的其實就這個事件。

快速互斥體Mutex用來處理併發訪問,相當於一個鎖的作用。

偵錯程式與除錯子系統連線時,除錯子系統會建立一個除錯物件(NtCreateDebugObject),並且將其儲存在偵錯程式當前執行緒的TEB的DbgSsReserved[1]中,而這個執行緒就是偵錯程式執行緒。

要建立偵錯程式與被除錯程式之間的聯絡,需要把這個除錯物件設定到被除錯程式的EPROCESS的DebugPort中。

DbgkpQueueMessage函式用於向一個除錯物件的訊息佇列中增加除錯事件

這裡EventList連結串列中每一項都是如下結構

 1 typedef struct _DEBUG_EVENT {
 2     LIST_ENTRY EventList;      // Queued to event object through this
 3     KEVENT ContinueEvent;     //用於等待偵錯程式回覆的事件物件
 4     CLIENT_ID ClientId;          //除錯事件所在的執行緒ID和程式ID
 5     PEPROCESS Process;         // 被除錯程式的EPROCESS
 6     PETHREAD Thread;            // 被除錯程式中觸發除錯事件的執行緒的ETHREAD
 7     NTSTATUS Status;            //除錯事件處理結果
 8     ULONG Flags;
 9     PETHREAD BackoutThread;    // 產生假訊息(Faked)的執行緒ETHREAD
10     DBGKM_APIMSG ApiMsg;       // 除錯事件的真正內容
11 } DEBUG_EVENT, *PDEBUG_EVENT;    

 以上來自WRK1.2,注意這個是與使用者層同名都是DEBUG_EVENT但是內容完全不同,這是個核心結構。是核心中的除錯事件結構。

DbgkpQueueMessage把這個結構插入到除錯物件的除錯事件連結串列中。

DbgkpQueueMessage有等待和不等待兩種方式,如果指定不等待(非同步處理)則函式直接返回。

如果沒有指定不等待則設定除錯物件的EventPresent,然後再等待(KeWaitForSingleObject)DEBUG_EVENT結構中的ContinueEvent物件用來等待偵錯程式回覆。

偵錯程式呼叫ContinueDebugEvent實際上就是設定這個ContinueEvent物件。

在偵錯程式程式執行的NtDebugActiveProcess中會呼叫一個函式DbgkpSetProcessDebugObject將一個除錯物件設定到要除錯的程式中(即EPROCESS的DebugPort)。

這樣被除錯程式就與偵錯程式程式產生了聯絡(通過除錯物件)

 而由於

  1. 這個函式是代表附加程式方式除錯(只有這種方式才會呼叫這個函式)
  2. 這個函式代表剛剛啟動對程式的除錯
  3. 附加程式方式除錯代表目標程式已經執行了一段時間

所以就需要進行虛假除錯資訊傳送。

會通過遍歷被除錯程式的所有執行緒,然後在除錯核心物件中放置這些執行緒的虛假除錯事件訊息。再防止虛假模組載入除錯事件訊息。

當取消對程式的除錯時,會將DebugPort埠清零。

偵錯程式的除錯執行緒的TEB中有特殊結構,這個是區別於普通執行緒的地方。DbgSsReserved[0]指向一個被除錯程式的所有執行緒的連結串列,用來描述被除錯程式中的每一個執行緒。

DbgSsReserved[1]指向除錯物件。

WaitForDebugEvent和ContinueDebugEvent這兩個函式會維護那個執行緒連結串列。

 一個程式被除錯會造成

  • 程式的EPROCESS的DebugPort值不為0
  • 程式的PEB的BeingDebugged值不為0
  • 可能會有偵錯程式建立在被除錯程式中的遠端執行緒——RemoteBreakin執行緒

大名鼎鼎的IsDebuggerPresent就是通過判斷BeingDebugged來實現的。

 

偵錯程式與被除錯程式之間的互動被稱做“除錯會話”

兩種除錯方式

  • 啟動被除錯程式
  • 附加到已經執行的被除錯程式

1.啟動被除錯程式

首先偵錯程式執行緒會呼叫一個DbgUiConnectToDbg來是偵錯程式執行緒與除錯子系統建立連線(初始化偵錯程式執行緒),具體做法是新建一個核心除錯物件,然後把這個核心除錯物件放入偵錯程式執行緒的DbgSsReserved[1]中,這樣偵錯程式執行緒就初始化好了。

當呼叫CreateProcess建立程式時指定DEBUG_PROCESS標誌即可。系統會把呼叫這個函式的程式當作偵錯程式程式,把新建立的程式當作被除錯的程式。

建立起除錯關係

 

當程式的初始執行緒創立時會檢視自己是否是被除錯中(BeingDebugged標誌),如果是被除錯中會呼叫DbgBreakPoint來觸發一個斷點

2.附加到已經執行的被除錯程式

通過DebugActiveProcess就可以附加到一個已經執行的程式中。

首先是DbgUiConnectToDbg,這一步與上面是一樣的。

開啟被除錯的程式,因為不開啟被除錯的程式也就沒辦法對其進行操作。

呼叫核心函式(向下分發呼叫)NtDebugActiveProcess

這個核心函式主要是

1.傳送偽造的執行緒建立、程式建立和模組載入除錯訊息。

2.設定被除錯程式的除錯埠,除錯物件在DbgUiConnectToDbg呼叫後就已經建立好了直接拿來用就可以,同時設定被除錯程式BeingDebugged欄位。

為什麼會先傳送除錯訊息,後設定除錯埠呢?

這個不是很奇怪嗎?傳送之後才去設定除錯埠?

其實是因為,所謂的傳送除錯訊息實質上指的是建立並設定好一些除錯事件,然後把這些除錯事件放入除錯物件的除錯事件連結串列中,這樣一來是否設定好了除錯埠也就無關緊要了。

因為這些資料只是儲存在除錯物件中,還沒有人去等待。

 

WaitForDebugEvent

偵錯程式在使用者層的操作接下來就會呼叫WaitForDebugEvent函式來實現,用來取出一個DEBUG_EVENT結構,這個函式是阻塞的,因而可以設定一個等待時間來防止無限等待。

這個函式會呼叫底層的等待除錯事件函式,然後把等待到的結構轉化為使用者態的DEBUG_EVENT結構。因為我們前面說過,一個除錯事件在不同的層次下的表示的資料結構是不同的。

 

 偵錯程式是如何實現讓執行中的被除錯程式立刻中斷到偵錯程式中的呢?這個功能叫做非同步阻停,一種實現方法是使用CreateRemoteThread函式來新建一個觸發int 3斷點的執行緒。

系統中已經提供一個用來觸發斷點的函式,NTDLL的DbgUiRemoteBreakin函式。

windows xp之後系統提供了一個現成的API DebugBreakProcess

1 BOOL WINAPI DebugBreakProcess(
2   _In_ HANDLE Process
3 );

這個函式會自動建立遠端執行緒

 

相關文章