Reverse Engineering Android Apps

by David Libertas

I will walk you through how to insert your own code into any Android application.  This article assumes you have a basic understanding of Android coding.  If not, it's a very easy platform to pick up from Google's documentation.

Not since the 1990s when we got apps with source code on our TI calculators has it been so easy to reverse engineer and modify app code, thanks to Android's use of Dalvik Java.  This opens the door for all sorts of fun: logging network traffic before it's sent over the wire, unlocking paid content, or any other behavior changes you can think of.  I've used it on an app that employed HMAC to prevent spoofed HTTPS requests not originating from the app, then reverse engineered its HMAC shared secret to create a shell script that spoofed HTTPS requests.  Edster's "YITM" (35:4) noted that his hack to intercept the network traffic was blocked by some banking apps that recognized his self-signed certificate; modifying the banking app can be used to disable that safety check and execute his hack for any app.

Let's hack the fun party app Heads Up!1 version 3.04 as an example.  In the game, you pick a deck of cards with words related to a theme.  You hold your phone on your forehead while friends shout clues for you to guess the word.  Flip up if you get it right and score a point or flip down to pass.  One thing that's notable is that the in-app purchasable decks are playable once for free, meaning the content for the decks is loaded on the phone even if you haven't purchased them.  Wouldn't it be nice to unlock those decks?  Or better yet, add your own decks with words unique to your own social circles?

All Android apps come as APK files.  To start, you will need two tools: APK Studio2 and APK Tool3.  APK Studio is the tool you launch, a sort of reverse engineering IDE.  It then uses APK Tool to convert an APK into decompiled SMALI code and can then recompile it back to an APK after you have made changes to the code and digitally sign it with your own certificate.  Review APK Studio's README to understand where to install APK Tool so APK Studio can find it.

Next is to get a copy of the APK file you want to hack onto your PC.  Google search how to download from your phone if you're not familiar, or go to apkpure.com if you want to get older versions.  Launch APK Studio, and from the "File" menu open the APK file.  Congrats!  You now have a decompiled Android app.  Go grab Heads Up! 3.04 if you want to follow along this tutorial.

Next step is get familiar with the app's SMALI code, which is sort of like assembly language for Dalvik Java.  It's hard to read at first, but this footnote3 has a good reference.  You can also attempt to decompile the SMALI to Java to compare how Java code is represented in SMALI.  Tools for that are dex2jar5 to convert the APK to a JAR file, then JD6 to decompile the JAR to Java.  Not all code can be decompiled, but you should get enough samples to compare Java side-by-side with SMALI to learn how it works.

The only thing that may be hard to catch onto from this technique is the variable naming, so I will explain that.  Every variable is a 32-bit register.  Function parameters are p0, p1, p2, etc.  Local variables are v0, v1, etc., and the number of local variables available is defined by the .locals command.  So .locals 2 means you have v0 and v1 available to use.

Now it becomes a detective game.  Want to hack the in-app purchase code to make all paid content appear paid for?  Many Android devs use Google's open-source IabHelper class (IAB is In-App Billing).  Sure enough, a file search of the decompiled Heads Up! finds the class constructor for Lcom/headsup/a/e; logs the string literal "IAB helper created."  This demonstrates one challenge: APK compilers attempt to obfuscate code by changing the names of everything to letters.  So, whereas the developer of this app probably had a Java class called com.headsup.Billing.IabHelper, the obfuscator changed it to com.headsup.a.e.

Here are tips to work around that.  First is to read string literals like the above example for more clues.  In this case, the SMALI function Lcom/headsup/a/e;->a logs a message indicating it was originally called getSkuDetails() prior to obfuscation.  Since this is from open-source, we can find the original IabHelper.java online, see the original Java getSkuDetails(), and compare to the SMALI to decipher it better.

Knowing commonly used open-source libraries like IabHelper can help you find code.  For example, Google's Volley7 is often used for making HTTP requests, such as RESTful APIs.  Finding string literals in Google's open-source code for Volley and matching them to your obfuscated code will quickly find where your APK makes HTTP calls where you can insert code to capture all HTTP requests and responses just as searching for IAB string literals can find the in-app billing code.

Another tip is searching for Android resource IDs.  Perhaps you are trying to hack an Android activity that shows a certain message or image.  You will find the message in res/values as a name/value pair or the image in res/drawable (where the resource name is the image file name).  Then search the SMALI code for reference to the resource's name: now you've found the source code you want to hack.  Note that if cracking SMALI code is not your thing, it's still fun to replace these message and image resources with your own content, then use APK Studio to rebuild a new APK with your customizations!

Surfing through the string and drawable resources can also show other interesting things.  For example, res/values/strings.xml in Heads Up! has messages related to Disney that reveal you can unlock a promo deck by checking in at a Disney park.  More digging can find promotions for Star Wars, Peanuts, Carnival Cruiselines, Crocs, and Geico Insurance.

