Mono obfuscator

Contents

What is Mono?

Mono is a framework with the full arsenal of tools to build and run .NET applications on many platforms. Creators of Mono have decided to make .NET available not only on Windows, but also on other platforms: first of all, on Linux and embedded systems. When Microsoft released the .NET framework, it was considered a competitor of Java (available already for years). The new framework was managed like Java but could run on Windows only. It was puzzling; today, we have .NET Core that is not limited by Windows, but developers in the past were curious why .NET was not available on Linux. Nevertheless, Microsoft published the standard of Common Language Infrastructure, so anyone could create his implementation of the runtime and tools to produce .NET binaries.

Why do you need an obfuscator for Mono?

As binaries produced by Mono meet the Common Language Infrastructure standard, they contain metadata like usual .NET binaries. They include embedded resources that are easy to read and write. Also, they have a code in the intermediate language that is human-readable, which can be easily converted to source code in C# or VB.NET.

Despite open-source being trendy these days, many companies still don’t want to disclose the source code of their products. That’s why you need a .NET obfuscator for Mono. Without it, all your algorithms are opened as if you published sources of your commercial product on GitHub.

We will show how to add a .NET obfuscator to the building process; we will use ArmDot, a cross-platform obfuscator compatible with Mono. Let’s start with a simple application.

What to obfuscate?

Let’s start with a simple C# application that checks an entered password by comparing hashes. We will work on the command line; the same steps can also be done on Linux or macOS.

Start Open Mono x64 Command Prompt to launch cmd.exe with the Mono environment.

Create a folder MonoObfuscation and a new file MonoObfuscation.cs with the following code:

using System;
using System.Text;
using System.Security.Cryptography;

public class MonoObfuscation
{
    public static void Main(string[] args)
    {
        string password = Console.ReadLine();

        if (CheckPassword(password))
            Console.WriteLine("Correct!");
        else
            Console.WriteLine("Wrong");
    }

    public static bool CheckPassword(string text)
    {
        using (var hash = SHA256.Create())
        {
            var result = hash.ComputeHash(Encoding.UTF8.GetBytes(text));
    
            return result[0] == 0x99 && result[1] == 0x97 && result[2] == 0xee &&
                result[3] == 0x6b && result[4] == 0xc0 && result[5] == 0x52 &&
                result[6] == 0x40 && result[7] == 0x93 && result[8] == 0xf7 &&
                result[9] == 0xdf && result[10] == 0xb2 && result[11] == 0xae &&
                result[12] == 0x8f && result[13] == 0x80 && result[14] == 0xa9 &&
                result[15] == 0x97 && result[16] == 0xd7 && result[17] == 0x55 &&
                result[18] == 0x04 && result[19] == 0xbf && result[20] == 0xd2 &&
                result[21] == 0xe8 && result[22] == 0x29 && result[23] == 0xf5 &&
                result[24] == 0x4a && result[25] == 0x2a && result[26] == 0x0c &&
                result[27] == 0xd3 && result[28] == 0x17 && result[29] == 0x2a &&
                result[30] == 0xda && result[31] == 0xd8;
        }        
    }
}

The application compares hashes to check if a user has entered a correct password.

Return to the command line and run the following command to build the executable file:

csc MonoObfuscation.cs

On Windows, I had to copy System.Text.Encoding.CodePages.dll as shown here.

If everything is going well, the compiler creates MonoObfuscation.exe that Mono can run. You can play with it by typing wrong passwords and the correct one (armdot):

V:\Projects\MonoObfuscation>mono MonoObfuscation.exe
123
Wrong
V:\Projects\MonoObfuscation>mono MonoObfuscation.exe
armdot
Correct!

Great, it works as expected. Now it is time to obfuscate the code making it hard to decode.

Enable obfuscation in Mono

You need to install two packages using NuGet: ArmDot.Client and ArmDot.Engine.MSBuildTasks.

