The winter holiday, combined with an illness that can best be described as a death-purge left me with plenty of time to mess around learning something new. I chose to take a dive into reverse engineering an android app, specifically, Secret. Secret is an app that allows users to share messages, comment, and chat anonymously. My goal wasn’t necessarily to de-anonymize Secret, but I thought if I could associate comments/messages with a user then I would consider it a great success (Tl;dr: I failed, but it was still a good learning experience). This post is going to be mainly a recounting of what I tried, what worked, what didn’t work, what tools I used and what discoveries I made along the way. Hopefully it’s helpful to anyone wanting to get a (very) basic understanding of how to reverse engineer an android app.
I should make it clear I had basically no experience with Android development before this, and still am basically a complete amateur, so feedback is appreciated!
Everything was run on a Nexus 5 running stock Android 4.4.4
The first thought that came to mind was that perhaps I could capture HTTP traffic between Secret’s app and server. A quick look showed they are using SSL, so I turned to looking for a web proxy capable of SSL proxying. I ended up using Charles, mostly because it was the first one that I was able to quickly get working. After installing the provided certs on my phone, I ran a capture and … no luck: Secret relies on another layer of encryption between the app and server.
Into the world of Android
As mentioned above, my knowledge of android apps is basically none. The first question I had to answer was: what actually makes up an android app? Android apps are packaged as APK files, which are based on JAR and therefore double as archive files in zip format. In order to extract the APK from the device, I turned to the powerful Android Debug Bridge (adb).
The APK format consists of some metadata, assets, resources, and, most importantly for this post, a classes.dex file. The dex file is the binary output from compiling an android app and contains the bytecode that Dalvik, androids VM, runs. As an aside, it appears Dalvik is being replaced by ART in 5.0 — I’m unclear how big this change is. Is the difference simply in the JIT-ness/ahead-of-time compilation of the VM? Or is it a larger change e.g. a new bytecode architecture? Something to look into.
After archiving the APK and extracting the dex file, I wanted to run it through a Java decompiler to get a feel for the app code itself. I used to a tool called dex2jar to convert back to plain old compiled Java, and then jd-gui to decompile back to mostly-readable Java. There were three packages that seemed interesting to me:
- ly.secret.android.* – Contained the REST API declaration, model, views and main meat of the application. Mostly unobfuscated.
- o – A heavily obfuscated package containing hundreds of clases
- retrofit – An open-source REST client
I wasn’t sure where to start so I just dove into the API layer. Secret defines a base class SlyResponse from which the various responses for API requests are derived. None of them looked very promising: I saw no blatant references to friends user IDs or other identifiable information.
In SlyGcmIntentService, I found some calls that looked like debug logging, referencing static methods in one of the obfuscated o.* classes. The referenced class, og, was quite clearly a simple wrapper around android.util.Log. Okay, second thought: let’s see if I can read some of these debug logs.
Some quick googling showed adb has a command, logcat, that will dump out all of the logging messages written to it. Unfortunately, no luck immediately came of this, as the og class has a boolean flag that needs to be true to allow calls into Log. I suspected it would be pretty simple to flip this flag in dalvik bytecode, though, so that is what I set out to do next
Low-level dalvik bytecode hacking
There were two tools that were crucial to me in hacking on dalvik bytecode: smali/baksmali, a dex assembler/disassembler, and apktool, which wraps smali/baksmali with a slightly simpler interface as well as adds some additional features. I ran “apktool d classes.dex” and was rewarded with a heirarchy of .smali files containing the Dalvik bytecode. Dalvik bytecode, which looks like this
I opened up og.smali and realized that I wasn’t quite sure how to initialize the static boolean variable guarding the Log calls to true, so instead opted for changing the calls from if-eqz (the named-opcode to branch to the label of the second parameter if the first parameter is equal to zero) to if-nez (branch if not equal to zero). Confident in my small changes, I ran “apktool b Secret_11″, “adb push Secret_11.apk” and clicked install. And… it failed with the unhelpful message: “App not installed”.
Some more googling led me to the knowledge that android applications must be signed in order to be installed. At this point, I almost gave up entirely: I assumed that Secret would have some method of ensuring that the APK was properly signed with their key at runtime. While I was confident that, given enough time, I could remove this check, I assumed it would take a lot of poking around. I decided that trying to self-sign the APK was worth the small amount of effort it took.
Using the utilities provided by the JDK, I created a keystore, ran jarsigner and verified that the application was signed. I then ran another adb push, clicked install… and it miraculously worked! Woo! And it didn’t immediately crash when I launched it (which, in this case, called for a celebration). I think it was about this point that I was mildly feverish from being sick and completely exhausted, but that wasn’t going to stop me from seeing some debug messages. I ran adb logcat, loaded up Secret… and it worked!
There is a lot of interesting stuff in there, first and foremost the decrypted HTTP responses, which is what I wanted all along, as well as the typical printf-style debugging messages one would expect (e.g. V/s (23040): DID IT (ed: Did what though? We want to know!)). I can say, though, from what I saw, there was nothing that would break the anonymity of the app.
After more poking around, a couple more attempts at some more complex bytecode editing, and some more code scrutiny, I decided to stop. I had learned what I had set out to learn, and was happy with what I was able to accomplish. And, although in the end I wasn’t able to deanonymize Secret, I was totally fine with that: it would take the fun out of the app anyway, wouldn’t it?