This article will walk you through how to get started using Dotfuscator to protect a Xamarin project. After you're done, the protection process will be integrated into your project, so whenever you build the project in Visual Studio or MSBuild, Dotfuscator will automatically protect it. You can apply these steps to each platform your app supports, creating an app with a proven, layered protection strategy, no matter what device it's running on.

Note: This Dotfuscator-Xamarin integration has been designed to work on Android, iOS, and the Universal Windows Platform (UWP). Other platforms, particularly Windows 8.x platforms, are not supported by these instructions.

Example App

The example for this article will be a Xamarin.Forms sample app named BugSweeper. It's a classic game, this time targeting Android, iOS, and Windows 10 devices.

BugSweeper running on Android, iOS, and Windows

How to Follow Along

There are a few ways to follow along with this article:

  1. You can download BugSweeper yourself and step through the instructions in this article.

  2. You can download the git repository we made while writing this article to see how these instructions are applied at every step.

  3. You can apply these instructions to your own app, using the article as a reference.

You can click the images in this article to view them at their full sizes.

Build the App for the First Time

First, download the BugSweeper ZIP archive from the Xamarin website and extract it to a new directory (example: C:\code\BugSweeper). You can then add the directory to local source control (such as a git repository).

The extracted BugSweeper files

Next, open the BugSweeper.sln solution file in Visual Studio 2017. Upon doing so, you'll get a warning saying that Windows 8.x projects will not load:

A Visual Studio warning indicating Windows 8.x projects can't be loaded

Click OK and finish loading the solution.

Note: You may receive another warning, stating that you need to download Universal Windows Platform (UWP) components in order to open a project in this solution. If you are not developing your Xamarin app to target UWP, then you can ignore this warning and continue through the article, ignoring instructions about the BugSweeper.UWP project.

Solution Explorer shows the projects in the solution:

The BugSweeper solution as downloaded

The solution consists of a Portable Class Library (PCL) and several platform-specific output projects that reference the PCL:

  • BugSweeper is the PCL that contains shared game logic and platform-agnostic business logic.

  • BugSweeper.Android is an output project for Android devices.

  • BugSweeper.iOS is an output project for iOS devices.

  • BugSweeper.UWP is an output project for the Universal Windows Platform (e.g., Windows 10).

    • Note that the output assembly for this project is named BugSweeper.WinUniversal.

    • This project targets build 10.0.10240.0. If you retarget it, do not target build 10.0.15063 (the Creators Update) or later. That build's reference paths are not supported by the current versions of Dotfuscator (Community Edition 5.27.0, and Professional Edition 4.28.2). Later releases of Dotfuscator will address this issue.

As indicated by the warning when opening the solution, the remaining projects (BugSweeper.Windows, BugSweeper.WinPhone, and BugSweeper.WinPhone81) do not load because Visual Studio 2017 does not support their platforms. Per the note at the top of this article, these instructions don't support those platforms either.

Remove the unsupported projects from the solution, save the solution, and remove the relevant project directories from local source control.

The solution after removing Windows 8.x projects

With the all of the supported projects loaded, test building and running the app to make sure it works as intended (and definitely not just to have an excuse to play the game during work hours).

Once you've done that, it's time to protect the app.

Setup

Before you can start integrating Dotfuscator into the Xamarin build pipeline, you need to decide what projects, and what configurations of those projects, you want to protect. Then there are some technical prerequisites: you must first install Dotfuscator and enable its command line interface, as well as download the necessary MSBuild targets file.

Select What to Protect

Visual Studio solutions consist of multiple projects, each of which produces a .NET assembly. Each project can be built in multiple configurations, such as Debug or Release.

The Dotfuscator-Xamarin protection described in this article operates on a single project/configuration combination at a time. You therefore should, at the outset, decide what projects and configurations will be protected. Here are some guidelines for choosing:

  1. You should only apply Dotfuscator to projects intended for distribution, not to internal libraries. When protecting an output project (e.g., an Android app), Dotfuscator also protects that project's copies of its dependencies (e.g., shared libraries).

  2. You should apply Dotfuscator to all releasable build configurations. You want all builds given to the public to be protected. For details on how to release a protected build, see this section at the end of the article.

  3. You should NOT apply Dotfuscator to builds meant for debugging. Dotfuscator's obfuscation makes debugging much more difficult, if not impossible, even when the source code is available.

  4. You should NOT apply Dotfuscator to builds used by team members who will not have Dotfuscator installed. When Dotfuscator is integrated into a project/configuration, Dotfuscator will become a dependency of building that project/configuration. Therefore, every machine that builds that project/configuration must have Dotfuscator installed.

For the example, we suggest protecting:

  • BugSweeper.Android in the Release configuration

  • BugSweeper.iOS in the Release, Ad-Hoc, and AppStore configurations

  • BugSweeper.UWP in the Release configuration

For future reference, we'll call these the projects and configurations to protect. When you're done, these will be your protected projects and configurations.

But what about protecting the shared code in the BugSweeper portable class library? Well, because each output project references the PCL, the unobfuscated PCL will be copied to the output project's binary directory. The integration will then obfuscate both the unobfuscated PCL copy and the unobfuscated platform-specific assembly at the same time. This way, the PCL is also protected when it is packaged within an app for the given platform.

Set Up Dotfuscator

