Skip to content

Instantly share code, notes, and snippets.

@ky28059
Last active February 10, 2024 20:46
Show Gist options
  • Select an option

  • Save ky28059/cec9f7e8071b52e890c6a2469360be48 to your computer and use it in GitHub Desktop.

Select an option

Save ky28059/cec9f7e8071b52e890c6a2469360be48 to your computer and use it in GitHub Desktop.

0xL4ugh CTF 2024 — MyVault

Welcome to our secure vault !

We're given an APK file that decompiles to, among other things,

    package com.tarek.myvault;

import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import d.m;
import i.c;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;

public class MainActivity extends m {
    public final void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        setContentView((int) R.layout.activity_main);
        File file = new File(getCacheDir() + "/vault.enc");
        if (!file.exists()) {
            try {
                InputStream open = getAssets().open("vault.enc");
                byte[] bArr = new byte[open.available()];
                open.read(bArr);
                open.close();
                FileOutputStream fileOutputStream = new FileOutputStream(file);
                fileOutputStream.write(bArr);
                fileOutputStream.close();
                ((Button) findViewById(R.id.btnSubmit)).setOnClickListener(new c(this, (EditText) findViewById(R.id.editTextOTP), 2));
            } catch (Exception e3) {
                throw new RuntimeException(e3);
            }
        }
    }
}

The main activity reads bytes from the encoded vault.enc file, then sets up a click listener via new c().

kevin@ky28059:/mnt/c/users/kevin/Downloads$ xxd vault.enc
00000000: 3797 080e c05e 8e05 90f0 9c57 bf4e d622  7....^.....W.N."
00000010: 6c56 fd13 e3c9 52b3 21ec 1871 fb08 b3a2  lV....R.!..q....
00000020: 23de 50c3 a027 4a15 0fd9 2251 0703 c3e2  #.P..'J..."Q....
00000030: 37c6 7ce3 0414 2328 cb24 ca12 1520 6d1e  7.|...#(.$... m.
00000040: ba4d 692a b1e3 cacb 847b a957 df74 9896  .Mi*.....{.W.t..
00000050: 578c 67b8 b837 cf6c 4993 df21 3728 e001  W.g..7.lI..!7(..
00000060: e357 9a89 b1a5 2624 57e9 d093 0242 c0e9  .W....&$W....B..
00000070: dd42 4cb7 4c77 7c8f 1c93 17c4 184a 1cb9  .BL.Lw|......J..
00000080: cb02 d559 b5db d990 6c73 7ae0 7e50 3b6b  ...Y....lsz.~P;k
00000090: 7255 8c0b fe25 3c08 39e9 205a bb09 3531  rU...%<.9. Z..51
000000a0: 00cc 6330 d44e f54c 4d8d 44f7 18c7 cdad  ..c0.N.LM.D.....
000000b0: 370d 48ed 2635 7b00 e492 2354 474e e616  7.H.&5{...#TGN..
000000c0: 35dc 780c 9e21 f756 0bcb 451e 7b64 cbd4  5.x..!.V..E.{d..
000000d0: fe95 bab6 fda9 9c26 08b5 c17c dd85 c421  .......&...|...!
000000e0: 0eaa 5e78 7512 8829 2331 8fd0 2be6 7401  ..^xu..)#1..+.t.

Looking in i.c,

    package i;

import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.widget.EditText;
import android.widget.Toast;
import com.tarek.myvault.MainActivity;
import h.a;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

public final class c implements View.OnClickListener {

    // ...

    public final void onClick(View view) {
        // ...
        switch (i3) {
            // ...
            default:
                String obj3 = ((EditText) obj2).getText().toString();
                MainActivity mainActivity = (MainActivity) obj;
                mainActivity.getClass();
                try {
                    String sb = new StringBuilder(obj3).reverse().toString();
                    File file = new File(mainActivity.getCacheDir(), "vault.txt");
                    File file2 = new File(mainActivity.getCacheDir(), "vault.enc");
                    SecretKeySpec secretKeySpec = new SecretKeySpec((obj3 + sb + obj3 + sb).getBytes(), "AES");
                    Cipher instance = Cipher.getInstance("AES");
                    instance.init(2, secretKeySpec);
                    FileInputStream fileInputStream = new FileInputStream(file2);
                    byte[] bArr = new byte[((int) file2.length())];
                    fileInputStream.read(bArr);
                    byte[] doFinal = instance.doFinal(bArr);
                    FileOutputStream fileOutputStream = new FileOutputStream(file);
                    fileOutputStream.write(doFinal);
                    fileInputStream.close();
                    fileOutputStream.close();
                    str = "Congrats!";
                } catch (Exception unused) {
                    str = "Incorrect OTP";
                }
                Toast.makeText(mainActivity, str, 0).show();
                return;
        }
    }

    // ...
}

On click, the handler pulls a string from a text input, creating an AES key equal to

{string}{reversed string}{string}{reversed string}

and uses it to attempt to decrypt the vault. On success, it writes the result to vault.txt.

String obj3 = ((EditText) obj2).getText().toString();
String sb = new StringBuilder(obj3).reverse().toString();

// ...

SecretKeySpec secretKeySpec = new SecretKeySpec((obj3 + sb + obj3 + sb).getBytes(), "AES");
Cipher instance = Cipher.getInstance("AES");
instance.init(2, secretKeySpec);

// ...

byte[] 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:

import java.io.File
import java.nio.ByteBuffer
import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec

fun main() {
    val encBytes = File("./vault.enc").readBytes()

    for (i in 0u..UInt.MAX_VALUE) {
        val bytes = i.toInt().toByteArray()

        val key = bytes + bytes.reversed() + bytes + bytes.reversed()
        if (i % 100000u == 0u) println("key (${key.size} bytes): ${key.toList()}")

        val spec = SecretKeySpec(key, "AES")
        val cipher = Cipher.getInstance("AES")
        cipher.init(2, spec)

        try {
            val res = cipher.doFinal(encBytes).toString(Charsets.UTF_8)
            if (res.contains("0xL4ugh")) {
                println(res)
                break
            }
        } catch (e: Exception) {
            // println("Decryption failed")
        }
    }
}

fun Int.toByteArray(): ByteArray {
    val buffer = ByteBuffer.allocate(Int.SIZE_BYTES)
    buffer.putInt(this)
    return buffer.array()
}

After a few hours, we get the flag:

key (16 bytes): [55, 46, -106, 64, 64, -106, 46, 55, 55, 46, -106, 64, 64, -106, 46, 55]
key (16 bytes): [55, 48, 28, -32, -32, 28, 48, 55, 55, 48, 28, -32, -32, 28, 48, 55]
key (16 bytes): [55, 49, -93, -128, -128, -93, 49, 55, 55, 49, -93, -128, -128, -93, 49, 55]
key (16 bytes): [55, 51, 42, 32, 32, 42, 51, 55, 55, 51, 42, 32, 32, 42, 51, 55]
{
  "vault": [
    {
      "id": 0,
      "name": "Mohamed Tarek",
      "content": "I hope You enjoyed with me!"
    },
    {
      "id": 1337,
      "name": "flag",
      "content": "0xL4ugh{Y0u_Ar3_FoRceR_Like_A_H0uRc3}"
    }
  ]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment