on in Android
Breaking an Android app to fix it
I planned to go to a gym today - I packed, prepared and hurried to my bus stop. However, when I tried to open First mTicket app, I was greeted with the following morbid screen. After trying all possible combinations of device reboots and application force stops, I faced reality - I am not getting on a bus today. As customer service in First does not work over weekend, I was basically stuck without transport till Monday evening in the best case. Buying daily/weekly tickets is unfortunately not an option for me now. So, given a lot of free time that I got from a night without gym - I sat down determined to figure out why the app keeps crashing on the startup.
I connected my phone to my laptop and brought up the Android system log
adb logcat). The following exception was staring back at me every time I
attempted to start an app (dull bits cut out).
Well, I got lucky pretty much like three times in here! First of all, there is no obfuscation on the executable whatsoever. Secondly, it runs on the MonoDroid (a.k.a. Xamarin.Android) platform, which I am really familiar with. Finally, the exception seems quite easy to prevent. So, let’s get to it then! We will get the app, decompile it and find out why this exception is thrown.
I used Online Google Play APK Downloader to quickly grab the binaries by the Android package name. Unpacking APK is really straightforward, but in case you don’t know it is just a special type of ZIP archive (specially aligned and with some signature records). With no hesitation I headed to the assemblies folder, as this is where all the .NET libraries (including the app logic) reside in a MonoDroid application. I had to quickly spin up my Windows VM to check the code in JetBrains dotPeek decompiler - and I found nothing unexpected whatsoever.
By the way, having settings as a static class is also not really beneficial
(code maintenance and abstraction after all). Personally, I really like the
.NET native approach in this case - auto-generated class with an instance in
Default property. Back to our app, it tries to read the file
ticket_last_opened which is completely empty (zero bytes). So, when the
application reaches this code it crashes completely because the developer
forgot (or intentionally decided against?) putting any error-handling logic in
the method. On the flip side, there is a condition checking for whether the
file exists - so we could try deleting the file and this should prevent the
application from crashing in this routine.
You must be already thinking ‘dropping a file is be easy’ - we will just type
rm last_ticket_opened in the right directory. Well, it really isn’t that easy
on Android - the system is well-protected when you are in production mode. In
other words, you do not have root access, you do not have file
ownership/permissions and you cannot use Android run-as trick to access the
application private data.
Seems like a dead end, as we cannot do it without rooting the phone. Rooting is definitely not an option considering that I have banking apps and just generally don’t want to deal with the security implications of the former.
After playing around with adb and app files, I got another idea - backups! This absolutely legitimate way creates a full backup of an application and packs it in an easy-to-break AB (Android backup file). Technically, we should be able to backup the app private data, delete ticket_last_opened file and restore from the corrected backup.
Creating a backup is quite straightforward and comes down to
command with a couple of parameters. Unpacking a backup - now this was a
challenge, as console utilities kept failing for me. Quick research showed that
the reason was that Android backups use a strange non-compatible Java Deflate algorithm.
Luckily, the author of the blog mentioned before has put together a tool able to manipulate
backups easily. At this point, I had an unpacked backup at my disposal.
I deleted the file and packed everything back in and… nothing was restored
at all! I went back and realised another important thing - order of the files
in the archive matters (e.g., manifest should always go in as a first file)! By
changing my tar commands (
tar tf to generate the file list and
-T option to
use it for compression), I managed to get a proper backup onto the device.
And… Voila! The app was back up and running, as if nothing changed!
The morale of this post is put try-catch blocks in your code and use Android backups to meddle with private data of your apps :)