Android Challenge in ASCWG Finals

Adham A. Makroum
5 min readFeb 21, 2023

--

Hello guys, my name is Adham Makroum, I started the Android PenTest app a short time ago, so I saw this challenge as training for me and it was also because it has a variety of tasks

Let’s Start

First, After installing the app using adb and try to lunch it, I noticed that the app doesn’t run, so I checked the source code using jadx_GUI

open AndroidManifes.xml I noticed that there are a Broadcast Receiver that has an intent-filter and AnotherActivity but it’s exported=”false”

In the MainActivity there’s Root Detection

if isRooted() is true it will make toast and exit from APK, then I checked this function

in RootBeer Class, most of these methods check for a specific technique to root detection, and in IsRooted() if there’s any function of them is true the isRooted() will be return true

it’s time for Frida

Java.perform(function x() {
let RootBeer = Java.use("com.scottyab.rootbeer.RootBeer");
RootBeer.isRooted.implementation = function () {
return false;
};

this script will hook isRooted function and return false to root detection

the root detection was bypassed and the APK opened

All the Frida scripts that i will use in this walkthrough must include root detection script like this

Java.perform(function x() {
// Root Detection Bypass
let RootBeer = Java.use("com.scottyab.rootbeer.RootBeer");
RootBeer.isRooted.implementation = function () {
return false;
};


// Another script
let MainActivity = Java.use("com.ctf.ascwg.MainActivity");
MainActivity.decrypt.implementation = function (algorithm, cipherText, key, iv) {
console.log('The Script Loaded');
let dec = this.decrypt(algorithm, cipherText, key, iv);
console.log('decrypted Value: ' + dec);
return dec;
};
});

there are another 2 functions in MainActivity class

public static final void m28onCreate$lambda0(MainActivity this$0, View view) {
Intrinsics.checkNotNullParameter(this$0, "this$0");
byte[] bytes = "1561615050650065".getBytes(Charsets.UTF_8);
Intrinsics.checkNotNullExpressionValue(bytes, "this as java.lang.String).getBytes(charset)");
String decrypt = this$0.decrypt("AES/CBC/PKCS5Padding", "kcjiK3pT/4QdvfqmnPel/A==", new SecretKeySpec(bytes, "AES"), new IvParameterSpec(new byte[16]));
if (((EditText) this$0._$_findCachedViewById(R.id.passEt)).getText().toString().equals(decrypt) & ((EditText) this$0._$_findCachedViewById(R.id.nameEt)).getText().toString().equals("badawy")) {
MainActivity mainActivity = this$0;
this$0.startActivity(new Intent(mainActivity, AnotherActivity.class));
this$0.finish();
Toast.makeText(mainActivity, "Welcome Badawy!", 1).show();
return;
}
Toast.makeText(this$0, "Credentials are wrong!", 1).show();
}
}

in this function, it compares the decrypted cipherText with a password that the user will enter, and compare the username with badawy

this function is Used to decrypt the cipherText

 public final String decrypt(String algorithm, String cipherText, SecretKeySpec key, IvParameterSpec iv) {
Intrinsics.checkNotNullParameter(algorithm, "algorithm");
Intrinsics.checkNotNullParameter(cipherText, "cipherText");
Intrinsics.checkNotNullParameter(key, "key");
Intrinsics.checkNotNullParameter(iv, "iv");
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(2, key, iv);
byte[] plainText = cipher.doFinal(Base64.decode(cipherText, 0));
Intrinsics.checkNotNullExpressionValue(plainText, "plainText");
return new String(plainText, Charsets.UTF_8);
}

let’s hook this function to get the password

when clicking on the login button the decrypt function will execute

let MainActivity = Java.use("com.ctf.ascwg.MainActivity");
MainActivity.decrypt.implementation = function (algorithm, cipherText, key, iv) {
console.log('The Script Loaded');
let dec = this.decrypt(algorithm, cipherText, key, iv);
console.log('decrypted Value: ' + dec);
return dec;
};

now we have login creds

username: badawy

password: B@d@wy#2020

Clicking on the BROADCAST button

let’s check the MyBroadcastReceiver class

public final class MyBroadcastReceiver extends BroadcastReceiver {
public final native String getKeys();

public MyBroadcastReceiver() {
System.loadLibrary("api-keys");
}

@Override // android.content.BroadcastReceiver
public void onReceive(Context context, Intent intent) {
Intrinsics.checkNotNullParameter(context, "context");
Intrinsics.checkNotNullParameter(intent, "intent");
byte[] bytes = "2561651651561651".getBytes(Charsets.UTF_8);
Intrinsics.checkNotNullExpressionValue(bytes, "this as java.lang.String).getBytes(charset)");
if (StringsKt.equals$default(intent.getStringExtra("data"), new MainActivity().decrypt("AES/CBC/PKCS5Padding", "3KO5tETzqPHQGYdRQXJZE1x4ePh44iLL08vQJGjvXb4=", new SecretKeySpec(bytes, "AES"), new IvParameterSpec(new byte[16])), false, 2, null)) {
Toast.makeText(context, Intrinsics.stringPlus("Flag: ", getKeys()), 1).show();
return;
}
Log.i("FLAG", "Hehehehe try again!");
Toast.makeText(context, "Hehehehe try again!", 1).show();
}
}

this will check if the broadcast contains a string extra named “data” which is equal to the result of decrypting a hardcoded ciphertext using the decrypt() method

if the condition is true the flag will toast a message from the getKeys() method if not Hehehehe try again! will toast

now we need to run the previous Frida script to hook decrypt() again like we did before

if your Frida script still running, when clicking on the broadcast button the cipher will decrypt FL@G_!s_S3cerT(x.x) this is that we will send it within the broadcast

now let’s check How this broadcast receiver work, in AnotherActivity Class there is an intent

// AnotherActivity    
public static final void m26onCreate$lambda0(AnotherActivity this$0, View view) {
Intrinsics.checkNotNullParameter(this$0, "this$0");
Intent intent = new Intent();
intent.setAction("com.ctf.MyBroadcastReceiver");
intent.setFlags(32);
intent.putExtra("data", "Nothing to see here, move along.");
this$0.sendBroadcast(intent);
}

it takes com.ctf.MyBroadcastReceiver as an action, so let’s try to send a broadcast using adb

adb shell am broadcast -a com.ctf.MyBroadcastReceiver -e data "FL@G_\!s_S3cerT\(x.x\)"

This is an incomplete flag

ASCWG{C0mplete_Me_

Another Solution Using frida: Hook getKeys() function

using frida-ps -U to get PID to the APK

this is our script

Java.choose("com.ctf.ascwg.MyBroadcastReceiver", {    
"onMatch":function(instance){
console.log("[*] Matched !!")
console.log(instance.getKeys());
},
"onComplete":function() {
console.log("[*] Finished")
}
});

frida -U -f com.ctf.ascwg -l ASCWG.js -p 24413

Now we will try to figure out how to get the rest of the flag

in the AnotherActivity Class, there are some functions let’s check it

this will retrieve the SharedPreferences object with the key FLAG , so the Flag may be in shared pref

Another Solution Using frida: Hook getKeys() function

let AnotherActivity = Java.use("com.ctf.ascwg.AnotherActivity");  
AnotherActivity.getKeys.implementation = function () {
console.log('The Script Loaded');
let ret = this.getKeys();
console.log('Flag: ' + ret);
return ret;
};

you can use java.choose also like I did before

and finally, there’s the rest of the flag

Flag: ASCWG{C0mplete_Me_N!cE_C4tCH_R3v3rsEr}

It’s great APKto training, I recommended it for beginners like me

In the end, If there’s any step wrong Feel free to ping me, Don’t forget to follow me on medium and Twitter

Thank you for reading.

--

--