0xL4ugh CTF 2024 — MyVault
Welcome to our secure vault !
We're given an APK file that decompiles to, among other things,
Code (java):
1 package com.tarek.myvault;
2
3import android.os.Bundle;
4import android.widget.Button;
5import android.widget.EditText;
6import d.m;
7import i.c;
8import java.io.File;
9import java.io.FileOutputStream;
10import java.io.InputStream;
11
12public class MainActivity extends m {
13 public final void onCreate(Bundle bundle) {
14 super.onCreate(bundle);
15 setContentView((int) R.layout.activity_main);
16 File file = new File(getCacheDir() + "/vault.enc");
17 if (!file.exists()) {
18 try {
19 InputStream open = getAssets().open("vault.enc");
20 byte[] bArr = new byte[open.available()];
21 open.read(bArr);
22 open.close();
23 FileOutputStream fileOutputStream = new FileOutputStream(file);
24 fileOutputStream.write(bArr);
25 fileOutputStream.close();
26 ((Button) findViewById(R.id.btnSubmit)).setOnClickListener(new c(this, (EditText) findViewById(R.id.editTextOTP), 2));
27 } catch (Exception e3) {
28 throw new RuntimeException(e3);
29 }
30 }
31 }
32}
The main activity reads bytes from the encoded vault.enc
file, then sets up a click listener via new c()
.
Code:
1kevin@ky28059:/mnt/c/users/kevin/Downloads$ xxd vault.enc
200000000: 3797 080e c05e 8e05 90f0 9c57 bf4e d622 7....^.....W.N."
300000010: 6c56 fd13 e3c9 52b3 21ec 1871 fb08 b3a2 lV....R.!..q....
400000020: 23de 50c3 a027 4a15 0fd9 2251 0703 c3e2 #.P..'J..."Q....
500000030: 37c6 7ce3 0414 2328 cb24 ca12 1520 6d1e 7.|...#(.$... m.
600000040: ba4d 692a b1e3 cacb 847b a957 df74 9896 .Mi*.....{.W.t..
700000050: 578c 67b8 b837 cf6c 4993 df21 3728 e001 W.g..7.lI..!7(..
800000060: e357 9a89 b1a5 2624 57e9 d093 0242 c0e9 .W....&$W....B..
900000070: dd42 4cb7 4c77 7c8f 1c93 17c4 184a 1cb9 .BL.Lw|......J..
1000000080: cb02 d559 b5db d990 6c73 7ae0 7e50 3b6b ...Y....lsz.~P;k
1100000090: 7255 8c0b fe25 3c08 39e9 205a bb09 3531 rU...%<.9. Z..51
12000000a0: 00cc 6330 d44e f54c 4d8d 44f7 18c7 cdad ..c0.N.LM.D.....
13000000b0: 370d 48ed 2635 7b00 e492 2354 474e e616 7.H.&5{...#TGN..
14000000c0: 35dc 780c 9e21 f756 0bcb 451e 7b64 cbd4 5.x..!.V..E.{d..
15000000d0: fe95 bab6 fda9 9c26 08b5 c17c dd85 c421 .......&...|...!
16000000e0: 0eaa 5e78 7512 8829 2331 8fd0 2be6 7401 ..^xu..)#1..+.t.
Looking in i.c
,
Code (java):
1 package i;
2
3import android.view.KeyEvent;
4import android.view.View;
5import android.view.Window;
6import android.widget.EditText;
7import android.widget.Toast;
8import com.tarek.myvault.MainActivity;
9import h.a;
10import java.io.File;
11import java.io.FileInputStream;
12import java.io.FileOutputStream;
13import javax.crypto.Cipher;
14import javax.crypto.spec.SecretKeySpec;
15
16public final class c implements View.OnClickListener {
17
18 // ...
19
20 public final void onClick(View view) {
21 // ...
22 switch (i3) {
23 // ...
24 default:
25 String obj3 = ((EditText) obj2).getText().toString();
26 MainActivity mainActivity = (MainActivity) obj;
27 mainActivity.getClass();
28 try {
29 String sb = new StringBuilder(obj3).reverse().toString();
30 File file = new File(mainActivity.getCacheDir(), "vault.txt");
31 File file2 = new File(mainActivity.getCacheDir(), "vault.enc");
32 SecretKeySpec secretKeySpec = new SecretKeySpec((obj3 + sb + obj3 + sb).getBytes(), "AES");
33 Cipher instance = Cipher.getInstance("AES");
34 instance.init(2, secretKeySpec);
35 FileInputStream fileInputStream = new FileInputStream(file2);
36 byte[] bArr = new byte[((int) file2.length())];
37 fileInputStream.read(bArr);
38 byte[] doFinal = instance.doFinal(bArr);
39 FileOutputStream fileOutputStream = new FileOutputStream(file);
40 fileOutputStream.write(doFinal);
41 fileInputStream.close();
42 fileOutputStream.close();
43 str = "Congrats!";
44 } catch (Exception unused) {
45 str = "Incorrect OTP";
46 }
47 Toast.makeText(mainActivity, str, 0).show();
48 return;
49 }
50 }
51
52 // ...
53}
On click, the handler pulls a string from a text input, creating an AES key equal to
Code:
1{string}{reversed string}{string}{reversed string}
and uses it to attempt to decrypt the vault. On success, it writes the result to vault.txt
.
Code (java):
1String obj3 = ((EditText) obj2).getText().toString();
2String sb = new StringBuilder(obj3).reverse().toString();
3
4// ...
5
6SecretKeySpec secretKeySpec = new SecretKeySpec((obj3 + sb + obj3 + sb).getBytes(), "AES");
7Cipher instance = Cipher.getInstance("AES");
8instance.init(2, secretKeySpec);
9
10// ...
11
12byte[] doFinal = instance.doFinal(bArr);
Because we know the key is the same string repeated (and reversed) 4 times, we only need to find the first fourth of the key to get the flag.
Unfortunately, there doesn't appear to be any other path forward than sheer brute force. AES accepts keys of 128, 192, and 256 bits — 16, 24, and 32 bytes, or 4, 6, and 8 bytes for us to guess. Assuming the smallest key size, here's a Kotlin script that brute forces just that:
Code (kt):
1import java.io.File
2import java.nio.ByteBuffer
3import javax.crypto.Cipher
4import javax.crypto.spec.SecretKeySpec
5
6fun main() {
7 val encBytes = File("./vault.enc").readBytes()
8
9 for (i in 0u..UInt.MAX_VALUE) {
10 val bytes = i.toInt().toByteArray()
11
12 val key = bytes + bytes.reversed() + bytes + bytes.reversed()
13 if (i % 100000u == 0u) println("key (${key.size} bytes): ${key.toList()}")
14
15 val spec = SecretKeySpec(key, "AES")
16 val cipher = Cipher.getInstance("AES")
17 cipher.init(2, spec)
18
19 try {
20 val res = cipher.doFinal(encBytes).toString(Charsets.UTF_8)
21 if (res.contains("0xL4ugh")) {
22 println(res)
23 break
24 }
25 } catch (e: Exception) {
26 // println("Decryption failed")
27 }
28 }
29}
30
31fun Int.toByteArray(): ByteArray {
32 val buffer = ByteBuffer.allocate(Int.SIZE_BYTES)
33 buffer.putInt(this)
34 return buffer.array()
35}
After a few hours, we get the flag:
Code:
1key (16 bytes): [55, 46, -106, 64, 64, -106, 46, 55, 55, 46, -106, 64, 64, -106, 46, 55]
2key (16 bytes): [55, 48, 28, -32, -32, 28, 48, 55, 55, 48, 28, -32, -32, 28, 48, 55]
3key (16 bytes): [55, 49, -93, -128, -128, -93, 49, 55, 55, 49, -93, -128, -128, -93, 49, 55]
4key (16 bytes): [55, 51, 42, 32, 32, 42, 51, 55, 55, 51, 42, 32, 32, 42, 51, 55]
5{
6 "vault": [
7 {
8 "id": 0,
9 "name": "Mohamed Tarek",
10 "content": "I hope You enjoyed with me!"
11 },
12 {
13 "id": 1337,
14 "name": "flag",
15 "content": "0xL4ugh{Y0u_Ar3_FoRceR_Like_A_H0uRc3}"
16 }
17 ]
18}