Flash CTF – Gretel

In this challenge, we’re given an Android app that contains the encrypted flag and need to find a way to decrypt it.

Solution

Android apps (represented with the file extension .apk) are typically written in Java or Kotlin. Assuming this is the case for our application, we can use something like Bytecode Viewer to decompile the bytecode.

After loading the APK into Bytecode Viewer, we can navigate through the folder structure via com -> example -> vaultchallenge until we get to MainActivity.class, which represents the code that gets executed when a user opens the app:

package com.example.vaultchallenge;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

/* loaded from: classes3.dex */
public class MainActivity extends AppCompatActivity {
    private native String getIV();

    private native String getKey();

    static {
        System.loadLibrary("native-lib");
    }

    /* JADX INFO: Access modifiers changed from: protected */
    @Override // androidx.fragment.app.FragmentActivity, androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, android.app.Activity
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button viewVaultButton = (Button) findViewById(R.id.viewVaultButton);
        final TextView ciphertextView = (TextView) findViewById(R.id.ciphertextView);
        viewVaultButton.setOnClickListener(new View.OnClickListener() { // from class: com.example.vaultchallenge.MainActivity$$ExternalSyntheticLambda0
            @Override // android.view.View.OnClickListener
            public final void onClick(View view) {
                MainActivity.this.m66lambda$onCreate$0$comexamplevaultchallengeMainActivity(ciphertextView, view);
            }
        });
    }

    /* JADX INFO: Access modifiers changed from: package-private */
    /* renamed from: lambda$onCreate$0$com-example-vaultchallenge-MainActivity, reason: not valid java name */
    public /* synthetic */ void m66lambda$onCreate$0$comexamplevaultchallengeMainActivity(TextView ciphertextView, View v) {
        ciphertextView.setText("AES Ciphertext: c41fb50049ffbee51fc2620abeb88bfcd7802183967d29e4c5941cd969e664cb");
        ciphertextView.setVisibility(0);
        getKey();
        getIV();
    }
}

We see from the code above that we’re dealing with AES ciphertext in hex form. The key and IV get loaded in from the functions getKey() and getIV(), respectively. Unfortunately, we aren’t able to see the IV and key because those functions are marked as native, which means that their actual implementation is defined in a library (native-lib in this case).

Frida

In order to recover the key and IV, we’re going to need to perform dynamic analysis. To do this, we can make use of Frida. In addition to Frida, we’ll install Android Studio to get access to adb and set up an emulated Android device for the app to run on.

We can launch our emulator through Android Studio. Once it’s up, we can install the APK using adb install. We can run adb push to send the Frida server to the emulator and adb shell to start it.

Next, we’ll write a script to modify the behavior of our APK such that the IV and key get printed to the console after getting loaded:

Java.perform(function () {

    var MainActivity = Java.use("com.example.vaultchallenge.MainActivity");

    MainActivity.getIV.implementation = function () {
        var iv = this.getIV();
        console.log("IV: " + iv);
        return iv;
    };

    MainActivity.getKey.implementation = function () {
        var key = this.getKey();
        console.log("Key: " + key);
        return key;
    };

    MainActivity.$init.implementation = function () {
        console.log("MainActivity created");
        return this.$init();
    };
});

After saving the script to a file called hook.js, we can use Frida to load the script and run the APK package like so:

frida -U -l hook.js -f com.example.vaultchallenge
     ____
    / _  |   Frida 16.6.6 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://frida.re/docs/home/
   . . . .
   . . . .   Connected to Android Emulator 5554 (id=emulator-5554)
Spawned `com.example.vaultchallenge`. Resuming main thread!
[Android Emulator 5554::com.example.vaultchallenge ]-> MainActivity created

In the emulator, once we click on the button in the app, we’ll see the key and IV get printed to the console:

Spawned `com.example.vaultchallenge`. Resuming main thread!
[Android Emulator 5554::com.example.vaultchallenge ]-> MainActivity created
Key: 11484248dbbef5822c496a273c5d83c4
IV: 1e55f2393f462666215d04fbbc85f3f1

Decryption

Now that we have all the parts needed to decrypt the ciphertext, we can use CyberChef to perform the decryption or write a Python script like the one below:

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

key = bytes.fromhex('11484248dbbef5822c496a273c5d83c4')
iv = bytes.fromhex('1e55f2393f462666215d04fbbc85f3f1')
ct = bytes.fromhex('c41fb50049ffbee51fc2620abeb88bfcd7802183967d29e4c5941cd969e664cb')

cipher = AES.new(key, AES.MODE_CBC, iv)
pt = unpad(cipher.decrypt(ct), AES.block_size)

print("Decrypted plaintext:", pt.decode())

After decrypting the ciphertext, we see the flag:

MetaCTF{Its_ezier_dyn4mic4lly}