可能是最全的WinDbg命令和调试过程

2024年 1月 3日 59.1k 0

1.设置符号路径

在启动调试会话之前,通过在管理程序 CMD.exe 中运行以下命令来设置希望 WinDBG 使用的符号路径。如果已经设置了变量 _NT_SYMBOL_PATH,则无需运行此命令。

setx _NT_SYMBOL_PATH SRV*C:symsrv*http://msdl.microsoft.com/download/symbols

2.启动目标进程

现在已经准备好启动调试器了。使用 WinDBG 调试进程有几种不同的方法,最常见的是附加到正在运行的进程和从 WinDBG 启动进程。在本篇文章中,将从 WinDBG 启动本地 64 位可执行文件。为了便于学习,这里选择每个 Windows 10 系统中都有的应用程序,即 notepad.exe。64 位 notepad.exe 位于 c:windowssystem32 目录中。

从 Windows 10 的 "开始 "菜单启动 WinDBG。启动 WinDBG 后,选择 "文件",然后选择 "启动可执行文件"。

图片图片

在 "启动可执行文件 "对话框中,浏览到 c:WindowsSystem32 目录,选择 notepad.exe,然后单击 "打开"。

图片图片

当 WinDBG 启动应用程序时,它会停在执行应用程序主入口点之前的初始断点处。当 WinDBG 启动 notepad.exe 时,WinDBG 的命令窗口中将显示以下几行。这样,我们就可以运行一些初始命令,并在调用主入口点之前设置所需的断点。

图片

3.调试器流程架构

WinDBG Preview 是一款 UWP 应用程序,对系统的访问非常有限,当然不足以调试进程。因此,WinDBG UI 和 WinDBG 调试器工作主程序分属于不同的进程,它们使用命名管道进程间通信(IPC)机制进行通信。WinDBG UI 预览进程是 DBG.X.Shell.exe,它通过命名管道连接到 EngHost.exe,后者是负责附加或启动被调试进程的进程。

图片图片

以下命令显示传递给调试器进程 (EngHost.exe) 的命令行选项。DbgX.Shell.exe 使用名称为 DbgX_c07674536fa94c33bdf0af63c782f816 的命名管道与 EngHost.exe 通信。

图片图片

4.进程初步调查

先获取一些有关操作系统版本和正在调试的进程的基本信息。显示目标计算机版本 (vertarget) 命令可显示 Windows 版本信息和调试会话时间信息。

图片

(vertarget)命令显示系统中有 4 个 CPU(内核),使用(!cpuid)调试器扩展命令进一步了解系统中 CPU/内核的系列(F)、型号(M)、步进(S)和速度。注意,(!cpuid)命令前的(!)符号表示该命令不受调试器本机支持,而是位于调试器扩展 DLL 中。

图片图片

在启动 WinDBG 之前,已经将环境变量 _NT_SYMBOL_PATH 设置为符号路径。WinDBG 应该会自动使用该变量中设置的符号路径, 使用 Set Symbol Path (.sympath) 命令来验证一下。注意,命令前面的 (.) 表示这是一条元命令。大多数此类命令都会改变调试器的行为。在这种特殊情况下,(.sympath) 命令可以与 (+) 选项一起使用,在当前符号路径上附加另一个路径。

图片图片

要查找调试器在记事本中使用的符号文件(.PDB)的路径,可以使用调试器扩展命令(!lmi)。该命令会解析 PE 头文件,并显示从 PE 文件的调试目录中获取的信息。

图片图片

5.进程和线程状态

进程状态 (|) 命令可用于查找正在调试的进程 ID 和进程名称。

图片图片

当 WinDBG 作为用户模式调试器使用时,线程状态(~)命令会显示当前进程中所有线程的信息。此时,记事本进程中只有一个线程,该线程的状态如下所示。这些信息包括线程 ID = 0x1df4、线程的 TEB 地址 0x000000e979a72000、线程的挂起次数以及线程的冻结状态信息。

图片图片

6.模块信息

要查找内存中已加载模块的虚拟地址,可以运行(List Loaded Modules)(lm)命令。运行 lm 命令本身将显示 notepad.exe 进程地址空间中每个模块加载的起始和终止地址范围:

图片图片

使用 (m) 选项运行 (lm) 命令可将输出限制在特定模块上。用它来检索分配给 notepad.exe 模块的 VA 范围。

图片

要获取存储在资源(.rsrc)部分的模块版本信息,使用 lm 命令的 (v) 选项。

图片图片

在 WinDBG 中,任何模块的名称都会被视为一个表达式,如果在模块 "记事本 "上使用 MASM 表达式求值运算符(?),该表达式会求值到模块加载到内存的起始 VA。

图片图片

7.设置一个断点

我们已经找到了被调试进程的一些合理信息。现在将在 notepad.exe PE 文件的主入口点上设置一个断点。为此,必须找到代表 notepad.exe 主入口点的符号。要获得所有此类符号的列表,可以使用 Examine Symbol (x) 命令,该命令接受通配符,因此可以非常方便地获得以 main 结尾的函数列表。传递给 (x) 命令的参数包括:模块名称(不含扩展名)、作为分隔符的感叹号(!)以及符号名称(包含通配符)。

