BtSCTF Better_AES - Pez1181/CTF GitHub Wiki
🎖️ Operation BetterAES: Mission Brief for New Recruits 🚀
Welcome, soldier! This mission debrief is written in plain English for beginners. Follow these steps carefully, and we’ll extract the secret flag from the hostile BetterAES service.
🎯 Mission Objective
- Goal: Connect to the BetterAES encryption service, recover the encrypted flag, then decrypt it without knowing the secret key.
- Key Insight: The enemy foolishly used an identity S-box, making their AES attackable with a single chosen-plaintext.
🕹️ Gear & Setup
- Tactical Terminal 1: Port forwarding with
scicat
(our tunnel drone).scicat -b 4000 betteraes.chal.bts.wh.edu.pl:1234
- Tactical Terminal 2: We’ll run our Python exploit from here.
🔍 Step-by-Step Plan Explained
1. Understand the Vulnerability
-
AES normally uses a non-linear S-box to introduce complexity.
-
BetterAES’s S-box is defined as
sbox = list(range(256))
, the identity mapping. ❌ -
Result: All AES steps (
SubBytes
,ShiftRows
,MixColumns
,AddRoundKey
) become linear operations over GF(2). -
Encryption collapses into:
Ciphertext = M(Plaintext) ⊕ b
where:
- M: a fixed, known 128×128 linear transformation (round functions)
- b: an unknown constant derived from the secret key
2. Chosen-Plaintext Strategy
- We need one special plaintext block P to recover b.
- Choose P =
0x01 00…00
(1 followed by fifteen zeros). This avoids the service’s “no all-zero” check. ✅
3. Offline Preparation (Before Connecting)
-
Implement the enemy’s ShiftRows and MixColumns (and inverses) in Python.
-
Build functions:
def M_forward(P): # Applies 13 rounds of ShiftRows+MixColumns, then final ShiftRows s = P for _ in range(13): s = shift_rows(s) s = mix_columns(s) return shift_rows(s)
def M_inv(C): # Inverse of M_forward: invShiftRows + invMixColumns ×13 + invShiftRows s = inv_shift_rows(C) for _ in range(13): s = inv_mix_columns(s) s = inv_shift_rows(s) return s
-
Test locally that applying M_forward then M_inv returns your original block.
4. Connect and Harvest Flag Ciphertext
import socket
from binascii import unhexlify
# Connect to local tunnel (localhost:4000)
sock = socket.create_connection(('127.0.0.1', 4000))
# Read the banner line containing the flag ciphertext
banner = sock.recv(4096).decode()
hex_flag = banner.split()[2] # the 3rd word is the flag in hex
flag_ciphertext = unhexlify(hex_flag)
5. Encrypt Chosen Plaintext P and Observe C_obs
# Define P = 01||00*15 (16 bytes)
P = bytes([1] + [0]*15)
# Send P in hex, followed by newline
sock.sendall(P.hex().encode() + b"\n")
# Receive the response lines
response = sock.recv(4096).decode().splitlines()
# Extract the line starting with 'Encrypted:'
encrypted_line = next(line for line in response if line.startswith('Encrypted:'))
hex_obs = encrypted_line.split()[-1] # last word
C_obs = unhexlify(hex_obs)
6. Recover the Secret Constant b
C_off = M_forward(P) # offline computed
b = bytes(x ^ y for x, y in zip(C_obs, C_off))
7. Decrypt the Flag
flag = b''
for i in range(0, len(flag_ciphertext), 16):
C_block = flag_ciphertext[i:i+16]
X = bytes(a ^ b for a, b in zip(C_block, b))
P_block = M_inv(X)
flag += P_block
# Remove padding
flag = flag.rstrip(b'\x00')
print('Recovered flag:', flag.decode())
✅ Mission Accomplished!
BtSCTF{sB0x35_vuln3r@b1liti3s_4re_d4ng3r0u5}