Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++/CLI

.NET Windbg Extension Development in Visual Studio

4.67/5 (5 votes)
27 Apr 2011Ms-PL6 min read 33.6K   467  
Develop Windbg extensions in Visual Studio and call .NET libraries
Visual Studio + WinDbg

Introduction

This article will show you how to develop Windbg extensions in Visual Studio, and to leverage the .NET platform through the C++/CLI technology.

Background

I was once assigned the task to evaluate the applicability of code obfuscation of .NET assemblies with respect to protection of intellectual property, but also its consequences for debugging and support.

Obfuscation is not without drawbacks. It makes debugging almost impossible, unless it is possible to reverse the obfuscation.

The Obfuscator I evaluated provided an external tool for deobfuscation with the use of map files. You had to manually copy and paste the text into this tool.

Deobfuscator tool

Although deobfuscation was possible, it was still very tedious to do so.

In order to make full obfuscation a viable approach, we needed it to be integrated in the build environment by using plugins for both Visual Studio and Windbg. Luckily, the Obfuscator company, provided a public .NET Assembly, which they encouraged you to use.

My first approach was to build a normal Windbg Extension with the Windows Driver Kit (WDK), and make it use a C++/CLI DLL built in Visual Studio. This C++/CLI DLL would in turn, use the .NET Assembly. The solution proved to be much simpler.

Enjoy the reading!

Abandon the Windows Driver Kit

Read the manual that comes with Windbg.

It clearly states that you should use the Windows Driver Kit.

WinDbg manual

Below, you see a screenshot of the build environment.

It is a command line based build environment.

Wdk checked build

After having been spoiled with Visual Studio, it is not really fun to go back to command line based environments. It only reminds me of the spartan development tools they have in UN*X.

Migrate to Visual Studio

There is a salvation for us Visual Studio fans. I came across this article Developing WinDbg Extension DLLs. It is a step by step guide on how to develop Windbg Extensions with Visual Studio. Praise the Lord!!

Leverage the C++/CLI

My contribution is showing you how to integrate .NET assemblies in your Windbg Extensions. The road to success was trial and error development, recompilation, and lots of crashes.

Partial Success

The earlier mentioned article showed only how to build Windbg Extensions in Visual Studio in native C++ using the C API.

I got that working. Then I changed the project into a managed C++/CLI project. The extension still worked. That implied that it would be possible to use .NET assemblies.

Then, I changed to the C++ API of windbg. That did not work. It crashed on loading the DLL. Since my concern was the Deobfuscator not troubleshooting, I reverted back to the C API.

The Implementation

Adding a Reference to the .NET Assembly

To add a reference to .NET library, you have two options:

Right clicking on the project, and selecting "references" to bring up this window.

Add reference via GUI

Another approach is just to add the following line in your C++ file.

C++
#using "StackTraceDeobfuscator.dll"

Calling Managed Code

Now it is possible to call the .NET Assembly from C++/CLI.

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

String Conversions

Managed objects, such as strings, cannot be passed around freely. Managed strings are references. The garbage collector performs memory compactation once in a while, meaning that the underlying memory can move around. Another restriction is that unmanaged code cannot use managed types. Fortunately, there is a special data type, called msclr::auto_gcroot that addresses this problem. For further information, please read Mixing Native and Managed Types in C++.

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);
}

I used this construction in order to save the filename to the mapfile between calls.

In order to avoid allocation when you use char* strings, try to use the std::string. That type will automatically handle deallocation and reallocation for you.

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

You might also need to convert a managed string to a native string and pass it to a native C/C++ function.

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

I had to use it in order to call the C API function dprintf for outputting text to Windbg.

Deployment

I copied the .NET assembly and the Windbg extension to winext, the standard extension folder of Windbg.

Then I tested my extension:

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

To my surprise, it crashed when it tried to access the .NET assembly. So I inserted some exception handling, in order to get the error message. This is what I saw:

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.

How was that possible? It existed in the same directory as the other DLL.

After many failed attempts to get it working, I almost included the assembly as an embedded resource, to be loaded manually through the LoadAssembly function.

To my rescue, was the Process Monitor program from sysinternals. It logs registry accesses, loading of libraries, assembly bindings, etc. In the log, I could see that Windbg looked in the GAC, then it looked in the installation folder, but never in the winext extension folder.

When I copied the .NET library to the installation folder, it worked. DLLs are supposed to be copied to the winext folder. Since I don't like ugly work-arounds, I continued to investigate. In Process Monitor, I saw that the assembly loader, also looked for a Windbg.config file, but none was found. Then it hit me. The .NET assembly loader, sees Windbg as a .NET application now.

After adding a windbg.config file in the installation folder, telling windbg to look in the winext folder for .NET extensions. It finally worked.

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

The Final Result

This is how the final result looked in 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]

My extension works as a filter on its input. It was written to deobfuscate the output from the ClrStack command in the Sos extension.

// 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>

The Source Code

The implementation was originally done for a specific Obfuscator. Since I was unsure of the appropriateness to include their DLL in this project, I removed it. The only thing changed from the original implementation is the reference to StackTraceDeobfuscator.dll, which has been replaced by a simple dummy .NET DLL which just converts its input to uppercase. The windbg command mapfile accepts any existing file. The command dclrstack and deobfuscate just converts its input to uppercase.

Points of Interest

I didn't get the C++ API of windbg to work. The C++ API, is a set of macros, which is expanded to C functions, which calls into the C++ methods. The C function gets called correctly, and the transition to the C++ method too. But the call to a base class constructor makes it crash. I analyzed the crashes a bit. To me, it seems to be some conflict in the calling convention between __cdecl and __stdcall. I still haven't figured out a way to make it work. Any contribution in that area is highly appreciated.

History

  • 26th April, 2011: Initial post

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)