Android Instrumentation with Smali: A survival guide

Hi everyone!

This article aims at providing you survival steps while tinkering around with Smali & Android applications. The name of the original application I did my search on will remain secret but I created a dummy application doing the same so that you can do it on your own. The link of the MainActivity.java and MainActivity.smali is here

Create a dummy application with Android Studio and just import this. PLACEHOLDER_* strings will have to be replaced with proper ones if you want to try.

But before we start diving, what's Smali ?

smali/baksmali is an assembler/disassembler for the dex format used by dalvik, Android's Java VM implementation.

Context

I needed to retrieve a password which was cached/stored within an Android application. As soon as I was opening the application, I stumbled upon a “Log in” page with with the password field already filled-up. I guessed this was my password, and I had to retrieve it.

After contacting the vendor, the only viable option from them to “retrieve” the password was to “flash” the equipment and put a new one instead. The whole configuration of underlying devices would be wiped.. and that was not an option.

Constraints

Hehe, in order to get this journey a bit spicier, here are some of my constraints (which led me to Smali instrumentation):

  • The application is running on my phone (a One Plus 3 not rooted). < I couldn't root it or I would lose all the data
  • The application is not transmitting the password in any way so that I could intercept it (with Burp for example)

Tools needed

In this tutorial, I am essentialy using:

Retrieving the file

I was able to use adb backup to backup this specific application on my laptop since the application had backup enabled within the APK, so at least… :)

adb -s xxyyzzxx backup my.little.application

After running this first command, you get a backup.ab file which is essentialy a tar archive that you can untar this way:

( printf "\x1f\x8b\x08\x00\x00\x00\x00\x00" ; tail -c +25 \
backup.ab \
 ) |  tar xfvz -

I was able to locate the .db file containing my password, but it was encrypted (and encoded in base64). I couldn't do anything with it.

The easy part

Since the password was loaded by the application (loaded from the DB and decrypted), an “easy” solution to retrieve the password would have been to modify the different layouts and look for fields with "android:inputType="textPassword"

The workflow to do this would be :

  1. Decompile the APK file
  2. Finding all occurences of textPassword in the res/ folder
  3. Changing all the textPassword occurences with text
    • To switch from a password field to a text one
    • Password would be in clear text, yay!
  4. Recompile the APK
  5. Install the APK + load your local DB on another phone
  6. Launch the app and enjoy!

This solution worked and I managed to retrive the password but I decided to keep going through the rabbit-hole to experience even more with Smali.

Going through the rabbit-hole

To continue, I decided to locate the encryption mechanism and after decompiling the application with Jadx and saving the sources on disk, I was able to locate the encryption class.

One of my grep-fu to do is… is as simple as that:

grep -r -i 'cipher'

This works almost everything time for me.

Decrypting part

I got couple of hits and got this specific one which looked quite promising, a simple method taking a byte array and returning a new byte array for the decrypted string.

public final byte[] b(byte[] bArr) {
    String str;
    String str2;
    Logger logger;
    try {
        Cipher instance = Cipher.getInstance("AES/CBC/PKCS5Padding", "BC");
        instance.init(2, new SecretKeySpec(this.f11885c, "AES"), new IvParameterSpec(this.f11884b), this.f11883a);
        return instance.doFinal(bArr);
    } catch (NoSuchAlgorithmException e2) {
        logger = this.f11886d;
        str = e2.getLocalizedMessage();
        str2 = "invalid algorithm: ";
        logger.warn("DECIPHER", (Object) str2, (Object) str);
        return new byte[0];
    }
    // truncated
}

I decided to create a simple Android Studio project, compile similar code in a basic Android Activity and see how it worked under the hood. By doing so, I had a lot of flexibility to start writing Java code and see the corresponding Smali code, which can be a bit overwhelming, at least at the beginning.

Logging it all out

My first thought was: “I am in the good method, I should logcat all those parameters and see what are they”. After a bit of Googling, I found IGLogger which can be used to perform instrumentation on a Android application but unfortunately, I didn't make it work. I also realized that the last time I played with Smali was couple of years back so I decided to brush up those skills and do it by myself.

My goal was as easy as : Perform a Log.i("My Activity", "XXX = " + new String(XXX)); for all the parameters that I needed, which were: encrypted (bArr), key and iv. Log is part of android.util.Log and does not require additional libraries to be injected in the APK. The less requirements, the better. Remember, I just wanted to print dummy variables to get my password back!

Automation ftw!

Changing the Smali code, building the app, resigning the application and pushing in on a device was a bit tyding so I decided to script all of it, making changes easier. After any change I was doing, I just add to execute ./run.sh and I could start interacting with the app.

#!/bin/bash
cd ../../
apktool b apktool_XXXXX
cd apktool_XXXXX/dist
jarsigner -keystore XXXXX.keystore -storepass XXXXXXXXXX XXXXX.apk XXXXX
adb push XXXXX.apk /sdcard/
adb shell

This might look painful when you start “playing” with Smali the first time but the langage is not that bad. Just make sure to be well rested and have a pen and paper to write down your notes. Inserting bad Smali code will often result in crashing the app when it starts and this might result in a lot of errors like this:

