Help us improve Softanics
We use analytics cookies to understand which pages and downloads are useful. No ads. Privacy Policy
Artem Razin
Low-level software protection engineer with 20+ years in native and managed code security. Creator of ArmDot, protecting commercial .NET applications since 2014.

.NET MAUI Obfuscation: Protecting Your Cross-Platform App

If you are here because Xamarin reached end-of-life in May 2024 and you are rebuilding your pipeline for .NET MAUI, this page covers exactly what changes and what stays the same for code protection. If you are building a new MAUI app and have just discovered that the compiled assembly sitting on a user's device can be decompiled, this page explains why that matters and how to address it. If you are building a MAUI app that handles backend API credentials, proprietary business logic, or sensitive data - and the binary lives on the device rather than behind a firewall - this page covers the specific configuration you need.

The threat is the same as any .NET application

A MAUI app compiles to a .NET assembly. That assembly is readable by any .NET decompiler - ILSpy, dnSpy, dotPeek. Method names, string literals, business logic, embedded credentials, license validation code - all of it is recoverable from an unprotected binary. On Android, the APK is a zip file that anyone can unpack to access the assembly directly.

This is not a MAUI-specific problem. It is the same vulnerability that affects every .NET application. What is MAUI-specific is the configuration - there are failure modes when applying obfuscation to a MAUI project that do not exist for a simple console app or class library, and they need to be addressed correctly to avoid breaking your app in subtle ways.

One pattern I notice repeatedly: developers who are careful about protecting their desktop .NET applications assume their Android app is somehow safer because it runs on a phone. It is not. If anything, the APK extraction path is more accessible - the tooling is better documented, more widely known, and requires less expertise than equivalent native reverse engineering on desktop. The sandbox protects users from malicious apps. It does not protect your code from anyone who wants to read it.

What changes from Xamarin

If you protected a Xamarin.Android or Xamarin.Forms application with ArmDot, the fundamental approach carries over to MAUI. The NuGet packages are the same - ArmDot.Client for attributes and ArmDot.Engine.MSBuildTasks for the build step. The attributes are the same. The protection techniques are the same.

What changes is the MSBuild target configuration. The Xamarin project file used a straightforward target pointing at the build output. The MAUI configuration needs to hook into the intermediate output path before the platform-specific packaging step, and needs to enable PDB generation for stack trace deobfuscation to work correctly:

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

The key differences from the Xamarin config: AfterTargets="AfterCompile" BeforeTargets="BeforePublish" rather than AfterTargets="Build", and the CreatePDB="true" flag on the assembly item. The intermediate output path approach ensures the obfuscated assembly is picked up correctly by the subsequent platform-specific build steps for all MAUI targets - Android, iOS, Windows, and macOS.

For the Xamarin blog post configuration and a side-by-side comparison of the old and new setup, see: [How to obfuscate Xamarin Applications →](routekey: BLOG.ARMDOT.AN_OBFUSCATOR_FOR_XAMARIN)

XAML data bindings: why this is usually not a problem

The most common concern raised about obfuscating MAUI apps is XAML data binding. MAUI resolves bindings like {Binding PropertyName} by looking up a property by name on the ViewModel at runtime. If the obfuscator renames that property, the binding silently fails - the UI element does not populate, no exception is thrown, and the cause is not obvious.

This sounds like a serious configuration challenge. In practice with ArmDot, it is not - because ArmDot does not rename public members by default.

ViewModel properties that are bound in XAML are almost always public. The MVVM pattern requires them to be public so the XAML engine can access them. ArmDot's default behavior is to leave public types, methods, and properties intact unless [ObfuscateNames] is applied explicitly to that member. This means that a standard MVVM MAUI app with public ViewModel properties will have its bindings work correctly after obfuscation without any special configuration.

Where you do need to be careful: if you explicitly apply [ObfuscateNames] to a ViewModel class or assembly-wide and want to protect internal implementation details, add [ObfuscateNames(Enable = false)] to any public property that is referenced in a XAML binding. The rule is simple - if a name is referenced in XAML at runtime, it must not be renamed.

The same logic applies to DI registration. builder.Services.AddSingleton<MyService>() uses the type token at the CIL level, not the type name as a string. Renaming MyService does not break this registration because the reference is a metadata token, not a string lookup. Standard MAUI DI patterns are safe.

Android-specific consideration: AOT compilation and virtualization

