Tutorials

Android Hacking: Part 2 – Manipulating Apps

Thumbnail Android Hacking

This post is also available in: Deutsch

In the second part of the Android hacking series, we explore how we can permanently manipulate the source code of an app to completely bypass a password prompt.

In the first part we already looked at the difference between decompiling and disassembling. We also learned how to convert the binary code of an app back to the Java source code. This allowed us to extract the password of the vault app and thus gain access to the treasure. If you haven’t read through the first part yet, you might want to do so now, since we will be relying on some information of this tutorial.

Android Hacking – Part 1: Decompilation & Source Code

Using the vault application (download), we will learn in this part how we can arbitrarily manipulate the behavior of an Android app. This way we will be able to set our own password for the vault app or even bypass the password prompt completely. As a bonus, I’ll show you an extremely helpful tool that combines all the steps we discussed in this and the previous tutorial into one single GUI tool. But first, it is important that we fully understand all the manual steps in detail.

Smali Code: Byte to Text to Byte


As we have seen in the first part, decompiling can restore the source code of an app in the best case almost to its original state. This way we can quickly understand the logic of an application or even retrieve individual static parameter values.

From a technical point of view, decompiling is anything but a trivial task. The compiler optimizes the source code in order to run it in the Dalvik VM with optimal performance. Reconstructing the binary code back to the original source code is something that was never intended. Therefore, there are always minor errors and some parts cannot be interpreted correctly or assigned to their Java equivalent 100 % accurately. So decompiling the application, manipulating the Java code and then recompiling it all again is practically impossible. That’s why we need an approach that is still very close to the machine code, but which puts the whole structure into a readable form for us – and that’s exactly what SMALI CODE brings into play. We’ ll have a look at an example to see what Smali code looks like in detail, but first we have to disassemble the byte code of our app.

Disassembling the App


We can use Apktool to disassemble any Android app. This handy tool combines several utilities that make our workflow with Android applications so much easier. For example, under the hood it uses the disassembler Baksmali to generate the Smali code. In addition, Apktool allows us to rebuild our modified source code back into a working application.

By using the parameter d the specified app is decoded, i.e. the resources and configuration files of the application are translated into a readable form and the byte code is converted into Smali code. All files are then output to the folder with the same name as the application. Alternatively, we can use -o to specify a different output folder.

apktool d <app.apk>

What’s the Difference: Java vs. Smali


The goal in this exercise is to modify the app so that we can defeat the password prompt with any given password. In the end, it should not matter if we entered the right or wrong password – the vault will open either way.

Such modification would be feasible, for example, if the app does not compare the password with a locally stored value, but with an external database to which we have no access (and cannot read its content).

With the knowledge we gained by decompiling the app in the first part, we know that the logic for gaining access to the vault is located in the class VaultDataSource.

One look at the file immediately will show you that the Smali code is much more complex to understand than the Java code. It is therefore recommended to always decompile and disassemble an application and then analyze both results in conjunction.

Beispielhafter Auszug aus VaultDataSource.smali
Sample extract from VaultDataSource.smali

We can use the .line numbers to help us navigate between the logical blocks within the methods.

line-Nummern als Orientierung im Smali Code

In Java the assignment of the vaultCombination, for example, is written in only one line. A new string variable is automatically declared and assigned the value Subscr1be!.

Zuweisung einer String-Variable in Java

In Smali things look a bit more complicated. First the private string vaultCombination is declared [1]. Then within the constructor of the class [2] the constant value Subscr1be! is assigned to the temporary variable v0 [3] and finally copied to the variable vaultCombination of the class VaultDataSource [4].

VaultDataSource.smali

...
    # instance fields
[1] .field private vaultCombination:Ljava/lang/String;

    # direct methods
[2] .method public constructor <init>()V
        .locals 1

        .line 8
        invoke-direct {p0}, Ljava/lang/Object;-><init>()V

        .line 10
[3]     const-string v0, "Subscr1be!"

[4]     iput-object v0, p0, Ldigital/basto/vault/data/VaultDataSource;->vaultCombination:Ljava/lang/String;

        return-void
    .end method

Modifying the Source Code


Since the vault app compares the password with a static value stored in the application itself, we could simply set our own password. However, we rather want to completely remove the if statement that compares the entered password with the vaultCombination. This will always open the vault when we click on unlock, no matter what password was actually entered.

In our Smali code we first search for the method unlock:

In the block starting with .line 15 we see the command :try_start_0, which initiates the try block. Further down we will also find the corresponding command :try_end_0, which represents } in the Java code and ends the block.

VaultDataSource.smali

.method public unlock(Ljava/lang/String;)Ldigital/basto/vault/data/Result;
    .locals 4
    .param p1, "password"    # Ljava/lang/String;
    .annotation system Ldalvik/annotation/Signature;
        value = {
            "(",
            "Ljava/lang/String;",
            ")",
            "Ldigital/basto/vault/data/Result<",
            "Ldigital/basto/vault/data/model/VaultData;",
            ">;"
        }
    .end annotation

    .line 15
    :try_start_0
