Android Application Reverse Engineering

Android Application Reverse Engineering

TLDR; Well you better start reading.
Estimated Read Time: ~45min (Depends on individual)

You’re just messing around with Burp to intercept traffic, trying to exploit the API but hit a road block when the app communicates with encrypted data and need to figure out a way to manipulate this data? In this post, we will learn many methods which can be used for Android application (apk) testing and reverse engineering. We will perform static and dynamic analysis of the target application.

I’ve created a simple Android application as a target which doesn’t do anything except validating a user input query against an encrypted key and if it matches the app shows “Congratulations” message on screen. (Note: Its been a while since I’ve touched any mobile application, so please let me know if you’ve any suggestions for methods/tools which can be used for this post.)

Lets have a look at the Application source code first so that we can compare it with decompiled APK later on.

This application uses the below helper class to perform some other critical functions.

You can download the compiled APK from -> CrackMe

Before we proceed any further, here are some requirements for this assessment;

Methods which we will use for this assessment;

  • Dynamic Analysis and Hooking.
  • Binary Patching (byte code modification)
  • Static Analysis and code replication.

 

Dynamic/Runtime Analysis and Function Hooks

For dynamic analysis, we will first start with Frida, dex2jar and JD-GUI.

Analysis Using Frida

So what is Frida, exactly?

It’s Greasemonkey for native apps, or, put in more technical terms, it’s a dynamic code instrumentation toolkit. It lets you inject snippets of JavaScript or your own library into native apps on Windows, macOS, Linux, iOS, Android, and QNX. Frida also provides you with some simple tools built on top of the Frida API. These can be used as-is, tweaked to your needs, or serve as examples of how to use the API.

In simple terms, it can be used to hook function calls, inject your own code to modify the behaviour etc. We will use this to bypass the checks and to identify different variables.

To install Frida, run the below commands while the phone is connected to your system with “USB Debugging” turned on

Our frida server is now running on the android device, so lets check whether it can communicate with our python running on our system.

Now we will create a python script to further do some stuff

Woah, lots of code? Lets break it down.

The encrypted variable is a NoneType object initially, this will be updated later to an encrypted value by the script itself. The on_message function is a callback function which can by utilised by frida javascript code which we will use to inject into our process to perform a callback to our python session. The callback can be performed by send() function inside the injected JS code. Next variable is jscode which is nothing but string representation of our JS code which we will inject into our target process. This code is kept inside a separate file called punsec.js because of syntax highlighting for easy readability. Next is the process_name variable which is our target process name. We can get the process name by using “ps” or “pm list packages” command via adb shell or you can use various different apps available in Playtstore which can show you all the apps with package names.

The device variable is for getting connected devices and connecting to them. The try except block is for handling the exception in case the target process isn’t running on the device, if it’s unavailable then start it otherwise connect to it. Attach to the running process by providing its PID and inject the jscode in the target process. script.on registers our callback function to be used by JS send() function. The while loop here is another example of how powerful frida actually is, in this loop we are trying to check for encrypted variable its type has been changed from None, if it is, then script.post will send a message to our injected JS into target application and this message will be handled by recv() inside the JS.

Before going any further, lets do some static analysis of the APK file. We need to first decompile the APK and convert the JAVA bytecode to readable .java format, for that we will use dex2jar

Now lets another tool called JD-GUI to further analyse the extracted CrackMe-dex2jar.jar file

The decompiled APK source looks a bit changed from the original source code. Lets analyse the changes. The first obvious change is the resource ids are now numeric instead of accessing them via inbuilt helper resources class R.x.x.

As we can see above, the MainActivity consists of only one function which is onCreate(). Lets have a look at android application lifecycle

The onCreate() function is called as soon as the app starts, we are hooking this function and then performing a call to original function in order to preserve actual app functionality, also to access the current activity context in order to get the string value for the below line

String str = MainActivity.this.getString(2131099669);

So lets start to get these values of these by writing our JS code in a file called punsec.js

