这篇文章是我学习windbg的一个笔记和总结,通过和OD的功能来对比学习windbg的一些理论和命令,达到能调试一个exe或者sys文件的目的。大神请飘过~
在开始调试之前,了解以下理论知识可以帮助你大大提高调试效率
windbg默认打开就只有一个Command窗口,但是这样调试效率很低,所以可以先设置好自己的用户界面,下面是我的Windbg界面,因为用惯了OD,所以这个完全就是仿制的OD的界面,
所有窗口都可以状态栏里调出来
设置完成之后,你可以保存到工作空间,这样下次再次打开时这个界面就会保留
工作空间保存有断点 用户定义的别名 调试器的设置 图形界面信息 调试会话状态等等信息,类似VS的项目文件,PS的工作区。
WinDBG主要是以命令方式工作的,WinDBG共支持三类命令:标准命令、元命令和扩展命令
标准命令通常是一两个字符(version除外)或者符号,用来提供适用于各种调试目标的最基本调试功能。标准命令是不分大小写的。比如:
元命令用来提供标准命令没有提供的调试功能,与标准命令一样,元命令也是内建在调试器引擎或者WinDBG程序文件中的。
所有元命令都以一个点(.)开始,所以元命令也被称为点命令,例如:
扩展命令用于扩展某一方面的调试功能。与标准命令和元命令是内建在WinDBG程序文件中不同,扩展命令是实现在动态加载的扩展模块(DLL)文件中的。
所有的扩展命令都以!开头
通过WinDBG的SDK,用户可以编写自己的扩展模块和扩展命令,例如漏洞测试常用的一个mona插件
在开始调试之前,有几个要点,记住以下几个点对于调试事半功倍
WinDBG自动定义了很多伪寄存器。在命令行和命令文件中都可以使用伪寄存器。WinDBG会自动将其替换(展开)为合适的值。例如下面这个@$scopeip就是一个伪寄存器,它代表当前的eip指针
下表列出了windbg所定义的部分寄存器(字典型知识,需要时查阅即可)
控制调试目标是调试器的一个核心任务。其宗旨就是使调试目标始终处于调试器的控制之下,让调试人员可以可以随心所欲的控制程序的执行状态。在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]
如果我们想了解一个函数的执行路径和它调用了哪些其它函数,每个函数包含了多少条指令,但我们又不想一步步的跟踪执行,那么可以使用wt命令让它帮我跟踪执行并生成一份报告给我
下面通过一个例子来解释wt命令的用法,注意:wt命令必须在函数的起始位置处,也就是步入call之后的第一条指令时执行
首先单步步入函数。然后使用wt命令生成报告
可以把wt命令的结果分为六个部分
区别在于停止调试时调试器和目标程序的偶停止运行,而分离调试器则是调试器显示No Target,而目标程序继续运行
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是命令格式如下:
其中的Options可以为以下内容
WinDBG的ba命令用来设置硬件断点,其格式如下:
ba [ID] Access Size [Option] [Address[Passes]] ["Command String"]
Access用来指定触发断点的访问方式 可以为以下几个字母之一
Size用来指定访问的长度 x86系统可以为1 2 4三种值
例如:
ba r1 0x401000
那么对内存地址0041717c的一字节访问、字访问、双字访问(读写)都会触发这个断点
如果想要查看硬件断点和状态可以直接看寄存器窗口的DR0-DR7寄存器
对 没有错!windbg也支持条件断点,但是这玩意有点复杂,而且实用性好像不大,反正我在OD里从来没用过,直接PASS吧。另外windbg好像是没有内存断点的
可以使用以下三种方法来指定断点命令中的地址参数
直接使用内存地址,比如bp 00411390
使用模块名加函数符号的方式,比如bp dbgee!wmain代表对dbgee模块中的wmain函数设置断点。也可以在符号后增加一个地址偏移,比如bp dbgee!wmain+3
如果是使用完全的调试符号,调试符号中包含源代码行信息,那么可以使用如下形式:
其中Module为模块名,Filename为源程序文件名,LInenumber为行号。整个表达式要用两个波浪号包起来``,要有调试符号才能实现,对于逆向来说没什么用 这个也pass
对于C++的类方法,也可以使用类名双冒号(::)或者双下划线(__)来连接类名和方法名,比如:
使用bl命令可以列出当前已经设置的所有断点,例如:
第二列是断点的状态
第三列是断点的地址
命令bc、bd、be分别用来删除、禁止和启用断点,它们的格式都是:
bc|bd|be 断点号
其中断点号可以使用*来通配所有断点,使用-来表示一个范围,或者使用逗号来指定多个断点号。例如以下命令都是有效的:
最后来一个表格总结
WinDBG的k系列命令就是用来帮助我们进行栈回溯的,先来看一个例子
K命令显示了函数名信息,但是没有显示每个函数的参数。命令kb可以显示放在栈上的前三个参数,例如(L是不显示源文件信息):
其他K系列的命令:
WinDBG的d系列命令用来显示指定内存区域的数据内容。这些命令的格式为:
其中大括号中的字母(区分大小写)用来指定数据的显示方式,含义如下:
Range参数用来指定要显示的内存范围。可以有以下几种表示方法:
可以以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命令的第三种用法是在指定范围内搜索某一内容模式,其语法格式为:
如果你觉得上面一段话理解起来有点费劲,不妨直接看下面的例子
它们都是等效的。意为在0012ff40到0012ff60之间搜索hello字符,-a参数指定以ACSII的方式搜索字符,类似的还有-u,它指定以UNICODE的方式搜索字符
命令e用来修改指定内置地址或者区域的内容,
第一种是按字符串编辑,其命令格式为:
第二种用法是以数值方式编辑,其格式为:
例如:
下表记录了我个人认为在调试时经常会用到的一些命令,记录的不全,欢迎补充
《windbg用法详解》
WinDbg命令三部曲
从Ollydbg说起-----WinDbg用户态调试教程:https://bbs.pediy.com/thread-34379.htm
bu[ID] [Options] [Address [Passes]] ["Command String"]
bm[Options] SymbolPattern [Passes] ["Command String"]
`[[Module!]Filename][:LineNumber]`
bp MyClass__MyMethod
bp MyClass:MyMethod
bp@@(MyClass::MyMethod)
bd 0-2,4 禁止0 1 2 4号断点
be * 启用所有断点
d{a|b|c|d|D|f|p|q|u|w|W} [Options] [Range]
dy{b|d} [Options] [Range]
d [Options] [Range]
s 0012ff40 L20 'H' 'e' 'l' 'l' 'o'
s 0012ff40 L20 48 65 6c 6c 6f
s -a 0012ff40 L20 "Hello"
Eb 00100000 01 02 03 04 数据类型为BYTE
Ed 00100000 0201 0403 数据类型为DWORD
Ea 00100000 ‘hello’ 数据类型为ASCII
Eu 00100000 ‘你好’ 数据类型为UNICODE
伪寄存器 |
含义 |
$ea |
调试目标所执行上一条指令的有效地址 |
$ea2 |
调试目标所执行上一条指令的第二个有效地址 |
$exp |
表达式评估器所评估的上一条表达式 |
$ra |
当前函数的返回地址 |
$eip |
指令指针寄存器 |
$eventip |
当前调试事件发生时的指令指针 |
$previp |
上一事件的指令指针 |
$relip |
与当前事件关联的指令指针 |
$scopeip |
当前上下文的指令指针 |
$exentry |
当前进程的入口地址 |
$retreg |
首要的函数返回值寄存器 |
$retreg64 |
64位格式的首要函数返回寄存器 |
$csp |
栈顶指针ESP |
$p |
上一个内存显示命令所打印的第一个值 |
$proc |
当前进程EPROCESS结构的指针 |
$thread |
当前线程ETHREAD结构的指针 |
$peb |
当前进程的进程环境块(PEB)的地址 |
$teb |
当前线程的线程环境块(TEB)地址 |
$tpid |
拥有当前线程的进程ID(PID) |
$tid |
当前线程的线程ID |
$bpx |
X号断点的地址 |
$frame |
当前栈帧的序号 |
$dbgtime |
当前时间 |
$callret |
使用.call命令调用的上一个函数的返回值 |
$ptrsize |
调试目标所在系统的指针类型宽度 |
$pagesize |
调试目标所在的系统的内存页字节数 |
命令 |
含义 |
说明 |
p |
Step |
单步步过 |
t |
Trace |
单步步入 |
pa |
Step to Address |
单步到指定地址 不进入子函数 |
ta |
Trace to Address |
追踪到指定地址 进入子函数 |
pc |
Step to Next Call |
单步执行到下一个函数调用 |
tc |
Trace to Next Call |
追踪执行到下一个函数调用 |
tb |
Trace to Next Branch |
追踪到下一条分支指令 |
g |
Go |
恢复运行 |
gu |
Go Up |
执行到函数返回 |
q |
Quit |
停止调试 |
.detach |
detach |
分离调试器 |
命令 |
含义 |
bp |
设置软件断点 |
bu |
对未加载的模块设置断点 |
bm |
批量设置断点 |
ba |
设置硬件断点 |
bl |
列出所有断点 |
bc |
删除断点 |
bd |
禁止断点 |
be |
启用断点 |
命令 |
含义 |
k |
显示调用堆栈 |
kb |
显示调用堆栈和栈上的前三个参数 |
kp |
参数和参数值都以函数原型格式显示出来(必须有符号) |
kv |
kb命令的基础上增加显示FPO信息和调用约定 |
kn |
命令会在每行前显示栈帧的序号 |
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!