Introduction
de4dot is a wonderful tool for deobfuscating known and unknown .NET protections. Dealing with known and supported protections is easy - drag&drop executable on de4dot and it will create deobfuscated assembly. Removing unknown protections is a little bit harder and requires supplying correct command-line parameters to de4dot.
In this article I'll show how de4dot can be used to deobfuscate strings in almost any .NET assembly, some undocumented features of de4dot, and a few bugs in de4dot too. ๐
Basics of string encryption/decryption
To show how string encryption works, let's start with a simple C# program.
Hint - you don't have to copy-paste all the code, sample files are available in the archive at the bottom of the post.
using System;
namespace Demo
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello, world!");
Console.Write("Please enter password: ");
string pass = Console.ReadLine();
if (pass == "secret")
{
Console.WriteLine("Password accepted");
}
else
{
Console.WriteLine("Bad password");
}
Console.ReadKey();
}
}
}
As you can see, all the strings are in the clear view and it's obvious that correct password is "secret". No protection whatsoever.
To slow down reversing efforts, obfuscators offer to encrypt user strings. They locate all strings in assembly, encode them somehow, and replace access to string with call to obfuscator code. To keep the code simple, I'll just encode all strings using Base64 - however, the same approach would work for almost any string encryption method (xor, Rijndael, or anything else).
New code looks like this:
using System;
using System.Text;
namespace Demo
{
static class Obfuscator
{
static string[] hiddenStrings = {
"SGVsbG8sIHdvcmxkIQ==",
"UGxlYXNlIGVudGVyIHBhc3N3b3JkOiA=",
"c2VjcmV0",
"UGFzc3dvcmQgYWNjZXB0ZWQ=",
"QmFkIHBhc3N3b3Jk"
};
public static string DecryptString(int index)
{
return Encoding.UTF8.GetString(Convert.FromBase64String(hiddenStrings[index]));
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Obfuscator.DecryptString(0));
Console.Write(Obfuscator.DecryptString(1));
string pass = Console.ReadLine();
if (pass == Obfuscator.DecryptString(2))
{
Console.WriteLine(Obfuscator.DecryptString(3));
}
else
{
Console.WriteLine(Obfuscator.DecryptString(4));
}
Console.ReadKey();
}
}
}
No more obvious strings! ๐ Let's see how we can decrypt them using de4dot..
de4dot and basic string decryption
If you check de4dot help, you'll see that you need to supply 2 command line options for a string decryption to work. First, you need to choose a string decrypter type using --strtyp option: static, delegate, emulate. Then you need to tell de4dot which is string decrypter method using --strtok option.
Let's find which method is responsible for string decryption. Good decompiler can show method tokens, for example, SAE shows it as a tooltip, when you hover over the method:
So, for our very basic protection we could run de4dot with commandline:
de4dot hello-2.exe --strtyp delegate --strtok 0x06000001
and the result will look like this:
So far, so good!
Advanced string decryption
But what happens if obfuscator uses more than one string decryptor? Let's change the code a little bit:
using System;
using System.Text;
namespace Demo
{
static class Obfuscator
{
static string[] hiddenStrings = {
"SGVsbG8sIHdvcmxkIQ==",
"UGxlYXNlIGVudGVyIHBhc3N3b3JkOiA=",
"c2VjcmV0",
"UGFzc3dvcmQgYWNjZXB0ZWQ=",
"QmFkIHBhc3N3b3Jk"
};
public static string DecryptStringA(int key)
{
return Encoding.UTF8.GetString(Convert.FromBase64String(hiddenStrings[key ^ 0x666]));
}
public static string DecryptStringB(int key)
{
return Encoding.UTF8.GetString(Convert.FromBase64String(hiddenStrings[key + 0xDEAD]));
}
public static string DecryptStringC(int key)
{
return Encoding.UTF8.GetString(Convert.FromBase64String(hiddenStrings[key - 0xC0DE]));
}
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine(Obfuscator.DecryptStringC(0xC0DE));
Console.Write(Obfuscator.DecryptStringA(0x667));
string pass = Console.ReadLine();
if (pass == Obfuscator.DecryptStringB(-57003))
{
Console.WriteLine(Obfuscator.DecryptStringA(0x665));
}
else
{
Console.WriteLine(Obfuscator.DecryptStringC(0xC0E2));
}
Console.ReadKey();
}
}
}
Now there are 3 methods that decrypt strings, and each of them is slightly different. Of course you could run de4dot 3 times, but it's just a pain-in-the-butt.
de4dot help doesn't tell you this, but it is possible to to specify more than one string decryption method:
de4dot hello-3.exe --strtyp delegate --strtok 0x06000001 --strtok 0x06000002 --strtok 0x06000003
This is a little bit awkward, but works.
But what to do if there are hundreds of methods - specifying each of them by token is time-consuming and command-line might get too long.
de4dot has solution for that too. Hidden in the middle of help file, is this little gem:
--strtok METHOD String decrypter method token or [type::][name][(args,...)]
It turns out that you can tell de4dot the name of obfuscator class/methods responsible for string decryption and it will resolve tokens automatically. ๐
So, the following command-lines will work:
-
de4dot hello-2.exe --strtyp delegate --strtok "Demo.Obfuscator::DecryptString"
This tells de4dot that string decryptor is method with full name Demo.Obfuscator::DecryptString.
-
de4dot hello-3.exe --strtyp delegate --strtok "Demo.Obfuscator::"
This tells de4dot to check all methods in class Demo.Obfuscator and pick the ones which look like string decryptors.
-
de4dot hello-3.exe --strtyp delegate --strtok "Demo.Obfuscator::(System.Int32)"
This tells de4dot which class to look at and what kind of parameters string decryption method has.
-
de4dot hello-3.exe --strtyp delegate --strtok "::DecryptStringA"
This tells de4dot to look at all classes for a method called DecryptStringA and use that as string decryptor.
If you want to know more about possible options and the combinations, I suggest that you look at de4dot source code, file de4dot.code\ObfuscatedFile.cs, lines 454-511.
You said something about bugs?
Ok, ok, there are few issues here.. It still works 99% of the time, so no complaining!
First bug is in the checking of string decryptor parameters. When I said that --strtok "Demo.Obfuscator::(System.Int32)" will select only methods that take Int32 as a parameter, I lied. ๐
Look at the source:
for (int i = 0; i < argsStrings.Length; i++) {
if (argsStrings[i] != sig.Params[i].FullName)
continue;
}
The continue instruction here does nothing, it just goes on to check next parameter. I guess 0xd4d wanted to stop evaluating this method and go to next one, but got it wrong.
Second bug is in selecting string decryptors - you cannot select a method which takes no parameters. Sample code showing this type of protection is in hello-4.cs (see below).
Look again at the source:
if (argsStrings == null) {
if (sig.Params.Count == 0)
continue;
else {
if (argsStrings.Length != sig.Params.Count)
continue;
}
If you don't supply hints about parameters, then methods with 0 parameters will be ignored because of 2nd if. If you do supply hints - then no method with 0 parameters can pass the 3rd if.
Fixing these 2 bugs is left as an exercise to the reader.
Conclusion
In this article explained how to use de4dot to decrypt strings in any .NET assembly, including some lesser known options. Hope this helps you next time you encounter .NET protection that's not directly supported by de4dot. ๐
All sample files and their source code: https://www.mediafire.com/?2kwnf7d7vre4uv8