Protecting MAUI Apps with .NET Obfuscator

In this article, you will learn how to protect your MAUI application with the help of a .NET obfuscator. Firstly, you will see how easy it is to retrieve non-protected code (you can even obtain C# code using some tools!). Lastly, we will show you how to integrate obfuscation into the building process and demonstrate how challenging it is to decompile obfuscated code.

What is MAUI?

MAUI stands for .NET Multi-platform App UI and is a framework introduced by Microsoft for building native mobile and desktop applications using a single codebase. It is an evolution of Xamarin.Forms and extends the developer experience to creating apps for Android, iOS, macOS, and Windows with .NET and C#.

By using MAUI, developers can leverage their .NET and C# skills to build interactive and performance-optimized applications while maintaining a shared codebase across all platforms. It’s a powerful tool for modern software development, enabling the swift creation of cross-platform applications with rich user interfaces.

Why are .NET applications so easy to hack?

.NET applications are compiled into an Intermediate Language (IL), which is then just-in-time (JIT) compiled to native code on the platform where it’s running. Since IL is easier to reverse-engineer than native code, attackers can more easily understand how the application works, find vulnerabilities, and modify the IL.

It is possible to convert IL back to C# code. This conversion is often referred to as decompilation. Decompilation involves translating the IL code, which is a lower-level, platform-independent set of instructions that .NET assemblies are compiled into, back into a higher-level language code like C#.

That is why obfuscation is crucial. After obfuscation, the obfuscated IL code becomes difficult to decompile because obfuscation techniques transform the original code into “mishmashed” code, concealing the logic from prying eyes.

A sample application

Let’s create a sample application that validates the entered password. The user will enter a password and click a button to verify it.

Open Visual Studio 2022, select “New Project”, and then choose “.NET MAUI App”. Open the “Solution Explorer”, right-click on “MainPage.xaml”, and select “View Markup”. Replace the existing content with the following:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ObfuscateMAUI.MainPage">

    <ScrollView>
        <VerticalStackLayout>
            <Entry x:Name="passwordTextBox" />
            <Button x:Name="myButton" Clicked="CheckPasswordButton_Clicked" Text="Check password" />
            <Entry x:Name="statusLabel" IsReadOnly="True" />
        </VerticalStackLayout>
    </ScrollView>

</ContentPage>

Open “MainPage.xaml.cs” and edit the button’s click event handler:

private void CheckPasswordButton_Clicked(object sender, EventArgs e)
{
    var hash = Convert.ToBase64String(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(this.passwordTextBox.Text)));
    Debug.Print($"{hash}");
}

Start debugging, enter “ArmDot” into the edit box, and click “Check Password”. Then return to Visual Studio and open “Output” to see the base64 encoding of the password’s hash:

hzTbLivrxGSCjSfDqNEIDuXEbj1W6IXdPikhanYqTu0=

Now that we know the hash value, return to “CheckPasswordButton_Clicked” and modify the code to verify the entered password:

private void CheckPasswordButton_Clicked(object sender, EventArgs e)
{
    var hash = Convert.ToBase64String(SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(this.passwordTextBox.Text)));

    if (hash == "hzTbLivrxGSCjSfDqNEIDuXEbj1W6IXdPikhanYqTu0=)
        this.statusLabel.Text = "OK";
    else
        this.statusLabel.Text = "Failed";
}

Rebuild the project and run the application to ensure that it is working as expected.

A .NET application can be easily decompiled

So, what’s the issue with our application? It functions and validates passwords, doesn’t it? Well, although it is working properly, anyone can understand the application’s logic and break it. If you load “ObfuscateMAUI.dll” into ILSpy, a tool for viewing MSIL code, you will see your code exactly as it is:

A MAUI application is easy to decompile

Of course, you wouldn’t want everyone in the world to know how your application is checking passwords! A .NET obfuscator can help protect your code.

How do you add a .NET obfuscator to the project?

There are two NuGet packages: ArmDot.Client and ArmDot.Engine.MSBuildTasks. ArmDot.Client provides obfuscation attributes that you can use to instruct the .NET obfuscator on which obfuscation techniques should be applied.

Add these NuGet packages to your project, then modify the project as shown below:

	<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>

We’ve just added a new target, “Protect”, which is executed after the compiler has built the assembly in order to obfuscate it.

As we need to protect the method “CheckPasswordButton_Clicked”, let’s virtualize it by specifying the respective attribute:

[ArmDot.Client.VirtualizeCode]
private void CheckPasswordButton_Clicked(object sender, EventArgs e)

Rebuild the project and ensure that it is working correctly.

Now, let’s open the obfuscated assembly in ILSpy and see what the obfuscated code looks like:
Obfuscated code is not readable

As you can see, the code has become very difficult to understand.

Conclusion

.NET allows the creation of applications that can run on virtually any platform. However, the problem is that .NET applications are easy to decompile. Naturally, companies do not want to give everyone access to their source code. A .NET obfuscator addresses this issue by modifying .NET code into a form that is very difficult to understand and decode.