Dive deep into Android Application Security - OWASP MSTG Uncrackable level 1 writeup
Sep 18, 2019 •
android
Uncrackable Apps for Android is a collection of mobile reversing challenges maintained by the OWASP MSTG (Mobile Security Testing Guide) authors. Cracking and solving these challenges is a fun way to learn Android security.
Introduction
Android is the most popular mobile operating system with over 85% in market share and as a result it’s important to look into the security aspects of the same as well. Mobile Security Testing Guide (MSTG) is one of the Flagship OWASP project which is a comprehensive manual for mobile app security development, testing and reverse engineering. The authors of MSTG have created some crackme’s for both Android and iOS platform using which we can dive deep into the application security of the respective platforms. In this article, we will focus on solving uncrackable level 1 for Android.
Let’s install the APK in an emulator and see how it works. I am using genymotion personal license with Google Nexus 5 (6.0 - API 23) device.
If we run the application, it detects the root access to the device and exit immediately. Root detection is one of the common techniques developers use to prevent installing their applications on the rooted device. Let’s decompile the APK and look into the source code to see if we can bypass this.
Decompiling into Java Source code
One of the tools used to decompile the APK into java source code is Jadx.
We can see the source code on ./UnCrackable-Level1/sources/sg/vantagepoint/uncrackable1
where MainActivity.java
has been decompiled successfully by the Jadx.
On line number 12, 13 there are 2 imports which is nothing but importing from the location ./UnCrackable-Level1/sources/sg/vantagepoint/a/
which has 3 files: a.java, b.java, c.java
Both b.java and c.java files are being imported to the MainActivity.java
file. The program has 2 protections inbuilt:
Root Detection:
This checks if the app has been installed on a rooted device and if so, exit immediately. The function is called within onCreate
but it’s defined in c.java file:
So for Root detection, they are doing 3 tests to confirm if the device is rooted or not:
- Su Binary: The function checks if SU binary exists or not. If so, the device as rooted.
- test-keys: During the release of kernel, keys are being used to sign it which is either
release-keys
or test-keys
. If it’s the latter, it means the kernel was signed with a custom key generated by a 3rd party developer. This is an indication that the device might be rooted (This info is located in the file /system/build.prop
).
- /system/ : These are common files/binaries which is accessible in a rooted device which is otherwise not accessible (on a non rooted device).
Debuggable App:
By modifying the AndroidManifest.xml
, app can be debuggable so at the run time we can connect into the app using JDB (java debugger) and modify its behaviour. In order to prevent this, a check is implemented to see if the app is debuggable and if so, the system will exit.
Now let’s look at the verify
function where the secret is being verified.
The function takes an input from the user and pass it to a.a()
:
Class a
which has a parameter named str
(which is nothing but user input) and has a predefined variable named str2, which looks like the encrypted string. The encrypted string is decrypted on the run time and is compared with the string we entered. If both values match, we have successfully completed the challenge.
There are multiple ways in which we can solve the challenge:
- Repackaging
- Frida Instrumentation
- JDB (Runtime debugging)
Let’s look into some of the above techniques:
Bypassing root detection with Repackaging
One of the ways to solve this challenge is to decompile the app with apktool, modify the smali byte code and reinstall the app to control the function flow. Using this technique, Root detections can be bypassed in several ways:
-
Modify the return of each of the functions inside class c
to always return false whether they have detected root or not.
-
Modify the function onClick
inside the MainActivity.java to return void instead of calling system.exit
so that even through root is detected, the app won’t exit.
Let’s use the latter (2) to evade the root detection:
If we look at the functions inside MainActivity$1.smali
, a function named OnClick
is calling system.exit(0)
. Let’s modify the function to simply return void and ignore the exit call.
Let’s run the newly installed APK and we can see that clicking on “ok” won’t exit the application. Essentially what we did here was to modify the function call “onClick” and we removed the system.exit()
function invocation line: invoke-static {p1}, Ljava/lang/System;->exit(I)V
. Then we recomplied, signed (using sign.jar) and reinstalled the application to bypass this check.
Leaking the secret with runtime instrumentation - Frida
Frida is a dynamic runtime instrumentation toolkit using which we can hook functions, spy on crypto APIs or trace private application code on runtime. In short, using frida, we can redefine functions, leak function variables and what not ? In order to run Frida, make sure to install frida server on your rooted device and it’s running in the background as root (Frida - Install Client and Server). Once the server is running, we can write our own script to leak the secret out of the APK during run time.
A sample frida script (modifying function implementation) will look like this:
In very simple terms, we can write JavaScript code to tell frida to use a class and hook its corresponding functions and reimplement it. In the above example, we hooked a function named function_name
from a class and rewrote its implementation to make sure function always returns false during runtime.
From the initial source code analysis, we know the decryption of the encrypted string is happening inside sg/vantagepoint/a/a.java
where the return of the function a()
has our secret. So using Frida, we can do the following to solve the challenge:
- Hook the function
a()
inside the class sg.vantagepoint.a.a
.
- Modify the function implementation and call the function internally to leak its return value (byte array).
- Convert the returned byte array to ascii and append it to a string to retrieve our secret.
So our final exploit code looks like the following:
Run the above exploit (exploit.js) using Frida: frida -U -f owasp.mstg.uncrackable1 -l exploit.js --no-pause
. Once you run it, the program will be launched inside the emulator. Give some random input so that the function gets invoked at least once and look back in the Frida terminal to see the leaked secret.
References
-
Frida: https://frida.re
-
Eduardo Novella’s solution (must read) to uncrackable level 1 (completely using Frida): https://enovella.github.io/android/reverse/2017/05/18/android-owasp-crackmes-level-1.html
Anirudh Anand
Product Security ♥ | CTF - @teambi0s | Security Trainer - @7asecurity | certs - eWDP, OSCP, OSWE