Writeup - Mammoth's Personnal Slot Machine
- Catégorie :
Cryptanalyse - Fichiers fournis :
server_REDACTED.py - Format du flag :
THC{...} - Auteur du WriteUp :
DonAsako
Contexte
Un serveur distant vous demande de "choisir un nombre". Si votre réponse est incorrecte, il révèle le nombre qu’il avait généré à l’aide de ce qui semble être la fonction random.getrandbits(32) de Python. Cette fonction repose sur le Mersenne Twister (MT19937), un générateur de nombres pseudo-aléatoires (PRNG) prédictible après observation de 624 sorties.
Cette faiblesse permet de reconstruire l’état interne du PRNG et de prédire les valeurs futures, ce qui permet de gagner au jeu et de récupérer le flag.
Observations Initiales
random.getrandbits(32)renvoie un entier sur 32 bits.- Après avoir vu 624 sorties de MT19937, il est possible de prédire la 625e avec une précision de 100 %.
- Le serveur dévoile la vraie valeur après chaque mauvaise tentative, ce qui nous donne tout ce dont nous avons besoin.
Démarche Étape par Étape
- Connexion au serveur.
- Envoyer des suppositions bidon (ex : toujours deviner
1), ce qui pousse le serveur à révéler ses vrais nombres. - Collecter 624 valeurs générées par le serveur.
- Les injecter dans RandCrack, un outil permettant de reconstruire l’état interne du PRNG.
- Prédire le 625e nombre généré avec
rc.predict_getrandbits(32). - Envoyer cette prédiction comme réponse.
- Si la prédiction est correcte, le serveur renvoie le flag.
Processus de Déchiffrement
Nous avons utilisé la librairie randcrack pour réaliser la prédiction.
import socket
import re
from randcrack import RandCrack
rc = RandCrack()
HOST = '74.234.198.209'
PORT = 33388
regex = re.compile(r'The number was (\d+)')
with socket.create_connection((HOST, PORT)) as s:
s_file = s.makefile('rwb', buffering=0)
count = 0
predicted_sent = False
while True:
line = s_file.readline().decode(errors='ignore').strip()
print(f"[recv] {line}")
if line == "":
break
if line.lower().startswith("pick a number"):
if count < 624:
s_file.write(b"1\n")
s_file.flush()
print("[send] 1")
else:
prediction = rc.predict_getrandbits(32)
s_file.write(f"{prediction}\n".encode())
s_file.flush()
print(f"[send predicted] {prediction}")
predicted_sent = True
match = regex.search(line)
if match and count < 624:
number = int(match.group(1))
rc.submit(number)
count += 1
print(f"[rc.submit] {number} ({count}/624)")
Exemple de Sortie
[rc.submit] 1605806052 (624/624)
[recv] Pick a number:
[send predicted] 3666696592
[recv] Correct! Here is your access code brave XSS member: THC{1ts_H4rD_T0_H4v3_Tru3_RNG}
Flag
Le flag récupéré depuis le serveur :
THC{1ts_H4rD_T0_H4v3_Tru3_RNG}
Conclusion
Ce challenge illustre bien la prédictibilité et l’insécurité du générateur pseudo-aléatoire de Python (Mersenne Twister) pour des tâches sensibles.
✅ Toujours préférer des générateurs aléatoires cryptographiquement sûrs comme :
- Le module
secretsde Python/dev/urandom- Des bibliothèques comme
cryptography