目录
WinDbg 调试技巧
目录
WinDbg调试
查看信息命令
查看当前进程上下文(.process)
0: kd> .process
Implicit process is now ffffbf01`4c48f040输出所有进程信息(!process 0 0)
// 列出所有的进程信息
!process 0 0
// 列出目标进程名的信息,这里以'任务管理器'为例
!process 0 0 Taskmgr.exe0: kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
PROCESS ffffbf014c48f040
SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 001ad000 ObjectTable: ffffe78cd3219ac0 HandleCount: 2317.
Image: System
PROCESS ffffbf014c47d080
SessionId: none Cid: 006c Peb: 00000000 ParentCid: 0004
DirBase: 00217000 ObjectTable: ffffe78cd3269c40 HandleCount: 0.
Image: Registry
PROCESS ffffbf014f68d080
SessionId: none Cid: 013c Peb: 1dcd6c0000 ParentCid: 0004
DirBase: 101152000 ObjectTable: ffffe78cd37f9080 HandleCount: 53.
Image: smss.exe
PROCESS ffffbf014f8e5140
SessionId: 0 Cid: 01bc Peb: 56a8bd4000 ParentCid: 01b0
DirBase: 104fc8000 ObjectTable: ffffe78cd67a2340 HandleCount: 475.
Image: csrss.exe查看对象符号(dt xxx)
- 如下命令为最常用的的,当加载符号表后,可以使用
dt xxx查看目标符号的完整信息
// 中括号为可选,-r2表示进一步查看子结构的层数
dt [nt!] _PEB [-rx]
// 将目标地址按照_PEB的结构进行解析
dt _PEB addr- 如下是其他查看符号命令,
// 显示所有nt符号中"_P"开头的符号
dt nt! _P*
// 直接查看PEB对象中Ldr字段的内容
dt [nt!] _PEB Ldr [addr]模糊搜索导出符号
- 在我们调试时,往往会遇到忘记了某符号的完整名称,但是记得其中的一小部分,就可以使用模糊搜索
// 搜索nt模块中所有以"Nt"开头的函数符号
x nt!Nt*// 输出结果
0: kd> x nt!nt*
fffff803`42dd6a8f nt!NtAcquireProcessActivityReference$filt$0 (void)
fffff803`42bdd620 nt!NtDeleteValueKey (void)
fffff803`42f51975 nt!NtQueryDriverEntryOrder$filt$1 (void)
fffff803`42cea060 nt!NtQueryEaFile (void)
fffff803`428d56b0 nt!NtCancelWaitCompletionPacket (void)
fffff803`42ce7930 nt!NtFlushBuffersFileEx (void)
fffff803`42dd5c80 nt!NtReleaseSemaphore$filt$0 (void)
fffff803`42dd66af nt!NtGetNlsSectionPtr$filt$0 (void)
fffff803`42c54950 nt!NtQueryMultipleValueKey (void)
fffff803`42f526d1 nt!NtSetSystemEnvironmentValue$filt$0 (void)
fffff803`42ce2640 nt!NtAlpcCreateResourceReserve (void)
fffff803`42c72ab0 nt!NtSetIoCompletionEx (void)
fffff803`42ed1172 nt!NtCreateEnclave$filt$0 (void)查看内存内容
- 我们需要查看某指针内的数据,可以使用如下命令
// 查看单字节数据
db xxx
// 还有查看双字节、4字节、8字节
dw、dd、dq- 在手工解析堆栈时,按照我们以往的阅读习惯,显示堆栈信息一般是竖着显示的
// 显示4字节、8字节堆栈信息
dds、dqs修改内存内容
- 这一部分跟上一小节的内容非常相似,如下所示:
// 修改单字节数据
eb Addr xxx
// 修改2字节、4字节、8字节数据
ew、ed、eq查看堆栈信息
- 在调试蓝屏时,我们往往需要查看堆栈,回溯查看一下调用方
// 查看调用堆栈
k
// 显示更加详细的堆栈信息
kb、kv、kp解析线性地址
- 我们在得到一个
线性地址后,可能需要得到其对应的PML4E、PDPTE、PDE、PTE信息,当然是可以手动计算的,但是WinDbg提供了一键转换命令
// 解析出va对应PML4E、PDPTE、PDE、PTE的详细信息
// PML4E、PDPTE、xxx的va、表项内容、pfn、属性
!pte va// 如下信息为一个2mb大物理页面的pte信息
1: kd> !pte ffffbf0152163280
VA ffffbf0152163280
PXE at FFFFCFE7F3F9FBF0 PPE at FFFFCFE7F3F7E028 PDE at FFFFCFE7EFC05480 PTE at FFFFCFDF80A90B18
contains 0A00000005334863 contains 0A00000005337863 contains 8A0000011B8009E3 contains 0000000000000000
pfn 5334 ---DA--KWEV pfn 5337 ---DA--KWEV pfn 11b800 -GLDA--KW-V LARGE PAGE pfn 11b823
线性地址转物理地址
- 这个命令其实用的比较少
// va转pa [页目录表基址] [线性地址]
!vtop DTB va查看物理内存内容
- 我们有时需要读出一个物理地址中的内容,其实用法跟读线性地址内容差不多,就是前面多了一个’!‘号
// 读单字节物理内存
!db [Pa]
// 读2字节、4字节、8字节物理内存
!dw、!dd、!dq1: kd> !vtop 11e9c8000 ffffbf0152163280
Amd64VtoP: Virt ffffbf0152163280, pagedir 000000011e9c8000
Amd64VtoP: PML4E 000000011e9c8bf0
Amd64VtoP: PDPE 0000000005334028
Amd64VtoP: PDE 0000000005337480
Amd64VtoP: Large page mapped phys 000000011b963280
Virtual address ffffbf0152163280 translates to physical address 11b963280.
1: kd> !dq 11b963280 L2
#11b963280 00000000`00000003 ffffbf01`52cb4200
1: kd> dq ffffbf0152163280 L2
ffffbf01`52163280 00000000`00000003 ffffbf01`52cb4200查看已加载、已卸载的驱动模块
- 连接了WinDbg后,可以查看虚拟机中已加载、已卸载的驱动模块
lmWinDbg当计算器使用
- 我们在计算某些地址、偏移时,比较初级的做法是调用一个独立的计算器出来,但是WinDbg自身其实可以充当计算器使用的
// 假设ServiceTableBase = fffff803`426c8340
// 假设Entry = 0271c104
// 可以直接使用WinDbg的算数运算
? fffff803`426c8340 + (0271c104 >> 4)
// WinDbg输出结果为
Evaluate expression: -8782091149488 = fffff803`42939f50
// 此时u、uf可以看到正确的函数地址
0: kd> u fffff803`42939f50
nt!NtAccessCheck:
fffff803`42939f50 4c8bdc mov r11,rsp
fffff803`42939f53 4883ec68 sub rsp,68h
fffff803`42939f57 488b8424a8000000 mov rax,qword ptr [rsp+0A8h]
fffff803`42939f5f 4533d2 xor r10d,r10d
fffff803`42939f62 458853f0 mov byte ptr [r11-10h],r10b
fffff803`42939f66 498943e8 mov qword ptr [r11-18h],rax
fffff803`42939f6a 488b8424a0000000 mov rax,qword ptr [rsp+0A0h]
fffff803`42939f72 498943e0 mov qword ptr [r11-20h],rax- 进制转换、类型转换,用法如下所示:
1: kd> .formats a9c
Evaluate expression:
Hex: 00000000`00000a9c
Decimal: 2716
Octal: 0000000000000000005234
Binary: 00000000 00000000 00000000 00000000 00000000 00000000 00001010 10011100
Chars: ........
Time: Thu Jan 1 08:45:16 1970
Float: low 3.80593e-042 high 0
Double: 1.34188e-320调试命令
载入符号表
- 在调试时,往往首先会加载PDB符号表,因为在你列举出xxx信息后,发现确实符号表、导致解析失败,那么再加载符号表的话,可能就需要重新列举一遍xxx信息,导致浪费调试时间
// 重新加载符号表
.reload- 强制重新加载符号表
.reload /f- 调用该命令重新载入符号表,往往是全局重载,那么会很耗时,此时我们可以重载指定的符号表,如下所示:
.reload /f TestDrv.sys内存断点
- 使用WinDbg你下内存断点,如下所示:
// 下内存断点
bp [Addr]硬件断点
- 再大部分情况下,内存断点是足够支撑我们的调试需求的,但是某些情况下,我们需要对xxx对象下访问断点、回溯访问方,此时就需要借助硬件断点了,如下所示:
// 阴硬件断点:访问、写入、执行断点
ba r/w/e查看断点列表
- 查看断点列表,如下所示:
// 列出所有的断点信息
bl- 单独禁用、启用某断点:
be、bd附加到目标进程上下文
- 我们在调试时按下WinDbg的中断按钮,此时断下的进程上下文是随机的,如果我们需要访问某个指定R3进程的内存,就需要主动附加过去:
// 获取目标进程的EPROCESS
2: kd> !process 0 0 Taskmgr.exe
PROCESS ffffbf014c55e080
SessionId: 1 Cid: 187c Peb: 5c525d2000 ParentCid: 1108
DirBase: 120507000 ObjectTable: ffffe78cd8f97cc0 HandleCount: 620.
Image: Taskmgr.exe
// 附加过去
2: kd> .process /i ffffbf014c55e080
You need to continue execution (press 'g' <enter>) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.
// 如上给出的提示非常明显了,此时需要再次按下'g',让虚拟机跑起来
2: kd> g
Break instruction exception - code 80000003 (first chance)
nt!DbgBreakPointWithStatus:
fffff803`429fefd0 cc int 3
// 到了这里,才是真正附加到目标进程上下文了
1: kd> r cr3
cr3=0000000120507000自动放行断点
- 可以设置异常的首次断下、二次断下
sxe c0000005 -> first chance 就中断
sxd c0000005 -> first chance 不中断,second chance 中断
sxn c0000005 -> first/second chance 都不处理,不中断- 在某些特殊的调试场景,可能会频繁抛出
C0000005异常,WinDbg会频繁反复断下,此时可以设置WinDbg自动放过(接管到特定的异常,自动帮你按下’g’键)
// Windbg首次捕获到c0000005错误,自动按下g放行
sxe -c "g" c0000005