As Android app packages are just ZIP files that contain the assemblies and metadata, the assemblies can easily be decompiled. As a result, we need a way to make it very difficult to understand the decompiled code, or even prevent it from happening altogether.
In this recipe, we will make use of the Babel for .NET obfuscator (https://babelfor.net/products/obfuscator). This is a commercial product, but they do have a demo version that can be used for free. The demo version does have a few limitations, and the obfuscated apps only run for a period of time, however this does not prevent us from using it.
There aren't many Mac/Linux obfuscators for .NET, and Babel for .NET is no different. We will also need a Windows machine to use the obfuscator, but we can use the MSBuild task to integrate with the build process for Xamarin.Android. As the MSBuild task does not have any specific IDE requirements, it will work with Xamarin Studio or Visual Studio.
Once we have installed Babel for .NET, we add the MSBuild task to our project file:
<UsingTask>
element into the project file as a child of the <Project>
element:<Project> <UsingTask TaskName="Babel" AssemblyName="Babel.Build, Version=8.0.0.0, Culture=neutral, PublicKeyToken=138d17b5bd621ab7" /> </Project>
<Babel>
task element as a child of the <Target Name="AfterBuild">
element:<Target Name="AfterBuild"> <Babel InputFile="$(TargetPath)" OutputFile="$(TargetPath)" VerboseLevel="5" ObfuscateTypes="true" ObfuscateEvents="true" ObfuscateMethods="true" ObfuscateProperties="true" ObfuscateFields="true" VirtualFunctions="true" FlattenNamespaces="false" UnicodeNormalization="false" SuppressIldasm="true" ControlFlowObfuscation= "goto=on;if=on;switch=on;case=on;call=on" ILIterations="5"/> </Target>
Release
configuration, we can use the Condition
attribute:<Babel Condition="'$(Configuration)'=='Release'" ... />
Some types and members shouldn't be obfuscated. This can be because we are using reflection or serialization to access them. As obfuscation might rename a type to something that we cannot use, we need tell the obfuscator to ignore or skip certain types and members. We do this by performing the following steps:
BabelRules.xml
:<?xml version="1.0" encoding="UTF-8"?> <Rules xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance version="2.0"> <Rule name="Serializable types" exclude="true"> <Target>Fields, StaticFields</Target> <Pattern><![CDATA[*]]></Pattern> <HasAttribute onEnclosingType="true"> System.SerializableAttribute </HasAttribute> <Description> Skip fields of serializable types. </Description> </Rule> </Rules>
RulesFiles
attribute to the babel build task, setting it to be the name of the rules file:<Babel RulesFiles="BabelRules.xml" ... />
Even though we add licensing checks to ensure that unauthorized users cannot access our app, they might be able to tamper with the package to bypass the license checks. It is not possible to avoid this completely, but we can make it much more difficult to do so.
There are several ways in which we can prevent or detect tampering, and one of them is to obfuscate the assemblies. Obfuscation removes unused code and renames types and members with semantically obscure names. It can also perform control flow obfuscation, which produces spaghetti logic that can be very difficult for a cracker to analyze. The result is a smaller sized app package file that is more difficult to reverse engineer. As almost all the app logic resides in the managed .NET assemblies, this is what we must obfuscate.
One such obfuscator that we can use, is babel for .NET. This obfuscator is great for Xamarin.Android projects because it provides a MSBuild task, which we use to easily integrate with the build process. There aren't many good obfuscators for non-Windows machines, and babel for .NET is one of the many that only run on Windows. But, it does work with both Xamarin Studio and Visual Studio as a result of its integration with MSBuild.
In order to integrate babel for .NET into our app's build, we do have to modify our project file manually in two places. The first is to add a reference to the build task that we want to include. This requires us to add the <UsingTask TaskName="Babel">
element as a child of the <Project>
element. This new element requires us to provide either the GAC-registered assembly name or the file path to the Babel.Build assembly. We provide the GAC-registered assembly name using the AssemblyName
attribute, or the file path to the assembly using the AssemblyFile
attribute.
The next place we modify the project file is in the <Target Name="AfterBuild">
element. This target executes directly after the assembly is compiled, but before the app is packaged. In this element, we add a <Babel>
child element and set the various obfuscator properties using attributes.
The two most important properties are set using the InputFile
and OutputFile
attributes. For both attributes, we must use the $(TargetPath)
variable. This variable points to the final output file of the compiled project. The obfuscator will load the input file and then save the obfuscated file back in the same place. We need to do this in order to preserve the existing build process.
We can also specify that the obfuscation only occurs when building with the release configuration. To do this, we set the Condition
attribute to be the value "'$(Configuration)'=='Release'"
. This makes use of the project variables and will only include the babel task when the Configuration
variable's value is Release
.
The remainder of the attributes are used to set which assemblies are obfuscated and how it is done. Some attributes, such as UnicodeNormalization
and FlattenNamespaces
, do not work well with Xamarin.Android and must be set to false
. For example, if we enable UnicodeNormalization
, then the types will be renamed with Unicode characters, which is not valid for the generated Java types or file names.
There might be instances where the obfuscation process renames a type or member that is used in reflection or serialization. At run time, the app will crash as it will not be able to find that type or member. In this case, we tell the obfuscator to ignore these types or members using the rules file.
Once we have the rules file created, we specify that we want to use it by specifying the path to the file using the RulesFiles
attribute. Rule files are just XML files that follow a specific structure.