These instructions 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.
How Dotfuscator Integrates into the Build Process
Before starting, it is helpful to have an understanding of where Dotfuscator inserts itself into the build system. The following diagrams give a broad overview of the various platforms in a Xamarin solution.
Example App
The example for these instructions will be a Xamarin.Forms sample app named BugSweeper. It's a classic game, this time targeting Android, iOS, and Windows 10 devices.
How to Follow Along
There are two ways to follow along with these instructions:
-
You can download the git repository we made while writing these instructions to see how these instructions are applied.
-
You can apply these instructions to your own app, using this as a reference.
You can click the screenshots in these instructions to view them at their full sizes.
Build the App for the First Time
First, clone the protected-bugsweeper repository and open the BugSweeper.sln
solution file in Visual Studio.
BugSweeper.UWP
project.Solution Explorer shows the projects in the solution:
The solution consists of two .Net Standard libraries and several platform-specific output projects that reference the BugSweeper Library:
-
BugSweeper
is the .NET Standard library that contains shared game logic and platform-agnostic business logic. It referencesBugSweeperTile.dll
. -
BugSweeper.Android
is an output project for Android devices. -
BugSweeper.iOS
is an output project for iOS devices.- Note that the output assembly for this project is named
FormsTemplateiOS
.
- Note that the output assembly for this project is named
-
BugSweeper.UWP
is an output project for the Universal Windows Platform (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 build10.0.15063
(the Creators Update) or later if you are using a version of Dotfuscator Community prior to 5.30.0. That build's reference paths are not supported by the earlier versions of Dotfuscator. Later releases of Dotfuscator have addressed this issue.
-
-
BugSweeperTile
is a .NET Standard library that contains the code ofTile.cs
. This library separates code out from theBugSweeper
library into a new library to demonstrate indirect references being protected by Dotfuscator.
Test building and running the app to make sure it works as intended on each platform (and definitely not just to have an excuse to play the game during work hours). This way later on you'll be able to recognize if the default Dotfuscator protection needs to be configured because it breaks or changes the app.
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 these instructions 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:
-
You should only apply Dotfuscator to projects intended for distribution, not to internal libraries. When protecting an output project (such as an Android app), Dotfuscator also protects that project's copies of its dependencies (such as shared libraries).
-
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 page.
-
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.
-
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
library? Well, because each output project references the BugSweeper
library, the unobfuscated BugSweeper
library will be copied to the output project's binary directory. The integration will then obfuscate both the unobfuscated library copy and the unobfuscated platform-specific assembly at the same time. This way, the BugSweeper
library is also protected when it is packaged within an app for the given platform. Similarly, BugSweeperTile
is a project reference of BugSweeper
library so it will also automatically be protected by Dotfuscator at the same time.
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.
To set up Dotfuscator
-
For help installing see the installation page.
-
Once, installed you need to register your copy of Dotfuscator Community.
-
Locate the command line interface's directory by following the instructions found here.
-
Note the absolute path to the command line interface executable (
dotfuscator.exe
) - this is the Dotfuscator CLI path, which will be needed later. -
You may want to verify that the command line interface is active. From the command line window, run the
dotfuscator.exe -?
.You must register Dotfuscator in order to execute command line builds. Run the Dotfuscator GUI which will explain how to register.
Download the custom set of MSBuild Targets and Tasks for Dotfuscator
After installing Dotfuscator, you'll also need to download the following custom MSBuild targets and tasks for Dotfuscator which include:
PreEmptive.Dotfuscator.Xamarin.targets
PreEmptive.Dotfuscator.Internal.Tasks.dll
We recommend extracting the zip file into your app's solution directory and adding it to your local source control, as it will be required for building your projects. For our example, we save it in the directory C:\code\protected-bugsweeper\PreEmptive.Dotfuscator.Xamarin\
and add it to local source control. The path to PreEmptive.Dotfuscator.Xamarin.targets
will be referred to later in the instructions as the targets file path.
Select a Project
Earlier, you decided what projects you wanted to protect. We recommend following the remaining steps in these instructions 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:
-
If you haven't already, build your project from Visual Studio.
-
Download the .NET decompiler ILSpy.
-
Extract the ZIP archive and run
ILSpy.exe
. -
In the ILSpy interface, open the File menu and select Open....
-
Browse to an output assembly binary directory (example:
C:\code\protected-bugsweeper\BugSweeper\BugSweeper.Android\bin\Release
) and select assemblies corresponding to your projects (example:BugSweeper.dll
andBugSweeper.Android.dll
). -
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.
-
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
).
The following steps will help you import the targets, set the required properties, and add the config file to the project. You will end up with a project file that looks like this:
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:
-
Open your app's solution in Visual Studio.
-
In Solution Explorer, right-click on the project you want to protect (example:
BugSweeper.Android
) and select Unload Project. -
In Solution Explorer, right-click on the project again and select Edit ProjectFilename (example: Edit BugSweeper.Android.csproj).
-
The project file appears in an XML editor.
-
Right-click on the file's tab and select Open Containing Folder.
-
File Explorer opens the project directory (example:
C:\code\protected-bugsweeper\BugSweeper\BugSweeper.Android
). -
Determine the relative path from the project directory to the targets file path (example:
../../PreEmptive.Dotfuscator.Xamarin/PreEmptive.Dotfuscator.Xamarin.targets
). -
Return to Visual Studio and scroll to the end of the project file.
-
Immediately before the
</Project>
tag, insert the following line, substituting the relative path from step 7 appropriately:<Import Project="../../PreEmptive.Dotfuscator.Xamarin/PreEmptive.Dotfuscator.Xamarin.targets"/>
-
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.
There are several MSBuild properties recognized by the Dotfuscator-Xamarin integration. To set them:
-
In Visual Studio, on the line above where you imported the project file, create a
<PropertyGroup>
with noCondition
attribute. -
In this new
<PropertyGroup>
section, add the following tags:-
Set the name of the Dotfuscator config file:
<DotfuscatorXamarinConfigFileName>DotfuscatorConfig.xml</DotfuscatorXamarinConfigFileName>
-
Set the path to the Dotfuscator CLI. Make sure to Substitute the value for the Dotfuscator CLI path noted when setting up Dotfuscator:
<DotfuscatorXamarinCliPath>Dotfuscator CLI Path</DotfuscatorXamarinCliPath>
-
Enable generation of a new config file since one does not exist yet:
<DotfuscatorXamarinGenerateNewConfigFile>true</DotfuscatorXamarinGenerateNewConfigFile>
-
Enable Dotfuscator to run for the configurations selected in the select what configurations to protect step
<!-- Enable Dotfuscator for Release --> <DotfuscatorXamarinEnabled Condition="'$(Configuration)' == 'Release'">true</DotfuscatorXamarinEnabled> <!-- Enable Dotfuscator for Ad-Hoc (only needed for iOS) --> <DotfuscatorXamarinEnabled Condition="'$(Configuration)' == 'Ad-Hoc'">true</DotfuscatorXamarinEnabled> <!-- Enable Dotfuscator for AppStore (only needed for iOS) --> <DotfuscatorXamarinEnabled Condition="'$(Configuration)' == 'AppStore'">true</DotfuscatorXamarinEnabled>
-
-
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:
-
In Visual Studio, locate the last
<ItemGroup>
tag in the project file. -
After that tag closes, add the following:
<ItemGroup> <None Include="DotfuscatorConfig.xml" /> </ItemGroup>
-
Save the project.
Build the Project with Dotfuscator's Protection
Now you can build the project with protection enabled. To do so:
-
In Visual Studio, close the project file.
-
In Solution Explorer, right-click on the protected project (example:
BugSweeper.Android
) and select Reload Project. -
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
). -
In Solution Explorer, right-click on the protected project and select Build.
-
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\protected-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: Setting up Dotfuscator .
-
Ensure the changes to the project file were saved by viewing the file (example:
C:\code\protected-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.
-
-
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.- 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 and indirectly references (example: theBugSweeper.dll
library and theBugSweeperTile.dll
library).
- Note that this default config file protects the selected project assembly (example:
-
Add that new Dotfuscator config file to local source control.
-
In Solution Explorer, right-click on the protected project and select Open Folder in File Explorer.
-
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. -
If not already ignored, add that new subdirectory to your local source control's ignore list.
Configure Renaming Exclusions
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 run time.
For iOS, you should be able to run the BugSweeper app with the default protection applied; however, you should notice that when you flag a bug in the app the flagged count displayed does not update. This means that a renaming exclusion is needed.
Similarly, if you try to run BugSweeper on Android or UWP at this point, you will notice it won't run, because renaming exclusions are needed.
For this sample, the following exclusions are necessary:
- Android:
-
FlaggedTileCount
property -
BugCount
property -
BugSweeperPage
type -
board
field
-
- iOS:
-
FlaggedTileCount
property
-
- UWP:
-
BugSweeper.Board
type -
board
field -
InitializeComponent
method -
OnBoardContentViewSizeChanged
method -
OnMainContentViewSizeChanged
method -
OnplayAgainButtonClicked
method
-
See the renaming exclusion example page for details on figuring out renaming exclusions for a project.
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.
Internal types, private fields, and local variables will no longer have their original names, removing semantic information about your code:
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:
-
Open your app's solution in Visual Studio.
-
In Solution Explorer, right-click on the project that is being protected (example:
BugSweeper.Android
) and select Unload Project. -
In Solution Explorer, right-click on the project again and select Edit ProjectFilename (example: Edit BugSweeper.Android.csproj).
-
The project file appears in an XML editor.
-
Locate the
<DotfuscatorXamarinGenerateNewConfigFile>
tag and change its value fromtrue
tofalse
. -
Save and close the project file.
-
In Solution Explorer, right-click on the protected project (example:
BugSweeper.Android
) and select Reload Project. -
Commit your changes to the project file to local source control.
Enhance Protection
Dotfuscator offers default protection settings when you first integrate it into a project. However, Dotfuscator protection can be made much stronger than these defaults.
Add Root Checks
Root Checks can be added to BugSweeper.Android
to prevent the app from running on rooted devices. The Root Check in this sample is placed on the OnSingleTap
method and configured to make the app exit when that method is called on rooted devices. After configuring the root check, you can test BugSweeper on a rooted device or emulator and see that when you try to flag a bug, the app will now exit. See our Protected-TodoAzureAuth sample for more detail on adding Root Checks to Xamarin apps.
Turn off Library mode
The Renaming obfuscation of the BugSweeper can be made stronger by turning off library mode. In Dotfuscator, on the Inputs tab, uncheck the library mode property for BugSweeper.dll
and BugSweeperTile.dll
. If you save the config, build in Visual Studio, and run the app, you will see that another renaming exclusion is needed after disabling library mode on these assemblies for the iOS and UWP platforms. Exclude the type BugSweeper.BugSweeperPage
and the app should now run correctly the next time you build.
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.
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\protected-bugsweeper\
):
- The Dotfuscator-Xamarin MSBuild custom targets and task (example:
$\PreEmptive.Dotfuscator.Dotfuscator.Xamarin\PreEmptive.Dotfuscator.Xamarin.targets
and$\PreEmptive.Dotfuscator.Dotfuscator.Xamarin\PreEmptive.Dotfuscator.Internal.tasks.dll
). - 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 namedDotfuscatorReports\
).
In a later step, you will push these changes to the rest of your team.
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.
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 and indirectly refers to (example:
BugSweeper.dll
is a direct reference ofBugSweeper.Android.dll
, andBugSweeperTile.dll
is an indirect reference becauseBugSweeper.dll
references it).
There is one scenario where this list of input assemblies may need to be manually updated: If you add a new project reference to the output project (the config file is not regenerated with this new reference).
To add a new input, follow the following process:
-
From Visual Studio, build your output projects in all protected configurations.
-
Open the Tools menu and select PreEmptive Protection - Dotfuscator.
-
In the user interface, open the File menu and Open the Dotfuscator config file (example:
C:\code\protected-bugsweeper\BugSweeper\BugSweeper.Android\DotfuscatorConfig.xml
). -
Select Properties from the navigation tree, then select the Project Properties tab if it is not already selected.
-
Note the paths displayed for the External Property
configdir
and the Project PropertyInDir
. Concatenated, this is the Dotfuscator input directory (example:C:\code\protected-bugsweeper\BugSweeper\BugSweeper.Android\obj\Release\DotfuscatorXamarin\dfin
). -
Select Inputs from the navigation tree.
-
Click the Add Input button
.
-
Browse to the Dotfuscator input directory and select the assembly to add to protection (example:
NewBugSweeperProject.dll
). -
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}\NewBugSweeperProject.dll
). -
Save the config file.
-
Repeat from step 3 for all output projects (example:
C:\code\protected-bugsweeper\BugSweeper\BugSweeper.iOS\DotfuscatorConfig.xml
, thenC:\code\protected-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 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 versions 2017 and later can locate it in their own reference assembly paths, 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 address this issue. See the Dotfuscator Downloads page for updates.
Otherwise, there is a workaround as follows:
-
Open the Dotfuscator user interface by opening the Tools menu and selecting PreEmptive Protection - Dotfuscator.
-
In the user interface, open the File menu and Open the Dotfuscator config file (example:
C:\code\protected-bugsweeper\BugSweeper\BugSweeper.Android\DotfuscatorConfig.xml
). -
Click the Settings node and select the Assembly Load Paths setting tab.
-
Click the Add assembly load path icon (
) 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
). -
Save the config file (from the File menu).
-
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.
-
Repeat from step 4 for all necessary directories.
Releasing your Protected App
When you are ready to release your app:
-
Build and package your app as normal for each output project.
-
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 ofC:\code\protected-bugsweeper\BugSweeper\BugSweeper.Android\DotfuscatorReports
to\\company_network_share\release_artifacts\BugSweeper\2.0\Android\DotfuscatorReports
.
- For example, if we release version 2.0 of
-
Release your app as normal.
-
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.