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