Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Running encrypted .NET assemblies

0.00/5 (No votes)
4 Jan 2017 1  
.NET running encryped assemblies

Introduction

In the past, we were using obfuscation to make it harder to reverse our .NET assemblies. Though this worked well, it was still only harder to read what the code does. So, we came up with the idea to simply encrypt our assemblies and only decrypt them when they are needed/executed. Here's the very first prototype - maybe this is useful.

Using the code

To demonstrate the whole thing, I created a Visual Studio solution which contains following four projects. I tried to keep everything as simple as possible so that everyone can understand what's happening.

Cryptor

is a command line executable which is used to encrypt an assembly. To make things as easy as possible, I added a call to the tool as post build step of each assembly which shall be encrypted. The call to the tool is as simple as Cryptor <inFile> <outFile> <password>. So, during the build, an additional file is created where an "e" is added to the extension (Assembly.dll -> Assembly.dlle). The password used is "test", what else :-)

TheApplication

This is my test application - simple as could be. The user can enter 2 numbers which are then added using the brilliant logic contained in "TheClassLib" and the result is shown.

TheClassLib

Even simpler - this lib is used by TheAppication - and required, as TheApplication does not know how to add two intergers. Sorry, just kidding - I created this lib to show how (and that) dependencies to other encrypted assemblies are not a problem.

SecStarter

The SecStarter is a tiny Windows Forms Application which asks the user for the password before the application is started. It also contains the magic which needs to happen to execute TheApplication, which has a dependency to TheClassLib. First of all the SecureLoader fetches all of the encrypted assemblies it can find in the current working directory. It assumes that there's only one encrypted exe (.exee) - if you have more, you'll have to add some code here.

 

internal void LoadAssembies(string password)
{
    string[] fileList = Directory.GetFiles(Environment.CurrentDirectory);

    foreach (string fileName in fileList)
    {
        if (fileName.EndsWith(".exee"))
        {
            this.executableBinary = LoadAndDecrypt(fileName, password);
        }

        if (fileName.EndsWith(".dlle"))
        {
            int startIndex = fileName.LastIndexOf("\\") + 1;
            string nameOnly = fileName.Substring(startIndex, fileName.Length - (startIndex + 5));
            this.dllList.Add(nameOnly, LoadAndDecrypt(fileName, password));
        }
    }
}

The call to

LoadAndDecrypt(fileName, password)

does the decryption work as such.

Now, the trick to start and run the app is to create my own AppDomain and ask it to execute TheApplication. 

Evidence adevidence = AppDomain.CurrentDomain.Evidence;
AppDomain domain = AppDomain.CreateDomain("MyAppDomain", adevidence);

domain.AssemblyResolve += Domain_AssemblyResolve;

domain.ExecuteAssemblyByName("TheAppication.exe");

As the exe does not sit in the working directory, the loading of the assembly will fail and we end up in an exception. Luckily, we can register for an event which is fired when the domain tries to load the assembly and we can add our own code:

private Assembly Domain_AssemblyResolve(object sender, ResolveEventArgs args)
{
    if (args.Name.EndsWith(".exe"))
    {
        Assembly assembly = Assembly.Load(this.executableBinary);
        return assembly;
    }
    else
    {
        string[] assemblyData = args.Name.Split(',');
        Assembly assembly = Assembly.Load(dllList[assemblyData[0]]);
        return assembly;
    }
}

Depending on what the domain is asking for, we load the Assembly into memory and return it to the domain so that it can be executed.

Important to note is, that the creation of the AppDomain as well as the execution of the exe has to be done in a seperate Thread - otherwise we end up with exceptions.

internal void StartTheApp()
{
    Thread theThread = new Thread(MainWorkerThread);
    theThread.Start();
}

public void MainWorkerThread()
{
    Evidence adevidence = AppDomain.CurrentDomain.Evidence;
    AppDomain domain = AppDomain.CreateDomain("MyAppDomain", adevidence);

    domain.AssemblyResolve += Domain_AssemblyResolve;

    domain.ExecuteAssemblyByName("TheAppication.exe");
}

There might be some code missing, e.g. cleaning up the AppDomain and the whole thing could be more professional (where are the unit tests?) - well, I wanted to share the idea and I'm looking forward to comments (and I hereby apologize for reading/answering them late as I am pretty busy at the moment)

 

Points of Interest

Still, as the user needs the password to start the app, the content of the assembly can be unwrapped - how to decrypt is visible in the (plain) starter code. However, encrypting the assemblies raises the bar and at least an attacker who does not know the password has it harder.

One could also combine the approach with some certificate e.g. in the cert store to avoid the password.

History

1st Version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here