标注的博客| 安全研究| 渗透测试| APT

首页

开放安全研究:windbg入门

作者 weah 时间 2020-02-28
all

布拉德·安东尼维奇。

在这一系列的博客文章中,我们已经介绍了如何安装、安装WinDBG,并通过附加到进程和设置断点来开始。我们的下一步是实际的调试部分,我们将逐步通过一个程序并查看内存。

实际上,使用调试器的全部原因是在特定操作或函数期间检查进程的状态。几乎每一条被执行的指令都会以某种方式改变程序的状态,这意味着有能力执行一条指令然后检查状态是非常重要的。第一部分是“步进”-执行指令,然后暂停。WinDBG提供了许多不同的步进命令,这取决于您在程序中的位置和您想去的地方。

大多数调试器使用以下术语描述如何浏览程序及其功能:

call call call call ret

这里要注意的是,Step-Into和Step-Over都将执行一条指令,而pause-behavior只在到达调用指令时才发生变化。

call

去吧

g(Go)命令更像是一个断点命令,但它的功能模糊了断点和单步执行命令之间的界限。它用于恢复程序的执行,但与大多数步进命令不同,它并不是真正意义上的逐条指令使用。g将继续程序,直到出现断点或异常。实际上,您可以使用g来执行所有的指令直到一个断点,而使用stepping命令执行指令时不设置断点。但是,要澄清的是,调试器在遇到断点时将暂停,而不管您是使用单步执行命令还是g之类的命令。

g g g g

g很容易使用:

g

当程序运行时,WinDBG将在命令输入框中给您一条消息:

如果您知道要执行的地址,请将其作为参数提供给g:

g

单步走

执行一条指令,然后暂停称为单步执行。这可以通过使用“Step Into”或“Step Over”命令来实现,因为这两个命令在非调用指令上的行为相同。与其在这里同时显示它们,不如让我们单独看一下这些命令。

call

踏入

要使用WinDBG,请使用t(Trace)命令。每个步骤都会显示寄存器的状态和将要执行的指令。在这个例子中,我们将暂停在程序的入口点(记事本!WinMainCRTStartup)并查看要执行的前几个指令(u eip)。第一条指令是调用记事本!__安全初始化cookie函数。让我们看看调试器在使用t时的行为:

t notepad!WinMainCRTStartup u eip call notepad!__security_init_cookie t

在这里我们可以看到我们在记事本内运行!WinMainCRTStartup,然后在通话时我们用t跟踪通话进入记事本!__security_init_cookie函数,在第一条指令上暂停。

notepad!WinMainCRTStartup call t notepad!__security_init_cookie

跨过

WinDBG使用p命令跳过函数调用。这意味着调用和被调用函数中的所有子指令将被执行,程序将暂停当前函数中的下一条指令(例如记事本!WinMainCRTStartup)。让我们看看相同的场景,但这次我们将使用p:

p call notepad!WinMainCRTStartup p

在这里我们可以看到,指令调用后的记事本!__security_init_cookie是push 58h。当我们使用p时,我们会自动执行记事本中的所有内容!__security_init_cookie函数,然后在其后面的推送处暂停。

call notepad!__security_init_cookie push 58h p notepad!__security_init_cookie push

走出去

使用WinDBG跳出可以通过gu(Go Up)命令实现。此命令扫描当前函数以获取ret,然后在执行后暂停。这是一个重要的行为,因为无论出于什么原因,如果函数没有以ret结尾,或者代码路径没有导致ret,那么gu可能会出现意想不到的结果。让我们看看它是什么样子:

gu ret ret gu

我们在记事本上停下来了!WinMainCRTStartup+0x1d,这是对记事本的调用!_小鬼获得创业信息。我们可以看到(u eip L2)调用后的指令是mov dword ptr[ebp-4],0FFFFFFFEh。所以我们将单步(t)进入函数,并在第一条指令处暂停。现在我们使用gu执行子函数中的所有指令和函数调用,然后暂停父函数中的下一条指令,即mov dword ptr[ebp-4],0FFFFFFFEh

notepad!WinMainCRTStartup+0x1d call notepad!_imp__GetStartupInfoA u eip L2 call mov dword ptr [ebp-4],0FFFFFFFEh t gu mov dword ptr [ebp-4],0FFFFFFFEh

执行到返回

gu是很好的,但有时您希望在函数返回之前查看堆栈,在这个场景中,您需要使用tt(Trace to Next Return)或pt。两者都很容易调用:

gu tt pt

这里要记住的重要一点是,tt将在下一次返回时停止,即使它不在当前函数中。例如,考虑下面的伪代码,我们的目标是在func中暂停ret:

tt ret func

在这个例子中,如果pause at call somefunc,然后使用tt,我们将在someotherfunc中的ret处结束pause。

call somefunc tt ret someotherfunc

对于这个场景,一个更好的方法可能是使用pt:使用相同的伪代码,如果我们暂停调用somefunc,然后使用pt,我们将执行somefunc中的所有代码(然后是somefunc),然后暂停在func中的ret。实际上,对于这个例子,我们可以使用p,但这并不能说明问题:)

