TDS
- CTF: blackhat-finals
- Challenge: TDS
- Category: Crypto
- Description: AES-GCM implementation with oracle access
TL;DR
This challenge exploits weaknesses in AES-GCM authentication when given:
- A flag ciphertext and its authentication tag
- Two encryption oracle calls with the same key/nonce
- Unlimited verification oracle with controllable AAD and ciphertext length
The solution:
- Recovers the GCM mask by encrypting an empty string
- Recovers the authentication key H by solving a polynomial equation
- Brute-forces the keystream byte-by-byte using the verification oracle
- XORs the recovered keystream with the flag ciphertext
Challenge Overview
The server implements AES-GCM encryption and provides several operations:
| |
The critical flaws:
- Nonce reuse: Same nonce used for all encryptions
- Verification oracle: We can verify arbitrary (AAD, ciphertext_prefix, tag) tuples
- Controllable AAD: The oracle lets us control additional authenticated data
This breaks the security model of AES-GCM completely!
Understanding GCM Authentication
AES-GCM authentication is based on GHASH, a polynomial hash over GF(2^128). Understanding this is crucial to solving the challenge.
For a comprehensive explanation, see: AES-GCM Deep Dive
This resource provides all the mathematical details needed to understand the attack.
GHASH Polynomial
The authentication tag in GCM is computed as:
$$\text{tag} = \text{GHASH}(H, A, C) \oplus E_K(N \mathbin\Vert 0^{31} \mathbin\Vert 1)$$
Where:
- $H = E_K(0^{128})$ is the authentication key
- $A$ is the additional authenticated data (AAD)
- $C$ is the ciphertext
- $E_K(N \mathbin\Vert 0^{31} \mathbin\Vert 1)$ is the mask (keystream at counter 0)
The GHASH function computes:
$$\text{GHASH}(H, A, C) = \sum_{i=1}^{m} A_i \cdot H^{m+n+1-i+1} + \sum_{j=1}^{n} C_j \cdot H^{n+1-j+1} + L \cdot H$$
Where:
- $A_i$ are 128-bit blocks of AAD
- $C_j$ are 128-bit blocks of ciphertext
- $L = (\text{len}(A) \mathbin\Vert \text{len}(C))$ is the length block
- All operations are in $\text{GF}(2^{128})$
Key insight: If we know the tag and all blocks except H, we can solve for H as a polynomial root!
Exploitation Strategy
Phase 1: Recover the Mask
The mask is $E_K(N \mathbin\Vert 0^{31} \mathbin\Vert 1)$. When we encrypt an empty string with no AAD:
$$\text{tag}_\emptyset = \text{GHASH}(H, \emptyset, \emptyset) \oplus \text{mask} = (0 \oplus 0 \oplus L \cdot H) \oplus \text{mask}$$
But since both AAD and ciphertext are empty, $L = 0$, so:
$$\text{tag}_\emptyset = 0 \oplus \text{mask} = \text{mask}$$
| |
Phase 2: Recover Authentication Key H
Now we have:
- Flag ciphertext blocks: $C_1, C_2, \ldots, C_n$
- Flag tag: $\text{tag}_{\text{flag}}$
- Mask value
We can compute:
$$\text{GHASH}{\text{flag}} = \text{tag}{\text{flag}} \oplus \text{mask}$$
The GHASH equation becomes:
$$\text{GHASH}_{\text{flag}} = C_1 \cdot H^{n+1} + C_2 \cdot H^n + \cdots + C_n \cdot H^2 + L \cdot H$$
This is a polynomial equation in H over $\text{GF}(2^{128})$:
$$C_1 \cdot H^{n+1} + C_2 \cdot H^n + \cdots + C_n \cdot H^2 + L \cdot H - \text{GHASH}_{\text{flag}} = 0$$
We solve this using SageMath’s polynomial root finding:
| |
Phase 3: Keystream Recovery via Oracle
With H recovered, we can now abuse the verification oracle. The key observation:
For any partial ciphertext and AAD, we can compute the required AAD value that makes verification succeed!
Strategy: Encrypt $\texttt{0x00} \times \text{len(flag)}$ to get a reference tag, then brute-force the keystream byte-by-byte.
First, get a reference tag:
| |
For each byte position $i$:
- Guess the keystream byte (0-255)
- Calculate partial GHASH of guessed ciphertext + length block
- Solve for required AAD that makes the tag verify
- Query oracle with this AAD
The math for step 3:
$$\text{GHASH}(H, A, C) = A \cdot H^{n+2} + C_1 \cdot H^{n+1} + \cdots + C_n \cdot H^2 + L \cdot H$$
Rearranging to solve for $A$:
$$A = \frac{\text{target} \oplus (C_1 \cdot H^{n+1} + \cdots + L \cdot H)}{H^{n+2}}$$
| |
Finally, decrypt the flag:
| |
Full Solution Script
| |
Attack Timeline
┌─────────────────────────┐
│ Phase 1: Mask │
├─────────────────────────┤
│ Encrypt("") │
│ → tag_empty = mask │
└────────┬────────────────┘
│
┌────────▼────────────────┐
│ Phase 2: Recover H │
├─────────────────────────┤
│ GHASH = tag_flag ⊕ mask │
│ Solve polynomial in H │
│ → H recovered │
└────────┬────────────────┘
│
┌────────▼────────────────┐
│ Phase 3: Keystream │
├─────────────────────────┤
│ Encrypt("\x00" * n) │
│ For each byte: │
│ ├─ Guess byte (0-255) │
│ ├─ Compute GHASH │
│ ├─ Solve for AAD │
│ └─ Query oracle │
└────────┬────────────────┘
│
┌────────▼────────────────┐
│ Phase 4: Decrypt │
├─────────────────────────┤
│ flag = ct ⊕ keystream │
└─────────────────────────┘
Runtime: ~2-5 minutes (256 queries per byte worst case)
Behind the Math
Why GCM Authentication is Vulnerable
GCM’s authentication relies on the secrecy of H. Once H is known:
- Forgery: Craft arbitrary (ciphertext, AAD, tag) tuples that verify
- Plaintext recovery: Use oracle to leak information bit by bit
The polynomial structure means:
$$\text{tag} = \text{poly}(H) \oplus \text{mask}$$
If we can:
- Remove the mask (by encrypting empty string)
- Know all coefficients of poly(H) except H itself
- Know the result poly(H)
Then we can solve for H algebraically!
Oracle Abuse Technique
The verification oracle checks:
$$\text{GHASH}(H, \text{AAD}, C) \stackrel{?}{=} \text{tag} \oplus \text{mask}$$
By controlling AAD and partial ciphertext, we can:
- Fix all terms except the AAD term
- Solve for the AAD value that makes the equation true
- Query the oracle with this AAD
This is a chosen-AAD attack that leaks one keystream byte per 256 queries (worst case).
GF(2^128) Arithmetic
All operations in GHASH are performed in $\text{GF}(2^{128})$ with irreducible polynomial:
$$f(x) = x^{128} + x^7 + x^2 + x + 1$$
This means:
- Addition is XOR: $a + b = a \oplus b$
- Multiplication is polynomial multiplication modulo $f(x)$
- Every non-zero element has a multiplicative inverse
Mitigations
- Never reuse nonces: Each (key, nonce) pair must be used for at most one encryption
- No verification oracle: Don’t expose tag verification as an oracle to attackers
- Limit encryption queries: Bound the number of encryptions with the same key
- Use AES-GCM-SIV: Nonce-misuse resistant variant that derives nonce from plaintext
- Proper key rotation: Regularly rotate encryption keys
In this challenge, all these rules were violated simultaneously!
Key Takeaways
- GCM is fragile: Nonce reuse + oracle access = complete break
- Polynomial algebra is powerful: Knowing coefficients lets us solve for unknowns
- Oracle abuse is subtle: Even a simple True/False oracle can leak full plaintexts
- Understanding crypto internals matters: Knowing how GHASH works made this challenge straightforward
References
- AES-GCM Deep Dive by Frederik Reitemeyer - Comprehensive explanation of GCM internals
- Forbidden Attack: Nonce reuse in AES-GCM
- NIST SP 800-38D: GCM Specification
- McGrew, D. A., & Viega, J. (2004). “The Galois/Counter Mode of Operation (GCM)”
This challenge demonstrates that implementation flaws can completely bypass cryptographic security. AES-GCM is provably secure under proper usage, but violating its assumptions (unique nonces, no oracle access) turns a secure primitive into a trivial break.
The lesson: Security proofs have assumptions. Violate them at your peril.