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}