Back to hacking Heads Up!... to unlock in-app purchases, it'd be helpful to understand how the obfuscated IabHelper is working, so I added calls to log to Android's logcat service in all its functions.  There are two code snippets to keep on hand for logging, depending on if you just want to log a message or if you also want to include the full-call stack.

Message:
const-string v0, "HAXOR"
const-string v1, "Message goes here"
invoke-static {v0, v1},
Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I

Message w/call stack:
const-string v0, "HAXOR"
const-string v1, "Message goes here"
new-instance v2, Ljava/lang/Throwable;
invoke-direct {v2}, Ljava/lang/Throwable;-><init>()V
invoke-static {v0, v1, v2},
Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I

Filter logcat entries in the Android monitor to only include HAXOR to see a dump of just your hack logs.  Note that this can break code if the function you inserted this into uses variables v0, v1, or v2 since you are overwriting them.  To fix that, increment the .locals declaration.  For example, if a function has .locals 3 then you know it uses v0 through v2; change it to .locals 6 and adjust the above code snippets to use v3, v4, and v5 instead.

Long story short, while trying to debug IabHelper using the above logging, I found Lcom/headsup/activities/d; loops over each deck and calls IabHelper for every deck that has a SKU via the unobfuscated function Deck.getSku().  Reading Deck.smali reveals Deck.getPrice(), and a string literal on the sixth line in this snippet confirms that empty string from getSku() indicates a purchased deck:

invoke-virtual {p0}, Lcom/headsup/model/Deck;->getSku()Ljava/lang/String;
move-result-object v0
invoke-virtual {v0}, Ljava/lang/String;->isEmpty()Z
move-result v0
if-eqz v0, :cond_0
const-string v0, *"this%heads*up#deck@is_purchased"*
iput-object v0, p0, Lcom/headsup/model/Deck;->price:Ljava/lang/String;
:cond_0
iget-object v0, p0, Lcom/headsup/model/Deck;->price:Ljava/lang/String;
return-object v0

I decided to make the SKUs empty string across the board rather.  This leads to my next tip.  Searching SMALI code is very easy because everything is fully qualified with namespace and function signature.  No "using" statements like Java.

For example, to find all code reference to Deck.setSku(), search for "Lcom/headsup/model/Deck;->setSku(Ljava/lang/String;)V" and you find one reference in headsup/b/a.smali:

invoke-virtual {v2, v3}, 
Lcom/headsup/model/Deck;->setSku(Ljava/lang/String;)V

Easy hack.  Add a line in front of it to always pass empty string:

const-string v3, ""
invoke-virtual {v2, v3},
Lcom/headsup/model/Deck;->setSku(Ljava/lang/String;)V

Now by inserting that one line, every deck is considered purchased!

If you keep exploring, then you will find that the decks are a SQLite database.  It downloads a hash to confirm that the local SQLite is in sync with the server.  If not, then it downloads the new SQLite.  This is how they push new decks to the app.  You will also find code in Lcom/headsup/b/a; that removes some decks based on their title, so remove that code to unlock secret decks no one else can play.

Finally, if you want to really stretch your skills, try inserting your own decks into the SQLite DB.  Here are some tips for that.

The SQLite is saved as system.db.  Find the code that downloads it to get its URL and download your own copy to your PC.  Use SQLite client to open it and learn about its contents and table structures.  Finally, find the code that reads and writes system.db.  You will want to clone a copy of the file, tamper with the clone, and load decks from the copy, since tampering with the original system.db will trigger the hashing to redownload the original from their server.

Create a new class called Hack.smali with static functions to insert what you want into the DB.  In my case, I created a function to insert a deck, another to insert a word into a deck.  Here's an example of inserting "2600 Magazine" as a card into a deck identified in SQLite by the primary key 0x2b:

const-string v2, "2600 magazine"
const/16 v3, 0x2b
invoke-static {v0, v1, v2, v3},
Lcom/headsup/Hack;->hackInsertWord(Landroid/database/sqlite/SQLiteDatabase;ILjava/lang/String;I)V

Then see if you can find the appropriate places to insert calls to your new code.

This may seem like a daunting task, but with patience it becomes easy.  Once you get the hang of it, it can become an addicting way to create new possibilities with apps, make them better, and make them do things their creators didn't intend.  That's the hacker spirit!

Footnotes

  1. play.google.com/store/apps/details?id=com.wb.headsup
  2. github.com/vaibhavpandeyvpz/apkstudio/
  3. ibotpeaches.github.io/Apktool/pallergabor.uw.hu/androidblog/dalvik_opcodes.html
  4. sourceforge.net/projects/dex2jar/
  5. jd.benow.ca
  6. developer.android.com/training/volley/simple
Return to $2600 Index