I didn’t manage to add the packages using nuget shipped with Mono. So I had to use standard nuget:

nuget install ArmDot.Client -OutputDirectory packages
nuget install ArmDot.Engine.MSBuildTasks -OutputDirectory packages

These two commands download and install the packages to the folder packages.

Let’s obfuscate CheckPassword by adding ArmDot.Client.VirtualizeCode:

using System;
using System.Text;
using System.Security.Cryptography;
using ArmDot.Client;

public class MonoObfuscation
{
    public static void Main(string[] args)
    {
        string password = Console.ReadLine();

        if (CheckPassword(password))
            Console.WriteLine("Correct!");
        else
            Console.WriteLine("Wrong");
    }

    [ArmDot.Client.VirtualizeCode]
    public static bool CheckPassword(string text)
    {
        using (var hash = SHA256.Create())
        {
            var result = hash.ComputeHash(Encoding.UTF8.GetBytes(text));
    
            return result[0] == 0x99 && result[1] == 0x97 && result[2] == 0xee &&
                result[3] == 0x6b && result[4] == 0xc0 && result[5] == 0x52 &&
                result[6] == 0x40 && result[7] == 0x93 && result[8] == 0xf7 &&
                result[9] == 0xdf && result[10] == 0xb2 && result[11] == 0xae &&
                result[12] == 0x8f && result[13] == 0x80 && result[14] == 0xa9 &&
                result[15] == 0x97 && result[16] == 0xd7 && result[17] == 0x55 &&
                result[18] == 0x04 && result[19] == 0xbf && result[20] == 0xd2 &&
                result[21] == 0xe8 && result[22] == 0x29 && result[23] == 0xf5 &&
                result[24] == 0x4a && result[25] == 0x2a && result[26] == 0x0c &&
                result[27] == 0xd3 && result[28] == 0x17 && result[29] == 0x2a &&
                result[30] == 0xda && result[31] == 0xd8;
        }        
    }
}

Build it using the following command (the switch -r helps the compiler finds the reference of ArmDot.Client):

csc MonoObfuscation.cs -r:packages\ArmDot.Client.2022.1.0\lib\net40\ArmDot.Client.dll

The method CheckPassword is not obfuscated yet. The assembly MonoObfuscation.exe needs to be processed by ArmDot.

Download ArmDot from the downloading page and install it.

Run ArmDotConsole to process the assembly using the command ArmDotConsole –input-assembly MonoObfuscation.exe:

V:\Projects\MonoObfuscation>"C:\Program Files (x86)\ArmDot\ArmDotConsole.exe" --input-assembly MonoObfuscation.exe
ArmDotConsole [Version 2021.21.0.0]
(c) 2004 - 2021 Softanics. All Rights Reserved

INFO: ------ Build started: Assembly (1 of 1): MonoObfuscation.exe (V:\Projects\MonoObfuscation\MonoObfuscation.exe) ------
INFO: Conversion started for method System.Boolean MonoObfuscation::CheckPassword(System.String)
INFO: Conversion finished for method System.Boolean MonoObfuscation::CheckPassword(System.String)
INFO: Writing protected assembly to V:\Projects\MonoObfuscation\MonoObfuscation.exe...
INFO: Finished

Run the obfuscated MonoObfuscation.exe to check it works well:

V:\Projects\MonoObfuscation>mono MonoObfuscation.exe
123
Wrong

V:\Projects\MonoObfuscation>mono MonoObfuscation.exe
armdot
Correct!

Let’s look at how to obfuscated code looks now (also available on GitHub):

The obfuscated code

There is a mess of strange arrays, moving values here and there… The code is tough to decode!

Conclusion

Any .NET application needs to be obfuscated if the vendor doesn’t want to disclose the code. ArmDot is a cross-platform obfuscator that supports different .NET Runtime implementations, including Mono. Obfuscation attributes help developers fine-tune protection options for application parts. Developers use the ArmDot command-line tool to add ArmDot to their automated build pipelines.