這篇文章是我學習windbg的一個筆記和總結,通過和OD的功能來對比學習windbg的一些理論和命令,達到能調試一個exe或者sys文件的目的。大神請飄過~
熟悉理論 提高調試效率在開始調試之前,了解以下理論知識可以幫助你大大提高調試效率
定製自己的Windbg界面windbg默認打開就只有一個Command窗口,但是這樣調試效率很低,所以可以先設置好自己的用戶界面,下面是我的Windbg界面,因為用慣了OD,所以這個完全就是仿製的OD的界面,
所有窗口都可以狀態欄裡調出來
設置完成之後,你可以保存到工作空間,這樣下次再次打開時這個界面就會保留
關於工作空間工作空間保存有斷點 用戶定義的別名 調試器的設置 圖形界面信息 調試會話狀態等等信息,類似VS的項目文件,PS的工作區。
命令概述WinDBG主要是以命令方式工作的,WinDBG共支持三類命令:標準命令、元命令和擴展命令
標準命令標準命令通常是一兩個字符(version除外)或者符號,用來提供適用於各種調試目標的最基本調試功能。標準命令是不分大小寫的。比如:
g 運行
t 單步步入
p 單步步過
r 查看和修改寄存器
元命令元命令用來提供標準命令沒有提供的調試功能,與標準命令一樣,元命令也是內建在調試器引擎或者WinDBG程序文件中的。
所有元命令都以一個點(.)開始,所以元命令也被稱為點命令,例如:
.reload 重新載入符號
.reboot 重啟目標機器
.restart 重啟調試器
.logfile 顯示信息
擴展命令擴展命令用於擴展某一方面的調試功能。與標準命令和元命令是內建在WinDBG程序文件中不同,擴展命令是實現在動態加載的擴展模塊(DLL)文件中的。
所有的擴展命令都以!開頭
通過WinDBG的SDK,用戶可以編寫自己的擴展模塊和擴展命令,例如漏洞測試常用的一個mona插件
調試技巧在開始調試之前,有幾個要點,記住以下幾個點對於調試事半功倍
直接按回車可以執行上一條命令
使用分分號作為分隔符,可以在同一行輸入多條命令
按上下方向鍵可以瀏覽和選擇以前輸入過的命令
當命令提示符顯示為BUSY時,即使命令編輯框可以輸入命令,但是這個命令也不會被馬上執行,要等WinDBG恢復到空閒狀態才能執行
使用Ctrl+Break 來終止一個長時間未完成的命令。如果使用KD或則CDB,那麼用Ctrl+C
選擇菜單->Edit_>Write Window Text to File可以把之前敲過的所有命令記錄到文件
偽寄存器WinDBG自動定義了很多偽寄存器。在命令行和命令文件中都可以使用偽寄存器。WinDBG會自動將其替換(展開)為合適的值。例如下面這個@$scopeip就是一個偽寄存器,它代表當前的eip指針
下表列出了windbg所定義的部分寄存器(字典型知識,需要時查閱即可)
偽寄存器含義$ea調試目標所執行上一條指令的有效地址$ea2調試目標所執行上一條指令的第二個有效地址$exp表達式評估器所評估的上一條表達式$ra當前函數的返回地址$eip指令指針寄存器$eventip當前調試事件發生時的指令指針$previp上一事件的指令指針$relip與當前事件關聯的指令指針$scopeip當前上下文的指令指針$exentry當前進程的入口地址$retreg首要的函數返回值寄存器$retreg6464位格式的首要函數返回寄存器$csp棧頂指針ESP$p上一個內存顯示命令所列印的第一個值$proc當前進程EPROCESS結構的指針$thread當前線程ETHREAD結構的指針$peb當前進程的進程環境塊(PEB)的地址$teb當前線程的線程環境塊(TEB)地址$tpid擁有當前線程的進程ID(PID)$tid當前線程的線程ID$bpxX號斷點的地址$frame當前棧幀的序號$dbgtime當前時間$callret使用.call命令調用的上一個函數的返回值$ptrsize調試目標所在系統的指針類型寬度$pagesize調試目標所在的系統的內存頁字節數開始實戰控制調試目標控制調試目標是調試器的一個核心任務。其宗旨就是使調試目標始終處於調試器的控制之下,讓調試人員可以可以隨心所欲的控制程序的執行狀態。在OD可以通過圖形界面和各種快捷鍵隨心所欲地控制程序,相比windbg就沒那麼方便了。但是WinDBG提供了強大的機制和豐富的命令來控制調試目標,這些命令要比OD的功能豐富的多。
單步步入和單步步過命令格式如下:
p|t [r] [=StartAddress] [count] ["Command"]
例如:
首先查看一下當前的反彙編,我想從77e40d8e這個位置開始執行單步 單步兩次 不顯示寄存器 單步執行完成之後顯示調用堆棧,就可以執行這麼一條命令,執行完成之後如圖:
tr =77e40d8e 2 "kb"
單步執行到指定地址WinDBG提供了pa和ta命令用來執行到指定的代碼地址,其命令格式為:
pa|ta [r] [=StartAddress] StopAddress
例如:
查看一下當前的反彙編
如果想直接單步到77e9f137的位置,就可以輸入下面這條命令,執行結果如下
par 77e9f137
單步執行到下一個函數調用與pa和ta命令類似,pc和tc命令用來單步執行到下一個函數調用指令(call)
pc|tc [r] [=StratAddress] [Count]
例如:
首先來查看一下當前的反彙編
如果想直接單步到77e9f137這個位置的call,就可以直接用下面一條命令
pcr
單步執行到下一個分支CPU有分支的監視和記錄功能,利用這一功能可以實現單步執行到分支,但是這個命令有一個缺陷就是不能在x86的用戶態模式下使用,命令格式如下:
tb [r] [=StartAddress] [Count]
繼續運行g(go)命令的一般形式為:
g[a][=StartAddress] [BreakAddress][;BreakCommands]
其實StartAddress用來指定開始執行的起始地址 這個功能有點像OD的此處為新的EIP
BreakAddress用來指定一個斷點地址
BreakCommands用來指定斷點命中後所指定命令
如果不帶任何參數,那麼g命令就是恢復目標運行 相當於OD的F9
可以用gu命令來執行到返回 相當於OD的Ctrl+F9
追蹤並監視如果我們想了解一個函數的執行路徑和它調用了哪些其它函數,每個函數包含了多少條指令,但我們又不想一步步的跟蹤執行,那麼可以使用wt命令讓它幫我跟蹤執行並生成一份報告給我
下面通過一個例子來解釋wt命令的用法,注意:wt命令必須在函數的起始位置處,也就是步入call之後的第一條指令時執行
首先單步步入函數。然後使用wt命令生成報告
可以把wt命令的結果分為六個部分
第一個部分是標題 顯示了追蹤的函數名和追蹤的結束地址
第二部分是詳細的執行情況 包括如下四列:
第一列為指令數,這一列的數字就是這個函數從入口進入到下一行所對應的函數入口所執行的指令
第二列用來顯示本行所對應的函數調用其他函數時所執行的總指令數
第三列表示函數的調用深度 沒進入一個函數深度加一
第四列為函數名稱 名稱前的縮進用來表示深度
停止調試區別在於停止調試時調試器和目標程序的偶停止運行,而分離調試器則是調試器顯示No Target,而目標程序繼續運行
總結命令含義說明pStep單步步過tTrace單步步入paStep to Address單步到指定地址 不進入子函數taTrace to Address追蹤到指定地址 進入子函數pcStep to Next Call單步執行到下一個函數調用tcTrace to Next Call追蹤執行到下一個函數調用tbTrace to Next Branch追蹤到下一條分支指令gGo恢復運行guGo Up執行到函數返回qQuit停止調試.detachdetach分離調試器使用斷點軟體斷點WinDBG設計了三條命令來設置軟體斷點,分別是bp、bu和bm。其中bp是基本的而且最常用的,其命令格式如下:
bp[ID] [Options] [Address [Passes]] ["Command String"]
bu命令用來設置一個延遲的以後再求解的斷點,用於對尚未加載模塊中的代碼設置斷點。當指定的模塊被加載時,WinDBG會真正落實這個斷點。所以bu命令對於調試動態加載模塊的入口函數或者初始化代碼特別有用
bm命令用來設置一批斷點,相當於幫我們自動執行很多次bp或者bu命令。比如以下命令對於msvcr80d模塊中的所有print開頭的函數設置斷點:
bm msvcr80d!print*
bm和bu是命令格式如下:
bu[ID] [Options] [Address [Passes]] ["Command String"]
bm[Options] SymbolPattern [Passes] ["Command String"]
其中的Options可以為以下內容
/1如果指定此選項,那麼這個斷點命中一次後便會便自動從斷點列表中刪除。這種斷點被稱為一次命中斷點
/p這個開關只能用在內核調試中,/p後跟一個進程的EPROCESS結構,作用是只有當前進程是指定進程時才觸發這個斷點
/t與/p開關類似,只能用在內核調試中,用來指定一個ETHREAD結構,作用是只有在執行指定的線程訪問斷點地址時才觸發斷點
/c和/C這兩個開關後面可以帶一個數字,用來指定中斷給用戶的最大函數調用深度和最小函數調用深度。舉例來說,使用命令bp/c5msvcr80d!printf設置的斷點只有當函數調用深度淺於5時才中斷給用戶
硬體斷點WinDBG的ba命令用來設置硬體斷點,其格式如下:
ba [ID] Access Size [Option] [Address[Passes]] ["Command String"]
ID用來指定斷點序號
Access用來指定觸發斷點的訪問方式 可以為以下幾個字母之一
e 讀取和執行時觸發斷點
r 讀取和寫入時觸發斷點
w 寫入時觸發斷點
i 有IO操作時觸發斷點
Size用來指定訪問的長度 x86系統可以為1 2 4三種值
Passes參數和CommandString參數的用法與設置軟體斷點命令中的一樣
例如:
ba r1 0x401000
那麼對內存地址0041717c的一字節訪問、字訪問、雙字訪問(讀寫)都會觸發這個斷點
如果想要查看硬體斷點和狀態可以直接看寄存器窗口的DR0-DR7寄存器
條件斷點對 沒有錯!windbg也支持條件斷點,但是這玩意有點複雜,而且實用性好像不大,反正我在OD裡從來沒用過,直接PASS吧。另外windbg好像是沒有內存斷點的
地址表達式可以使用以下三種方法來指定斷點命令中的地址參數
直接使用內存地址,比如bp 00411390
使用模塊名加函數符號的方式,比如bp dbgee!wmain代表對dbgee模塊中的wmain函數設置斷點。也可以在符號後增加一個地址偏移,比如bp dbgee!wmain+3
如果是使用完全的調試符號,調試符號中包含原始碼行信息,那麼可以使用如下形式:
`[[Module!]Filename][:LineNumber]`
其中Module為模塊名,Filename為源程序文件名,LInenumber為行號。整個表達式要用兩個波浪號包起來``,要有調試符號才能實現,對於逆向來說沒什麼用 這個也pass
使用bl命令可以列出當前已經設置的所有斷點,例如:
命令bc、bd、be分別用來刪除、禁止和啟用斷點,它們的格式都是:
bc|bd|be 斷點號
其中斷點號可以使用*來通配所有斷點,使用-來表示一個範圍,或者使用逗號來指定多個斷點號。例如以下命令都是有效的:
bd 0-2,4 禁止0 1 2 4號斷點
be * 啟用所有斷點
最後來一個表格總結
命令含義bp設置軟體斷點bu對未加載的模塊設置斷點bm批量設置斷點ba設置硬體斷點bl列出所有斷點bc刪除斷點bd禁止斷點be啟用斷點觀察棧顯示棧回溯WinDBG的k系列命令就是用來幫助我們進行棧回溯的,先來看一個例子
K命令顯示了函數名信息,但是沒有顯示每個函數的參數。命令kb可以顯示放在棧上的前三個參數,例如(L是不顯示源文件信息):
其他K系列的命令:
總結命令含義k顯示調用堆棧kb顯示調用堆棧和棧上的前三個參數kp參數和參數值都以函數原型格式顯示出來(必須有符號)kvkb命令的基礎上增加顯示FPO信息和調用約定kn命令會在每行前顯示棧幀的序號分析內存顯示內存WinDBG的d系列命令用來顯示指定內存區域的數據內容。這些命令的格式為:
d{a|b|c|d|D|f|p|q|u|w|W} [Options] [Range]
dy{b|d} [Options] [Range]
d [Options] [Range]
其中大括號中的字母(區分大小寫)用來指定數據的顯示方式,含義如下:
a表示ASCII碼
b表示字節和ASCII碼
c表示DWORD和ASCII碼
d表示DWORD
D表示雙精度浮點數
f表示單精度浮點數
p表示按指針寬度顯示
q表示四字(8位元組)
u表示UNICODE字符
w表示字
W表示字和ASCII碼
yb表示二進位和字節
yd表示二進位和雙字
Range參數用來指定要顯示的內存範圍。可以有以下幾種表示方法:
第一種方法是起始地址加空格加終止地址,比如dd 0012fd9c 0012fda8命令以雙字格式顯示從0012fd9c開始到0012fda8結束的16位元組內存數據
第二種方法是起始地址加空格加L(或者1)和對象個數,比如上面的命令可以等價的寫為:dd 0012fd9c L4
第三種方式是結束地址加空格加L(或者1)加負號和對象個數。使用這種方式可以把上面的命令寫為:dd 0012fdac L-4
顯示字符串可以以0結尾的簡單字符串,可以使用da或者du命令來顯它的內容,前者用於使用單字節字符集的字符串,後者用於採用UNICODE字符集的字符串。當遇到字符串末尾的0時,會自動停止顯示。例如:du 003a2e9c
顯示數據類型WinDBG的dt命令用來顯示數據類型以及按照類型來顯示數據。Dt的含義是Dump symbolic Type information。Dt是個比較複雜的命令,下面我們按照用法分別來介紹
首先,可以使用dt來顯示一個數據類型(數據結構)。這種用法的典型格式是:
dt[模塊名!]類型名
其中模塊名部分可以省略,如果省略,那麼調試器會自動搜索所有模塊。類型名即程序中定義數據結構或者通過typedef定義的類姓名。類型名中可以包含通配符,比如以下命令會列出NTDLL模塊中的所有類型:
dt ntdll!*
如果類型名是確定的類型,那麼dt便會顯示這個類型的定義,如果類型中還包含子類型,那麼可以用-b開關來遞歸式顯示所有子類型,也可以使用-r開關來指定顯示深度。-r0表示不顯示子類型,-r1表示顯示1級子類型,依此類推,例如:
dt -r1 _TEB
如果不想顯示整個結構,而只顯示某些欄位,那麼可以在類型名後使用-ny開關附加欄位搜索選項,比如以下命令只顯示TEB結構的LastError開始的欄位:
dt _TEB -ny LastError
Dt命令的第二種用法是在上一種方法的基礎上增加內存地址,讓dt 按照類型顯示指定地址的變量。例如,以下命令使用_PEB結構來顯示內存地址7ffdd000出的數據:
dt _PEB 7ffdd000
Dt命令的第三種用法是顯示類型的實例,包括全局變量、靜態變量和函數。比如以下命令顯示dbgee程序的g_szGlobal全局變量:
dt dbgee!*wmain*
搜索內存S命令用於搜索內存,有三種使用方法。
第一種用法是在指定的內存範圍內搜索任何ASCⅡ字符或者UNICODE字符串,其格式如下:
例如,以下命令搜索nt!PsInitialSystemProcess變量所指向地址開始的512個字節範圍內任何長度不小於5的ASCIⅡ字符串:
s-[l5]sa poi(nt!)PsInitialSystemProcessl200
第二種用法是在指定內存地址範圍內搜索與指定對象相同類型的對象,這裡的對象是指包含虛擬函數表的使用面向對象語言(如C++)編寫的類(Class)對象。其格式為:
S命令的第三種用法是在指定範圍內搜索某一內容模式,其語法格式為:
如果你覺得上面一段話理解起來有點費勁,不妨直接看下面的例子
s 0012ff40 L20 'H' 'e' 'l' 'l' 'o'
s 0012ff40 L20 48 65 6c 6c 6f
s -a 0012ff40 L20 "Hello"
它們都是等效的。意為在0012ff40到0012ff60之間搜索hello字符,-a參數指定以ACSII的方式搜索字符,類似的還有-u,它指定以UNICODE的方式搜索字符
修改內存命令e用來修改指定內置地址或者區域的內容,
第一種是按字符串編輯,其命令格式為:
e{a|u|za|zu}Address "String"
其中Address是要修改的內存的起始地址
za代表以0結尾的ASCII字符 zu代表以0結尾的Unicode字符
a和u分別代表不是以0結尾的ASCII和Unicode字符
第二種用法是以數值方式編輯,其格式為:
例如:
Eb 00100000 01 02 03 04 數據類型為BYTE
Ed 00100000 0201 0403 數據類型為DWORD
Ea 00100000 『hello』 數據類型為ASCII
Eu 00100000 『你好』 數據類型為UNICODE
下表記錄了我個人認為在調試時經常會用到的一些命令,記錄的不全,歡迎補充
命令含義.attach.attach PID 附加到指定ID的進程.restart重啟被調試應用.cls清理屏幕.formats顯示數字的各種格式信息lm顯示模塊.reboot重啟虛擬機r查看或修改寄存器u顯示彙編.help查看幫助參考資料《windbg用法詳解》
WinDbg命令三部曲
從OllyDbg說起WinDbg用戶態調試教程:https://bbs.pediy.com/thread-34379.htm
--官方論壇
www.52pojie.cn
--推薦給朋友
公眾微信號:吾愛破解論壇
或搜微信號:pojie_52