Java.perform() is a requirement by frida, it tells frida server to run the encapsulated JS code. Java.use() is a wrapper for loading dynamic packages into our target process. We will use the send() callback to send data to our python program for further use. Running our python script gives us

Remember that onCreate() will trigger after you rotate the device, so after the process has been started we need to rotate our device in order to perform a callback.

We can also see that there are few calls to Base64.decode() by getting the string through numeric ids. We might need to have a look at these values too, so lets modify our code accordingly

Running our python program again will now gives us the below output;

Hmm, this looks like we’re done. Well not yet, lets have a look at the decompiled code

In the above code, there are 2 calls to Util.a() both having different types of arguments, we’ve already hooked the Base64.decode() function so now lets create a hook for Util.a() with below updated code;

Running our python program again will provide the below output;

There seems to be some error. Well it seems that our Util class does something which is known as method overloading (having common method names but different types of argument). To overcome this issue, frida provides us with additional method called overload() by which we can explicitly set which method to override/hook. We will hook the Util.a(String, SecretKey) function for further analysis because its an encryption function.

But how we’ve identified that it is an encryption function? The return type is byte which means it’s obviously not returning a clear text string, also the localCipher initialization has been passed with 1 as the first argument which represents

Lets modify our JS code accordingly in order to place a proper hook at this function

And again run our python program to observe the changes;

Awesome, we can intercept our user input string. Also the Util class has another function Util.a(byte, SecretKey) which is a decryption function but it’s not being utilised anywhere in the app, so what can we do with it? The encrypting function already receives SecretKey argument which we can utilise in our decryption function, but we also need the first argument to be passed to this. The first argument is the base64 decoded str variable, so lets modify our code to receive this argument in our JS and further pass it to our decryption function to decrypt the final key in order to pass the challenge. Lets modify our JS code one last time.

We have placed a recv() call inside our function to receive the encrypted str which our python program stores already. Now decode this received base64 encoded key and send this along with SecretKey to our decrypting function. Lets run our python program once again and see how far we came already.

Woah, we got the key. Also this overwrites any user input and replaces it with the decrypted str, so now every input works 😀


Not only we are dynamically overwriting the user input value with actual secret but we’ve also recovered the actual secret phrase in order to pass the challenge.

What if there was no decryption method inside the APK, can we still do something? No worries, we can craft our JS code to load the packages required for performing the decryption and overload the method with required arguments, or we can just do the decryption in python with the below code

And our updated JS code will look something like this

Now run our python program to see if it works or not


Analysis Using Inspeckage

For this, we will use Inspeckage, Xposed Framework and APKStudio/ByteCodeViewer.

Inspeckage – Android Package Inspector

Inspeckage is a tool developed to offer dynamic analysis of Android applications. By applying hooks to functions of the Android API, Inspeckage will help you understand what an Android application is doing at runtime.

Inspeckage lets you do analysis via an easy to use web interface. Inspeckage requires you to install Inspeckage Xposed module and enable it from Xposed Framework. Start Inspeckage app in your android device and select our target application and browse to Inspeckage webserver.

Turn  on the Auto-Refresh toggle, click on the Settings button in Inspeckage webserver and disable some tabs like below because our app wont be doing any of these activities and finally click on Start App and refresh the page.

Once our app is started on device, enter any test in input field and press OK button in the app and observe notifications on our Inspeckage webserver.

Both of these screenshots show similar results which we obtained using the Frida method. Its super easy to perform analysis with Inspeckage, you can monitor File system activities, SQL queries performed by the app and many other activities, the main concept behind this is the same which we did with the Frida method, place hooks on all the functions which deals with Cryptography, Filesystem, Hashing etc. Great, but can we perform function hooking? Indeed we can, as you can see in the last tab it does provides an option to create hooks. But here comes the problem, unlike Frida, Inspeckage doesn’t provides overriding for overloaded methods, switch to hooks tab and click on create new hook to verify this

So in order to create a valid hook, we will use ByteCodeViewer or APKStudio to modify the Java bytecode of the APK. Here we will do byte patching (Monkey patching).