Each machine that will be building a protected project/configuration combination will need to have Dotfuscator installed, with that installation's command line interface active.

Dotfuscator comes in two editions: the free Community Edition and the commercially-licensed Professional Edition. Your entire team should use the same edition to ensure the same level of protection no matter who builds the app.

Follow the directions in the relevant subsection for your chosen edition, for each machine that will build a protected project and configuration:

The screenshots shown throughout this article reflect Professional Edition, unless otherwise mentioned.

Set Up Dotfuscator Community Edition

Dotfuscator Community Edition (a.k.a. Dotfuscator CE) is included with all editions of Visual Studio for no additional charge, and offers basic obfuscation and protection for personal use.

Note: The Dotfuscator Community Edition license expressly prohibits use by commercial organizations for anything other than personal research and education. If you would like to use Dotfuscator on commercial projects, please consider evaluating Dotfuscator Professional Edition.

To set up Dotfuscator Community Edition:

  1. Install Dotfuscator Community Edition from Visual Studio 2017.

    • We on the Dotfuscator team periodically provide updates to Community Edition out-of-band with Visual Studio's release cycle. You can always download and install the latest Community Edition from the Dotfuscator Downloads page.
  2. Once installed, you will need to start the Community Edition user interface and then register your copy to enable the command line interface.

  3. Locate the command line interface's directory.

  4. The command line interface is dotfuscatorCLI.exe. Note the absolute path to this executable - this is the Dotfuscator CLI path, which will be needed later.

  5. You may want to verify the command line interface is active. From a command line window, run dotfuscatorCLI.exe /? and verify that the following text does not appear before the usage instructions:

    You must register Dotfuscator CE in order to execute command line builds. Run the Dotfuscator GUI which will
    explain how to register.
    

Set Up Dotfuscator Professional Edition

Dotfuscator Professional Edition is licensed for commercial use, and offers advanced obfuscation features to protect against automated decompilation. Fully-supported trials are available on request.

To set up Dotfuscator Professional Edition:

  1. Download the latest Professional Edition installer from the Dotfuscator Downloads page and run it.

  2. Once installed, you will need to run Dotfuscator from the Start Menu to register and activate your copy.

  3. Locate the directory where you installed Dotfuscator. This is normally something like C:\Program Files (x86)\PreEmptive Solutions\Dotfuscator Professional Edition 4.28.0.

  4. The command line interface is dotfuscator.exe. Note the absolute path to this executable - this is the Dotfuscator CLI path, which will be needed later.

  5. You may want to verify the command line interface is active. From a command line window, run dotfuscator.exe /? and verify that the following text does not appear before the usage instructions:

    You must register Dotfuscator Professional Edition in order to execute command line builds. Run the
    Dotfuscator GUI to enter your serial number.
    

Download the Dotfuscator-Xamarin Targets File

After installing Dotfuscator, you'll also need to download the following Dotfuscator-Xamarin MSBuild targets file:

  • PreEmptive.Dotfuscator.Xamarin.targets - Download

We recommend saving it in your app's solution directory and adding it to your local source control, as it will be required for building your projects. The path to this file is the targets file path.

For our example, we save it as C:\code\BugSweeper\PreEmptive.Dotfuscator.Xamarin.targets and add it to local source control.

Select a Project

Earlier, you decided what projects you wanted to protect. We recommend following the remaining steps in this article with one of those projects at a time. Later, you'll repeat the process for each remaining project -- we'll tell you when it's time to do this.

Our example from here on will focus on BugSweeper.Android, though we will also mention details required for iOS and UWP projects.

View the Unprotected Assembly

Before you continue, it can be helpful to learn what a reverse-engineer can see in a normal, unprotected app. After you integrate Dotfuscator into the build pipeline, you will repeat these steps to demonstrate how the app has been protected.

To view a decompiled version of your app:

  1. If you haven't already, build your project from Visual Studio.

  2. Download the .NET decompiler ILSpy.

  3. Extract the ZIP archive and run ILSpy.exe.

  4. In the ILSpy interface, open the File menu and select Open....

  5. Browse to an output assembly binary directory (example: C:\code\BugSweeper\BugSweeper\BugSweeper.Android\bin\Release) and select assemblies corresponding to your projects (example: BugSweeper.dll and BugSweeper.Android.dll).

  6. Using the code tree, explore the contents of your assemblies. Note the resemblance to the original source code: private member and local variable names are preserved, and the control flow is essentially the same.

    ILSpy showing unprotected code decompiled to easily-readable C#

  7. Close ILSpy, as it can sometimes conflict with Visual Studio's access to the assemblies on the filesystem.

Integrate Dotfuscator with your Xamarin Project

Now it's time to actually integrate Dotfuscator into your Xamarin project. You'll do this by editing your Visual Studio project file (example: BugSweeper.Android.csproj).

Import the Targets File

Each Visual Studio project file is an XML file containing MSBuild definitions. You can add the Dotfuscator-Xamarin integration to the project by importing the downloaded targets file into the project file.

