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

  1. Connexion au serveur.
  2. Envoyer des suppositions bidon (ex : toujours deviner 1), ce qui pousse le serveur à révéler ses vrais nombres.
  3. Collecter 624 valeurs générées par le serveur.
  4. Les injecter dans RandCrack, un outil permettant de reconstruire l’état interne du PRNG.
  5. Prédire le 625e nombre généré avec rc.predict_getrandbits(32).
  6. Envoyer cette prédiction comme réponse.
  7. 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 secrets de Python
  • /dev/urandom
  • Des bibliothèques comme cryptography