图片图片

在上述输出中,第一列显示的是符号所在的地址,后面是与给定通配符匹配的符号全名。

设置断点(bp)命令可以在符号名称或地址上设置断点。我们用它在 notepad.exe 的主入口点上设置断点。

图片图片

使用断点列表 (bl) 命令来验证断点是否设置正确。

图片图片

值得注意的是,bp 命令能够将符号 notepad!wWinMain 解析到适当的地址,即 00007ff6`f883b090,并且能够设置执行断点并启用它,如上面输出中的 (e) 所示。

图片图片

一旦继续执行,设置断点的函数就会被调用,断点触发,WinDBG 将进程控制权交还给我们。

8.触发断点

在函数 notepad!wWinMain 的第一条指令处,WinDBG 停止了 notepad.exe 的执行。通过检索所有 x64 CPU 寄存器的值来确定这一点。寄存器中的值还有助于检索传递给该函数的参数,因为在 x64 中,前 4 个参数都是通过 CPU 寄存器传递的。要检索 CPU 寄存器,可以使用寄存器 (r) 命令。

图片图片

上述输出结果证实,指令指针 (RIP) 中的地址确实指向函数 notepad!wWinMain 的第一条指令。函数 wWinMain 的原型以及包含相应参数的 CPU 寄存器如下所示。

图片图片

从 (r) 命令的输出中可以看到 hInstance = 0x00007ff6f8830000、hPrevInstance = 0x 0000000000000000 (NULL)、lpCmdLine=0x0000023771d528b6、nCmdShow=0000000000000a (SW_SHOWDEFAULT) 的值。

9.显示栈内容

RSP 寄存器指向当前线程的堆栈顶部。对于 64 位进程,堆栈中存储的每个值都是 64 位,即指针大小的值。要显示从 RSP 寄存器地址开始的内存内容,可以使用显示内存命令的 (dp) 变体。

注意,如果当前的表达式求值器是 C++,寄存器前面的 (@) 符号是必需的,这里默认选择C++。

图片图片

默认情况下,(dp) 命令在两列中显示 64 位数值。可以使用 (dp) 命令的列 (/c) 选项来更改,以 4 列显示内存内容,如下图所示。

图片图片

要显示多于默认的 16 个值,我们可以使用对象计数 (L) 选项,后面跟上要显示的值的个数。

图片图片

如果希望 WinDBG 自动尝试将显示的每一个值映射到符号,可以使用显示引用内存命令的 (dps) 。

图片图片

现在可以使用显示堆栈回溯(k)命令及其变体,按照调试器的预期方式查看堆栈。

图片图片

从该线程开始执行(ntdll!RtlUserThreadStart)一直到设置断点的当前函数(notepad!wWinMain)。显示的信息是调用链,现在深入研究一下显示的调用堆栈。

上面显示的每一行都代表一个函数的堆栈框架。最下面的一帧是最近的一帧,最上面的一帧是最近的一帧。

Child-SP 下列出的值是该帧的栈指针(RSP)寄存器值。这是在调用站点列所列函数的序逻辑执行完毕后 RSP 寄存器的值。RSP 寄存器的值在整个函数体中保持不变。函数的局部变量和基于堆栈的参数使用 RSP 中的这个值进行访问。

RetAddr 是当前函数(即调用站点下所列函数)执行完毕后的返回地址。该地址对应于下一个(较低)堆栈帧中显示的位置。例如,在最上面一帧的 RetAddr 上运行 List Nearest Symbols(ln)命令,就会映射到最上面一帧下面一帧的 Call Site 下所列函数和偏移量。

图片图片

既然已经了解了如何解释 (k) 命令所显示的信息,那么尝试一下它的一些变体。要在堆栈显示中包含帧号,请使用显示堆栈回溯(k)命令的(kn)变体。

图片图片

要显示仅列出模块和函数名称的简洁堆栈跟踪,请使用显示堆栈跟踪 (k) 命令的 (kc) 变体。

图片图片

最后,要显示包含传递给堆栈上每个函数的堆栈参数的详细堆栈跟踪,请使用显示堆栈跟踪 (k) 命令的 (kv) 变体。需要注意的是,根据 x64 调用约定,函数的前四个参数是通过 CPU 寄存器而不是堆栈传递的。因此,"Args to Child(子参数)"下显示的值是堆栈上的值,并不代表函数的实际参数,使用这些值可能会产生误导。因此,(kv) 命令在 x64 上的使用非常有限。

图片图片

10.显示字符串

现在来看看一些 WinDBG 命令,它们可用于显示应用程序使用的不同类型的字符串,如 ASCII 字符串、宽字符串和 Unicode 字符串。查找此类字符串的一种相对直接的方法是在 notepad.exe 或 NTDLL.dll 等模块中查找代表数据值(而非函数)的符号,这些符号的名称表明它们代表字符串。使用带 (/d) 选项的 "检查符号 (x)" 命令可以找到此类变量名的列表。我们还添加了 (/a) 选项,该选项将按地址升序显示输出结果。

图片图片

在 notepad.exe 上使用上述技术,我们得到了 notepad!_sz_ADVAPI32_dll 符号。数据变量名中的 "sz "意味着内存中包含一个以 NULL 结尾的 ASCII 字符串。根据这一假设,我们运行显示内存命令的 (da) 变体。

图片图片

再次使用上述符号列表技术,我们得到了 ntdll!SlashSystem32SlashString 符号。与前一种情况不同的是,这个名称并没有暗示这个数据变量所代表的字符串类型。它可能是 ASCII 字符串、宽字符串或 Unicode 字符串。如果事先不知道内存中的数据格式,可以使用显示内存命令的 (dc) 变体,以 DWORD(32 位)格式和 ASCII 字符显示内存内容,前提是这些字符是可打印的。

图片图片

通过观察内存位置的内容和识别模式 - 16 位整数、16 位整数、32 位 NULL、64 位地址,可以假设内存中有一个 Unicode 字符串头。为了确定这一点,可以使用显示类型 (dt) 命令来显示 Unicode 字符串头的数据类型。

图片图片

现在已经确认 ntdll!SlashSystem32SlashString 包含一个 Unicode 字符串头,继续使用显示字符串命令的 (dS) 变体来显示该字符串。

图片图片

除了使用 UNICODE_STRING 结构本身的地址,还可以使用 UNICODE_STRING 结构的 Buffer 字段(即 0x00007ff9`ff1b75c8)中的地址来显示字符串。可以使用显示内存命令的 (du) 变体。注意,宽字符串必须以 NULL 结尾。