To import the Dotfuscator-Xamarin MSBuild targets file:

  1. Open your app's solution in Visual Studio.

  2. In Solution Explorer, right-click on the project you want to protect (example: BugSweeper.Android) and select Unload Project.

  3. In Solution Explorer, right-click on the project again and select Edit ProjectFilename (example: Edit BugSweeper.Android.csproj).

  4. The project file appears in an XML editor.

  5. Right-click on the file's tab and select Open Containing Folder.

  6. File Explorer opens the project directory (example: C:\code\BugSweeper\BugSweeper\BugSweeper.Android).

  7. Determine the relative path from the project directory to the targets file path (example: ..\..\PreEmptive.Dotfuscator.Xamarin.targets).

  8. Return to Visual Studio and scroll to the end of the project file.

  9. Immediately before the </Project> tag, insert the following line, substituting the relative path from step 7 appropriately:

    <Import Project="..\..\PreEmptive.Dotfuscator.Xamarin.targets"/>
    

    A project file with an inserted <Import> tag

  10. Save the file.

Set MSBuild Properties

At this point, the Dotfuscator-Xamarin integration is included in the project; however, it is not enabled by default. You can enable the protection process, as well as set some additional parameters, using MSBuild properties.

MSBuild properties are defined in <PropertyGroup> tags throughout the project file. For instance, consider this excerpt:

  <PropertyGroup>
    <AssemblyName>BugSweeper.Android</AssemblyName>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <OutputPath>bin\Debug\</OutputPath>
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <OutputPath>bin\Release\</OutputPath>
  </PropertyGroup>

There are three <PropertyGroup> sections defined here: a section that's always applied, a section that's applied if the project configuration is Debug, and a section that's applied if the project configuration is Release. The output assembly name will always be BugSweeper.Android, but the path to that assembly varies between the two configurations (bin\Debug\ for a Debug configuration, bin\Release\ for a Release configuration).

Note: In addition to the Configuration property, the project file also references a Platform property. This is not the platform in the sense used in this article, but rather the device that the build is for. For instance, an iOS project defines separate <PropertyGroup>s for the Release project configuration running in an iPhone Simulator and running on an iPhone, but all builds of this project target the iOS platform:

  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhoneSimulator' ">
    <DebugType>none</DebugType>
    <CodesignEntitlements />
  </PropertyGroup>
  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' ">
    <DebugType>none</DebugType>
    <CodesignKey>iPhone Developer</CodesignKey>
  </PropertyGroup>

There are several MSBuild properties recognized by the Dotfuscator-Xamarin integration. To set them:

  1. In Visual Studio, scroll to the top of the project file.

  2. Locate a <PropertyGroup> with no Condition attribute.

  3. In this section, add the following tags:

    • <DotfuscatorXamarinCliPath>Dotfuscator CLI Path</DotfuscatorXamarinCliPath>, substituting the value for the Dotfuscator CLI path noted when setting up Dotfuscator.

    • <DotfuscatorXamarinConfigFileName>DotfuscatorConfig.xml</DotfuscatorXamarinConfigFileName>

    • <DotfuscatorXamarinGenerateNewConfigFile>true</DotfuscatorXamarinGenerateNewConfigFile>

    A project file with the inserted Dotfuscator-Xamarin properties

  4. Locate all of the <PropertyGroup> sections corresponding to configurations to protect for this project, per your earlier decision.

    • If there are multiple <PropertyGroup>s that correspond to a build configuration to protect, locate all of them. For instance, we chose to protect BugSweeper.iOS in the Release, Ad-Hoc, and AppStore configurations, so when we perform this step on that project, we would locate the sections beginning with the following tags:

      <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhoneSimulator' ">
      
      <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' ">
      
      <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Ad-Hoc|iPhone' ">
      
      <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'AppStore|iPhone' ">
      

      Note that we included both Release configuration sections: the first for an iPhone Simulator, and the second for a physical iPhone. The remaining configurations (Ad-Hoc and AppStore) only have one section each, for a physical iPhone.

    • For the example, we chose to protect the project (BugSweeper.Android) in just the Release configuration, so we locate the <PropertyGroup> section beginning with the following tag:

      <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
      
  5. In each of the sections located in step 4, add the following tag:

    • <DotfuscatorXamarinEnabled>true</DotfuscatorXamarinEnabled>

    A project file with the property that enables Dotfuscsator

  6. Save the file.

Add Dotfuscator Config File to Project

At this point in the process, the Dotfuscator config file does not exist. It will later be generated by the Dotfuscator-Xamarin integration when the protected build is first run.

However, while you're in the project file, you should take a moment to add the config file's path to the project's tracking. This way, once the config file exists, Visual Studio will take it into account when determining whether to rebuild your app or, when there's no changes, skip doing so to save time.

To make your project track the Dotfuscator config file:

  1. In Visual Studio, locate the last <ItemGroup> tag in the project file.

  2. After that tag closes, add the following:

     <ItemGroup>
       <None Include="DotfuscatorConfig.xml" />
     </ItemGroup>
    

    A project file with the Dotfuscator config file item

  3. Save the project.

Build the Project with Dotfuscator's Protection

