Obfuscator for .NET Core

How to obfuscate a .NET Core application?

Contents

Why to obfuscate .NET Core applications?

Before Microsoft released the first version of .NET, there was the only similar platform in the world, Java. At the first glance, .NET had everything required to be cross-platform (for example, machine independent intermediate language, JIT compiler), but suddenly it was available for Windows only. It took a lot of time to bring .NET to non-Windows platforms (first of all, Linux and MacOS). Developers were waiting about 16 years, from the releasing the beta version of .NET in 2000 to the release of .NET Core 1.0 in 2016.

Today, .NET Core (or just .NET, since .NET 5) is used widely to develop desktop applications, mobile applications, and web-sites. That’s why obfuscation is really important. In most cases, companies want to protect their intellectual property, and don’t intend to make the source code available for everyone. Obfuscation to the rescue! It jumbles the original code, so restoring original code becomes a very hard task. Let’s see how to enable obfuscation for a .NET Core application.

The .NET Core application to be obfuscated: a password validator.

You can find the complete source code of the sample on GitHub: todo

Let’s start Visual Studio and create a new project for a console application. Select .NET Core 3.1 as a framework. Allow Visual Studio to prepare files.

The program will ask a user to enter a password and checks whether it is valid or not. To validate the password, its hash is calculated and compared with the correct value.

First, we need to get the hash:

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

namespace NetCoreConsoleApp
{
    internal class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Enter password: ");

            var password = Console.ReadLine();

            var sha256 = new SHA256Managed();

            var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(password));

            Console.WriteLine($"The hash: {Convert.ToBase64String(hash)}");

            Console.WriteLine("Press ENTER to exit...");
            Console.ReadLine();
        }
    }
}

Build and run the application. Copy the hash value:

Copy the correct hash

Now modify the program to validate the entered password:

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

namespace NetCoreConsoleApp
{
    internal class Program
    {
        static void Main(string[] args)
        {
            while (true)
            {
                Console.WriteLine("Enter password: ");

                var password = Console.ReadLine();

                var sha256 = new SHA256Managed();

                var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(password));

                var valid = Convert.ToBase64String(hash) == "hzTbLivrxGSCjSfDqNEIDuXEbj1W6IXdPikhanYqTu0=";

                if (valid)
                    Console.WriteLine($"The password is valid");
                else
                    Console.WriteLine($"The password is invalid");
            }
        }
    }
}

Run and ensure that it is working as expected:

Checking hash

Great! Let’s now look at the compiled code. Click to Start, find Command Prompt for Visual Studio, and run it. Type ildasm and press Enter. Click to FileOpen and choose bin\Debug\netcoreapp3.1\NetCoreConsoleApp.dll:

Without obfuscation, the code is clear

The code is easy to read, all string literals are available as is. How to hide the logic of the method? Let’s use a cross-platform obfuscator, ArmDot, that fully supports .NET Core.

Close ildasm and let’s add ArmDot to the project.

Enable obfuscation in .NET Core

To turn obfuscation on, you need to add two ArmDot packages: ArmDot.Client (provides obfuscation attributes) and ArmDot.Engine.MSBuildTasks (provides the obfuscation task for MSBuild). Right-click the project, choose Manage NuGet packages…, switch to Browse and type ArmDot. Install both packages:

Add ArmDot NuGet packages to the project

After adding the packages, you need to modify the project to enable obfuscation. Right-click the project and choose Edit Project File. Now add the obfuscation task:

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="ArmDot.Client" Version="2022.7.0" />
    <PackageReference Include="ArmDot.Engine.MSBuildTasks" Version="2022.7.0" />
  </ItemGroup>

	<Target Name="Protect" AfterTargets="AfterCompile" BeforeTargets="BeforePublish">
		<ItemGroup>
			<Assemblies Include="$(ProjectDir)$(IntermediateOutputPath)$(TargetFileName)" />
		</ItemGroup>
		<ArmDot.Engine.MSBuildTasks.ObfuscateTask
		  Inputs="@(Assemblies)"
		  ReferencePaths="@(_ResolveAssemblyReferenceResolvedFiles->'%(RootDir)%(Directory)')"
		  SkipAlreadyObfuscatedAssemblies="true"
  />
	</Target>
</Project>

Additionally, specify obfuscation options using obfuscation attributes. Open Program.cs and enable control flow obfuscation and virtualization at the assembly level:

[assembly: ArmDot.Client.ObfuscateControlFlow]
[assembly: ArmDot.Client.VirtualizeCode]

Rebuild the project to ensure that ArmDot obfuscated the assembly. Switch to Output Window. You will see:

Rebuild started...
1>------ Rebuild All started: Project: NetCoreConsoleApp, Configuration: Debug Any CPU ------
Restored V:\Projects\NetCoreConsoleApp\NetCoreConsoleApp.csproj (in 4 ms).
1>[ArmDot] ArmDot [Engine Version 2022.7.0.0] (c) Softanics. All Rights Reserved
1>[ArmDot] Reading license key from C:\ProgramData\ArmDot\ArmDotLicense.key
1>[ArmDot] ------ Build started: Assembly (1 of 1): NetCoreConsoleApp.dll (V:\Projects\NetCoreConsoleApp\obj\Debug\netcoreapp3.1\NetCoreConsoleApp.dll) ------
1>[ArmDot] Conversion started for method System.Void NetCoreConsoleApp.Program::Main(System.String[])
1>[ArmDot] Conversion finished for method System.Void NetCoreConsoleApp.Program::Main(System.String[])
1>[ArmDot] Conversion started for method System.Void NetCoreConsoleApp.Program::.ctor()
1>[ArmDot] Conversion finished for method System.Void NetCoreConsoleApp.Program::.ctor()
1>[ArmDot] Writing protected assembly to V:\Projects\NetCoreConsoleApp\obj\Debug\netcoreapp3.1\NetCoreConsoleApp.dll...
1>V:\Projects\NetCoreConsoleApp\NetCoreConsoleApp.csproj(17,3): warning : [ArmDot] warning ARMDOT0002: The assembly NetCoreConsoleApp.dll will stop working in 7 days because it is protected by the ArmDot demo version
1>[ArmDot] Finished
1>NetCoreConsoleApp -> V:\Projects\NetCoreConsoleApp\bin\Debug\netcoreapp3.1\NetCoreConsoleApp.dll
1>Done building project "NetCoreConsoleApp.csproj".
========== Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========

Let’s run ildasm and open bin\Debug\netcoreapp3.1\NetCoreConsoleApp.dll. Now it is very hard to understand what the program is doing:

The obfuscated code is hard to decrypt

Conclusion

Since .NET Core was released, .NET had become really cross-platform. Developers now can write applications in .NET which run on Windows, Linux, MacOS, mobile, and the Web. To protect .NET applications, developers use .NET obfuscators. ArmDot is a modern obfuscator with a rich set of obfuscation options: control flow obfuscation, virtualization, string encryption, protection of embedded resources. It is easy to add ArmDot to the building process and specify obfuscation options. Using ArmDot, you are positive that your intellectual property is well protected and the application code is hard to decrypt.