.NET obfuscator for macOS

How to obfuscate a .NET application on macOS?

In this guide, you will learn how to protect .NET applications if your primary development environment is macOS.

Contents

.NET on macOS

Initially .NET Framework was available on Windows only, but later .NET was modified for a wide range of platforms including macOS. Not only .NET Runtime and .NET SDK run on macOS, but also there is a macOS version of Visual Studio, a well-known IDE among .NET developers. Today you can develop and run .NET applications without leaving your favorite operating system.

Why is .NET obfuscation absolutely necessary?

The main issue when you develop .NET applications is that such applications are easy to decompile. There are a bunch of tools that can even restore original source code. Anyone can later modify the source code (for instance, change strings, your company’s name, or copyright’s owner name), rebuild and release it. Or just extract your implementations of some algorithm to use it in their own applications. The risk of intellectual property theft is high if you keep your .NET application as is. The traditional way to solve this problem is obfuscation.

Obfuscation process lies in renaming types, methods, and other items to confuse hackers. Unreadable and nonsense names don’t allow others to understand what a particular type is purposed to, and what some method has been designed to do. A typical application contains more than hundreds of methods so this approach (called as name obfuscation) helps a lot.

Another way is to make a method’s code illegible, so it takes a lot of time to understand what the method is actually doing. It is achieved by method’s control flow obfuscation, or by virtualization. While control flow obfuscation hides conditional and unconditional branches, virtualization hides each instruction by converting original code to a code for some virtual machine with a unique set of opcodes, its own stack and locals. Such a virtualized method contains a virtual machine that executes its own instructions one by one.

Additionally, embedded resources can be hidden, but, of course, the application still can access them at runtime.

Prerequisites

You need to install the .NET SDK for macOS in order to create and execute the sample .NET application we are going to use further.

Optionally, install an IDE for .NET developers, for example, Visual Studio for Mac, Visual Studio Code, or JetBrains Rider.

Sample project

Open Terminal, create a new directory for the sample project, and create initial files:

mkdir CheckPassword
cd CheckPassword
dotnet new console

Open Program.cs in your favorite editor (we use nano):

nano Program.cs

You will see the following:

Console.WriteLine("Hello World!");

Let’s modify it to allow a user enter a password whose hash is displayed:

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

var password = Console.ReadLine();

using (HashAlgorithm algorithm = SHA256.Create())
{
    var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(password));
    var s = Convert.ToBase64String(hash);
    Console.WriteLine(s);
}

Build it and run:

dotnet build
bin/Debug/net6.0/CheckPassword

Type ArmDot, press Enter, and you will see the hash value:

hzTbLivrxGSCjSfDqNEIDuXEbj1W6IXdPikhanYqTu0=

Now we know the hash value we can use to check the entered password:

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

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

var password = Console.ReadLine();

using (HashAlgorithm algorithm = SHA256.Create())
{
    var hash = algorithm.ComputeHash(Encoding.UTF8.GetBytes(password));
    var s = Convert.ToBase64String(hash);

    if (s == "hzTbLivrxGSCjSfDqNEIDuXEbj1W6IXdPikhanYqTu0=")
        Console.WriteLine("Valid");
    else
        Console.WriteLine("Invalid");
}

Rebuild the project, and run it to ensure the application works as expected.

The problem is that the correct hash value is not hidden, and the logic of the application is obvious. Look at the snippet:

      IL_0014:  callvirt   instance uint8[] [System.Runtime]System.Text.Encoding::GetBytes(string)
      IL_0019:  callvirt   instance uint8[] [System.Security.Cryptography.Primitives]System.Security.Cryptography.HashAlgorithm::ComputeHash(uint8[])
      IL_001e:  stloc.2
      IL_001f:  ldloc.2
      IL_0020:  call       string [System.Runtime]System.Convert::ToBase64String(uint8[])
      IL_0025:  stloc.3
      IL_0026:  ldloc.3
      IL_0027:  ldstr      "hzTbLivrxGSCjSfDqNEIDuXEbj1W6IXdPikhanYqTu0="
      IL_002c:  call       bool [System.Runtime]System.String::op_Equality(string,
                                                                           string)
      IL_0031:  stloc.s    V_4
      IL_0033:  ldloc.s    V_4
      IL_0035:  brfalse.s  IL_0044

      IL_0037:  ldstr      "Valid"
      IL_003c:  call       void [System.Console]System.Console::WriteLine(string)
      IL_0041:  nop
      IL_0042:  br.s       IL_004f

      IL_0044:  ldstr      "Invalid"
      IL_0049:  call       void [System.Console]System.Console::WriteLine(string)
      IL_004e:  nop
      IL_004f:  nop
      IL_0050:  leave.s    IL_005d

Obfuscation to the rescue!

Enable .NET obfuscation on macOS

First, we need to add two packages, ArmDot.Client and ArmDot.Engine.MSBuildTasks. ArmDot.Client contains obfuscation attributes that you can use to specify obfuscation options for a method or a type. ArmDot.Engine.MSBuildTasks provides an obfuscation task for MSBuild.

Let’s add the packages and enable the obfuscation task. Modify CheckPassword.csproj as shown below:

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

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net6.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="ArmDot.Client" />
    <PackageReference Include="ArmDot.Engine.MSBuildTasks" />
  </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>

Rebuild the project and launch the obfuscated application:

Obfuscation for .NET on macOS

Great! It is working as expected!

Conclusion

.NET obfuscation is vitally important for commercial applications. With the help of a .NET obfuscator, you can effectively rename types and methods, and confuse hackers by converting original well-readable code to an illegible mixture of instructions.

ArmDot is a cross-platform .NET obfuscator that is available on Windows, Linux, and macOS. ArmDot is tightly integrated with the building process.