(Note: De-select “Decode Resources” checkbox while opening the APK otherwise you will run into the following issue)

On line 168 in the above screenshot, we’ve successfully located the Encrypting function by identifying the argument types and return type on line 168, also on line 197 a variable is created with flag 1 which we already saw earlier. We have changed this function name to b, and the decrypting function name to c. Now we need to update the same in the MainActivity byte code in order to make our app work again.

Everything seems perfect and now its time to sign our APK and for that we need to create keystore and sign our newly created APK

Move the signed APK to the device and install it. Restart Inspeckage again and move to Hooks tab to verify whether our modification took place or not.

Excellent, our modifications are clearly visible and we can now add a hook to target function Util.b(). Select the function and click on Add hook button. Now in our mobile application, let tap the ok button again and watch the notifications in Inpeckage server.

We can see that Inspeckage is successfully intercepting the data from the hooked function and is able to provide us with passed arguments and return value. Now click on Replaces button and configure the replacement options like below.

Here we will replace the first argument passed to our encrypting function which is a string with our actual secret value that we have identified with Frida method. Whenever Test(case sensitive) is typed inside the app, the hook will replace the data and pass our provided value which will bypass the check and the Congratulations message will appear on our screen again.



Binary patching (byte code modification)

For this method, we will be using APKStudio and Jarsigner.

We will try to modify the programming logic itself by modifying the compiled APK and recompiling it. Start APKStudio and load up the file again (remember to de-select the “Decode Resources” checkbox) and locate the line where the comparison logic is happening in MainActivity$1.smali

We can see on line 113 that the program compares two different values against each other to perform checks and if it fails the application shows a “Umm, Try Again” message. But what if the program always compares two same objects against each other? In that case, the return boolean will be always True and the program skips the else condition. So lets do that, recompile and resign our APK again to test

patched object

Run the application again to verify the behaviour whether it works as expected or not by bypassing the original program logic.



Static Analysis and code replication.

Here we will use Android Studio/IntelliJ and ByteCodeViewer to perform static code analysis.

Static analysis 

Also called static code analysis, is a method of computer program debugging that is done by examining the code without executing the program. The process provides an understanding of the code structure, and can help to ensure that the code adheres to industry standards.

Start ByteCodeViewer(BCV) and wait for it to install dependencies. Once everything is installed, BCV will start and we can open up our APK file directly in it. In BCV, click File->Add and select the CrackMe.apk and let it finish loading up the file. Click View->Pane1->Procyon->Java and View->Pane2->Smali/Dex->Smali/Dex. Your BCV should look something like this

On line 9, we can see “final String string2 = this.this$0.getString(2131099669);”, the getString() method on the current activity context which can be accessed by “this“, “MainActivity.this” or “getApplicationContext()” is used to get resource values by an integer value. The index of these numeric ids is created inside inbuilt R class, so we will look for this resource id in R$string.class in BCV to further identify the contents inside strings.xml file.

We can see that this integer value is assigned to a, now we have to do a lookup for a inside strings.xml which you can find in BCV by expanding the tree on left hand side CrackMe.apk->Decoded Resources->res->values->strings.xml.

Sometimes BCV can go bonkers and opens the file in binary format instead of .xml, in that case click on File->Save As Zip, extract the zip and finally open up the strings.xml in your favourite text editor.

Great, we have recovered the string here. We will recover all the strings with this method and save them

We will use IntelliJ to write our code and try to achieve/reverse the functionality of the original application by copying the code from BCV decompiled files and making it work. When all the code is put together, it will look something like this

Save the above code in a file named Decrypt.java. We need to compile this file now and finally run it to check whether our code works.

We can do the same in Python (or any other language) as shown earlier in the Frida method already, but sometimes its easy to copy paste the code itself and doing minor fixes in order to make it run.

We’ve covered all of the mentioned tools and methods and now its time to have some coffee.

—–END—–

5 Replies to “Android Application Reverse Engineering”

Leave a Reply

Your email address will not be published. Required fields are marked *