[1] iget-object v0, p0, Ldigital/basto/vault/data/VaultDataSource;->vaultCombination:Ljava/lang/String;

[2] invoke-virtual {v0, p1}, Ljava/lang/String;->equals(Ljava/lang/Object;)Z

[3] move-result v0

[4] if-eqz v0, :cond_0

    .line 16
    new-instance v0, Ldigital/basto/vault/data/model/VaultData;

    const v1, 0x44a72d71

    invoke-direct {v0, v1}, Ldigital/basto/vault/data/model/VaultData;-><init>(F)V

    .line 19
    .local v0, "unlockData":Ldigital/basto/vault/data/model/VaultData;
    new-instance v1, Ldigital/basto/vault/data/Result$Success;

    invoke-direct {v1, v0}, Ldigital/basto/vault/data/Result$Success;-><init>(Ljava/lang/Object;)V

    return-object v1

    .line 21
    .end local v0    # "unlockData":Ldigital/basto/vault/data/model/VaultData;
    :cond_0
    new-instance v0, Ldigital/basto/vault/data/Result$Error;

    new-instance v1, Ljava/security/AccessControlException;

    const-string v2, "Wrong password!"

    invoke-direct {v1, v2}, Ljava/security/AccessControlException;-><init>(Ljava/lang/String;)V

    invoke-direct {v0, v1}, Ldigital/basto/vault/data/Result$Error;-><init>(Ljava/lang/Exception;)V
    :try_end_0

[1] First, the string value is read from vaultCombination and stored in the temporary variable v0. [2] Then the method equals is called with the parameters v0, i.e. the correct vaultCombination, and p1, i.e. the entered password, to compare both values with each other. [3] With move-result the result of the previously invoked equals method is stored in the variable v0.

In Smali, 0 is equal to the boolean value false and 1 is equal to the boolean value true

[4] if-eqz is short for If Equals Zero. This means that the program checks if v0 is equal to 0, i.e. if the previous comparison [2] returned false. If this is true, the program jumps to :cond_0. Otherwise, it continues to run with the instruction below and unlocks the vault.

If we now completely remove the instruction if-eqz v0, :cond_0, the result of the comparison is never evaluated. Instead, we always run into the code section that unlocks the vault, regardless of whether the password was wrong or right.

Rebuilding the Modified App


With apktool we now rebuild the modified Smali code back to a working APK file. Using the parameter -o, we can specify the name of the new output file.

apktool b <app_folder> -o <out.apk>

Signing the APK File


Apps must be signed before they can be installed on our devices. The signature is used to unambiguously identify the author of the app. Afterwards, we can no longer make changes to the source code without also changing the signature of the application. This prevents anyone from manipulating an app and then re-releasing it as an official application, for example published by Google.

For Android, the signature keys are stored within a construct called keystore. The keystore itself is protected with a private key which only you should know. Inside we can store any public/private keys securely, which can later be referenced by a user-defined alias. The private keys in the keystore are protected with an additional password.

Visualisierung Keystore

With the following command we can now create our own keystore:

keytool -genkey -v -keystore my.keystore -alias cert -keyalg RSA -keysize 2048 -validity 10000

The -alias parameter is used to set the name for our own public/private key pair and automatically add it to the keystore. The expiration date of the signature is specified in days using the -validity parameter.

When we create a new keystore, we also need to provide the author’s information. Here we can either enter our own information or just leave the fields empty.

With keytool -list -v -keystore <name.keystore> we can then check if creating the keystore and adding the alias worked as intended. In the following excerpt we see that the keystore holds exactly one key pair with the alias cert.

Keystore auflisten

We can now sign our modified APK file with jarsigner. The parameter -keystore specifies the path to our newly created keystore.

jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore <path_keystore> <path_apk> cert

If we now install our modified and signed app on our device, we’ ll see that we are able to enter any password and the vault will always unlock successfully.

The All-Rounder: APK Studio


Decompiling, disassembling, analyzing Java and Smali code, modifying the code, then rebuilding and signing the app as well as installing it on the device are all tasks that we have to repeat again and again. However, in the long run this can become quite tedious. As promised at the beginning, I want to conclude by introducing a tool that bundles all these small tasks into one tool.

APK Studio is a free-to-use open source tool that runs on Windows, Linux or macOS. Initially, we only need to specify a few local paths to the individual tools we want to use.

APK Studio Settings: Pfade

If you haven’t downloaded all the additional tools yet, you can also find the download links right in the application (“Get it here!”).

In order to sign the created APK files, we have to enter the information of the keystore:

APK Studio Settings: Keystore

We can now select an APK file and automatically decompile and disassemble it.

In addition, APK Studio directly includes an integrated development environment (IDE). So we can make changes to the Smali code right from the application itself, such as changing the password to 12345.

APK Studio: Code manipulieren

When we are finished, we can rebuild the app and automatically have it signed.

It is also extremely helpful that APK Studio is able to install the app if we are running a local emulator or have our smartphone connected to the computer via ADB.


I hope you’ve learned something new from this tutorial. In the next parts we will take a look at more complex topics such as debugging Smali code, network traffic and obfuscation.

Stay positive and happy hacking!