In my previous post I detailed how to decompile .NET code and explained how this was possible given the nature of the .NET platform. This post will walk you through how to unpack and decompile android mobile applications built on the Xamarin platform. Additionally I will also talk about a utility app I have created to automate the process. The procedure outlined in this guide will assist you in analysing and reviewing code inside Xamarin android applications.
If you have never heard of Xamarin it is an open-sourced platform for building Android and iOS apps with .NET and C#. For this post I’m going to focus on applications built with Xamarin for the Android OS. You can read more about Xamarin here for a deeper dive. Don’t get too invested in it though as Microsoft is looking to merge Xamarin.iOS and Xamarin.Android as apart of the .NET unification process with .NET 6.
In order to demonstrate I’ve created a example Xamarin android app which you can clone from my blog demos repo. Build the
android-xamarin-MobileApp project and test it works on your mobile device.
Let us start with a brief understanding on how these android apps are built. Similar to my explanation on how the .NET platform works below is a diagram that demonstrates that build process with Xamarin.
Essentially your source code gets compiled into CIL (IL) instructions that are stored in Dynamic Link Library (DLL) managed assemblies. The android package builder then bundles these along with app resources and other data into an APK package. The package then can be installed either ad-hoc via sideloading or deployed to an app distribution platform such as the Google Play Store.
It is important to note that there are various options and settings that can influence what the APK contains. For example, when you are building you may select the option shown in the diagram below to bundle assemblies into native code. The tool tip displayed in Visual Studio suggests this option protects the managed assemblies from examination but you will soon see that this not so true.
To explain why this impacts the unpacking process I’ve created a diagram that shows the differences focusing on the steps that occur between the package bundler and the final output APK.
This option is the default when building an app. The package builder will utilize LZ4 Compression and create files with the .dll extension. These files contain the compressed assemble with a custom header file that consists of a magic identifier ‘XALZ’, an index into the internal assembly descriptor table and the uncompressed size of the assembly. The files will then be put into a directory titled assemblies and packaged into the APK.
When selecting the “bundles assemblies into native code” option, the package builder will utilize another compression algorithm, this time using GZip to compress each of the assemblies into the data segment inside the shared library titled
libmonodroid_bundle_app.so. The shared library is then stored inside the
lib directory which is packaged into the final APK.
You can probably observe that the unbundled build will be fairly straight forward to unpack. The latter however requires a little more effort. First we must parse the shared library object then decipher where the data is stored and extract this.
My approach to unpacking these libraries consists of two parts.
Firstly an APK can contain libraries targeting different architectures. These are organized in the the
lib directory like:
I’ll be explaining the process with the the ARM64 version however the exact same process applies for ARM. Having a look at the
libmonodroid_bundle_app.so inside IDA we can see that the
.data section contains a list of RVA references to each of the packed assemblies. These actually point to a data structure we will call ‘bundle entry’.
Each bundle entry structure looks like this:
This is sufficient enough to provide us the name of each file along with compression sizes both packed/unpacked. The data pointer field however inside the file is empty, this is because at runtime that is dynamically populated by the system image loader. Although we can resolve this with some work a simpler option is to find the matching compressed segment.
This brings us to the second part which involves searching for the GZIP streams inside the
.rodata section. We can achieve this by searching the magic numbers
0x8b which make up the first part of the 10-byte GZIP header. This method however may present false positives as there could be data anywhere in there containing a
0x8b sequence so we need to check the uncompressed size present at the tail end of a GZIP stream which is a 4byte unsigned integer. If the uncompressed size present in the current bundle entry matches the one at the end of the found GZIP stream then we have a match.
Luckly for you I’ve created a small CLI tool that does this all for you. All you need to do is extract the APK package with an archive tool such a 7Zip, provide the tool with either the assemblies directory or the location of the
libmonodroid_bundle_app.so and the tool will take care of the rest.
- Example usage for an “Unbundled” build
XamAsmUnZ -dir "C:\com.cihansol.mobileapp\assemblies"
- Example usage for a “Bundled” build
XamAsmUnZ -elf "com.cihansol.mobileapp\lib\arm64-v8a\libmonodroid_bundle_app.so"
The output binaries can then be viewed inside a decompiler like dnSpy to inspect code and logic.
Unpacking Xamarin android apps are trivial and this can be useful for code analysis and reviews. Within this blog post, I have demonstrated how Xamarin apps are built and packaged for the Android environment. Through a personal outline I have shown the procedure involved in unpacking .NET assemblies which can later be decompiled back into source code. The significance of this blog post highlights that it is very easy to dissect mobile applications built on the Xamarin platform. We can see that the bundling option available through the build settings does not provide the security it is perceived to perform.