How to obfuscate a Universal Windows Platform (UWP) application?

Contents

Why is it essential to obfuscate UWP applications?

UWP is a modern platform aimed to create client applications that run on a wide range of devices. Developers write UWP applications in C++, Visual Basic, Javascript, or a .NET language such as C# or VB.NET.

If a UWP application is written in .NET, it is distributed as a set of assemblies that contain code in some intermediate language. When a .NET application enters a method, instructions are compiled to machine code to be executed on a target machine. Any .NET application works this way, and a UWP application is not an exception.

Though companies do not intend to show the source code of their commercial applications to everyone, it is elementary to restore the original code of a UWP application.

But not only can the code be retrieved easily. Assets like images and other resources are stored as embedded resources in .NET applications. Like instructions, embedded resources are available for prying eyes. Anyone can extract or even modify and place back these assets.

Undoubtedly, obfuscators are essential tools because they protect code, hide its logic, and encrypt embedded resources. It makes reverse engineering of an obfuscated application an arduous task.

In this article, we will create a UWP application that checks the entered password’s correctness and shows how to protect this application. Also, you will learn how to run obfuscation as a part of the building process automatically.

Prepare a sample application.

You can find the complete source code of the sample on GitHub.

Let’s create a brand new UWP application. Run Visual Studio, click on New Project, find the UWP Blank Application template, type a project name and click to Finish to complete:

Create a brand new UWP application

Double-click MainPage.xaml, drop a TextBox to input a password, and a TextBlock that will show entered password status. Name the controls. You will get the following:

<Grid>
    <TextBox x:Name="password" HorizontalAlignment="Center" Margin="0,460,0,0" Text="Enter Password" TextWrapping="Wrap" VerticalAlignment="Top" Height="40" Width="780"/>
    <TextBlock x:Name="passwordStatus" HorizontalAlignment="Left" Margin="570,540,0,0" Text="Checking password..." TextWrapping="Wrap" VerticalAlignment="Top"/>
</Grid>

Open MainPage.xaml.cs in the editor and add a method that checks passwords and updates controls accordingly, as shown below. The method calculates a hash of entered text and then compare it with the correct one:

private void CheckPassword()
{
    this.passwordStatus.Text = "Checking...";

    using (var sha256 = SHA256.Create())
    {
        var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(this.password.Text));
        bool correct = Convert.ToBase64String(hash).Equals("mZfua8BSQJP337Kuj4Cpl9dVBL/S6Cn1SioM0xcq2tg=");
        this.passwordStatus.Text = string.Format($"The password \"{this.password.Text}\" is {(correct ? "correct" : "incorrect")}");
    }
}

When a user enters a password, it is checking, and the result is displayed:

public MainPage()
{
    this.InitializeComponent();

    CheckPassword();
    this.password.TextChanged += Password_TextChanged;
}

private void Password_TextChanged(object sender, TextChangedEventArgs e)
{
    CheckPassword();
}

If you enter armdot you will find that the password is correct:

Entering the correct password

Great! It works as expected!

We have just created an application displaying whether an entered password is correct or not. But there is a problem: the magic string mZfua8BSQJP337Kuj4Cpl9dVBL/S6Cn1SioM0xcq2tg= is available for everyone, and the logic of CheckPassword() is obvious. Just open UWPSample.exe in dotPeek to review its code:

Without obfuscation, the code is open for everyone

We have an additional problem: it is easy to replace the magic string with another one. Now it is time to hide the string and confuse anyone who will try to spy on the code.

Prepare the project for obfuscation

In this tutorial, we will show how to obfuscate the application with the help of ArmDot. ArmDot is a .NET obfuscator with the full range of features: control flow obfuscation, code virtualization, and embedded resources protection.

ArmDot provides two NuGet packages: ArmDot.Client and ArmDot.Engine.MSBuildTasks. ArmDot.Client contains attributes that developers use to specify what kind of obfuscation should be used to protect a particular method, entire type, or even an assembly.

ArmDot.Engine.MSBuildTasks provides a task that MSBuild uses to obfuscate a compiled assembly. The obfuscation task is executed right after an assembly is built.

Let’s return to Visual Studio. Right-click the project, select Manage NuGet Packages…, type ArmDot. You will see ArmDot.Client and ArmDot.Engine.MSBuildTasks. Install both:

Installing ArmDot NuGet packages

An additional step is required. ArmDot.Engine.MSBuildTasks provides a task to obfuscate an assembly. To run the task, close the solution, and open the project in the text editor. Add new target Protect that will be executed after an assembly is compiled:

<Target Name="Protect" AfterTargets="Build">
    <ItemGroup>
      <Assemblies Include="$(TargetDir)$(TargetFileName)" />
    </ItemGroup>
    <ArmDot.Engine.MSBuildTasks.ObfuscateTask
      Inputs="@(Assemblies)"
      ReferencePaths="@(_ResolveAssemblyReferenceResolvedFiles->'%(RootDir)%(Directory)')"
    />
  </Target>
</Project>

Let’s review each line:

<Target Name="Protect"
  AfterTargets="Build">
A new target Protect is declared. AfterTargets tells MSBuild to build the target Protect after the build process finishes.
<ItemGroup>
<Assemblies
  Include="$(TargetDir)$(TargetFileName)" />
