Analysing smali code

Mobile apps have become increasingly widespread compared to their desktop counterparts. In addition, many apps often have stricter security requirements since they incorporate micropayments. We also perform sensitive transactions through mobile apps. For example, there are no desktop internet banking applications, we use the browser to perform such transactions. But nowadays, we have internet banking apps. If the app is a webview that simply opens up a webpage, then there is less cause for concern. However, if server side code is moved into the app, there is a need to ensure that the code cannot be reverse engineered and bypassed.

Disclaimer

Reverse Engineering certain apps may be illegal. Fraudulently obtaining tokens, credits or other in app items may also be illegal. This article is strictly for educational purposes. I am not responsible for your actions.

Reverse Engineering Process

The first step in reverse engineering an android app is to extract the APK file from the device. We then need to unpack the APK file and disassemble it to obtain smali code. smali is an assembly language that runs on Dalvik VM, which is Android's JVM. smali code can be obtained by 'baksmaling' Dalvik executable files (.dex). Fortunately, there are tools which automate the entire process. I recommend APK Studio, which I found to be very handy, especially since you can build, export and install the app directly from the IDE.

The next step is to try to obtain the java source code. JADX is a dex to Java decompiler. Java source code is much easier to read compared to dex code, just as C is much easier to read compared to assembly code. However, I have found that JADX does not produce recompilable source code. Hence, I make use of the Java source to understand the code, identify the location that I wish to make a change, find the corresponding location in the smali code, edit the smali code, build, export, install and run. If proguard was used during the compilation process, you will get obfuscated class and method names, which will slow you down quite a bit. Otherwise, you get Java code that is very close to the original, including the comments left by the developer.

Example

We will look at the Java and smali code of a very simple example.

1
2
3
4
5
6
public int getTokens(int amt) {
    if (this.isPaid && this.handler != null) {
        return this.handler.creditTokens(amt);
    }
    return 0;
}

And the smali equivalent.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
.method public getTokens(I)I
    .locals 2
    .param p1, "amt"    # I

    .prologue
    const/4 v0, 0x0

    .line 2
    iget-boolean v1, p0, Lcom/limbenjamin/Example;->isPaid:Z

    if-nez v1, :cond_1

    .line 5
    :cond_0
    :goto_0
    return v0

    .line 2
    :cond_1
    iget-object v1, p0, Lcom/limbenjamin/Example;->handler:Lcom/limbenjamin/ExampleHandler;

    if-eqz v1, :cond_0

    .line 3
    move v3, p1

    iget-object v0, p0, Lcom/limbenjamin/Example;->handler:Lcom/limbenjamin/ExampleHandler;

    invoke-interface {v0, v3}, Lcom/limbenjamin/ExampleHandler;->creditTokens(I)V

    move-result v0

    goto :goto_0
.end method

As you can see, the smali code is much more verbose compared to the Java code. Note that the full class path for all objects is used and there are no imports in smali. Also, try to use the .line numbers to navigate. I have found it very difficult to write smali code. Other than keeping track of what is stored in each register, I have encountered unexplained errors when injecting my own code. I find it easier to simply hijack the control flow. For example, if one wishes to enter the if condition, I would change if-nez v1 at line 11 to if-eqz v1, or I could iput-boolean v1, 0x1 at line 10. To complete the example, I would have to add const v3, 0xA at line 28, and enjoy 10 free tokens.

Happy Hacking!