CTRL Alt Frida
- Category: Mobile
- 450 points
- Solved by JCTF Team
Description
Challenge Overview
This challenge lets us upload a frida script. In our script, we will retrieve the flag from the encrypted shared preferences.
Solution
We start by decompiling the apk using jadx-gui and opening the android manifest to check for the main activity:
<activity
android:theme="@style/Theme.HelloWorld"
android:label="@string/app_name"
android:name="com.appsecil.wakeup.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
We then dive into this activity's onCreate
method and find the following code:
if (bVar.f701a.f698a.getString("WakeUp", null) == null) {
char[] cArr = new char[38];
// fill-array-data instruction
cArr[0] = 'A';
cArr[1] = 'p';
cArr[2] = 'p';
cArr[3] = 'S';
cArr[4] = 'e';
cArr[5] = 'c';
cArr[6] = '-';
cArr[7] = 'I';
cArr[8] = 'L';
cArr[9] = '{';
cArr[10] = 'T';
cArr[11] = 'h';
cArr[12] = 'i';
cArr[13] = 's';
cArr[14] = '_';
cArr[15] = 'i';
cArr[16] = 's';
cArr[17] = '_';
cArr[18] = 'n';
cArr[19] = 'o';
cArr[20] = 't';
cArr[21] = '_';
cArr[22] = 'a';
cArr[23] = '_';
cArr[24] = 'f';
cArr[25] = 'l';
cArr[26] = 'a';
cArr[27] = 'g';
cArr[28] = '}';
bVar.f701a.edit().putString("WakeUp", new String(cArr)).apply();
Arrays.fill(cArr, 0, 38, (char) 0);
}
By going into bVar.f701a
we can see that it is an object from the a
class, initialized with an instance of EncryptedSharePreferences:
public b(Context context) {
SharedPreferences create = EncryptedSharedPreferences.create(context, "secure_prefs", new MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(), EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM);
d.k(create);
this.f701a = new a(create);
}
And finally, this is the some code of the a
class:
public final class a implements SharedPreferences {
/* renamed from: a, reason: collision with root package name */
public final SharedPreferences f698a;
public a(SharedPreferences sharedPreferences) {
this.f698a = sharedPreferences;
}
@Override // android.content.SharedPreferences
public final boolean contains(String str) {
return this.f698a.contains(str);
}
@Override // android.content.SharedPreferences
public final SharedPreferences.Editor edit() {
SharedPreferences.Editor edit = this.f698a.edit();
d.p(edit, "edit(...)");
return edit;
}
@Override // android.content.SharedPreferences
public final Map getAll() {
return new LinkedHashMap(h.f2194a);
}
@Override // android.content.SharedPreferences
public final boolean getBoolean(String str, boolean z2) {
return this.f698a.getBoolean(str, z2);
}
This class is a wrapper around the SharedPreferences
class, which is used to store key-value pairs in Android. The app is using EncryptedSharedPreferences
, which implements the SharedPreferences
interface, allowing us to store sensitive data securely (https://developer.android.com/reference/androidx/security/crypto/EncryptedSharedPreferences). This class abstracts the encryption and decryption of the keys and values stored in shared preferences, making it easier to work with sensitive data.
Encrypted shared preferences work by using two sets of keys: A master key and two subkeys. The master key is stored on the keystore, and it’s used to decrypt two subkeys - one for decrypting keys on the shared preferences, and the other to decrypt values.
So going back to our initial code, we are calling the getString
method, to retrieve the “WakeUp” string from the encrypted shared preferences file. If the string is not there, which means this is the first time we are doing it, we save the flag with the key being "WakeUp". The objective of using an char array is probably so we won’t try looking for java string objects in memory and find the flag this way.
I tried creating an instance of the b
class (which will initialize the EncryptedSharedPreferences
), and then calling getString
on it's inner a
object, but it only worked locally - the obfuscation method used made the names of the class be different on the server side. This means this wasn't the intended approach.
So how can we decrypt the flag and print it using a frida script?
Looking at the documentation, we learn how to create an instance of EncryptedSharedPreferences
:
MasterKey masterKey = new MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build();
SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
context,
"secret_shared_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
Our plan is to create an instance of EncryptedSharedPreferences
using the same parameters as in the app, and then retrieve the flag using the getString
method.
We just need to change the name of the file to secure_prefs
, and we can use the getString
method to retrieve the flag.
As mentioned before, encrypted shared preferences uses a master key.
How do we make sure that we are using the correct master key? What is the key alias?
Looking at the Builder
class, the constructor uses a default key alias of master_key
:
public Builder(Context context) {
this(context, MasterKey.DEFAULT_MASTER_KEY_ALIAS);
}
Which is defined in the MasterKey
class as:
public static final String DEFAULT_MASTER_KEY_ALIAS = "_androidx_security_master_key_";
This means that we don't need to specify the key alias, and we can use the default one (the same way the app does).
Now we can write our frida script to retrieve the flag:
const EncryptedSharedPreferences = Java.use('androidx.security.crypto.EncryptedSharedPreferences');
const Context = Java.use('android.app.ActivityThread').currentApplication().getApplicationContext();
const MasterKeyBuilder = Java.use('androidx.security.crypto.MasterKey$Builder');
const PrefKeyEncryptionScheme = Java.use('androidx.security.crypto.EncryptedSharedPreferences$PrefKeyEncryptionScheme');
const PrefValueEncryptionScheme = Java.use('androidx.security.crypto.EncryptedSharedPreferences$PrefValueEncryptionScheme');
Java.perform(function () {
var builder = MasterKeyBuilder.$new(Context);
var KeyScheme = Java.use("androidx.security.crypto.MasterKey$KeyScheme");
builder.setKeyScheme(KeyScheme.AES256_GCM.value);
var masterKey = builder.build();
var keyEncryptionScheme = PrefKeyEncryptionScheme.valueOf("AES256_SIV");
var valueEncryptionScheme = PrefValueEncryptionScheme.valueOf("AES256_GCM");
var encShared = EncryptedSharedPreferences.create(Context, "secure_prefs", masterKey, keyEncryptionScheme, valueEncryptionScheme);
console.log(encShared.getString("WakeUp", null))
});
And we get the flag: