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

在 Visual Studio 中开发 .NET Windbg 扩展

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (5投票s)

2011 年 4 月 27 日

Ms-PL

6分钟阅读

viewsIcon

34192

downloadIcon

511

在 Visual Studio 中开发 Windbg 扩展并调用 .NET 库

Visual Studio + WinDbg

引言

本文将介绍如何使用 C++/CLI 技术在 Visual Studio 中开发 Windbg 扩展,并利用 .NET 平台。

背景

我曾接到一项任务,评估 .NET 程序集代码混淆对于知识产权保护的适用性,以及它对调试和支持的后果。

混淆并非没有缺点。它几乎不可能进行调试,除非能够逆转混淆。

我评估的混淆器提供了一个外部工具,可以通过映射文件进行反混淆。你需要手动将文本复制粘贴到该工具中。

Deobfuscator tool

尽管可以进行反混淆,但这样做仍然非常繁琐。

为了使完全混淆成为一种可行的方法,我们需要通过 Visual Studio 和 Windbg 的插件将其集成到构建环境中。幸运的是,Obfuscator 公司提供了一个公开的 .NET 程序集,他们鼓励我们使用。

我的第一个方法是使用 Windows Driver Kit (WDK) 构建一个普通的 Windbg 扩展,并使其使用在 Visual Studio 中构建的 C++/CLI DLL。这个 C++/CLI DLL 又会使用 .NET 程序集。事实证明,这个解决方案要简单得多。

祝您阅读愉快!

放弃 Windows Driver Kit

阅读 Windbg 附带的手册。

它清楚地说明了您应该使用 Windows Driver Kit。

WinDbg manual

下面是构建环境的截图。

这是一个基于命令行的构建环境。

Wdk checked build

在享受了 Visual Studio 的便利之后,回到基于命令行的环境确实不那么有趣了。这不禁让我想起了 UN*X 中简陋的开发工具。

迁移到 Visual Studio

对于我们 Visual Studio 的爱好者来说,还有一线希望。我偶然发现了这篇文章 Developing WinDbg Extension DLLs。这是一篇关于如何使用 Visual Studio 开发 Windbg 扩展的分步指南。感谢主!!

利用 C++/CLI

我的贡献是向您展示如何将 .NET 程序集集成到您的 Windbg 扩展中。成功的道路充满了反复试验的开发、重新编译和大量的崩溃。

部分成功

前面提到的文章只展示了如何使用 C API 在 Visual Studio 中使用原生 C++ 构建 Windbg 扩展。

我实现了那个功能。然后我将项目更改为托管的 C++/CLI 项目。扩展仍然有效。这意味着可以使用 .NET 程序集。

然后,我切换到了 windbg 的 C++ API。这不起作用。加载 DLL 时崩溃了。由于我的关注点是反混淆器而不是故障排除,所以我回退到了 C API。

实现

添加对 .NET 程序集的引用

要添加对 .NET 库的引用,您有两种选择

右键单击项目,然后选择“引用”以调出此窗口。

Add reference via GUI

另一种方法是在您的 C++ 文件中添加以下行。

#using "StackTraceDeobfuscator.dll"

调用托管代码

现在可以从 C++/CLI 调用 .NET 程序集了。

StackTraceDeobfuscator^ decoder = gcnew StackTraceDeobfuscator(filename);
String^ s = decoder->DeobfuscateText(obfuscatedText);

字符串转换

托管对象,如 string,不能随意传递。托管 string 是引用。垃圾收集器会不时进行内存压缩,这意味着底层内存可能会移动。另一个限制是,非托管代码不能使用托管类型。幸运的是,有一个特殊的数据类型,称为 msclr::auto_gcroot,可以解决这个问题。有关更多信息,请阅读 Mixing Native and Managed Types in C++

#include <msclr\auto_gcroot.h>
 
struct NativeContainer
{
	msclr::auto_gcroot<String^> obj;
};

NativeContainer container;

void foo(const char* text)
{
    container.obj = gcnew String(text);
}

我使用这种结构来在调用之间保存映射文件的文件名。

为了避免在使用 char* string 时发生分配,请尝试使用 std::string。该类型将自动为您处理释放和重新分配。

std::string str = "abc";
str += "def";
char* backAgain = str->c_str();

您可能还需要将托管 string 转换为原生 string 并将其传递给原生 C/C++ 函数。

void OutCliString(String^ clistr)
{
	IntPtr p = Marshal::StringToHGlobalAnsi(clistr);
	const char* linkStr = static_cast<char* />(p.ToPointer());
	dprintf(linkStr);
	Marshal::FreeHGlobal(p);
}

我必须使用它来调用 C API 函数 dprintf,以便将文本输出到 Windbg

部署

我将 .NET 程序集和 Windbg 扩展复制到 winext,这是 Windbg 的标准扩展文件夹。

然后我测试了我的扩展

0:000> .loadby sos mscorwks
0:000> .load DeobfuscateExt.dll
0:000> !mapfile C:\SecretApp_1.0.0.0.nrmap
0:000> !dclrstack

令我惊讶的是,它在尝试访问 .NET 程序集时崩溃了。所以我插入了一些异常处理,以获取错误消息。这是我看到的

0:000> !dclrstack
Exception
Could not load file or assembly
'StackTraceDeobfuscator, Version=..., Culture=neutral, PublicKeyToken=...'
or one of its dependencies. The system cannot find the file specified.

这怎么可能?它与另一个 DLL 在同一个目录中。

