軟體調試的應用場景一般是: 查找程式的BUG 逆向破解 本篇文章是本人在閱讀張銀奎老師的 <軟體調試>做的筆記. 主要闡述調試器獲取到的調試事件是何時產生,如何收集的. 閱讀目錄 進程創建和線程創建事件的採集過程 進程和線程退出事件的採集過程 模塊映射和反映射事件的採集過程 異常事件的採集 能夠採集
軟體調試的應用場景一般是:
- 查找程式的BUG
- 逆向破解
本篇文章是本人在閱讀張銀奎老師的 <軟體調試>做的筆記.
主要闡述調試器獲取到的調試事件是何時產生,如何收集的.
閱讀目錄
能夠採集到的調試事件(消息)有:
1 typedef enum _DBGKM_APINUMBER 2 { 3 DbgKmExceptionApi=0, // 異常事件 4 DbgKmCreateThreadApi,//創建線程事件 5 DbgKmCreateProcessApi,//創建進程事件 6 DbgKmExitThreadApi,// 線程退出事件 7 DbgKmExitProcessApi,// 進程退出事件 8 DbgKmLoadDllApi, // 載入Dll事件 9 DbgKmUnLoadDllApi, // 卸載Dll事件 10 DbgKmErrorReportApi,// 內部錯誤事件 11 DbgKmMaxApiNumber, // 這組常量的最大值 12 }
PS:DbgKmErrorReportApi 是用來報告調試子系統內部的錯誤 ,目前已經不再使用
創建新的進程和線程 ,進程的通信,終止進程和線程,資源分配回收這些任務通常稱為進程管理,完成這些功能的是ntoskrnl.exe中有Ps或Psp開頭的系列函數. 這寫系列函數被泛稱為進程管理器.進程管理器創建新的用戶態Windows線程時,有如下工作
- 為該線程建立必要的內核對象和數據結構
- 分配棧空間
- 掛起該線程
- 通知環境子系統(子系統會作必要的設置和登記)
- 調用 PspUserThreadStartup,準備啟動線程(函數總是會調用 調試子系統的內核函數 DbgkCreateThread.)
- 調試子系統的內核函數 DbgkCreateThread()函數會檢查新創建線程所在的進程是否正在被調試(根據 DebugPort 是否為NULL),如果為NULL,便立即返回(返回到 PspUserThreadStartup()函數),如果不是NULL,則會繼續檢查該進程的用戶態時間(UserTime)是否為0,目的是判斷該線程是否是進程中的第一個線程 ,如果是第一個線程,則通過DbgkpQueueMessage()函數向調試埠(DebugPort)發送DbgKmCreateProcessApi消息. 如果不是第一個線程,則發送DbgkmCreateTheadApi消息.
具體流程如下:
建立內核對象和數據結構
||
\/
分配棧空間
||
\/
掛起線程
||
\/
通知環境子系統
|| |--> 必要的設置和登記
\/
----->>>PspUserThreadStartup()
|| |--> DbgkCreateThread()/*在函數內部調用*/
|| ||
返回到上層函數 \/
||<--是<<- [DebugPort==NULL]
||
不是
||
\/ --------------------------------
[UserTime==0]--是-->>|通過DbgkpQueueMessage()函數 |
|| |發送DbgKmCreateProcessApi消息 |
不是 --------------------------------
||
\/
-------------------------------
| 通過DbgkpQueueMessage()函數 |
| 發送DbgkmCreateTheadApi消息 |
-------------------------------
- 進程和線程退出事件的採集過程
進程管理器的PspExitThread函數負責線程的退出和清除.在函數銷毀線程的結構和資源之前 , 該函數會調用調試子系統的函數讓調試器(如果有)得到處理機會.如果退出的是一個進程中的最後一個線程,PspExitThread會調用DbgkExitProcess函數 , 否則調用DbgkExitThread函數.DbgExitThread函數被調用後,會檢查進程的DebugPort是否位0,如果不為0,則會先將該進程掛起,然後通過 DbgkpQueueMessage函數向DebugPort發送DbgKmExitThreadApi消息.並且會等待DbgKmExitThreadApi函數返回才將掛起的線程恢復運行.DbgExitProcess函數執行過程和DbgExitThread函數非常類似,只不過發送的是DbgKmExitProcessApi消息.且沒必要執行掛起和恢復動作,因為進程管理器已經對該線程做了刪除標記(??)
- 模塊映射和反映射事件的採集過程
當系統要dll時,會首先判斷該dll是否被載入過(判斷條件是什麼?)如果是,則不會重覆載入,只將該dll對應的記憶體頁面映射到目標進程的記憶體空間(如何得知要已載入的dll在記憶體中的位置和大小),並把該dll的引用次數加1. 當一個進程退出或調用FreeLibrary函數卸載一個Dll時 , 系統會從該進程的虛擬記憶體空間中把該Dll的映射刪除(如何得知映射到了進程的虛擬記憶體空間的哪個地址) , 並遞減該Dll的引用次數, 如果引用次數為0,那麼該Dll會被徹底移出記憶體(從哪移出)系統內核中的記憶體管理器(Memory Manager)便是負責DLl的映射和發映射. 記憶體管理器使用Section對象來表示一塊可被多個進程共用的記憶體區域. 並設計了一系列的內核服務和函數來實現各種映射和反映射任務. NtMapViewOfSection函數就是用來映射模塊的內核服務,NtUnmapViewOfSection是用來反映射的.當NtMapViewOfSection在把一個模塊映像成功映射到指定進程空間中時,(主要是使用MmMapViewOfSection映射),NtMapViewOfSection函數會調用調試子系統的DbgkMapViewOfSection函數通知調試子系統.
模塊映射過程如下:
- 遞歸遍歷模塊的輸入表(LdrpWalkImportDescriptor)
- 載入輸入表依賴的模塊(LdrpLoadImportModule)
- 將模塊映射到進程的虛擬空間(用戶態函數:ZwMapViewOfSection)
- 將模塊映射到進程的虛擬空間(內核態函數:NtMapViewOfSection)
- 通知調試子系統(DbgkMapViewOfSection)
- 檢查DebugPort欄位是否為空
- 如果為空則發送調試信息到調試埠(DbgkpQueueMessage)
MnUnmapViewOfSection函數的執行過程也類似 , 該函數會調用調試子系統
的DbgkUnmapViewOfSection函數,
DbgkUnmapViewOfSection函數內部會檢測DebugPort不為空後,會發送
DbgKmUnLoadDllAPi消息
- 異常事件的採集
KiDispatchException函數:異常分發的樞紐,它會給每個異常安排最多兩輪被處理的機會,對於每一輪處理機會, 它都會調用調試子系統的DbgkForwardException函數來通知調試子系統. DbgkForwardException函數可以向進程的異常埠發消息,也可以向調試埠發消息,具體決定給哪一個發消息 , 是由KiDispatchException函數在調用它時通過傳遞一個布爾類型的形參給這個函數傳參決定的.如果DbgkForwardException決定了給異常埠發消息, 那麼DbgkForwardException函數會判斷進程的DebugPort欄位是否為空,如果不為空,則通過DbgkpQueueMessage函數發送DbgKmExceptionApi消息.