调试符号
windbg使用一个或多个目录来存放符号条件,并使用环境变量_NT_SYMBOL_PATH来指向这些环境变量的位置,
对操作系统内部模块的符号文件,一般用http://msdl.microsoft.com/download/symbols
配置如下:
SRV*C:\Symbols*http://msdl.microsoft.com/download/symbols
ld命令:从符号文件目录或者符号服务器中加载符号
lm命令:观察符号模块的文件情况 或者单机“Debug" -> "Modules"选项
符号的表示:
表示方法为”模块名称!符号名称“,操作系统内核的表示为 "nt!符号名称"
符号查找功能:模块名可以使用模糊搜索
X [Options] Modules!Symbol
有符号文件的情况下,windbg可以调试源码,Ctrl+P键,在窗口中指定源文件的代码路径,多个路径使用分号相隔
调试过程 (应用层实时调试)
开始调试时,默认停留在ntdll中的系统断点处,不会直接停留再程序的入口处,可以在命令窗口输入 ":g@$exentry"转到程序入口处
单步相关指令:
命令 | 快捷键 | 功能 |
t | F8 或 F11 | 追踪执行,遇到call指令进去 |
p | F10 | 单步执行,遇到call指令不跳进去 |
g | F5 | 运行程序 |
pa 地址 | 单步到指定地址,不进入call指令 | |
ta地址 | 追踪到指定地址,进入call指令 | |
pc [count] | 单步执行到下一个call指令调用,count参数用于指定call指令的个数 | |
tc [count] | 追踪执行到下一个call指令,遇到call指令跳进去 | |
tb [count] | 追踪执行到吓一跳分支指令,遇到call指令时跳进去,只用于内核调试 | |
pt | 单步执行到下一条call返回指令 | |
tt | 追踪执行下一条call返回指令,遇到call指令时跳进去 | |
ph | 单步执行到下一条分支指令 | |
th | 追踪执行到下一条分支指令,遇到call指令时跳进去 | |
wt | 自动追踪函数执行过程 |
断点指令
1、软件断点:bp、bu、bm
bp是最常用的,其格式如下
bp [ID] [Options] [Address [Passes]] ["CommandString"]
ID:指定断点ID,可不指定,内核调试限制32个断点,用户模式不限制
Options:可不指定:
/I:中断后自动删除该断点,即一次性断点
/c:指定最大调用深度,大于这个深度则断点不工作
/C:指定最小调用深度
Adress:地址或者符号,例如MesssageBoxW
Passes:忽略中断的次数,可不指定
CommondString:当中断时执行指令,用双引号包裹起来,多个指令用分号分隔
bu命令对某个符号下断点,例如"bu kernel32!GetVersion",bu命令设置的断点是和符号关联的,如果符号的地址变了,断点会保持与原符号的关联
bm命令设置通配符的断点,可以一次创建多个断点,例如对模块中所有 print函数开头的函数设置断点:"bm msvcr80!print*"
2、硬件断点
硬件断点可以实现例如IO访问的的断点,格式如下:
ba [ID] Access Size [Options] [Address] [Passess] ["CommandString"]
Access:指定出发断点的访问防止
e:在读取或执行指令时出发断点
r:在读取数据时出发断点
w:在写入数据时触发断点
i:在执行IO时触发断点
Size:访问的长度。在x86系统中其值可以为1、2、4,代表一字节、字、双字,x64系统中多了一个8,代表四字节访问。
3、条件断点
软件断点和硬件断点都支持条件断点,这两条命令是等价的。
bp | bu | ba _Address "j (Condition) 'OptionalCommands'; 'gc' "
bp | bu | ba _Address ".if (Condition) 'OptionalCommands'; .else 'gc' "
例如 , 当GetVersion被调用是检测eax寄存器,如果其值等于0x12ffc4就中断,否则使用指令gc继续。
bp kernel32!GetVersion ".if(@eax=0x12ffc4){} .else{gc}"
在内核态下,eax高位会补齐,会变为0xffffffffc012ffc4,这时可以用&操作对高位清零
bp kernel32!GetVersion ".if(@eax & 0x0`ffffffff)=0xc012ffc4{} .else{gc}"
在不中断进程的情况下,打印所有的CreateFileA函数调用,代码如下
bp kernel32!CreateFileA ".echo; .printf\"CreateFileA(%ma,%p,%p), ret=\",poi(esp+4),dwo(esp+8),dwo(esp+c);gu.printf\"%N\",eax;.echo;g"
poi的作用是取这个地址上的值,dwo用于从(esp+8)地址中取8个字节。
4、管理断点
bl命令可以列出当前的断点,bc命令、bd命令和be命令分别用于删除、禁用、启用断点,断点号可以用*通配符匹配。例如:
bd 1-3,4 //禁止1、2、3、4号断点
bc * //删除所有断点
栈窗口
call指令会将函数的返回地址记录在栈中,所以可通过遍历栈帧来追溯函数的调用过程。使用k[b|p|P|v|d] 命令可以查看栈回溯(显示的是一定数量的栈帧),第二个字母大小写敏感。
00行描述的是当前中断所在的函数(call),
01行描述的是调用00行中函数的上一级函数。
第一列是栈帧的基地址,因为x86系统用EBP寄存器来记录栈帧的基地址,x64用
第二列是函数的返回地址,这个地址是调用本行函数的那条call指令的下一条指令的地址
第三列是函数名及执行位置
kb命令只用于显示放在栈上的前三个参数,前两列与最后一列的内容跟上面一样。中间三列是子函数的参数,不管函数的参数是多少,这里只显示三个。
kb命令可以携带参数,例如”kb 2“,即显示上面两层调用堆栈。
kp名可以把参数和参数值以函数原型的形式显示出来,包括参数类型、名字、取值(必须有符号)
kv命令可以在kb命令的基础上增加帧指针省略信息和调用约定的显示
kd命令用于列出栈中的数据
内存命令
1、查看内存
d命令用于显示指定地址的内存数据,格式如下
d[类型] [地址范围]
d命令有d、da、db、dc、dd、dD、df、dp、dq、du、dw、dW、dyb、dyd、ds、dS等。
dw表示双字节形式
dd表示4字节形式
dq表示8字节形式
df表示4字节单精度浮点数格式
dD表示8字节双精度浮点数格式
dp表示指针大小格式,在32位系统下为4字节,在64位系统下为8字节。
地址范围可以L(l)参数设置,例如 "dd 401000 L4" 表示显示前四个数据
da表示ASCII字符串,
db表示字节和ASCII字符串,
dc表示DWORD和ASCII字符串
du表示Unicode字符串
dW表示双字节WORD和ASCII字符串
ds用于显示ANSI_STRING类型的字符串格式
dS用于显示UNICODE_STRING类型字符串格式
dyp表示显示二进制和字节
dyd表示显示二进制和DWORD值
dt [模块名!类型名] 用于显示数据类型和数据结构,例如使用“dt ntdll!*”可以列出ntdll中所有的结构
dds、dps、dqs用于显示地址及相关符号
2、搜索内存
s命令用于搜索内存:
s -[type] range pattern
type 表示搜索内容的数据类型。b表示 BYTE, w表示WORD,d表示DWORD,a表示ASCII,u表示Unicode。默认类型为b
range表示地址范围,可以用两种方式表示,一是起始地址,二是起始地址加长度L。如果搜索长度超过256MB,则用 “L?length”
pattern 用于指定要搜索的地址内容,可以用空格分隔要搜索的数值。
例如,要在 400000h和403000h之间搜索Unicode字符串"pediy":
s -u 400000 403000 "pediy"
在目标空间为2GB的user mode内存空间中搜索ASCII字符串 "mytest"
s -u 0x00000000 L?0x7fffffff mytets
3、修改内存
e命令用于修改指定的内存数据,他有两种格式
按字符串方式编辑指定地址的内容,格式如下:
e{a|u|za|zu} adress "String"
其中,“za”和"zu" 表示以零结尾的ASCII和Unicode字符串,
z和u则表示不以零结尾
按数值方式编辑,格式如下:
e{a|b|d|D|f|q|u|w} adress [values]
a表示ASCII码,b表示BYTE,d表示DWORD,D表示double,f表示float,q表示8字节,u表示Unicode,w表示WORD,例如 "eb 287897 70 65 64 69 79"表示写入 "pediy"
执行完e命令,在以d命令查看修改结果
4、观察内存属性
!address 用于显示指定地址的内存属性
!address [Adresss]