Android Hacking: Part 2 – Manipulating Apps

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.

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

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!
.

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
.
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.

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
.

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.

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:

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
.

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!