Now you can build the project with protection enabled. To do so:

  1. In Visual Studio, close the project file.

  2. In Solution Explorer, right-click on the protected project (example: BugSweeper.Android) and select Reload Project.

  3. Using the Solution Configurations, Solution Platforms, and Startup Projects drop-downs, put your solution in a configuration that will exercise the protected build configuration (example: Release, Any CPU, BugSweeper.Android).

  4. In Solution Explorer, right-click on the protected project and select Build.

  5. When the build completes, note text similar to the following in the Output tab:

    2>  Running Dotfuscator with a new config file based on project references...
    2>  Finished running Dotfuscator with a new config file.
    2>C:\code\BugSweeper\BugSweeper\BugSweeper.Android\BugSweeper.Android.csproj : warning : A new Dotfuscator config file was generated because it did not exist: 'DotfuscatorConfig.xml'.
    

    Note that this text only appears when DotfuscatorConfig.xml doesn't exist. Later builds (that are not skipped due to all files being up-to-date) will produce different text:

    2>  Running Dotfuscator with config file 'DotfuscatorConfig.xml'...
    2>  Finished running Dotfuscator.
    

    Note that your MSBuild project build output verbosity affects the display of this text. These instructions assume the default of Minimal. If this is set to Quiet, only the initial build's warning will appear. If this is set to Normal or more detailed, additional lines of text will appear interleaved among these lines.

    If you still do not see these lines of text after adjusting the verbosity, check the following before rebuilding:

    • Ensure you are building the project whose file you modified (example: BugSweeper.Android).

    • Ensure you are building a protected configuration of that project (example: Release).

    • Ensure that Dotfuscator's command line interface is enabled (see the relevant section: for Community Edition or for Professional Edition).

    • Ensure the changes to the project file were saved by viewing the file (example: C:\code\BugSweeper\BugSweeper\BugSweeper.Android\BugSweeper.Android.csproj) in a text editor and checking for the changes made earlier in this section.

    If you see an error message:

    • If the error says the Dotfuscator process exited with an error code, you can get more information by setting the verbosity to Normal. When you rebuild, if you see an error like this:

      2>  [Build Output] Couldn't load external type because its assembly can't be found
      

      ...then see the Handle Reference Errors During Build section.

    • Ensure that an unprotected configuration rebuilds successfully. If it does not, then there is likely a problem in the regular build, not the protection step.

  6. In Solution Explorer, under the protected project, note that the DotfuscatorConfig.xml file has been added. This is the Dotfuscator config file, which determines how protection is performed on your project.

    The solution with the added Dotfuscator config file

    • Note that this default config file protects the selected project assembly (example: BugSweeper.Android.dll), as well as all assemblies derived from projects that the selected project directly references (example: the BugSweeper.dll portable class library). Other assemblies, including those derived from projects that the selected project indirectly references, are not protected by default. You can change this later.
  7. Add that new Dotfuscator config file to local source control.

  8. In Solution Explorer, right-click on the protected project and select Open Folder in File Explorer.

  9. In the project directory shown, note the presence of a new subdirectory, DotfuscatorReports. This directory contains reports generated during the obfuscation process, including the renaming map file (Renaming.xml), which indicates how code elements were renamed.

    Report files generated by Dotfuscator

  10. If not already ignored, add that new subdirectory to your local source control's ignore list.

At this point, Dotfuscator is integrated into the Xamarin build process for this project. However, you likely will still need to configure the protection to suit your app and ensure correct behavior at runtime.

Configure Renaming Exclusions

The Xamarin framework relies heavily on reflection, which assumes that names of types and members at compile time remain the same at runtime. Dotfuscator's renaming protection applies various rules to ensure proper behavior with these scenarios. As rule improvements are introduced with releases of Dotfuscator, many cases may be handled automatically, especially if you have the latest version of Dotfuscator (the latest releases are posted on the Dotfuscator Downloads page).

However, there may be some cases that Dotfuscator cannot detect. When this happens, you will have to exclude the problematic code items from renaming in order to maintain correct behavior of the app.

The exact renaming exclusions needed vary significantly from app to app. This section is divided into two subsections: one that provides the general framework for configuring renaming exclusions, and one that walks through a real-world scenario with the BugSweeper app.

General Notes

To configure Dotfuscator's renaming protection:

  1. From Visual Studio, build your output project in all protected configurations.

  2. Open Dotfuscator's user interface:

    • For Community Edition, from Visual Studio, open the Tools menu and select PreEmptive Protection - Dotfuscator.

    • For Professional Edition, run Dotfuscator Professional Edition from the Start Menu.

  3. In the user interface, open the File menu and Open the Dotfuscator config file (example: C:\code\BugSweeper\BugSweeper\BugSweeper.Android\DotfuscatorConfig.xml).

  4. Make renaming exclusions per the appropriate documentation:

  5. If you've already configured renaming exclusions for other projects in your solution, then you may want to apply similar exclusions to the project you are currently configuring.

    • For instance, let's say you are configuring BugSweeper.iOS and have already configured BugSweeper.Android (per the detailed example below). All of the exclusions made when configuring BugSweeper.Android actually apply to types defined in the BugSweeper portable class library (PCL), which is shared among all output projects. As these exclusions are in shared code, they are likely necessary for the other output projects as well. Therefore, when you start configuring BugSweeper.iOS, you can re-apply the exclusions from BugSweeper.Android.
  6. Save the config file (from the File menu).

  7. From Visual Studio, build the project again.

    • Note that, when using the Dotfuscator-Xamarin integration, you should always build from Visual Studio, not from the Dotfuscator user interface. Building from the Dotfuscator user interface will not copy the protected assemblies to locations necessary for packaging the output project.
  8. Investigate any issues by consulting device logs, exception messages, or other diagnostic tools.

  9. Repeat from step 3 to continue making renaming exclusions as appropriate until the app functions normally.

  10. Commit any changes to DotfuscatorConfig.xml to your local source control.

