Posts OWASP Android Uncrackable Level 1 Writeup
Post
Cancel

OWASP Android Uncrackable Level 1 Writeup

Information

Recently I have taken a liking to mobile devices pentesting, so I’m doing this series as a primer for my later series.

UnCrackable App for Android Level 1

This app holds a secret inside. Can you find it?

  • Objective: A secret string is hidden somewhere in this app. Find a way to extract it.
  • Author: Bernhard Mueller.
  • Maintained by the OWASP MSTG leaders.

Installation

This app is compatible with Android 4.4 and up.

1
  $ adb install UnCrackable-Level1.apk

Requirements

  • A rooted android device or emulator (with adb)
  • Frida for dynamic instrumentation
  • Java decompiler: JADX, JD-GUI, etc..

Analyzing the logic

First, we are going to open the app to see how it functions, only to see it shun us away, so rude.

Decompiling the apk with JADX, we see that the app is very simple, but most of the classes’ and functions’ name is obfuscated.

The message above is a clue for us to find how exactly the application checks if the device is rooted or not. We can search for strings using JADX’s text search functionality (Ctrl + Shift + F):

The search tell us that there’s a “root” string in sg.vantagepoint.uncrackable1.MainActivity.onCreate(), this function will check to see if the device is rooted and the app is debuggable:

The check is done by class sg.vantagepoint.a.c, function a(), b() and c(), these function will return true if the checks indicates that the device is rooted and false otherwise:

If any of those functions return true, the app will call sg.vantagepoint.uncrackable1.MainActivity.a(), which will tell us that the app is rooted/debuggable and will exit upon clicking OK like we see at the beginning of the post.

Using Frida to bypass the root check

Here, Frida can intercept or replace the return value of the 3 root checking functions like so:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Java.perform(function(){
	console.log("start hooking");
	const rootCheck = Java.use("sg.vantagepoint.a.c");
	rootCheck.a.implementation = function(){
		console.log("function a");
		return false;
	}
	rootCheck.b.implementation = function(){
		console.log("function b");
		return false;
	}
	rootCheck.c.implementation = function(){
		console.log("function c");
		return false;
	}
});

After hooking these functions, Frida replaced the function definitions with our own definitions, making the call to those 3 functions always return false, thus bypassing the root check.

Alternatively, we can ignore these checks and just hook straight to System.exit() to prevent the app from exiting.

1
2
3
4
5
6
Java.perform(function(){
    var System = Java.use("java.lang.System");  
    System.exit.overload("int").implementation = function() {    
        console.log("Won't exit.");
    };  
});

Connect to the device with adb and start frida-server if you haven’t already. Run the script and you shouldn’t see the app exit anymore:

1
 $ frida -U -l uncrackable-1.js -f owasp.mstg.uncrackable1 --no-pause

The secret

The application use sg.vantagepoint.uncrackable1.MainActivity.verify() to verify the entered secret.

We can clearly see that the string we enterted into the textbox is stored in the obj string variable, which is then passed to sg.vantagepoint.uncrackable1.a.a() in the if condition, we can safely conclude that this function will return true if the secret is correct.

Our string is compared to a string represented by the variable bArr. The value of bArr is calculated with a function call to sg.vantagepoint.a.a.a(byte[] bArr, byte[] bArr2) with some encoding shenanigans happening, we won’t need to bother with figuring out how the function works, as we are going to hook the function to get the byte array that the function returns with this snippet:

1
2
3
4
5
6
7
8
9
Java.perform(function(){
    const class2 = Java.use("sg.vantagepoint.a.a");
	class2.a.implementation = function(bArr, bArr2){
		var retVal = this.a(bArr, bArr2);
		var secret = String.fromCharCode.apply(null,retVal);
		console.log(secret);
		return retVal;
	}
});

This will call the original sg.vantagepoint.a.a.a(byte[] bArr, byte[] bArr2) function and convert the byte array output to String, then returning the byte array to the caller function. This code will be triggered when we verify the secret from the GUI:

So that’s our secret:

Complete Frida script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Java.perform(function(){
	console.log("start hooking");
	const rootCheck = Java.use("sg.vantagepoint.a.c");
	rootCheck.a.implementation = function(){
		console.log("function a");
		return false;
	}
	rootCheck.b.implementation = function(){
		console.log("function b");
		return false;
	}
	rootCheck.c.implementation = function(){
		console.log("function c");
		return false;
	}
	
	const class2 = Java.use("sg.vantagepoint.a.a");
	class2.a.implementation = function(bArr, bArr2){
		var retVal = this.a(bArr, bArr2);
		var secret = String.fromCharCode.apply(null,retVal);
		console.log(secret);
		return retVal;
	}
});

Further Reading

  1. Frida github page
  2. Getting started with Frida by Briskinfosec
  3. JADX
This post is licensed under CC BY 4.0 by the author.