Obfuscating a .NET Program: Worthwhile?

So, you just wrote a great new .NET app and you’re ready to release it into the world. Great! But… how do you ensure your awesome ideas and intellectual property won’t be ripped off? After all, a person with malice in her heart could simply decompile your work and see the code laid bare, right? In this post, I’ll consider what obfuscation can do for us, what .NET obfuscation tools are available, and what the drawbacks are of using them.

How Obfuscation Works

Obfuscation is a way of modifying a program to make it harder to reverse-engineer. Usually that means attempting to defeat a decompiler, or at least make the decompiled output useless to a human reader. Most obfuscation tools available for .NET are commercial (a list can be found here), though there are some free alternatives available. Most obfuscation tools can apply some combination of the following transforms:

  • Renaming. Renames functions, classes, etc.
  • Encryption of strings, code, and resources which otherwise would show up as plaintext in a decompiler.
  • Control flow. Adds gibberish methods, inserts unnecessary functions, case statements, logical branches, etc.
  • Counter measures to stop debuggers, decompilers, or or memory debugging.

Renaming and control flow can make it very difficult (but not impossible!) for a human reader to understand how your code works, while encryption and countermeasures in theory provide some measure of protection against hackers/crackers.

Three .NET Obfuscation Tools

I’m going to look at several obfuscators available for .NET: Dotfuscator and Confuser. I wrote a very simple program consisting of an algorithm to compute Fibonacci sequences, and a “secret” hard-coded string:

static void Main(string[] args)
{
    var fibonacci = new Fib();
    foreach (var x in fibonacci.Calculate(50))
    {
        Console.Write("{0}, ", x);
    }

    var chest = new TreasureChest();
    Console.WriteLine(chest.Treasure());

    Console.Read();
}

class Fib
{
    public IEnumerable Calculate(uint x)
    {
        long prev = -1;
        long next = 1;
        for (uint i = 0; i < x; i++)
        {
            long sum = prev + next;
            prev = next;
            next = sum;
            yield return sum;
        }
    }
}

class TreasureChest
{
    public String Treasure()
    {
        return "She sells sea shells by the sea shore.";
    }
}

Without Obfuscation

In all screenshots that follow, I am using a free tool from Telerik, JustDecompile. Here's how the un-obfuscated executable looks (and here I simply drag and drop .exe and .dlls into JustDecompile).

JustDecompile_NoOb1

The important core algorithm.

JustDecompile_NoOb_2

The secret string, exposed.

Wow, that decompiled code sure looks similar to the original! The reason is that the C# compiler's generated Intermediate Language (IL) contains a wealth of metadata that can be used by the decompiler. Practically speaking, the output of the decompiler with a .NET C# application is a lot more readable than if we decompiled a native x86 app written in C or C++.

Dotfuscator

Dotfuscator is the obfuscation tool that ships with Visual Studio 2012. Dotfuscator can be configured in a separate GUI application, via command line settings, or added a project to your VS solution. The ability to add it as a project to the solution is especially convenient. In any case, it will be run as a post-process on the selected .dll and .exes.

Out of the box, without a Pro license, it will only do renaming and control flow obfuscation. A free 14-day trial unlocks the other methods. For demonstration purposes, I have only enabled renaming, control flow, and string encryption methods. Here's the decompiled, Dotfuscated .exe:

JustDecompile_Dot1

The important core algorithm.

Right away we can see renaming and control flow obfuscation in action. Dotfuscator has renamed methods to eval_a, eval_b, eval_c, etc. (and it provides a mapping as part of its output so the developer can see what original method/class maps to what renamed method/class). It has also inserted meaningless assignments, logical assertions, and switch statements as part of the control flow obfuscation.

JustDecompile_Dot2

The secret string, now more secret!

Because string encryption was enabled, the string is not longer in plaintext.

Another interesting detail is external types (such as IEnumerable) are not renamed. It's also possible to see external methods, such as Console.WriteLine, which have not been renamed.

Confuser

Confuser is an open source obfuscation tool with a wealth of advanced features, in addition to basics such as renaming, control flow, string encryption, anti-debugging. Integration into Visual Studio is not as easy as Dotfuscator, but it can be run as a post-build command line operation.

