You spin me round
Last updated
Was this helpful?
Last updated
Was this helpful?
Catégorie: Cryptanalyse - Difficulté: Moyen
Description:
Solution:
Voici le script pour répondre correctement aux attentes du système tournant derrière la connexion nc :
import re
import sys
import binascii
# ───────────────────────────────────────────────────────── helpers ──
def bytes2matrix(b: bytes):
"""Transforme 16 octets en une matrice 4×4 (colonne‑major)."""
return [list(b[i:i + 4]) for i in range(0, 16, 4)]
def matrix2bytes(m):
"""Inverse de bytes2matrix."""
return bytes(sum(m, []))
def xtime(a: int) -> int:
"""Multiplication par x dans GF(2^8)."""
return (((a << 1) ^ 0x1B) & 0xFF) if a & 0x80 else (a << 1)
# ───────────────────────────────────────────── MixColumns & inverse ──
def mix_single_column(a):
t = a[0] ^ a[1] ^ a[2] ^ a[3]
u = a[0]
a[0] ^= t ^ xtime(a[0] ^ a[1])
a[1] ^= t ^ xtime(a[1] ^ a[2])
a[2] ^= t ^ xtime(a[2] ^ a[3])
a[3] ^= t ^ xtime(a[3] ^ u)
def mix_columns(s):
for col in s:
mix_single_column(col)
def inv_mix_columns(s):
for col in s:
u = xtime(xtime(col[0] ^ col[2]))
v = xtime(xtime(col[1] ^ col[3]))
col[0] ^= u
col[1] ^= v
col[2] ^= u
col[3] ^= v
mix_columns(s)
# ────────────────────────────────────────────── ShiftRows & inverse ──
def shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3]
def inv_shift_rows(s):
s[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][1]
s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
s[0][3], s[1][3], s[2][3], s[3][3] = s[1][3], s[2][3], s[3][3], s[0][3]
# ────────────────────────────────────────── Inversion de la diffusion ──
def decrypt_linear(diff: bytes, rounds: int = 10) -> bytes:
"""Inverse la partie linéaire (ShiftRows + MixColumns) quand SubBytes=Id."""
state = bytes2matrix(diff)
# Tour final : seulement InvShiftRows
inv_shift_rows(state)
# Tours 9 → 1 : InvMixColumns puis InvShiftRows (ordre inverse du chiffrement)
for _ in range(rounds - 1): # 9 fois pour AES‑128
inv_mix_columns(state)
inv_shift_rows(state)
return matrix2bytes(state)
# ──────────────────────────────────────────── I/O et UX interactives ──
def afficher_bytes(nom: str, val: bytes, preview: int = 4):
print(f"{nom} (hex): {val.hex()}")
print(f"{nom} (bin): {' '.join(format(b, '08b') for b in val[:preview])} …")
def is_hex(s: str) -> bool:
return bool(re.fullmatch(r"[0-9a-fA-F]+", s))
def calculer_reponse(message: bytes, missile: bytes):
print("\n—— Calcul de la différence delta = missile ⊕ message ——")
delta = bytes(a ^ b for a, b in zip(missile, message))
afficher_bytes("delta", delta)
print("\n—— Inversion de la diffusion (10 tours) ——")
plaintext = decrypt_linear(delta)
afficher_bytes("plaintext", plaintext)
return plaintext
# ─────────────────────────────────────────────────────────── main ──
def main():
print("=== Solveur « You Spin Me » ===")
print("Ce script résout le défi « You Spin Me Round » de 404CTF 2025.\n")
print("Voici le niveau de sécurité à envoyer : 277182\n")
print("Choisissez l'option 1 pour demander à communiquer avec le serveur.")
print(f"Maintenant, envoyez ce message au serveur : {'0' * 32}.\n")
# Entrée de message
while True:
response = input("Entrez la réponse du serveur : ").strip()
if is_hex(response) and len(response) == 32:
break
print("⟹ valeur hexadécimale attendue (32 caractères).")
print("\nMaintenant, choisissez l'option 2 pour utiliser votre arme secrète.")
print("Vous devez fournir la valeur de missile pour calculer la réponse.\n")
# Entrée de missile
while True:
missile = input("Entrez le missile reçu (32 hex) : ").strip()
if is_hex(missile) and len(missile) == 32:
break
print("⟹ valeur hexadécimale attendue (32 caractères).")
message = bytes.fromhex(response)
missile = bytes.fromhex(missile)
print("\n—— Valeurs reçues ——")
afficher_bytes("message", message)
afficher_bytes("missile", missile)
plaintext = calculer_reponse(message, missile)
print("\n=== Renvoyer ceci au serveur : ===\n")
print(plaintext.hex())
print("\nBonne chance ! ✨")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\nInterrompu.")
Le script tourne quelques secondes et nous demande d’interagir avec lui. En lui renvoyant les réponses, il nous donne les infos à renvoyer au serveur pour récupérer le flag :
En renvoyant les valeurs au bon moment, le script calcule la bonne valeur à renvoyer au serveur afin de récupérer le flag.