文章目录
windbg既能用户态调试,也能内核态调试。
不支持exe拖拽。
尽量熟悉调试菜单和视图菜单。
0. 配置
用户空间
符号
设置symbol path或者环境变量_NT_SYMBOL_PATH
.
u指令现实的函数名基于调试符号,存于pdb文件。vs调试-反汇编窗口-右键符号,可以看到函数名、全局变量等。
一般调试自己的程序时,自带pdb,lm列出,ld加载即可,并且会自动弹出源码窗口,工具栏由source mode on/off开关。
vs编译时也会生成pdb文件,可执行文件中rdata区段包含了符号路径,010Editor打开exe搜索pdb就可以看到路径。所以调试本地编译的exe时不设置路径直接ld
也能加载符号。
lm
列出模块ld 模块名
加载某一个模块的符号。.reload /f
刷新并强制加载所有符号。或.reload /f 文件路径
设置符号选项:.symopt
命令,举例:
SYMOPT_UNDNAME
SYMOPT_DEFERRED_LOADS
SYMOPT_NO_CPP
SYMOPT_OMAP_FIND_NEAREST
SYMOPT_LOAD_ANYTHING
SYMOPT_FAIL_CRITICAL_ERRORS
SYMOPT_EXACT_SYMBOLS
SYNOPT_NO_IMAGE_SEARCH
1. 调试
1.1 用户层3种调试方式
直接调试
file - open execuable
附加调试
分为:
- 侵入式,可以调试。
- 非侵入式,用来读取进程/线程信息,如u、r指令,没有调试,
小知识点,一个程序只能被一个调试器调试。想查看一个被调试程序的信息,可以用非侵入式附加。
attach to process:左下角noninvasive。
本地内核调试
File - kernel Debug
1.2 内核层调试
自己的系统无法调试自己的系统,因为不能中断自己的系统。两种方法:
- 一种是类似用户层非侵入式调试,不接管系统,仅查看本系统内核信息。
- 另一种,使用VirtualKD双机调试
双机调试
主机运行vmmon.exe;VirtualKD的target文件夹放进虚拟机,运行vminstall,虚拟机重启,选择调试,主机会默认开启windbg(前提设置好路径),连接好后显示int 3.
本地内核调试
虚拟机开机时f8选择调试模式,管理员方式运行windbg,kernel debug - local,命令窗口显示lkd。
2. 命令
先列举下地址表示方式,也就是文档中所有出现的Address:常量、符号(如函数名)或伪寄存器。
寄存器又叫线程环境。
命令窗口:
- busy
0.0002>
进程号:线程号;0.kd
:内核调试,多核环境kd前会有核的标号;lkd
:本地内核调试。
三类命令:
- 常规/标准命令,>130
- 元命令,>100
- 扩展命令,无限
按回车直接执行上一条命令。上下键查看历史。
输入问号查询帮助。
0:000> ?
Open debugger.chm for complete debugger documentation
B[C|D|E][<bps>] - clear/disable/enable breakpoint(s)
BL - list breakpoints
BA <access> <size> <addr> - set processor breakpoint
BP <address> - set soft breakpoint
D[type][<range>] - dump memory
...
Hit Enter...b
b
<expr> unary ops: + - not by wo dwo qwo poi hi low
binary ops: + - * / mod(%) and(&) xor(^) or(|)
comparisons: == (=) < > !=
operands: number in current radix, public symbol, <reg>
...
2.1 常规命令
windbg内置,130多条。
常用:
- lm,列出模块
- ld,加载模块
- q,退出调试
程序控制类
g执行:
- g,可跟地址
- gn,不处理异常并继续执行
- gu,执行到父函数
t系列:
- t,单步步入,F11;
- ta ADDR,执行到某地址
- tc,执行到下一个call
p系列:
- p,单步步过,F10;
- pa;
- pc;
内存查看修改
d系列:
- d:查看数据
- da:ascii
- du:unicode
- dt:查看数据结构
- dd:查看内存
e系列:
s系列:
断点类
软件断点
- bp:软件断点
- bu:符号断点,推荐使用
bp[ID] [Options] [Address [Passes]] ["CommandString"]
- options:
-
- /1: 一次性
-
- /c:指定最大调用深度
-
- /C:指定最小调用深度
- address:地址,也可以是符号
- passes:忽略中断次数,可缺省
- CommandString:记着带引号
bu对符号下断点,且符号地址改变后,断点仍关联。
bm支持通配符,可设置多个bu或bp。
bp kernel32!GetVersion
无效,要先ld kernel32
加载模块,lm
可查看。
硬件断点
ba [i|w|r|e] Size Addr
有数量限制,但相比软件断点,可以监视IO访问(即i选项)等。
size: 1,2,4.
注意访问方式和size之间不能有空格。
条件断点
j命令:j Exp CMD1; CMD2
。相当于C的Exp ? CMD1 : CMD2
软件断点和硬件断点都支持条件断点。
断点附加命令,断下来时执行一段命令:
bp ADDR ".if (CONDITION) {cmd} .else {gc}"
bp ADDR "j (CONDITION) 'cmd'; 'gc'"
,gc就是继续执行。
eg. :
bp kernel32!GetVersion ".if (@eax=0x12345678) {} .else {gc}"
bp @$exentry "r @eax; r @edx"
这个条件未必断下,因为在内核态masm会对eax的值进行符号扩展,最好这样改条件:
(@eax & 0xffffffff)=0x12345678
管理断点
bl
列出断点bc
删除bd
禁用be
启用br
改变编号
bc *
删除所有断点。
堆栈
k
系列命令可以查看栈回溯。
k[b|p|P|v|d]
查看某个线程的堆栈:~TID k
带参:kb 2
,只显示2层堆栈调用。
查看第4个参数:dd ebp+14h
- kb,显示函数的前3个参数,即
ebp+8h/ch/10h
;; - kd,列出栈的内容;
- kp,显示函数原型,有符号时使用;
- kv,在kb基础上增加栈帧省略信息FPO。
反汇编
u系列:
u [Addr] [ l count ]
:反汇编指定地址之后的指令,count默认8行;- ub:反汇编指定地址之前的指令;
- uf:反汇编函数,有符号时使用,eg.
uf USER32!MessageBoxA; uf func
注意l和行数之间没有空格。
线程和寄存器
寄存器属于线程环境,所以放在一块。
线程相关命令(带波浪号):
~.
,当前线程;~#
,导致当前异常的线程;~*
,进程中的所有线程;~Ord
,序号查询,例如4核0-3~~[TID]
eg.:
~1 k
,查看1号线程堆栈
windbg很多命令需要携带线程,如下面的寄存器命令。
r系列:
- r,查询所有
r @eax
,查询r @eax=表达式
,修改,eg.r @eax=1
r @eax=@ebx
建议不要省略@符号。比如监视窗口一定要有@符号。
其它
x, q, ls
$exentry
入口点
!wow32
2.2 元命令
windbg内置,以点开头,后跟单词。
.symopt, .sympathy, .asm, .restart, .reboot
.help
查看元命令帮助,相当于标准命令的问号。
常用:
- .reload
- .restart
- .detach
2.3 扩展命令
叹号+单词
,非内置,实现于扩展模块dll中。
.chain
列出所有扩展模块。
叹号开头的操作内存的指令都是对物理内存操作,ring 3下不起作用。
!teb
得到地址,可搭配dt _TEB 地址
使用。
3. 分析基础
3.1 模块分析
lm
lmvm
,列出某个模块具体信息,eg.lmvm user32
;!dh ImageBase
,查看某一个模块PE信息
3.2 搜索
s命令,内存搜索:
s [-flags]sa|su
,搜索ascii或unicode字符串s [-flags]type
,搜索特定数据类型
flags: s|r|n|w|l|1
; type: b|w|d|q|a|u
eg.
s -su 0000000140000000 L400
,可能查到中文s -a 0012ff40 L20 "Hello"
s -b 0012ff40 L20 4d 5a
x命令,符号搜索:
x 符号
,eg.x *er*!Message*
井号#
搜索符号引用:
# 符号 范围
eg:
# MessageBoxA 0012ff40 L5000
3.3 分析模块链
PEB.Ldr的3个LIST_ENTRY
节点,同属LDR_TABLE_ENTRY
结构。
dt 符号|链表节点 ADDR
参数:
- -r: -r2,展开2层结构体,常用
遍历链表:dt _LDR_DATA_TABLE_ENTRY -l InLoaderOrderLinks.Flink [字段 字段 ...] ADDR
eg. dt _LDR_DATA_TABLE_ENTRY -l InLoaderOrderLinks.Flink DllBase SizeOfImage ADDR
3.4 分析驱动
单独整理在内核编程部分。
地址表示方式不变,如? DriverUnload
3.5 分析进程
内核层调试时,如果想调试某个进程(虚拟空间),需要切入该进程。
- 枚举进程:
!process 0 0
- 切入进程虚拟空间:
.process /i EPROCESS地址
3.6 分析蓝屏dump
系统属性-高级-写入调试信息-核心内存转储。
dump文件可以直接拖拽进windbg分析。
如果时分析别人的驱动,可能既没有源码也没有符号,就比较困难了。
3.7 re
*
,0或多个?
,任意单个[]
,范围#
,0或多个前一字符+
,1或多个前一字符
通常搭配x使用:
x test!m[a-z]#n
,找到main
4. 脚本
脚本保存为txt即可,执行命令$$>< ScriptPath
单行注释用$$
4.1 数据类型(c++和masm)
4.2 变量定义(伪寄存器和别名)
伪寄存器分为:
- 自定义伪寄存器:
$t0 - $t19
,相当于10个变量r $t0 = 100
? $t0+200
r $t1 = $t0*2
- 自动伪寄存器:存储关键信息
常用自动伪寄存器:
- $exentry
- $peb
- $teb
0:000> ? $exentry
Evaluate expression: 4264746 = 0041132a
0:000> u 0041132a
CTest!ILT+805(_mainCRTStartup):
0041132a e9d10b0000 jmp CTest!mainCRTStartup (00411f00)
0:000> dd $peb
002c2000 00010000 ffffffff 00400000 773bdca0
0:000> dt _PEB
CTest!_PEB
+0x000 Reserved1 : [2] UChar
+0x002 BeingDebugged : UChar
+0x003 Reserved2 : [1] UChar
...
0:000> !peb
windbg别名机制
可以理解为宏,而伪寄存器更像变量。
-
自动别名:调试器设置
-
固定别名:
$u0 - $u9
r $.u0 = 1
带点使用? $u0
,不用带点r $u0
,错误指令,相当于r 1
r $.u0
,可以执行
-
自定义别名
- as/aS 定义,可带参
-e
,环境变量作为别名-ma
,内存地址字符串作为别名-f
,文件内容作为别名,用在aS中
- ad 删除
- al 列出
- as/aS 定义,可带参
-
as CMD bp user32!MessageBoxA
-
r $.u0 = bp user32!MessageBoxA
4.3 表达式
windbg支持2种表达式:
- masm,默认且推荐默认
- c++,
-ee {masm|c++}
设置表达式类型。
masm表达式特点:
- 问号求值
- 默认16进制,可指定前缀
- 0x
- 0n,十进制
- 0t,八进制
- 0y,二进制
- 所有符号均为地址
- 一些特有的运算符(美元符表示物理地址)
- hi/low,取高/低16位,
? hi(0018ffcc)
- by/$pby,取地址处的1个字节
- wo/$pwo,取地址处的2个字节
- dwo/$pdwo,取地址处的4个字节
- qwo/$pqwo,取地址处的8个字节
- poi/$ppoi,取地址处的数据
- hi/low,取高/低16位,
c++表达式特点:
??
求值- 进制默认十进制,前缀0x/0
- 符号根据自身类型解析,如数据结构、指针、类,而不仅仅是地址。
- 使用寄存器和伪寄存器,必须有@前缀,且不能用等号赋值,而是用r命令。
masm/c++中@@(exp)
,则exp由c++/masm规则解析。或者@@c++(), @@masm()
指定。也可以.expr /s c++|masm
切换。
eg.
dd stcVar
dd @@(stcVar.szString)
? @@(stcVar.nNum)
0:000> ? 100
Evaluate expression: 256=00000100
0:000> ?? 100
int 0n100
0:000> .formats 100
Evaluate expression:
Hex: 00000100
Decimal: 256
Octal: 00000000400
Binary: 00000000 00000000 00000001 00000000
Chars: ....
Time: Thu Jan 1 08:04:16 1970
Float: low 3.58732e-043 high 0
Double: 1.26481e-321
0:000> .formats @@c++(100)
Evaluate expression:
Hex: 00000064
...
4.4 三大结构
选择:
.if (CONDITION) {CMD} .else {CMD}
.if (CONDITION) {CMD} .elsif(CONDITION) {CMD} .else {CMD}
循环:
.for (;;) {}
.while() {}
.do {} while()
如果没有符号,可以通过脚本来锁定某个地址,如main。