在多次尝试使其正常工作失败后,我几乎将程序集作为嵌入式资源包含,以便通过 LoadAssembly 函数手动加载。

来自 sysinternals 的 Process Monitor 程序拯救了我。它可以记录注册表访问、库加载、程序集绑定等。在日志中,我可以看到 Windbg 在 GAC 中查找,然后在安装文件夹中查找,但从未在 winext 扩展文件夹中查找。

当我将 .NET 库复制到安装文件夹时,它奏效了。DLL 应该被复制到 winext 文件夹。由于我不喜欢丑陋的解决方法,我继续进行调查。在 Process Monitor 中,我看到程序集加载器还在查找 Windbg.config 文件,但找不到。然后我明白了。 .NET 程序集加载器现在将 Windbg 视为一个 .NET 应用程序。

在安装文件夹中添加了一个 windbg.config 文件,告诉 windbgwinext 文件夹中查找 .NET 扩展。它终于奏效了。

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <runtime>
    <assemblyBinding
          xmlns="urn:schemas-microsoft-com:asm.v1">
      <probing privatePath="winext" />
    </assemblyBinding>
  </runtime>
</configuration>

最终结果

最终结果在 Windbg 中的样子是这样的

0:000> g
// Load in the SOS .Net extension
0:000> .loadby sos mscorwks

// Display the .Net callstack
0:000> !ClrStack
OS Thread Id: 0x748 (0)
ESP       EIP     
003bed14 770b22a1 [NDirectMethodFrameStandalone: 
003bed14] EP8d6KKZNOc1stcvCF.rW7Nf5orPqjZvnA6qV.VeInoLCS1()
003bed24 0029390b EP8d6KKZNOc1stcvCF.rW7Nf5orPqjZvnA6qV.glsAhkOLS(System.String)
003bed30 002938e9 EP8d6KKZNOc1stcvCF.rW7Nf5orPqjZvnA6qV.RqruoQ7Wy(System.String)
003bed38 002938c9 EP8d6KKZNOc1stcvCF.rW7Nf5orPqjZvnA6qV.y870B2Qwn(System.String)
003bed40 002938a9 EP8d6KKZNOc1stcvCF.rW7Nf5orPqjZvnA6qV.MshWmjm77(System.String)
003bed48 00293889 EP8d6KKZNOc1stcvCF.rW7Nf5orPqjZvnA6qV.EQqEmimFN(System.String)
003bed50 00293869 EP8d6KKZNOc1stcvCF.rW7Nf5orPqjZvnA6qV.lTK9lM3pf(System.String)
003bed58 00293849 EP8d6KKZNOc1stcvCF.rW7Nf5orPqjZvnA6qV.CRBliDoRv(System.String)
003bed60 00293829 EP8d6KKZNOc1stcvCF.rW7Nf5orPqjZvnA6qV.rEXh0q6dg(System.String)
003bed68 0029009d yIRVo677UilAvTI0XH.LoYYaNO29PLFbE32gm.ayXxZy7mO(System.String[])
003bef94 6f0a1b6c [GCFrame: 003bef94] 

// Load my deobfuscator extension
0:000> .load DeobfuscateExt.dll
0:000> !mapfile C:\SecretApp_1.0.0.0.nrmap
0:000> !dclrstack

OS Thread Id: 0x748 (0)
ESP       EIP     
003bed14 770b22a1 [NDirectMethodFrameStandalone: 003bed14] 
SecretApp.NestingCalls.DebugBreak()
003bed24 0029390b SecretApp.NestingCalls.Ti(System.String)
003bed30 002938e9 SecretApp.NestingCalls.La(System.String)
003bed38 002938c9 SecretApp.NestingCalls.So(System.String)
003bed40 002938a9 SecretApp.NestingCalls.Fa(System.String)
003bed48 00293889 SecretApp.NestingCalls.Mi(System.String)
003bed50 00293869 SecretApp.NestingCalls.Re(System.String)
003bed58 00293849 SecretApp.NestingCalls.Do(System.String)
003bed60 00293829 SecretApp.NestingCalls.Execute(System.String)
003bed68 0029009d SecretApp.Program.Main(System.String[])
003bef94 6f0a1b6c [GCFrame: 003bef94]

我的扩展充当输入过滤器。它被编写用于反混淆 Sos 扩展的 ClrStack 命令的输出。

// Other possibilities

0:000> !dclrstack -a                   	// Same as !ClrStack -a 
0:000> !deobfuscate !ClrStack -a       	// The general deobfuscate function 
					// executes "!ClrStack -a"
0:000> !deobfuscate <cmd> <cmd> <cmd>

源代码

实现最初是针对特定的混淆器完成的。由于我不确定将其 DLL 包含在此项目中的适当性,因此我将其删除了。与原始实现唯一的变化是引用 StackTraceDeobfuscator.dll,它已被一个简单的虚拟 .NET DLL 取代,该 DLL 只会将输入转换为大写。windbg 命令 mapfile 接受任何现有文件。命令 dclrstackdeobfuscate 只会将输入转换为大写。

关注点

我没有成功让 windbg 的 C++ API 工作。C++ API 是一组宏,它会被展开为调用 C++ 方法的 C 函数。C 函数被正确调用,到 C++ 方法的转换也正确。但是对基类构造函数的调用会导致崩溃。我分析了一些崩溃。对我来说,这似乎是 __cdecl__stdcall 之间的调用约定冲突。我还没有找到一种方法使其正常工作。在该领域任何贡献都将非常感激。

历史

  • 2011 年 4 月 26 日:初始帖子
© . All rights reserved.