可能是最全的WinDbg命令和調試過程
1.設置符號路徑
在啟動調試會話之前,通過在管理程序 CMD.exe 中運行以下命令來設置希望 WinDBG 使用的符號路徑。如果已經設置了變量 _NT_SYMBOL_PATH,則無需運行此命令。
setx _NT_SYMBOL_PATH SRV*C:\symsrv*http://msdl.microsoft.com/download/symbols
2.啟動目標進程
現在已經準備好啟動調試器了。使用 WinDBG 調試進程有幾種不同的方法,最常見的是附加到正在運行的進程和從 WinDBG 啟動進程。在本篇文章中,將從 WinDBG 啟動本地 64 位可執行文件。為了便于學習,這里選擇每個 Windows 10 系統中都有的應用程序,即 notepad.exe。64 位 notepad.exe 位于 c:\windows\system32 目錄中。
從 Windows 10 的 "開始 "菜單啟動 WinDBG。啟動 WinDBG 后,選擇 "文件",然后選擇 "啟動可執行文件"。
圖片
在 "啟動可執行文件 "對話框中,瀏覽到 c:\Windows\System32 目錄,選擇 notepad.exe,然后單擊 "打開"。
圖片
當 WinDBG 啟動應用程序時,它會停在執行應用程序主入口點之前的初始斷點處。當 WinDBG 啟動 notepad.exe 時,WinDBG 的命令窗口中將顯示以下幾行。這樣,我們就可以運行一些初始命令,并在調用主入口點之前設置所需的斷點。
3.調試器流程架構
WinDBG Preview 是一款 UWP 應用程序,對系統的訪問非常有限,當然不足以調試進程。因此,WinDBG UI 和 WinDBG 調試器工作主程序分屬于不同的進程,它們使用命名管道進程間通信(IPC)機制進行通信。WinDBG UI 預覽進程是 DBG.X.Shell.exe,它通過命名管道連接到 EngHost.exe,后者是負責附加或啟動被調試進程的進程。
圖片
以下命令顯示傳遞給調試器進程 (EngHost.exe) 的命令行選項。DbgX.Shell.exe 使用名稱為 DbgX_c07674536fa94c33bdf0af63c782f816 的命名管道與 EngHost.exe 通信。
圖片
4.進程初步調查
先獲取一些有關操作系統版本和正在調試的進程的基本信息。顯示目標計算機版本 (vertarget) 命令可顯示 Windows 版本信息和調試會話時間信息。
(vertarget)命令顯示系統中有 4 個 CPU(內核),使用(!cpuid)調試器擴展命令進一步了解系統中 CPU/內核的系列(F)、型號(M)、步進(S)和速度。注意,(!cpuid)命令前的(!)符號表示該命令不受調試器本機支持,而是位于調試器擴展 DLL 中。
圖片
在啟動 WinDBG 之前,已經將環境變量 _NT_SYMBOL_PATH 設置為符號路徑。WinDBG 應該會自動使用該變量中設置的符號路徑, 使用 Set Symbol Path (.sympath) 命令來驗證一下。注意,命令前面的 (.) 表示這是一條元命令。大多數此類命令都會改變調試器的行為。在這種特殊情況下,(.sympath) 命令可以與 (+) 選項一起使用,在當前符號路徑上附加另一個路徑。
圖片
要查找調試器在記事本中使用的符號文件(.PDB)的路徑,可以使用調試器擴展命令(!lmi)。該命令會解析 PE 頭文件,并顯示從 PE 文件的調試目錄中獲取的信息。
圖片
5.進程和線程狀態
進程狀態 (|) 命令可用于查找正在調試的進程 ID 和進程名稱。
圖片
當 WinDBG 作為用戶模式調試器使用時,線程狀態(~)命令會顯示當前進程中所有線程的信息。此時,記事本進程中只有一個線程,該線程的狀態如下所示。這些信息包括線程 ID = 0x1df4、線程的 TEB 地址 0x000000e979a72000、線程的掛起次數以及線程的凍結狀態信息。
圖片
6.模塊信息
要查找內存中已加載模塊的虛擬地址,可以運行(List Loaded Modules)(lm)命令。運行 lm 命令本身將顯示 notepad.exe 進程地址空間中每個模塊加載的起始和終止地址范圍:
圖片
使用 (m) 選項運行 (lm) 命令可將輸出限制在特定模塊上。用它來檢索分配給 notepad.exe 模塊的 VA 范圍。
要獲取存儲在資源(.rsrc)部分的模塊版本信息,使用 lm 命令的 (v) 選項。
圖片
在 WinDBG 中,任何模塊的名稱都會被視為一個表達式,如果在模塊 "記事本 "上使用 MASM 表達式求值運算符(?),該表達式會求值到模塊加載到內存的起始 VA。
圖片
7.設置一個斷點
我們已經找到了被調試進程的一些合理信息?,F在將在 notepad.exe PE 文件的主入口點上設置一個斷點。為此,必須找到代表 notepad.exe 主入口點的符號。要獲得所有此類符號的列表,可以使用 Examine Symbol (x) 命令,該命令接受通配符,因此可以非常方便地獲得以 main 結尾的函數列表。傳遞給 (x) 命令的參數包括:模塊名稱(不含擴展名)、作為分隔符的感嘆號(!)以及符號名稱(包含通配符)。
圖片
在上述輸出中,第一列顯示的是符號所在的地址,后面是與給定通配符匹配的符號全名。
設置斷點(bp)命令可以在符號名稱或地址上設置斷點。我們用它在 notepad.exe 的主入口點上設置斷點。
圖片
使用斷點列表 (bl) 命令來驗證斷點是否設置正確。
圖片
值得注意的是,bp 命令能夠將符號 notepad!wWinMain 解析到適當的地址,即 00007ff6`f883b090,并且能夠設置執行斷點并啟用它,如上面輸出中的 (e) 所示。
圖片
一旦繼續執行,設置斷點的函數就會被調用,斷點觸發,WinDBG 將進程控制權交還給我們。
8.觸發斷點
在函數 notepad!wWinMain 的第一條指令處,WinDBG 停止了 notepad.exe 的執行。通過檢索所有 x64 CPU 寄存器的值來確定這一點。寄存器中的值還有助于檢索傳遞給該函數的參數,因為在 x64 中,前 4 個參數都是通過 CPU 寄存器傳遞的。要檢索 CPU 寄存器,可以使用寄存器 (r) 命令。
圖片
上述輸出結果證實,指令指針 (RIP) 中的地址確實指向函數 notepad!wWinMain 的第一條指令。函數 wWinMain 的原型以及包含相應參數的 CPU 寄存器如下所示。
圖片
從 (r) 命令的輸出中可以看到 hInstance = 0x00007ff6f8830000、hPrevInstance = 0x 0000000000000000 (NULL)、lpCmdLine=0x0000023771d528b6、nCmdShow=0000000000000a (SW_SHOWDEFAULT) 的值。
9.顯示棧內容
RSP 寄存器指向當前線程的堆棧頂部。對于 64 位進程,堆棧中存儲的每個值都是 64 位,即指針大小的值。要顯示從 RSP 寄存器地址開始的內存內容,可以使用顯示內存命令的 (dp) 變體。
注意,如果當前的表達式求值器是 C++,寄存器前面的 (@) 符號是必需的,這里默認選擇C++。
圖片
默認情況下,(dp) 命令在兩列中顯示 64 位數值??梢允褂?(dp) 命令的列 (/c) 選項來更改,以 4 列顯示內存內容,如下圖所示。
圖片
要顯示多于默認的 16 個值,我們可以使用對象計數 (L) 選項,后面跟上要顯示的值的個數。
圖片
如果希望 WinDBG 自動嘗試將顯示的每一個值映射到符號,可以使用顯示引用內存命令的 (dps) 。
圖片
現在可以使用顯示堆?;厮荩╧)命令及其變體,按照調試器的預期方式查看堆棧。
圖片
從該線程開始執行(ntdll!RtlUserThreadStart)一直到設置斷點的當前函數(notepad!wWinMain)。顯示的信息是調用鏈,現在深入研究一下顯示的調用堆棧。
上面顯示的每一行都代表一個函數的堆棧框架。最下面的一幀是最近的一幀,最上面的一幀是最近的一幀。
Child-SP 下列出的值是該幀的棧指針(RSP)寄存器值。這是在調用站點列所列函數的序邏輯執行完畢后 RSP 寄存器的值。RSP 寄存器的值在整個函數體中保持不變。函數的局部變量和基于堆棧的參數使用 RSP 中的這個值進行訪問。
RetAddr 是當前函數(即調用站點下所列函數)執行完畢后的返回地址。該地址對應于下一個(較低)堆棧幀中顯示的位置。例如,在最上面一幀的 RetAddr 上運行 List Nearest Symbols(ln)命令,就會映射到最上面一幀下面一幀的 Call Site 下所列函數和偏移量。
圖片
既然已經了解了如何解釋 (k) 命令所顯示的信息,那么嘗試一下它的一些變體。要在堆棧顯示中包含幀號,請使用顯示堆?;厮荩╧)命令的(kn)變體。
圖片
要顯示僅列出模塊和函數名稱的簡潔堆棧跟蹤,請使用顯示堆棧跟蹤 (k) 命令的 (kc) 變體。
圖片
最后,要顯示包含傳遞給堆棧上每個函數的堆棧參數的詳細堆棧跟蹤,請使用顯示堆棧跟蹤 (k) 命令的 (kv) 變體。需要注意的是,根據 x64 調用約定,函數的前四個參數是通過 CPU 寄存器而不是堆棧傳遞的。因此,"Args to Child(子參數)"下顯示的值是堆棧上的值,并不代表函數的實際參數,使用這些值可能會產生誤導。因此,(kv) 命令在 x64 上的使用非常有限。
圖片
10.顯示字符串
現在來看看一些 WinDBG 命令,它們可用于顯示應用程序使用的不同類型的字符串,如 ASCII 字符串、寬字符串和 Unicode 字符串。查找此類字符串的一種相對直接的方法是在 notepad.exe 或 NTDLL.dll 等模塊中查找代表數據值(而非函數)的符號,這些符號的名稱表明它們代表字符串。使用帶 (/d) 選項的 "檢查符號 (x)" 命令可以找到此類變量名的列表。我們還添加了 (/a) 選項,該選項將按地址升序顯示輸出結果。
圖片
在 notepad.exe 上使用上述技術,我們得到了 notepad!_sz_ADVAPI32_dll 符號。數據變量名中的 "sz "意味著內存中包含一個以 NULL 結尾的 ASCII 字符串。根據這一假設,我們運行顯示內存命令的 (da) 變體。
圖片
再次使用上述符號列表技術,我們得到了 ntdll!SlashSystem32SlashString 符號。與前一種情況不同的是,這個名稱并沒有暗示這個數據變量所代表的字符串類型。它可能是 ASCII 字符串、寬字符串或 Unicode 字符串。如果事先不知道內存中的數據格式,可以使用顯示內存命令的 (dc) 變體,以 DWORD(32 位)格式和 ASCII 字符顯示內存內容,前提是這些字符是可打印的。
圖片
通過觀察內存位置的內容和識別模式 - 16 位整數、16 位整數、32 位 NULL、64 位地址,可以假設內存中有一個 Unicode 字符串頭。為了確定這一點,可以使用顯示類型 (dt) 命令來顯示 Unicode 字符串頭的數據類型。
圖片
現在已經確認 ntdll!SlashSystem32SlashString 包含一個 Unicode 字符串頭,繼續使用顯示字符串命令的 (dS) 變體來顯示該字符串。
圖片
除了使用 UNICODE_STRING 結構本身的地址,還可以使用 UNICODE_STRING 結構的 Buffer 字段(即 0x00007ff9`ff1b75c8)中的地址來顯示字符串??梢允褂蔑@示內存命令的 (du) 變體。注意,寬字符串必須以 NULL 結尾。
圖片
11.顯示內存內容
符號 notepad!_sz_ADVAPI32_dll 中的內存包含 ASCII 字符串,可以以其他各種格式顯示相同的內存,如 8 位字節 (db)、16 位字 (dw)、32 位雙字 (dd) 和 64 位四元字 (dq)。注意,只有 (db) 命令以 ASCII 和十六進制數字顯示輸出。
顯示為字節 (char)。
圖片
顯示為short類型:
圖片
顯示為long類型:
圖片
顯示為int64類型:
圖片
12.在匯編程序中導航
雖然有比 WinDBG 更好的逆向工程工具,如 Ghidra,但 WinDBG 確實提供了瀏覽匯編函數的功能。WinDBG 缺少的最明顯的功能是執行交叉引用的能力。
要反匯編從當前指令指針(RIP)開始的指令,我們使用反匯編(u)命令,并將 RIP 寄存器作為地址參數。(u)命令使用線性掃描算法反匯編接下來8條指令的操作碼。
圖片
為了反向反匯編指令,我們使用反匯編命令的 (ub) 變體,并再次指定 RIP 寄存器作為地址參數,反匯編 RIP 寄存器地址之前的 8 條指令。下面的列表顯示了在函數 notepad!wWinMain 開始之前的一系列 INT 3 指令。編譯器添加了這些 INT 3 指令,以確保 notepad!wWinMain 以 16 (0x10) 字節邊界開始,并提供了一個小代碼洞穴,可用于內聯掛接的潛在用途。
圖片
要反匯編 8 條以上的指令,可以指定一個地址范圍,其中包括地址 (RIP) 和對象計數 (L),然后是要顯示的指令數。
圖片
(u) 和 (ub) 變體都使用線性掃描算法來反匯編函數,因此不知道函數邊界或函數內的基本模塊。而反匯編函數 (uf)命令則使用遞歸算法,通過評估函數中的每個基本模塊來查找其他基本模塊。為簡潔起見,對以下輸出進行了編輯。
圖片
如果感興趣的只是函數的調用而不是實際的反匯編,(uf) 命令的 (/c) 標志可以列出這些調用。為簡潔起見,對以下輸出進行了編輯。
圖片
13.繼續執行
上面已經完成了所有調試步驟,讓 WinBDG 再次使用 Go (g) 命令繼續執行進程 notepad.exe。
14.Windbg命令列表
vercommand | 顯示調試器命令行 |
vertarget | 顯示目標計算機版本 |
!cpuid | 顯示CPU相關信息 |
.sympath | 設置符號路徑 |
!lmi | 顯示模塊相關的詳細信息 |
| | 進程狀態 |
~ | 線程狀態 |
lm | 列出已加載模塊 |
? | 評估表達式 |
x | 檢查符號 |
bp | 設置斷點 |
bl | 啟用斷點 |
g | 執行 |
r | 寄存器 |
dp | 顯示內存 |
dps | 顯示已知符號的內存引用 |
k | 顯示堆棧回溯 |
ln | 列出最近的符號 |
da | 以ASCII字符顯示內存 |
dc | 以DWORD和ASCII顯示內存 |
dt | 顯示類型 |
dS | 顯示UNICODE_STRING 結構字符串 |
du | 以寬字符顯示內存 |
db | ASCII字符顯示內存 |
dw | Word類型顯示內存 |
dd | DWORD類型顯示內存 |
dq | 四字格式顯示內存 |
u | 反匯編 |
ub | 向后反匯編 |
uf | 反匯編函數 |