Detailed Example

The advice above outlines the basics of making renaming exclusions, but it helps to have an example. Here's a detailed set of steps we took to determine renaming exclusions for BugSweeper.Android on Dotfuscator Professional Edition 4.28.2. Newer versions of Dotfuscator may require fewer exclusions.

  1. When we launched the protected BugSweeper.Android app on an Android device, we were greeted with this error:

    An "app crashed" dialog box in an Android emulator

    This didn't tell us much, so we dug deeper into the issue.

  2. With the device attached for USB debugging, we opened Android device log in Visual Studio. We filtered the log down to just Error and Warning for clarity.

    Visual Studio tools menu, showing Android Device Log option

    The Android Device Log, filtered to Warnings and Errors

  3. We then tried to launch the app from the device. As it crashed, we saw a number of error messages in the device log, including one starting like this:

    Caused by: android.runtime.JavaProxyThrowable: Xamarin.Forms.Xaml.XamlParseException: Position 1:426. No method OnMainContentViewSizeChanged found on type BugSweeper.BugSweeperPage
    
  4. In Dotfuscator's user interface, we manually excluded the method XamarinApp.MainPage.OnMainContentViewSizeChanged : void(object,System.EventArgs) from renaming by checking its checkbox. We then saved the config file.

    Dotfuscator showing that the method is excluded from renaming

  5. In Visual Studio, we built and redeployed BugSweeper.Android. This time, when we launched the app, we saw the following (abridged) error in the device log:

    Caused by: android.runtime.JavaProxyThrowable: System.Exception: Can't resolve name on Element
     at Xamarin.Forms.Xaml.ReferenceExtension.ProvideValue (System.IServiceProvider serviceProvider) [0x000b6] in <cdab8e5cc6744897b152dd4075cc1cb0>:0 
    ...
     at Xamarin.Forms.Xaml.Extensions.LoadFromXaml[TXaml] (TXaml view, System.Type callingType) [0x00000] in <cdab8e5cc6744897b152dd4075cc1cb0>:0 
     at BugSweeper.BugSweeperPage.b () [0x00000] in <7a7fbb7c3c1f42e59202c72d51d629fe>:0 
     at BugSweeper.BugSweeperPage..ctor () [0x00006] in <7a7fbb7c3c1f42e59202c72d51d629fe>:0 
     at BugSweeper.App..ctor () [0x00006] in <7a7fbb7c3c1f42e59202c72d51d629fe>:0 
     at BugSweeper.Droid.MainActivity.OnCreate (Android.OS.Bundle bundle) [0x0000e] in <bf5a3ea557124c988f94e73e801e2727>:0
    ...
    
  6. Based on this stack trace, we saw that the error happened during a method named b() on type BugSweeper.BugSweeperPage. As the BugSweeper source code doesn't have a method by that name on that type, we deduced this must be an obfuscated name. We opened the renaming map file (C:\code\BugSweeper\BugSweeper\BugSweeper.Android\DotfuscatorReports\Release\Renaming.xml) and looked up the appropriate module (BugSweeper.dll), name (BugSweeper.BugSweeperPage), and method (new name of b):

    <mapping>
       <module>
           <name>BugSweeper.dll</name>
           <type>
               <name>BugSweeper.BugSweeperPage</name>
               <methodlist>
                   <method>
                       <signature>void()</signature>
                       <name>InitializeComponent</name>
                       <newname>b</newname>
                   </method>
               </methodlist>
           </type>
       </module>
    </mapping>
    

    The map file indicated the original name of this method was InitializeComponent.

  7. The InitializeComponent method is called from the BugSweeperPage constructor. The contents of the method are auto-generated, but Visual Studio allowed us to navigate to it anyway by right-clicking on the call site and selecting Go to Definition:

    Using "Go to Definition" in Visual Studio

    The method contains a number of private field lookups by compile-time name (the FindByName calls):

    [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Forms.Build.Tasks.XamlG", "0.0.0.0")]
    private void InitializeComponent() {
       global::Xamarin.Forms.Xaml.Extensions.LoadFromXaml(this, typeof(BugSweeperPage));
       mainGrid = global::Xamarin.Forms.NameScopeExtensions.FindByName<global::Xamarin.Forms.Grid>(this, "mainGrid");
       textStack = global::Xamarin.Forms.NameScopeExtensions.FindByName<global::Xamarin.Forms.StackLayout>(this, "textStack");
       timeLabel = global::Xamarin.Forms.NameScopeExtensions.FindByName<global::Xamarin.Forms.Label>(this, "timeLabel");
       board = global::Xamarin.Forms.NameScopeExtensions.FindByName<global::BugSweeper.Board>(this, "board");
       congratulationsText = global::Xamarin.Forms.NameScopeExtensions.FindByName<global::Xamarin.Forms.StackLayout>(this, "congratulationsText");
       consolationText = global::Xamarin.Forms.NameScopeExtensions.FindByName<global::Xamarin.Forms.StackLayout>(this, "consolationText");
       playAgainButton = global::Xamarin.Forms.NameScopeExtensions.FindByName<global::Xamarin.Forms.Button>(this, "playAgainButton");
    }
    

    Because Dotfuscator renames these private fields, these lookups are failing at runtime.

  8. Back in the Dotfuscator user interface, we created renaming rules to exclude fields on BugSweeper.BugSweeperPage whose type is in the Xamarin.Forms namespace. You can create this rule as follows:

    • In the Renaming exclusion editor, click Add Type.

    • In the type rule that appears, set the Name property to BugSweeper.BugSweeperPage and uncheck both Regular Expression and Exclude Type (the latter because our goal is to exclude members of this type, not the type name itself).

    • Right-click on the new type rule and select Add Field.

    • In the field sub-rule that appears, set the Name property to .* and the Signature property to Xamarin\.Forms\..*.

    • Click the Preview button and note that all field names corresponding to the BugSweeperPage controls are selected.

  9. We also excluded BugSweeper.BugSweeperPage.board manually by checking its checkbox. We then saved the config file.

    Dotfuscator showing the rule and the excluded fields

  10. In Visual Studio, we built and redeployed BugSweeper.Android. This time, when we launched the app, we saw the following error in the device log:

    Caused by: android.runtime.JavaProxyThrowable: Xamarin.Forms.Xaml.XamlParseException: Position 1:1803. No method OnBoardContentViewSizeChanged found on type BugSweeper.BugSweeperPage
    
  11. This is a very similar error to the one we saw in step 3. We decided to exclude all BugSweeperPage methods that begin with On, obsoleting the manual exclusion of OnMainContentViewSizeChanged we did in step 4. You can create this rule as follows:

    • In Dotfuscator's Renaming exclusion editor, right-click on the BugSweeper.BugSweeperPage type rule and select Add Method.

    • In the method sub-rule that appears, set the Name property to On.*.

    • Click the Preview button and note that all methods declared on BugSweeperPage whose names begin with On are selected.

  12. We then saved the config file.

    Dotfuscator showing the rule and excluded methods

  13. In Visual Studio, we built and redeployed BugSweeper.Android. This time the app ran! However, we noticed the text that reports how many bugs are left to find (e.g., "Flagged 2 out of 10 bugs.") was missing:

    BugSweeper running in an Android emulator, sans some text

  14. In our source code we noted that the BugSweeperPage.xaml file defines this text using two labels, each with a BindingContext to the board field:

    <StackLayout Orientation="Horizontal" Spacing="0" VerticalOptions="CenterAndExpand" HorizontalOptions="Center">
      <Label BindingContext="{x:Reference board}" Text="{Binding FlaggedTileCount, StringFormat='Flagged {0} '}" />
      <Label BindingContext="{x:Reference board}" Text="{Binding BugCount, StringFormat=' out of {0} bugs.'}" />
    </StackLayout>
    
  15. As we had already excluded the board field referenced here from renaming (in step 9), we decided the issue must be due to the FlaggedTileCount and BugCount properties being renamed. We excluded these manually from the Dotfuscator user interface, then saved the config file.

    Dotfuscator showing that the properties are excluded from renaming

  16. In Visual Studio, we built and redeployed BugSweeper.Android. This time the app ran, and there were no problems when playing, winning, or losing the game.

    BugSweeper running in an Android emulator

  17. We committed all changes we've made to DotfuscatorConfig.xml to local source control.

Notice that all of these exclusions apply to types in the BugSweeper portable class library (PCL), and not to the Android output project BugSweeper.Android itself. The exclusions are probably necessary for correct behavior on all output projects, not just Android. Therefore, when you repeat this article's instructions for other output projects (i.e., BugSweeper.iOS and BugSweeper.UWP) and reach this section on renaming exclusions, you can start off by applying these exclusions, rather than starting from scratch and going through this process again.

View the Protected Assembly

At this point, you can re-inspect your assemblies by repeating the steps in the View the Unprotected Assembly section. This time, your assembly should provide much less information than it previously did.

In both Community Edition, internal types, private fields, and local variables will no longer have their original names, removing semantic information about your code:

ILSpy showing protected code decompiled to C# with renamed symbols

In Professional Edition, all the protection of Community Edition applies, and control flow will also be jumbled, making the logic difficult to follow:

ILSpy showing a jumbled mess of decompiled C# code

If you're using Professional Edition, you can configure additional obfuscation features before moving on. This will create an assembly that's even harder to reverse-engineer.

Distribute the Build Changes to your Team

Now that you've protected your app during builds on your machine, you will need to add this protection step to all relevant builds done by your team.

Note: All machines that build a protected project/configuration combination must have Dotfuscator installed and activated.

Use Local Source Control

If not already done so, you should check the following items into local source control, given $ as the repository root (example: C:\code\BugSweeper\):

  • The Dotfuscator-Xamarin MSBuild file (example: $\PreEmptive.Dotfuscator.Xamarin.targets).
  • The Dotfuscator config file (example: $\BugSweeper\BugSweeper.Android\DotfuscatorConfig.xml).

You should have your source control ignore the following:

  • Any Dotfuscator report files (example: the entire directory $\BugSweeper\BugSweeper.Android\DotfuscatorReports\, or all directories named DotfuscatorReports\).

In a later step, you will push these changes to the rest of your team.

Disable Config File Generation

The Dotfuscator-Xamarin integration automatically generates a Dotfuscator config file if one isn't present. This is helpful when initially setting up the integration, but once a config file has been established, this feature can cause subtle problems.

For instance, suppose that, by accident, the Dotfuscator config file is removed from source control. Then, whenever the build server goes to build the project, it cannot find the config file, so the integration generates a new one and uses that. Any configuration customizations you've made will be lost, and your build server may be creating an app that doesn't operate correctly.

In this case, it would be preferable for the build to fail loudly, rather than just continue on with a default config file and emit a warning. That way, your team would know as soon as possible that there's something wrong with the build process, and re-add the config file to source control.

To disable Dotfuscator config file generation and cause an error if the config file is not present:

  1. Open your app's solution in Visual Studio.

  2. In Solution Explorer, right-click on the project that is being protected (example: BugSweeper.Android) and select Unload Project.

  3. In Solution Explorer, right-click on the project again and select Edit ProjectFilename (example: Edit BugSweeper.Android.csproj).

  4. The project file appears in an XML editor.

  5. Locate the <DotfuscatorXamarinGenerateNewConfigFile> tag and change its value from true to false.

  6. Save and close the project file.

  7. In Solution Explorer, right-click on the protected project (example: BugSweeper.Android) and select Reload Project.

  8. Commit your changes to the project file to local source control.

Share Source Control Changes

You can now share your local source control changes to the rest of your team (example: git push). Provided Dotfuscator is installed on your teammates' machines, builds will be protected automatically. This also applies to build machines, provided they are using Visual Studio or MSBuild to build the projects.

Apply to Other Projects

At this point, you should repeat these instructions, starting from the Select a Project section, for the remaining output projects you chose to protect.

For example, let's say we just finished protecting BugSweeper.Android. We now repeat these instructions for BugSweeper.iOS, then for BugSweeper.UWP.

Continuing Development

This section gives general advice for developing your app further once you have Dotfuscator's protection in place for your output projects.

Updating Renaming Settings

As you develop your app, you may need to adjust Dotfuscator's renaming exclusions due to new or modified code. Follow the instructions on configuring renaming exclusions as appropriate.

Updating Professional Edition Protection Settings

In addition to the renaming protection used by both editions of Dotfuscator, Dotfuscator Professional Edition also offers various other obfuscation and protection features.

The default config file generated by the Dotfuscator-Xamarin integration enables control flow obfuscation, not including control flow transforms that are incompatible with Mono (as Xamarin is based on the Mono runtime). You can also enable other protection features.

To change how Dotfuscator Professional Edition protects your projects:

  1. From Visual Studio, build the output project you want to modify, in all protected configurations.

  2. Open the Professional Edition user interface (from the Start Menu, search for Dotfuscator Professional Edition).

  3. In the user interface, open the File menu and Open the Dotfuscator config file corresponding to the project you want to modify (example: C:\code\BugSweeper\BugSweeper\BugSweeper.Android\DotfuscatorConfig.xml).

  4. On the Settings tab, on the Global Options page, enable or disable protection features under the Feature section.

    • Important: As these property names are expressed as Disable , in order to enable a feature, set its corresponding property to No.

    • Currently, the Linking feature is not compatible with this Dotfuscator-Xamarin integration.

  5. Configure the features under their various tabs.

  6. When ready to build, save the config file, then build the project from Visual Studio.

    • Note that, when using the Dotfuscator-Xamarin integration, you should always build from Visual Studio, not from the Dotfuscator user interface. Building from the Dotfuscator user interface will not copy the protected assemblies to locations necessary for packaging the output project.
  7. After you're satisfied with your changes, commit the updated config file to source control and share the changes to the rest of your team.

Protecting New Reference Assemblies

When you first integrate Dotfuscator with your Xamarin project, a Dotfuscator config file is automatically generated. This generated config file specifies which assemblies will be protected for the given output project:

  • The assembly of the output project itself (example: BugSweeper.Android.dll), and

  • All assemblies derived from other projects in the solution that the output project directly refers to (example: BugSweeper.dll, the portable class library).

There are two scenarios where this list of input assemblies may need to be manually updated:

  1. If you want to protect assemblies that are not directly referenced by the output project, but are nonetheless packaged with the app package.

  2. If you add a new project reference to the output project (the config file is not regenerated with this new reference).

Consider our example. Let's say we want to split the Tile class, and the images it refers to, out of the BugSweeper portable class library into a separate BugSweeperTile PCL. After doing so, the Tile class will no longer be protected in our BugSweeper.Android, BugSweeper.iOS, or BugSweeper.UWP builds, because it is in the new BugSweeperTile assembly. To rectify this and bring the library under protection:

  1. From Visual Studio, build your output projects in all protected configurations.

  2. Open Dotfuscator's user interface:

    • For Community Edition, from Visual Studio, open the Tools menu and select PreEmptive Protection - Dotfuscator.

    • For Professional Edition, run Dotfuscator Professional Edition from the Start Menu.

  3. In the user interface, open the File menu and Open the Dotfuscator config file (example: C:\code\BugSweeper\BugSweeper\BugSweeper.Android\DotfuscatorConfig.xml).

  4. Navigate to the Project Properties screen.

    • For Community Edition: Select Properties from the navigation tree, then select the Project Properties tab if it is not already selected.

    • For Professional Edition: Select the Settings tab, then select Project Properties from the navigation tree.

  5. Note the paths displayed for the External Property configdir and the Project Property InDir. Concatenated, this is the Dotfuscator input directory (example: C:\code\BugSweeper\BugSweeper\BugSweeper.Android\obj\Release\DotfuscatorXamarin\dfin).

  6. Navigate to the Inputs screen.

    • For CE: Select Inputs from the navigation tree.

    • For Pro: Select the Input tab.

  7. Click the Add Input button (Community Edition: Add input icon in Dotfuscator Community Edition, Professional Edition: Add input icon in Dotfuscator Professional Edition).

  8. Browse to the Dotfuscator input directory and select the assembly to add to protection (example: BugSweeperTile.dll).

  9. After adding the assembly by its absolute path, click the Edit Input button and replace the drive and directory part of the path with ${configdir}\${InDir}\ (example: ${configdir}\${InDir}\BugSweeperTile.dll).

  10. Save the config file.

  11. Repeat from step 3 for all output projects (example: C:\code\BugSweeper\BugSweeper\BugSweeper.iOS\DotfuscatorConfig.xml, then C:\code\BugSweeper\BugSweeper\BugSweeper.UWP\DotfuscatorConfig.xml).

Now, the protected builds will also protect the new assembly when packaged into an output project. You can verify this using the steps in the View the Protected Assembly section.

Handle Reference Errors During Build

Recent updates to Visual Studio have changed the way Xamarin reference assemblies are stored. This can cause an issue with older versions of Dotfuscator, where these reference assemblies cannot be found. The issue causes the integrated project to encounter an error when built from Visual Studio.

If you encounter a build error, details can be seen in detail by setting the build verbosity to Normal. The issue discussed by this section is indicated by an error such as the following:

2> [Build Output] Couldn't load external type because its assembly can't be found: Android.Content.PM.ConfigChanges,Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=84e04ff9cfb79065 (TaskId:173)

In this case, Dotfuscator cannot locate the type ConfigChanges in the Xamarin reference assembly Mono.Android. Visual Studio 2017 can locate it in its own reference assembly path, such as C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\ReferenceAssemblies\Microsoft\Framework\MonoAndroid\v6.0, but older versions of Dotfuscator only look in the common reference assembly path, such as C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\MonoAndroid\v6.0.

First, ensure you are using the latest version of Dotfuscator. Newer versions of Dotfuscator will address this issue. See the Dotfuscator Downloads page for updates to both Community Edition and Professional Edition.

Otherwise, there is a workaround as follows:

  1. Open a command prompt as an Administrator.

  2. Create a subdirectory in the common reference assembly path for assemblies defined in Visual Studio 2017:

    mkdir "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\VS2017"
    
  3. Change the active directory to the Visual Studio 2017 framework reference assembly directory. For instance, if you installed Visual Studio 2017 Professional in its default location:

    cd "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\ReferenceAssemblies\Microsoft\Framework"
    
  4. Run the following command to create directory symbolic links from the common reference assembly path to the Visual Studio 2017 reference assembly path:

    for /d %G in (*) do mklink /d "C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\VS2017\%G" "%CD%\%G"
    

An alternative workaround exists in Professional Edition only:

  1. Open the Professional Edition user interface by running Dotfuscator Professional Edition from the Start Menu.

  2. In the user interface, open the File menu and Open the Dotfuscator config file (example: C:\code\BugSweeper\BugSweeper\BugSweeper.Android\DotfuscatorConfig.xml).

  3. On the Settings tab, select User Defined Assembly Load Path from the navigation tree.

  4. Click the Add assembly load path icon ( Add Add Assembly Load Path icon in Dotfuscator Professional Edition) and add a path that Dotfuscator should probe (example: C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\Common7\IDE\ReferenceAssemblies\Microsoft\Framework\MonoAndroid\v6.0).

  5. Save the config file (from the File menu).

  6. Click the Build icon to see if Dotfuscator generates any further errors.

    • Note that since you are building from Dotfuscator, not Visual Studio, the build serves as a validation step only. In order for the protected assemblies to be packaged into your output project for running the app, you must build from Visual Studio.
  7. Repeat from step 4 for all necessary directories.

Releasing your Protected App

When you are ready to release your app:

  1. Build and package your app as normal for each output project.

  2. Copy the report files from each project's DotfuscatorReports\ directory to a secure location associated with the release and the project. Do NOT distribute these files to end-users; they contain information that can be used to reverse the renaming obfuscation, among other things.

    • For example, if we release version 2.0 of BugSweeper, we may archive the contents of C:\code\BugSweeper\BugSweeper\BugSweeper.Android\DotfuscatorReports to \\company_network_share\release_artifacts\BugSweeper\2.0\Android\DotfuscatorReports.
  3. Release your app as normal.

  4. When troubleshooting issues with a stack trace, use the archived Renaming.xml file to determine the original source code names of code elements that were renamed during obfuscation.

Further Reading

We on the Dotfuscator team are always adding new features to improve the protection provided by Dotfuscator, as well as making the configuration process easier. Stay up-to-date with the latest Dotfuscator version by visiting the Dotfuscator Downloads page. For announcements and other information, keep an eye on our blog and follow our Twitter account, @PreEmptive.