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.
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.
Below, you see a screenshot of the build environment.
It is a command line based build environment.
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.
Another approach is just to add the following line in your C++ file.
#using "StackTraceDeobfuscator.dll"
Calling Managed Code
Now it is possible to call the .NET Assembly from C++/CLI.
StackTraceDeobfuscator^ decoder = gcnew StackTraceDeobfuscator(filename);
String^ s = decoder->DeobfuscateText(obfuscatedText);
String Conversions
Managed objects, such as string
s, cannot be passed around freely. Managed string
s 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++.
#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* string
s, try to use the std::string
. That type will automatically handle deallocation and reallocation for you.
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.
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.
="1.0"="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