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

  1. Tactical Terminal 1: Port forwarding with scicat (our tunnel drone).
    scicat -b 4000 betteraes.chal.bts.wh.edu.pl:1234
    
  2. 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}