pt call somefunc pt somefunc someotherfunc ret func p

最终,这取决于作为使用调试器的人,您想要做什么。

现在我们终于可以进入调试的最重要部分:检查内存。WinDBG为此提供d(显示内存)命令。以最简单的形式运行它:

d

但这或多或少是没用的。第一次单独运行d将输出eip指向的内存。这是无用的,因为eip应该指向一个代码段,为了理解这一点,您需要使用u(Unassemble)命令。所以一个更好的启动命令是:

d eip eip u

这将显示堆栈上的值。对于d,WinDBG将使用最后执行的d命令指定的格式显示数据。如果这是您第一次运行d,则它没有存储先前的命令,因此WinDBG将为您提供db(Display Byte)命令的输出。

d d d db

显示字节

db将以字节为单位输出数据,并提供相应的ASCII值:

db

显示文字

单词或2字节值可以用dw(显示单词)显示。或者,可以使用dW显示单词和ASCII值:

dw dW

显示单词

我最喜欢的内存查看命令是dd(Display DWORDs)。双字是一个双字,所以是4字节。dd将只显示DWORDS,而dc将显示DWORDS和ASCII值:

dd dd dc

显示四字

要在WinDBG中显示四字(4字/8字节),请使用dq:

dq

显示位

你甚至可以用dyb显示二进制:

dyb

显示字符串

字符串用da显示,实际上WinDBG将把所有内容都打印为ASCII,直到它达到一个空字节。所以在这里,即使esp不是字符串,它也会将所有内容都视为字符串,直到它达到空值。为了进一步说明这一点,我在esp和db esp L5中打印出了5个字节:

da esp esp db esp L5

寻址

到目前为止,我们只是通过使用esp作为内存检查命令的参数来查看esp指向的内存,但是有许多不同的方法可以在启动时引用内存。

esp esp

寄存器-如我们所见,您可以使用任何寄存器,WinDBG将使用该寄存器中的地址作为内存地址:

内存地址-您也可以通过提供内存地址来使用它本身:

偏移量-也可以使用数学表达式对寄存器或内存地址使用偏移量:

这些表达式可以在任何可以使用地址的地方使用。以下是WinDBG中的外观:

WinDBG将输出问号(?)用于无效/空闲内存。

?

指针

有时堆栈上的值只是指向另一个位置的指针。如果你想看看这个值,你需要做两个查找。例如,假设我们知道值ebp+4是指向要读取的某些程序集代码的指针。要查看该程序集,需要两个命令。第一个命令显示ebp+4的内存地址:

ebp+4 ebp+4

然后,第二个命令要求我们手动复制该地址的值,然后将其作为参数粘贴到u命令中,以便我们可以查看该程序集:

u

这很好,但是poi()函数有一个更简单的方法。使用poi()我们只提供ebp+4作为参数,它将自动获取该地址的值并使用它,而不是仅使用ebp+4的值:

poi() poi() ebp+4 ebp+4

限制输出

默认情况下,WinDBG将输出一组数据,但是我们可以限制如何使用L(大小范围说明符)属性输出该数据。我将处理大多数命令,只需要在结尾附加一个值:

L L

用L指定的数字是与执行的命令相关的大小。例如,对于db,L表示要打印的字节数,而对于dd,L表示要打印的dword数。

L db L dd L

这真的是让你开始检查内存-我知道,三篇博客文章建立了这个功能,它只是这个小部分?是的-还有一些内存检查命令,但是要开始,d是核心命令。查看下面的提示了解更多信息。

d

现在您已经脱离实际,让我们看看一些方便的技巧和技巧,它们可以使您的调试体验更好。

键盘快捷键

在调试过程中,您可能会启动和停止应用程序数百次,因此从长远来看,任何一个小的捷径都可以为您解决大量的时间问题。键盘快捷键很大,下面是我使用最多的四个:

g

转换格式

如果你还没有弄清楚,WinDBG默认打印十六进制数字。这意味着12和十进制12不一样。一个快速提示是.formats命令。使用它很简单:

12 .formats

其中值是要转换的内容。因此,格式将接受您提供的任何内容,并以多种格式输出:

.formats

现在我们知道12实际上是18:)。但是,也可以使用0n说明符提供十进制值:

12 0n

数学

有时你可能需要计算一个偏移量或只是做一些基本的数学运算。WinDBG将使用计算表达式?命令:

?

这些表达式可以是简单的,也可以是复杂的,可以包含WinDBG使用的所有标准地址:

…当你有WinDBG时谁需要calc.exe!

calc.exe

为了让生活更轻松,人们为WinDBG创建了许多扩展。这些是很好的小工具,可以在调试器中使用以提供功能。有些甚至是微软制造的。有用的是!堆!地址!dh,还有!佩布。我将在另一篇博客文章中讨论这些和更多内容-请继续关注!

!heap !address !dh !peb

如果你不喜欢,这里有一些非常好的WinDBG命令参考、备忘单和教程。hh,这里有几个好的:

.hh

还有什么窍门吗?在下面的评论中分享!