AndroidRuntime: java.lang.VerifyError: Verifier rejected class com.foobar.barfoo.authdd.d.a.a.a: byte[] com.foobar.barfoo.authdd.d.a.a.a.a(byte[]): [0xFFFFFFFF] register index out of range (8 >= 8) (declaration of 'com.foobar.barfoo.authdd.d.a.a.a' appears in /data/app/com.foobar.barfoo-1/base.apk)
AndroidRuntime: 	at java.lang.reflect.Constructor.newInstance0(Native Method)
AndroidRuntime: 	at java.lang.reflect.Constructor.newInstance(Constructor.java:430)
AndroidRuntime: 	at com.foobar.barfoo.authdd.d.a.a.a(Unknown Source)
AndroidRuntime: 	at com.foobar.barfoo.authdd.d.b.d(Unknown Source)
AndroidRuntime: 	at com.foobar.barfoo.authdd.e.c.<init>(Unknown Source)
AndroidRuntime: 	at com.foobar.barfoo.authdd.e.a(Unknown Source)
AndroidRuntime: 	at a.b.b.a(Unknown Source)
AndroidRuntime: 	at com.foobar.barfoo.core.database.c.j.a(Unknown Source)
AndroidRuntime: 	at a.b.b.a(Unknown Source)
AndroidRuntime: 	at com.foobar.barfoo.core.database.c.b.a(Unknown Source)
AndroidRuntime: 	at a.b.b.a(Unknown Source)
AndroidRuntime: 	at com.foobar.barfoo.core.database.f.a(Unknown Source)
AndroidRuntime: 	at a.b.b.a(Unknown Source)
AndroidRuntime: 	at com.foobar.barfoo.app.i.r.a(Unknown Source)
AndroidRuntime: 	at com.foobar.barfoo.app.Application.onCreate(Unknown Source)
AndroidRuntime: 	at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1024)
AndroidRuntime: 	at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5403)
AndroidRuntime: 	at android.app.ActivityThread.-wrap2(ActivityThread.java)
AndroidRuntime: 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1545)
AndroidRuntime: 	at android.os.Handler.dispatchMessage(Handler.java:102)
AndroidRuntime: 	at android.os.Looper.loop(Looper.java:154)
AndroidRuntime: 	at android.app.ActivityThread.main(ActivityThread.java:6119)
AndroidRuntime: 	at java.lang.reflect.Method.invoke(Native Method)
AndroidRuntime: 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
AndroidRuntime: 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
ActivityManager:   Force finishing activity com.foobar.barfoo/.app.SplashActivity
OpenGLRenderer: Initialized EGL, version 1.4
OpenGLRenderer: Swap behavior 2
Icing   : Usage reports ok 3, Failed Usage reports 0, indexed 0, rejected 0
ActivityManager: Activity pause timeout for ActivityRecord{2301260 u0 com.foobar.barfoo/.app.SplashActivity t15 f}
ActivityManager:   Force finishing activity com.foobar.barfoo/.app.SplashActivity
ActivityManager: Killing 5682:com.foobar.barfoo/u0a101 (adj 900): crash

Why did I get those kind of errors? Well, I basically started using registers which were not declared. And it crashed. The whole application. Multiple times.

To give you some ideas, one Java line for printing a Log resulted in about ~11 lines of Smali code and let's dive in this to give you some heads-up. Let's take this specific like :

Log.i("My Activity", "KEY = " + new String(key));

And this results in Smali code with:

const-string v1, "My Activity"

new-instance v5, Ljava/lang/StringBuilder;

invoke-direct {v5}, Ljava/lang/StringBuilder;-><init>()V

const-string v6, "KEY= "

invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

new-instance v6, Ljava/lang/String;

invoke-direct {v6, v2}, Ljava/lang/String;-><init>([B)V

invoke-virtual {v5, v6}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;

invoke-virtual {v5}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;

move-result-object v5

invoke-static {v1, v5}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I

This last Smali line is basically the call to android.util.Log method with the String we've built within the last 10 lines. Clever, right? Smali works with registers by passing data from one register to the other.

Let's continue on this last line: invoke-static will allow you to invoke/call any methods/functions but before, you need to define what will be the parameters. In our case, v1 and v5 are the necessary parameters to call Log. This is correct because we also have two parameters in the Java code. For example, v1 refers to “My Activity” which has been declared on the first line as a const-string type (read a constant string - that will never change)

The other parameter is the "KEY = " + ... which is the string we've built previously. First, we instantiate a StringBuilder with the empty string and then we start appending data to it (KEY = ) and then the string that we've wanted.

Here is a small picture to show what are those lines about in corresponding files:

Smali & Java correspondance

I did the exact same thing for all the parameters that I needed to dump, by making sure that I would not erase the information in a specific registry that would be needed for post-processing purposes. It turns out that by doing this specific mechanism, it worked - I was able to retrieve the password!

By doing this technique, I realised that all strings in the application were all encrypted with one specific key but not my password, which was with another one. Anyhow, this took me couple of hours but I learnt some cool things along the way and it might be helpful if you also want to tinker around with Android application.

References