For Android targets, MAUI supports AOT compilation via RunAOTCompilation. When AOT is enabled, the Mono runtime pre-compiles the IL to native code before the app runs on the device. This improves startup performance but creates a specific constraint for ArmDot's control flow obfuscation.

Control flow obfuscation uses the calli CIL instruction for dispatch - a call-by-address instruction. Code virtualization's VM interpreter also uses calli. Neither can be translated to native code by the AOT compiler. If you apply [ObfuscateControlFlow] or [VirtualizeCode] to methods in a MAUI Android build with AOT enabled, the build will fail or the affected methods will not AOT-compile correctly.

The solution is to set RunAOTCompilation to false in your Android build configuration when using either technique:

<PropertyGroup Condition="$(TargetFramework.Contains('-android'))">
  <RunAOTCompilation>false</RunAOTCompilation>
</PropertyGroup>

For the highest-value methods where you want the strongest protection, [VirtualizeCode] is the right choice - but remember to disable AOT on Android when using it.

Applying obfuscation to a MAUI project

Here is a complete working configuration for a MAUI app with virtualization applied to sensitive methods:

Step 1: Add NuGet packages

Add ArmDot.Client and ArmDot.Engine.MSBuildTasks to your MAUI project.

Step 2: Add the MSBuild target

Edit your .csproj and add the target shown above before the closing </Project> tag.

Step 3: Add attributes to the methods you want to protect

using ArmDot.Client;
 
public partial class MainPage : ContentPage
{
    [VirtualizeCode]
    private void ValidateLicense(string key)
    {
        // License validation logic - fully virtualized
    }
 
    [HideStrings]
    [ObfuscateControlFlow]
    private string GetApiEndpoint()
    {
        // API endpoint - strings encrypted, control flow obfuscated
        return "https://api.internal.example.com/v2";
    }
}

Step 4: Apply assembly-wide renaming

In any source file:

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

This renames all non-public members and encrypts all string literals assembly-wide. Public members - including ViewModel properties referenced in XAML bindings - are left intact by default.

Step 5: Build and test

Build in Release configuration and test all XAML bindings, navigation, and DI-resolved services. For a complete walkthrough with screenshots showing before and after ILSpy output, see: [Protecting MAUI Apps with .NET Obfuscator →](routekey: BLOG.ARMDOT.PROTECTING_MAUI_APPS_WITH_NET_OBFUSCATOR)

App store submission

ArmDot-protected MAUI apps can be submitted to both Google Play and the Apple App Store without issues. The obfuscation runs as a build step before platform-specific packaging, so the signed and packaged output is the protected binary. The native packaging, signing, and binary structure requirements of both stores are unaffected.

What to protect in a MAUI app

The selection logic for MAUI is the same as for any .NET application, with the mobile context making certain categories more urgent:

Virtualize: license validation, serial key checking, activation logic, any algorithm with significant commercial value. These are the methods most likely to be targeted on Android where decompilation is trivial.

Control flow obfuscation + string encryption: business logic methods, API client code, authentication routines. Note the AOT constraint for Android described above.

String encryption assembly-wide: covers API endpoints, internal URLs, error messages, and any configuration that reveals backend structure. One line in the project file handles the entire assembly.

Symbol renaming assembly-wide: removes all non-public identifiers permanently. Zero performance cost. No MAUI-specific risks given ArmDot's default behavior of preserving public members.

If you are migrating from Xamarin

The summary for Xamarin migrators:

  • The NuGet packages are the same
  • The attributes are the same
  • Update the MSBuild target to use AfterTargets="AfterCompile" BeforeTargets="BeforePublish" and add CreatePDB="true"
  • Add RunAOTCompilation=false if you use control flow obfuscation or code virtualization on Android
  • Test your bindings - but if your ViewModel properties are public (which they almost certainly are), they will work without any changes

The migration is a build configuration update, not a redesign of your protection strategy.

If the MAUI migration also involved rebuilding your CI/CD pipeline, integrating obfuscation into GitHub Actions or Azure DevOps follows the same MSBuild approach described above. Dedicated guides for those environments are coming in a later section of this cocoon.

Back to: .NET Obfuscation Platforms →

Protect your MAUI app with ArmDot

ArmDot supports .NET MAUI on all four platforms - Android, iOS, Windows, and macOS - with full NuGet integration and cross-platform build support. Free trial available - protected assemblies stop working after two weeks. A single developer license is $499.