Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions ApoorvCTF-26-Writeups/Cryptography/Batman's Secret Batvault/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Batman's Secret Batvault

**author**: accord
**flag**: `apoorvctf{__gr3at_w0rk_p0lynomi4l_G0dS_67}`

## Description

> Batman has been captured by the Riddler, and Gotham is running out of time. Before disappearing, he left behind a clue: the password needed to activate the emergency protocol that can save him is stored inside his secret BatVault. Robin has managed to access the vault, but all the passwords inside are encrypted with a unique encryption invented by and known only to Batman. Among the many stored credentials, one of them holds the real key to rescuing the Dark Knight. Can you uncover Batman's password and save him before the Riddler's plan succeeds?
> `nc chals2.apoorvctf.xyz 13420`

## Writeup

No source code provided here. We have to reverse-engineer Batman's custom encryption purely as a black-box.

Connecting to the netcat service gives us a password manager interface.

```bash
=== BatVault v1.0 — Secure Password Storage ===
Commands remaining: 200
Commands: list | add | del <i> | edit <i> | quit

>>
```

If we run `list`, we see 10 pre-loaded entries that look like this:

```bash
1 | wise.com | 3cf.56fd3.3729629.d1c34714;39f.4ea24.2f68c14.ab6b8180;...
```

Each block is separated by semicolons and has four dot-separated hex values (`A.B.C.D`). Clearly, one of these, or maybe some of these, have to be decrypted to figure out the flag.

Let's play around and `add` some of our own passwords to see what happens.

|**Password**|**Ciphertext block**|
|---|---|
|`abcd`|`d2.4097.8d3f6.73d088`|
|`dcba`|`d2.4097.8d3f6.73d088`|

Notice anything? `abcd` and `dcba` yield the exact same ciphertext block. The passwords are being chunked into 4-byte blocks, and the encryption is completely **order-invariant** within each block!

Now, there are two ways of solving this problem.

**Method 1: The Mathematician's Approach**

Order-invariant functions of 4 values? This screams Elementary Symmetric Polynomials. If we have 4 transformed values $(y_1, y_2, y_3, y_4)$, the symmetric sums are:

$$e_1 = y_1 + y_2 + y_3 + y_4$$

$$e_2 = y_1y_2 + y_1y_3 + y_1y_4 + y_2y_3 + y_2y_4 + y_3y_4$$

$$e_3 = y_1y_2y_3 + y_1y_2y_4 + y_1y_3y_4 + y_2y_3y_4$$

$$e_4 = y_1y_2y_3y_4$$

The ciphertext `A.B.C.D` is just $(e_1, e_2, e_3, e_4)$ in hex!

Before these sums are calculated, each byte of the plain-text is XOR-ed with a random per-session key byte $k$. To recover this XOR key, we can just send `AAAA` (where all bytes are 65). The first symmetric sum will simply be:

$$e_1 = 4 \times (65 \oplus k)$$

So recovering the key is trivial: `key = (e1 // 4) ^ ord('A')`.

Now, how do we decrypt the other vault entries?

Because $(e_1, e_2, e_3, e_4)$ are the coefficients of a polynomial whose roots are the original bytes, you can brute-force the roots $t \in [0, 255]$ for:

$$P(t) = t^4 - e_1t^3 + e_2t^2 - e_3t + e_4$$

**Method 2: The Hacker's Approach (Dictionary Attack)**

There is a much lazier (and arguably smarter) way if you look closely at the constraints! The service lets us add passwords of **arbitrary length**. We don't need to solve any polynomials at all; we can just build a massive lookup map!

How many possible unordered 4-byte blocks are there? This is a classic "combinations with replacement" problem. If we assume a reasonable character set size $N$ (say, $N=64$ for alphanumeric plus common symbols), the number of unique unordered 4-character multisets is given by:

$$\binom{N+k-1}{k}$$

Plugging in our values ($N=64, k=4$):

$$\binom{64+4-1}{4} = \binom{67}{4} = 766,480$$

Since an `add` command accepts an arbitrarily long password, we can pack thousands of 4-byte combinations into a single payload. With 200 commands available, we can easily dump the entire viable permutation space into the vault in just a handful of network requests. We read the ciphertext blocks the server spits back, and instantly build a perfect `ciphertext_block -> unordered_plaintext_characters` dictionary. No polynomial root finding required! :D

**Unscrambling the Flag**

Regardless of which method you use, symmetric polynomials inherently destroy the order of the bytes. We get the correct 4 characters for each block, but they are scrambled.

You can honestly just eyeball it and manually unscramble the anagrams since it's mostly English text and leetspeak. Alternatively, some solvers wrote a search scripts using a trigram language model to automatically reshuffle and rank the candidates. Either way works perfectly fine.

Watch out for the decoys, though! The vault is filled with fake flags and Rickrolls to waste your time:

- `apoorvctf{__N3v3r_g0nn4_g1v3_y0u_fl4g}`
- `apoorvctf{Schr0d1ng3r5_Fl4g_1s_b0th_r34l_4nd_f4k3}`
- `apoorvctf{__1f_y0u_c4n_r34d_th1s_y0u_br0k3_th3_crypt0}`

The real flag, however, distinguishes itself by referencing the underlying cryptographic technique used to encrypt it:

```
apoorvctf{__gr3at_w0rk_p0lynomi4l_G0dS_67}
```

And you beat the BatVault! :)
Loading