Confuser1

Decompilation of the Confuser obfuscated application.

One problem with Confuser is that its documentation is very thin, bordering on non-existent. Also, support is scarce. Positives are that it works very well, I found that with aggressive or maximum obfuscation settings, it can create an executable that is so modified that it actually sends JustDecompile into an infinite loop/crash. I have also found cases where the Confuser obfuscated executables are rendered broken by aggressive or higher settings. The author suggests in these situations to turn the obfuscation level down to normal, which did solve the problem.

ILProtector

ILProtector takes a different approach that Dotfuscator and Confuser. It protects against reverse engineering by converting the IL into a "virtual machine specific form," which is incompatible with .NET decompilers. New .dlls are created that will run on this VM, and then calls are made to them from within the executable. The .dlls are not readable by JustDecompile:

ILProtector

In the above screenshot, nothing is renamed, but the actual code is moved to a .dll and called via Module.invoke. Attempting to decompile the .dll containing the code is not possible as it is not valid IL.

The Problem with Obfuscation

All this is interesting, but the fact remains that all these methods work... until they're broken by hackers/crackers. Internet searches will reveal tools that can de-obfuscate obfuscated programs.

Here's an example of the de-obfuscated Dotfuscator executable. Note that the previously encrypted string is now in plaintext:

deobfuscated

Renaming and control flow cannot be undone, so some measure of obfuscation still exists. But ultimately, obfuscation does not protect against a determined attacker. I think of obfuscation more like a padlock put on the shed in your yard. It may help the owner sleep better at night and keep disinterested vandals out, but anyone with the right set of bolt cutters can get in — so you'd better not store anything irreplaceable in there!

There are other potential downsides to obfuscation:

  • In the event of a crash, the call stack will reflect the obfuscated program and require deciphering via a special tool provided by the obfuscation program (if one exists), making debugging problems tedious.
  • Control path obfuscation has the potential to negatively affect performance of highly tuned/specialized algorithms.
  • Special care must be taken with objects used in Serialization (renaming will break them).
  • Any public interfaces meant to be used by other code outside of the program obviously cannot be renamed.
  • Security through obscurity is not real security.
  • Obfuscation can introduce instability.
  • Managing the obfuscation step in the build process can be non-trivial.

Given these drawbacks and the real possibility that any currently unbroken obfuscation method (Confuser, ILProtector, for example) will conceivably be broken by crackers in the future, a programmer or project manager should consider carefully whether or not a business case can be made for obfuscation.
 

 
Conversation
  • phil says:

    And without obfuscation, any .Net prog is an open book, so what choice does the small developer have? Give up all the great features of .Net and write everything in native C++
    or even pure assembler? Isn’t the evolution in ease of development, ie RAD, one of the primary benefits of tools like .Net? Use the old COM versions of Listview, but do it all in assembler? I think not.
    Some outfit did a rather thorough study and found donation ware to be unprofitable.
    http://www.donationcoder.com/Articles/One/index.html

    And a heartwarming story about generosity, shared by “hanzolo”, can be found here:
    http://programmers.stackexchange.com/questions/165184/is-donationware-a-good-monetization-model-for-developers

    Security through obscurity is the only kind available where the application is run within the confines of the cracker’s machine. You know in this environment they can see everything, using a complete dissassembler such as IDA if necessary. If they can see , the only option those doing the protecting have is to add algorithmic, symbolic and time complexity to , hopefully raising the bar too high for the most determined crackers.
    If obscurity is all you can have, more is better.
    I would say only the corporate guys can establish the trust needed to offer their wares in remotely served/phoning home style, where the cracker’s ability to confine everything is far less certain.

  • Julien says:

    Thanks for this very clear article thats shows the methods and downsides of obfuscation. For my application I chose to only obfuscate a few parts of my code which are adding the most value against competitors, to decrease the risk of performance or unexpected issues.

  • Timothy Dunn says:

    I’d like to add, that confuser has a successor: ConfuserEx. An other good obfuscator is Disguiser.NET

  • Comments are closed.