Recently I stumbled upon an Android app that didn’t work as I expected it to. So, I had no choice but to examine its viscera!
I launched the latest version of GDA, opened the studied APK, and immediately noticed that it looks somewhat suspicious. Classes of all activities contain approximately the same boilerplate code looking something like this:
public class MainActivity extends BaseActivity{ private ArrayList refList; public static final String __md_methods; static { MainActivity.__md_methods = "n_onCreate:\(Landroid/os/Bundle;\)V:GetOnCreate_Landroid_os_Bundle_Handler ..... _ILandroid_os_Bundle_Handler:Android.Locations.ILocationListenerInvoker, Mono.Android, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null\n"; Runtime.register("Megaprogram.Activity.MainActivity, Megaprogram", MainActivity.class, MainActivity.__md_methods); } public void MainActivity(){ super(); if (this.getClass() == MainActivity.class) { Object[] objArray = new Object[0]; TypeManager.Activate("Megaprogram.Activity.MainActivity, Megaprogram", "", this, objArray); } return;
This makes me thinking that something is wrong with this APK. Is it some kind of framework?.. If you change the .
extension to .
, then the assemblies
folder immediately catches your eye: first, such folders are uncommon for ordinary mobile apps; and second, it contains numerous DLL libraries.
Names of many libraries contain the word Xamarin; so, it becomes clear where such uniformity comes from: the main program code is written in C# and is located in a DLL library; only boilerplate pieces of code designed for interaction between the Mono runtime and the Android Runtime (ART) virtual machine are written in Java.
Enough theory, let’s get to work!
Selecting tools
When I deal with .Net, I always use three tools.
-
dotPeek by JetBrains. The program makes it possible to decompile and examine
.
anddll .
files. In my opinion, dotPeek offers the most handy navigation through the decompiled code, which makes it the optimal tool for algorithm analysis.exe -
dnSpy is used to decompile, edit, compile, and debug .Net assemblies. It must be noted that not all of its functions always work (except for decompiling): much depends on the specific situation. In my case, the compilation function didn’t work: the program was unable to link together the Mono and Android namespaces.
-
Simple Assembly Explorer is a rather old, but still efficient utility making it possible to decompile
.
anddll .
into C# or CIL code (Common Intermediate Language is a ‘high-level assembler’ used by the .NET virtual machine – an intermediate language developed by Microsoft for the .NET Framework platform). Most importantly, this tool can compile CIL code, which enables you to easily make changes in the studied files.exe
Instead of the standard apktool, I am going to use 7z to unpack and pack the APK. A bit later, I will explain why.
Unpacking the APK
Initially, I tried to use the well-known apktool to unpack the APK, but encountered problems with subsequent packing: too bad, apktool ‘doesn’t know’ such a file type as .
and doesn’t consider it a standard file type for an APK. Only files whose names are present in the array below are considered standard ones:
private final static String[] APK_STANDARD_ALL_FILENAMES = new String[] {"classes.dex", "AndroidManifest.xml", "resources.arsc", "res", "lib", "libs", "assets", "META-INF" };.
When an APK is assembled, all unknown files are compressed (compression type: DEFLATED), while Xamarin expects its DLLs to be uncompressed (STORED) and cannot read them properly. My first idea was to fix and rebuild apktool, but then I decided to take an easier way: unpack and pack files without compression using a standard archiver. After all, I don’t need to decode the manifest or get the Smali code.
Unpacking:
7z.exe x program.apk -oprogram_apk
After unpacking, in addition to typical APK files, I see the assemblies
directory with a bunch of dll
files inside. Out of them, the library whose name matches the name of the app is of special interest. Let’s dissect it.
Patching .Net
Analysis of the DLL and identification of the place to be modified is beyond the scope of this article (frankly speaking, this topic requires a separate book); so, I will focus only on technical aspects. As said above, dnSpy refused to compile the modified library, and I had to use Simple Assembly Explorer (SAE) for this purpose.
Let’s say I need a given function to always return true
:
protected bool IsOnline(){ ConnectivityManager connectivityManager = (ConnectivityManager)this.GetSystemService#0x0a0001d5("connectivity"); if (connectivityManager == null) { return false; } NetworkInfo activeNetworkInfo = connectivityManager.get_ActiveNetworkInfo#0x0a0002ad(); return activeNetworkInfo != null && activeNetworkInfo.get_IsConnected#0x0a0002ae();}
Since modifications can only be made in IL code, I switch to the Details tab in the SAE window and see the following picture:
0 L_0000: ldarg.0
1 L_0001: ldstr "connectivity"
2 L_0006: callvirt Java.Lang.Object Android.Content.Context::GetSystemService(System.String)
3 L_000b: castclass Android.Net.ConnectivityManager
4 L_0010: stloc.0
5 L_0011: ldloc.0
6 L_0012: brtrue.s 9 -> ldloc.0
7 L_0014: ldc.i4.0
8 L_0015: ret
9 L_0016: ldloc.0
10 L_0017: callvirt Android.Net.NetworkInfo Android.Net.ConnectivityManager::get_ActiveNetworkInfo()
11 L_001c: stloc.1
12 L_001d: ldloc.1
13 L_001e: brfalse.s 17 -> ldc.i4.0
14 L_0020: ldloc.1
15 L_0021: callvirt System.Boolean Android.Net.NetworkInfo::get_IsConnected()
16 L_0026: ret
17 L_0027: ldc.i4.0
18 L_0028: ret
I have two options:
- Take a fundamental approach: learn the CIL language and write the required code myself; or
- Write the required function in C# and compile it to CIL, thus, getting the required code automatically.
I chose the second option since it’s much easier and faster. Furthermore, I found a very useful site sharplab.io where you can convert code from C# to CIL.
On the left tab, I enter:
bool function() { return true;}
On the right tab, I see among other things:
IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: stloc.0
IL_0003: br.s IL_0005
IL_0005: ldloc.0
IL_0006: ret
I insert the obtained code into the library using Simple Assembly Explorer and save the modified DLL. Time to assemble a new APK (provided that I did everything correctly).
Rebuilding the APK
As said above, I am going to use 7z in the uncompressed mode for building. The APK built this way will be larger than the original one, but in this particular case, its size doesn’t matter:
7z.exe a -tzip -mx0 -r0 program_patched_unalign_unsigned.apk .\program_apk\*.*
FYI: -tzip
is the archive format, -mx0
means no compression, and -r0
means recursive traversal of all subdirectories.
Prior to the building, I delete the META-INF
directory containing the old signature. It’s not needed anymore because I have to sign the APK myself. Then I have to create a signing certificate and copy it to the keystore. If you already have a certificate, you can skip this step. I create a certificate using the keytool utility from the JDK:
"c:\Android\Android Studio\jre\bin\keytool.exe" -genkey -v -keystore keys.keystore -alias key -keyalg RSA -keysize 2048 -validity 10000
As usual, it asks the standard questions:
Enter keystore password:
Re-enter new password:
What is your first and last name?
[
What is the name of your organizational unit?
[
What is the name of your organization?
[
What is the name of your City or Locality?
[
What is the name of your State or Province?
[
What is the two-letter country code for this unit?
[
Is CN=x, OU=x, O=x, L=x, ST=x, C=x correct?
[
Generating 2 048 bit RSA key pair and self-signed certificate (SHA256withRSA) wi
th a validity of 10 000 days
for: CN=x, OU=x, O=x, L=x, ST=x, C=x[
After that, I proceed to signing:
jarsigner.exe -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore keys.keystore -signedjar program_pathed_unalign.apk program_pathed_unalign_unsigned.apk key
The newly-created file, program_pathed_unalign.
, is almost ready for installation. ‘Almost’ because it must be first aligned using the zipalign program from Android SDK Build-Tools. This procedure ensures that all uncompressed files in the archive are aligned relative to the file beginning. This, in turn, makes it possible to access files directly, without copying the data to RAM, which reduces the app’s memory usage.
Aligning:
zipalign.exe -v 4 program_pathed_unalign.apk program_pathed.apk
That’s it! Now I can safely install the program on a phone or emulator and start testing it.
Conclusions
As you can see, the analysis of Xamarin assemblies is no more difficult in comparison with native Android OS apps; you just need to take into account some APK building aspects.
Ka tome