Android Challenge in ASCWG Finals
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.