Assemblies contains array of assemblies to obfuscate. In this sample, there is only one assembly whose full path can be get using $(TargetDir)$(TargetFileName)
</ItemGroup>
<ArmDot.Engine.MSBuildTasks.ObfuscateTask
The ArmDot obfuscation task is declared.
Inputs="@(Assemblies)"
Inputs is a ArmDot obfuscation task parameter that specifies an array of assemblies to obfuscate. Assemblies is created above.
ReferencePaths="@(_ResolveAssemblyReferenceResolvedFiles->'%(RootDir)%(Directory)')"
ReferencePaths is another parameter that specifies directories that ArmDot uses to locate referenced assemblies. ArmDot needs to find an assembly for some types to get more information about them.
/>
</Target>

Load the solution and rebuild the project to be sure that ArmDot is enabled now:

Rebuild the project

In the Output you see:

1>  [ArmDot] ArmDot [Engine Version 2021.10.0.0] (c) Softanics. All Rights Reserved
1>  [ArmDot] No license key specified, or it is empty. ArmDot is working in demo mode.
1>  [ArmDot] THIS PROGRAM IN UNREGISTERED. Buy a license at https://www.armdot.com/order.html
1>  [ArmDot] ------ Build started: Assembly (1 of 1): UWPSample.exe (V:\Projects\UWPSample\UWPSample\bin\x86\Debug\UWPSample.exe) ------
1>V:\Projects\UWPSample\UWPSample\UWPSample.csproj(178,5): warning : [ArmDot] warning ARMDOT0003: No methods to protect in the assembly V:\Projects\UWPSample\UWPSample\bin\x86\Debug\UWPSample.exe
1>  [ArmDot] Writing protected assembly to V:\Projects\UWPSample\UWPSample\bin\x86\Debug\UWPSample.exe...
1>V:\Projects\UWPSample\UWPSample\UWPSample.csproj(178,5): warning : [ArmDot] warning ARMDOT0002: The assembly UWPSample.exe will stop working in 7 days because it is protected by the ArmDot demo version
1>  [ArmDot] Finished

ArmDot issues a warning, “No methods to protect in the assembly”. Indeed, no one method or type is added to obfuscation. A developer informs ArmDot what methods, classes, or even an entire assembly should be obfuscated.

With the help of the standalone version of ArmDot, one can select a method, a type, or an assembly and set what obfuscation options. In this tutorial, we will use a declarative way of specifying obfuscation options. ArmDot provides attributes for that.

Let’s return to the code. Add the attribute ArmDot.Client.VirtualizeCode to the method CheckPassword() as shown below:

[ArmDot.Client.VirtualizeCode]
private void CheckPassword()
{
    this.passwordStatus.Text = "Checking...";

    using (var sha256 = SHA256.Create())
    {
        var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(this.password.Text));
        bool correct = Convert.ToBase64String(hash).Equals("mZfua8BSQJP337Kuj4Cpl9dVBL/S6Cn1SioM0xcq2tg=");
        this.passwordStatus.Text = string.Format($"The password \"{this.password.Text}\" is {(correct ? "correct" : "incorrect")}");
    }
}

Rebuild the project. Now we see that ArmDot obfuscated the method:

Build started...
1>------ Build started: Project: UWPSample, Configuration: Debug x86 ------
1>  UWPSample -> V:\Projects\UWPSample\UWPSample\bin\x86\Debug\UWPSample.exe
1>  [ArmDot] ArmDot [Engine Version 2021.11.0.0] (c) Softanics. All Rights Reserved
1>  [ArmDot] No license key specified, or it is empty. ArmDot is working in demo mode.
1>  [ArmDot] THIS PROGRAM IN UNREGISTERED. Buy a license at https://www.armdot.com/order.html
1>  [ArmDot] ------ Build started: Assembly (1 of 1): UWPSample.exe (V:\Projects\UWPSample\UWPSample\bin\x86\Debug\UWPSample.exe) ------
1>  [ArmDot] Conversion started for method System.Void UWPSample.MainPage::CheckPassword()
1>  [ArmDot] Conversion finished for method System.Void UWPSample.MainPage::CheckPassword()
1>  [ArmDot] Writing protected assembly to V:\Projects\UWPSample\UWPSample\bin\x86\Debug\UWPSample.exe...
1>V:\Projects\UWPSample\UWPSample\UWPSample.csproj(178,5): warning : [ArmDot] warning ARMDOT0002: The assembly UWPSample.exe will stop working in 7 days because it is protected by the ArmDot demo version
1>  [ArmDot] Finished
========== Build: 1 succeeded, 0 failed, 0 up-to-date, 0 skipped ==========

Run the application to ensure it is working well.

Do you remember what a non-obfuscated code looks like? What about a virtualized one? Let’s open dotPeek again, load the obfuscated assembly, and find CheckPassword(). It’s just a mess of hundreds of instructions that move data here and there and invoke methods whose names are unknown before running because their pointers are calculated dynamically:

Virtualized code is hard to decode

Conclusion

A UWP application is easy to decode, and anyone can explore the code, extract it, change and rebuild. Companies need some efficient way to stop hackers. The obfuscation is a time-tested solution.

ArmDot is a .NET obfuscator that supports a wide range of .NET applications, including UWP applications written in C#. ArmDot efficiently protects UWP applications by converting its code to a tricky set of instructions. ArmDot is easily added to the building process that makes obfuscation comfortable for developers.