Skip to main content

调试与逆向

在这小节,我们将学习简单的调试以及逆向技能,这对于恶意软件开发领域有这些帮助:理解当下的恶意软件的技术、功能、原理分析恶意软件并且改进,开发漏洞利用以及 Shellcode理解安全产品的原理以规避检测等

我们将主要使用 WinDBG 作为动态调试工具,IDA 作为逆向工具。两者相辅相成,动态与静态相结合。WinDBG 可以在微软商店中获取:

image.png

IDA 可以从 https://hex-rays.com/ida-free/ 下载,拥有专业版自然更好。


WinDBG

调试器是插入在目标应用程序和 CPU 之间的计算机程序,充当类似代理的角色。使用调试器使我们能够查看应用程序的内存和执行流程并与之交互。接下来的课程内容中,我们将与用户模式交互。

CPU以二进制级别处理代码,这对人类来说很难阅读和理解,而汇编语言引入了二进制内容和编程语言之间的一对一映射。尽管汇编语言应该是人类可读的,但它仍然是一种低级语言,并且需要时间来掌握。 操作码是由 CPU 解释为特定指令的二进制序列,这在调试器中显示为十六进制值以及汇编语言的翻译。

接下来,利用 WinDbg,我们将学习如何使用断点来单步执行和控制应用程序的流程。


自定义界面

为了能有一个舒适的调试环境,我们可以对 WinDBG 的界面进行自定义,这样当我们调试应用程序的时候,可以在用户界面中查看到所需的信息,例如内存汇编代码断点信息等。

在导航栏中选中 View,我们可以添加多个窗口,这里面对我们调试过程会十分有帮助的有 WinDBG 命令 (Command)寄存器 (Registers)内存 (Memory)栈 (Stack)汇编代码 (Disassembly)

image.png点中之后,对应的窗口会浮现出来,可以使其与 WinDBG 主窗口相互独立,也可以将其嵌入至 WinDBG 主窗口,我会更推荐将它们嵌入至主窗口。不过因为主窗口空间有限,如果添加的窗口数量过多,也会影响我们对信息的提取效率,诸如模块 (Module)、断点 (Breakpoints) 等窗口我们不一定要添加到主页面,而是通过 WinDBG 的命令来查看相关信息。

下图是个人偏好的一个界面布局,你们可能注意到我添加了 2 个内存窗口,因为我们还想查看 RSP 的状态。

image.png

当我们想要调试一个进程或者应用的时候,可以选择使用 WinDBG 启动目标程序,或者附加到正常运行的进程中。

image.png

附加上之后,进程会自动被设置一个软件断点,对应的汇编指令为 int 3

image.png

我们可以执行 WinDBG 命令 g 来继续执行。

image.png

在进入对 WinDBG 基本命令的学习之前,我们还需要知道调试符号。符号 (Symbol) 文件允许 WinDbg 使用名称而不是地址来引用内部函数结构体全局变量。例如,我们想给 kernelbase.dll 中的 CreateProcessA 函数设置软件断点,我们不需要先得到载入的 kernelbase.dll 中的 CreateProcessA 函数的地址,使用名称即可,命令为 bp kernelbase!CreateProcessA。在后面,我们也可以用符号来查看一些结构体。

符号文件以 .pdb 为拓展名,当应用程序的 pdb 与 PE 文件在同一目录下,符号文件会被自动载入从而识别应用程序中的符号。

image.png

这样,我们可以使用 process_calc!Main 来定位到 process_calc.exe 程序中的 Main 函数。

image.png



基本命令

有了对 WinDBG 的初始了解以及配置了最适合自己的界面布局后,我们来学习 WinDBG 的基本常用命令。WinDBG 内置的命令数量十分庞大,并且考虑到该小节的内容是作为恶意软件开发的前置知识,因此我们不会过于深入。接下来,我们依次介绍以下这些常用命令。


反汇编

在 WinDBG 中,使用命令 u <内存地址> 来检视给定内存地址的汇编代码。例如,我们可以查看 user32.dll 中的 MessageBoxA 函数的汇编实现:

0:000> u user32!messageboxa
USER32!MessageBoxA:
00007ffb`7e9a7a90 4883ec38        sub     rsp,38h
00007ffb`7e9a7a94 4533db          xor     r11d,r11d
00007ffb`7e9a7a97 44391dcaf70300  cmp     dword ptr [USER32!gfEMIEnable (00007ffb`7e9e7268)],r11d
00007ffb`7e9a7a9e 742e            je      USER32!MessageBoxA+0x3e (00007ffb`7e9a7ace)
00007ffb`7e9a7aa0 65488b042530000000 mov   rax,qword ptr gs:[30h]
00007ffb`7e9a7aa9 4c8b5048        mov     r10,qword ptr [rax+48h]
00007ffb`7e9a7aad 33c0            xor     eax,eax

对于内存地址,可以是符号、内存地址、寄存器的形式,只要是合法的内存地址。可以通过 l* 来指定显示的行数。

0:000> u 00007ffb`7e9a7a90 l3
USER32!MessageBoxA:
00007ffb`7e9a7a90 4883ec38        sub     rsp,38h
00007ffb`7e9a7a94 4533db          xor     r11d,r11d
00007ffb`7e9a7a97 44391dcaf70300  cmp     dword ptr [USER32!gfEMIEnable (00007ffb`7e9e7268)],r11d


读取内存

我们可以使用命令 d* <内存地址> 来读取给定内存地址,星号可以是不同的数据类型,例如 byte,word,dword 等。我们可以在微软文档 https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/d--da--db--dc--dd--dd--df--dp--dq--du--dw--dw--dyb--dyd--display-memor 中了解更加详细的用法。

 

 

读取结构体


修改和写入内存


搜索内存空间


检视寄存器


计算器


列举模块


调用栈


TEB


内存状态


软件断点


硬件断点


单步执行

t

p

执行至

ph 和 th

pa 和 ta



解析 PE



IDA