Visual Studio 的 WPF 反混淆器插件






4.75/5 (4投票s)
一个即时反混淆调用堆栈的 WPF 控件

引言
本文展示了如何创建一个 Visual Studio 2010 插件,该插件在 WPF 控件中显示反混淆的调用堆栈。此窗口将与标准调用堆栈窗口保持同步更新。
背景
我曾被分配一项任务,评估 .NET 程序集代码混淆在保护知识产权方面的适用性,以及其对调试和支持的影响。代码混淆并非没有缺点。它几乎使调试变得不可能,除非能够逆转混淆。如果您调试一个混淆的程序集,您将看不到可读的方法名称,而是加密的名称。我评估的混淆器提供了一个外部工具,可以使用映射文件进行反混淆。您必须手动将文本复制并粘贴到该工具中。
手动执行此操作相当繁琐,因此前段时间我编写了一个 Windbg 扩展,用于直接显示反混淆的调用堆栈,Visual Studio 中的 .NET Windbg 扩展开发。这对我来说已经足够了,但一位不喜欢 Windbg 的同事向我请求了一个 Visual Studio 插件。
这是我第一次尝试创建 Visual Studio 插件。此插件的目的是通过使用映射文件将这些加密名称反混淆为可读的名称。每当调用堆栈更新时,反混淆的调用堆栈也会自动更新。
创建插件
用于创建 Visual Studio 插件的向导实际上可以帮助您完成很多工作。
将创建一个名为 Connect.cs 的文件,该文件实现了接口 IDTExtensibility
。
public class Connect : IDTExtensibility2
{
public Connect()
{
}
public void OnConnection(object application,
ext_ConnectMode connectMode, object addInInst, ref Array custom)
{
_applicationObject = (DTE2)application;
_addInInstance = (AddIn)addInInst;
}
public void OnDisconnection(ext_DisconnectMode disconnectMode, ref Array custom)
{
}
public void OnAddInsUpdate(ref Array custom)
{
}
public void OnStartupComplete(ref Array custom)
{
}
public void OnBeginShutdown(ref Array custom)
{
}
private DTE2 _applicationObject;
private AddIn _addInInstance;
}
为了与 Visual Studio 交互,有一个对象 _application
,可用于遍历调试的进程、线程、调用堆栈等。它还包含您可以注册自己的处理程序的事件。我使用其中一个事件来更新调用堆栈。
您通常做的第一件事是在工具菜单中添加至少一个命令,以便您可以与插件交互。此 GUI 代码应放置在 OnConnection
方法中。如果您选择“是否为您的插件创建命令栏 UI?”选项为是,向导将已为此生成代码。
与 Visual Studio 交互
第二步是实现接口 IDTCommandTarget
。
public class Connect : IDTExtensibility2, IDTCommandTarget
您应该实现 QueryStatus
和 Exec
。
我认为 QueryStatus
是关于告知 GUI 命令的可用性。例如,您可能希望在某些情况或上下文中使其不可用。
public void QueryStatus(string commandName, vsCommandStatusTextWanted neededText,
ref vsCommandStatus status, ref object commandText)
{
if (neededText == vsCommandStatusTextWanted.vsCommandStatusTextWantedNone)
{
if (commandName == "Deobfuscator.Connect.Deobfuscator")
{
status = (vsCommandStatus)vsCommandStatus.vsCommandStatusSupported |
vsCommandStatus.vsCommandStatusEnabled;
return;
}
}
}
Exec
是我们应该放置命令代码的回调。
public void Exec(string commandName, vsCommandExecOption executeOption,
ref object varIn, ref object varOut, ref bool handled)
{
handled = false;
if (executeOption == vsCommandExecOption.vsCommandExecOptionDoDefault)
{
if (commandName == "Deobfuscator.Connect.Deobfuscator")
{
handled = true;
// We will insert code here
}
}
}
在向您展示代码之前,让我先解释一下我的第一次尝试以及我遇到的问题。
第一次尝试 - 拦截命令
我不知道是否有简单的方法可以过滤正常的调用堆栈窗口并显示反混淆的调用堆栈。我选择了在一个新的独立窗口中显示反混淆的调用堆栈的简单解决方案。
在我的第一次尝试中,我试图拦截对“单步进入”和“单步跳过”命令的调用。在 _applicationObject.Events.DebuggerEvents
中,您可以为所有调试命令注册 BeforeExecute
和 AfterExecute
事件。因此,一个直接的方法是为这些命令的 AfterExecute
事件注册一个事件处理程序。不幸的是,它不起作用。当我收到 AfterExecute
事件时,调用堆栈并不总是可用。_application
对象中的许多子对象都为 null
。奇怪的是,它有时可用,有时不可用。大多数情况下不可用。当我调试它时,子对象几乎总是可用的。
var dte2 = _application;
if (dte2.Debugger.DebuggedProcesses == null)
return null;
我不明白为什么如果我确实正在调试一个程序,这些对象却为 null
。我做了一个变通方法。在 AfterExecute
事件之后,我告诉一个后台工作线程在对象再次可用时更新调用堆栈。有效,但这是一个非常非常丑陋的解决方案。一个有一点自尊的程序员不会选择这样的解决方案。
第二次尝试 - OnEnterBreakMode
寻找更好的解决方案,我最终找到了一个更好的解决方案。有一个名为 OnEnterBreakMode
的回调,我应该从一开始就使用它。
_debugEvents = _applicationObject.Events.DebuggerEvents;
_debugEvents.OnEnterBreakMode += DebugEvents_OnEnterBreakMode;
然后,无论命令是什么。无论调试器因何原因中断,命中断点或完成单步跳过/进入命令,都会触发该事件。最重要的是,当此事件到达时,调用堆栈始终可用。在此回调中,我只是告诉窗口刷新其调用堆栈。
void DebugEvents_OnEnterBreakMode(dbgEventReason Reason,
ref dbgExecutionAction ExecutionAction)
{
_wpfToolWindow.RefreshCallStack();
}
反混淆调用堆栈
输出调用堆栈是直接的。_application.Debugger.CurrentThread
指向调用堆栈窗口中当前活动的线程。然后,只是循环遍历堆栈帧的问题。
var thread = _application.Debugger.CurrentThread;
foreach (EnvDTE.StackFrame sf in thread.StackFrames)
{
var sb = new StringBuilder();
var filename = System.IO.Path.GetFileName(sf.Module);
sb.Append(filename);
sb.Append('!');
sb.Append(sf.FunctionName);
sb.Append('(');
// Optionally loop over the arguments
sb.Append(')');
// Deobfuscate sb.ToString()
// and add the result to the Callstack ListBox
}
收尾
总共,我使用了 3 个事件
OnEnterBreakMode
- 用于更新调用堆栈OnEnterRunMode
- 用于更新显示活动线程的组合框OnContextChanged
- 用于始终显示与标准调用堆栈窗口中相同的线程
_debugEvents = _applicationObject.Events.DebuggerEvents;
_debugEvents.OnEnterBreakMode += DebugEvents_OnEnterBreakMode;
_debugEvents.OnEnterRunMode += DebugEvents_OnEnterRunMode;
_debugEvents.OnContextChanged += DebugEvents_OnContextChanged;
在 Exec
方法中,我创建 WPF 窗口,并将 _application
对象的引用传递给 WPF 控件。我还注册了事件回调。当回调执行时,它们会调用 WPF 控件并告诉它更新自身。通过使用 _application
对象,WPF 控件可以遍历当前调试的线程并提取堆栈帧。
部署
手动安装可以通过将文件复制到 C:\Users\[您的用户登录名]\Documents\Visual Studio 2010\Addins 来完成。
我选择制作一个小型安装程序。首选的方法是使用 wsix 或 msi 制作一个不错的安装程序,但我在这里走了捷径,使用了 Visual Studio 内容安装程序。使用 Visual 内容安装程序,您只需要创建一个 .vscontent 文件。
<VSContent xmlns="http://schemas.microsoft.com/developer/vscontent/2005">
<Content>
<FileName>Deobfuscator.Addin</FileName>
<FileName>Deobfuscator.dll</FileName>
<FileName>StackTraceDeobfuscator.dll</FileName>
<DisplayName>.Net Reactor Deobfuscator</DisplayName>
<Description>Deobfuscates callstack on the fly</Description>
<FileContentType>Addin</FileContentType>
<ContentVersion>2.0</ContentVersion>
</Content>
</VSContent>
下一步是将所有必要的文件放入一个 zip 存档中,并将其扩展名设置为 .vsi。
要安装它,只需双击该文件。
您可能需要手动激活插件。在 Visual Studio 的“工具”菜单中,打开“插件管理器”,然后激活它。
使用插件
这个特定的插件只是将调用堆栈转换为大写字母,因为我不想重新分发商业 DLL。
在我的评估中,我实际使用了 .NET reactor。如果您有兴趣使用他们的反混淆器二进制文件而不是我的虚拟库,只需将 StackTraceDeobfuscator.dll 替换为他们的版本(您可以在安装目录的 SDK 文件夹下找到),然后将命名空间限定符更新为 eziriz
而不是 StackTraceDeobfuscator
并重新构建。如果您使用其他混淆器库,此代码应该很容易适应。
加载程序后,执行命令“Deobfuscator
”。这将创建反混淆器调用堆栈窗口。将其停靠在您想要的位置。下一步是读取映射文件。之后,窗口应与标准调用堆栈窗口一起自动更新。
如果您在开始调试之前启动插件,窗口可能会隐藏,在这种情况下,再次启动它就会可见。
待办事项
运行“单步跳过”和“单步进入”命令会自动将焦点置于普通调用堆栈窗口。如果焦点能停留在反混淆窗口上会很好。
我偷工减料了。OnConnection
回调已实现,但 OnDisconnection
未实现。这是我的第一个 Visual Studio 插件。欢迎任何有助于改进的评论和建议。此插件仅适用于 Visual Studio 2010,不幸的是我找不到任何方法阻止安装程序将其添加到 Visual Studio 2008。因此,如果您的计算机上同时安装了 2008 和 2010,它将安装两次。
关注点
我前段时间写了一个 Windbg 扩展,Visual Studio 中的 .NET Windbg 扩展开发,它也实现了反混淆器。
Visual Studio 2010 引入了 基于 WPF 的 GUI,它简化了在 GUI 中直接使用 WPF 控件,但它仍然需要一些调整,在你的插件中创建一个基于 WPF 的工具窗口。
历史
- 2011 年 6 月 2 日:首次发布