图片图片

11.显示内存内容

符号 notepad!_sz_ADVAPI32_dll 中的内存包含 ASCII 字符串,可以以其他各种格式显示相同的内存,如 8 位字节 (db)、16 位字 (dw)、32 位双字 (dd) 和 64 位四元字 (dq)。注意,只有 (db) 命令以 ASCII 和十六进制数字显示输出。

显示为字节 (char)。

图片图片

显示为short类型:

图片图片

显示为long类型:

图片图片

显示为int64类型:

图片图片

12.在汇编程序中导航

虽然有比 WinDBG 更好的逆向工程工具,如 Ghidra,但 WinDBG 确实提供了浏览汇编函数的功能。WinDBG 缺少的最明显的功能是执行交叉引用的能力。

要反汇编从当前指令指针(RIP)开始的指令,我们使用反汇编(u)命令,并将 RIP 寄存器作为地址参数。(u)命令使用线性扫描算法反汇编接下来8条指令的操作码。

图片图片

为了反向反汇编指令,我们使用反汇编命令的 (ub) 变体,并再次指定 RIP 寄存器作为地址参数,反汇编 RIP 寄存器地址之前的 8 条指令。下面的列表显示了在函数 notepad!wWinMain 开始之前的一系列 INT 3 指令。编译器添加了这些 INT 3 指令,以确保 notepad!wWinMain 以 16 (0x10) 字节边界开始,并提供了一个小代码洞穴,可用于内联挂接的潜在用途。

图片图片

要反汇编 8 条以上的指令,可以指定一个地址范围,其中包括地址 (RIP) 和对象计数 (L),然后是要显示的指令数。

图片图片

(u) 和 (ub) 变体都使用线性扫描算法来反汇编函数,因此不知道函数边界或函数内的基本模块。而反汇编函数 (uf)命令则使用递归算法,通过评估函数中的每个基本模块来查找其他基本模块。为简洁起见,对以下输出进行了编辑。

图片图片

如果感兴趣的只是函数的调用而不是实际的反汇编,(uf) 命令的 (/c) 标志可以列出这些调用。为简洁起见,对以下输出进行了编辑。

图片图片

13.继续执行

上面已经完成了所有调试步骤,让 WinBDG 再次使用 Go (g) 命令继续执行进程 notepad.exe。

图片

14.Windbg命令列表

vercommand

显示调试器命令行

vertarget

显示目标计算机版本

!cpuid

显示CPU相关信息

.sympath

设置符号路径

!lmi

显示模块相关的详细信息

|

进程状态

~

线程状态

lm

列出已加载模块

?

评估表达式

x

检查符号

bp

设置断点

bl

启用断点

g

执行

r

寄存器

dp

显示内存

dps

显示已知符号的内存引用

k

显示堆栈回溯

ln

列出最近的符号

da

以ASCII字符显示内存

dc

以DWORD和ASCII显示内存

dt

显示类型

dS

显示UNICODE_STRING 结构字符串

du

以宽字符显示内存

db

ASCII字符显示内存

dw

Word类型显示内存

dd

DWORD类型显示内存

dq

四字格式显示内存

u

反汇编

ub

向后反汇编

uf

反汇编函数

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论