65.9K
CodeProject 正在变化。 阅读更多。
Home

高级 .NET 调试:从内存中提取信息

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (15投票s)

2009年8月9日

CPOL

6分钟阅读

viewsIcon

56727

downloadIcon

243

高级 .NET 调试:从内存中提取信息

引言

在实际生活中,任何软件产品都有开发和生产阶段。在开发阶段,我们发现问题并拥有代码,更容易检测和修复,但在 QA(有时)和生产环境中则不然。

作为质量保证工程师,我们的工作不仅仅是发现问题,更重要的是隔离问题,以便尽可能地向开发人员提供最佳信息。在 QA 中,我们需要一个与用户环境相似的环境,有时我们无法使用源代码来隔离问题。

在这个例子中,我们将展示最简单的案例:“如何在没有运行时源代码的情况下查看类中简单函数的输入参数”。为此,我们将使用可执行文件中包含的一个非常简单的函数。我们的主要工具将是用于 Windows 调试的 Windows Debugging 和用于 .NET 调试的 SOS。假设我们没有从开发中获得良好的日志级别

这里的主要挑战是:找到感兴趣的函数以获取输入参数,并直接从进程内存中打印它们。这篇文章有点长,但同一篇文章适用于 ASP.NET 进程或 Windows 进程,我将来会发布更多文章。如果您需要帮助,请发送电子邮件至 rpally@reversingsoftware.comjrpally@hotmail.com,我很乐意为您提供帮助。要继续阅读本文,请使用附件文件进行调试。

开始吧

http://www.microsoft.com/whdc/devtools/debugging/installx86.mspx 安装并下载 Windows Debugger 工具。

安装完成后,将 .NET 帮助调试文件从 %WINDIR%\Microsoft.NET\Framework\v2.X.XXXXX\SOS.dll 复制到 %PROGRAMFILES%\Debugging Tools for Windows (x86)。

启动 Windbg

让我们使用 Windbg (文件->打开可执行文件) 启动进程

此时,可执行文件尚未启动,并且 Windbg CMD 提示符将打开。

加载库 SOS.DLL 以加载 .NET 调试帮助程序。.load sos

只需按 F5 即可运行可执行文件。

请注意,Sample.exe 程序正在调试器下运行。

我们希望冻结我们的 sample.exe 进程。为此,只需单击调试器并按 CTRL+BREAK。

让我们探索可执行文件的主线程和当前的堆栈跟踪。为此,我们将使用 ~ (线程) * (所有) e (执行命令) !clrstack (打印给定线程的堆栈)

~*e !clrstack 

我们正在显示所有线程及其堆栈(请注意,我们正在使用 !clrstack ,它包含在 SOS.DLL 文件中)。

输出将是:

有趣!看起来进程正在尝试从用户那里读取一个 string (System.Console.Readline),并且有一个 Main 函数。

现在我们想获取封装类 ConsoleApplication12.Program 的方法,有很多方法可以做到。让我们探索这个方法

!ip2md:IP 指令指针到方法描述,给定一个 IP (本例中为 EIP) 地址,我们想将其转换为方法描述,这是 CLR .NET 的内部结构,它为我们提供了方法名称和 IL 代码

我们得到了

太棒了,我们得到了这个类的 Method Table 的地址,Method Table 是一个内部结构,包含一个类的 Methods 列表。

让我们探索 Method Table

!dumpmt 允许我们获取 Method Table,而 –MD 为我们提供了此 Method Table 中包含的每个 Method 的方法描述。

我们得到了

太棒了,我们有一个 ConsoleApplication12.Program.NumberOfWords 方法。如果我们决定在这里设置一个断点呢?在 Windbg (Windows 调试器)非托管模式下,我们有命令 bp(断点),但我们可以观察到 NumberOfWords 方法尚未 Jitted(该方法尚未运行,我们没有它们的精确入口点),但 SOS 允许我们使用 Method Desc 设置断点

!bpmd –md 0097c011

结果

0:000> !bpmd -md 00973378

MethodDesc = 00973378

Adding pending breakpoints... 

我们可以继续运行我们的应用程序并使用它(按 F5)

并在我的小应用程序中按 ENTER。

请注意,如果按下回车键,我们的程序就会冻结。发生了什么?返回 Windbg

很好。看起来我们的可执行文件在预期的函数处中断,为确保这一点,请运行命令 !clrstack

太棒了!我们的堆栈跟踪显示可执行文件在 NumberOfWords 方法上停止,但是如果我们能显示更详细的堆栈呢?;) 使用带有 –p 标志的 !clrstack

!clrstack –p (-p 表示参数)

SOS 辅助程序有一个有趣的命令 (!do DumpObject),让我们使用它。给定一个 Object 地址的指针,它会为我们打印 object

我们得到了

太棒了,这是我们在 Sample.exe 控制台中输入的 string

使用 !do 命令打印 string 可能会在显示时出现问题,因为它还会打印其他元素,例如名称、方法表等。

我们只对打印 string 本身感兴趣:让我们稍微探索一下这个对象的内存。

单击 Windbg 视图->内存 / 在虚拟:写入 014e9b90(包含对象的内存地址)

很好,内存中有了 string ,我们如何在控制台中打印它呢?(请注意,string 中的每个字符都有一个 00 字节)Unicode string?答案是肯定的。

但是为什么真正的 string (“rene pally from玻利维亚”)没有从 0x014e9b90 地址开始?答案是:因为 0x014e9b90 是 CLASS 的地址,而不是第一个 CHAR 的地址。

第一个字符有字节 00 72 Unicode 表示法,其字符是:r

如图所示,第一个字符从 0x014e9b90c 开始,初始地址是 0x014e9b90。

让我们使用

.printf “%mu”, 0x014e9b90+c 

执行命令

太棒了,我们写入了 string .printf ,它在 Windbg 屏幕上打印一个以 NULL 结尾的 string ,给定一个地址,%mu 用于打印 Unicode 格式的字符数组。

我们解释了为什么我们需要在 string 的初始地址上加上 0xc,它是有逻辑的,因为在 String 结构中,第一个 char 的 OFFSET 显示为 0xc。

String 对象地址在进程下次执行时可能会改变,我们该怎么办?输入地址的对象地址存储在堆栈和一些处理器寄存器中

第二次运行后,对象地址变为 0x0155fe28。我们使用命令 r 显示寄存器,如果我们将 ECX 与 Object 地址匹配,它们是相等的,让我们使用

.printf “%mu”,ecx+c 

设置断点:既然我们已经找到了直接从内存中打印 String 类的方法,我们就可以使用 BP 及其命令

bp eip “.printf \“\\n%mu\”,ecx+c;gc” 

让我们解释一下这个断点。

  • BP = 设置断点
  • EIP = 当前 EIP 地址(当前指令指针),请记住我们在函数 NumberOfWords 的开头停止了函数

BP 在断点停止时支持额外的命令,一旦我们停止在断点处,我们需要打印 ECX+C,一旦我们打印完,我们需要继续 GC,这是一个继续执行的命令。

\” 转义 char 以包含在 “” string

\\n=\n 回车。按 F5 并继续使用 Sample.exe

谢谢!:)

© . All rights reserved.