BtSCTF Roulette - Pez1181/CTF GitHub Wiki

🎖️ Mission Brief: Operation Lucky 13 🎖️

Classification: Top Secret (and slightly absurd)
Commander: Cadet Noobinator
Objective: Land the cursed number 13 for 37 consecutive rounds on the “provably fair” roulette game and exfiltrate the flag (redacted to the pun BTS{N0_W4Y_Y0U_W1N_13_37}).


⚔️ Phase 1: Recon & Intel Gathering

  1. Service Topology

  2. Provable-Fair Illusion

    • Server prints a commitment:
      Server seed hash (verify later): <64-hex-chars>
    • Fatal Flaw: entropy = 17 bits (secrets.randbits(17)).

💣 Phase 2: Breaking the Commitment

  • Capture the printed server_seed_hash.

  • Brute-force all 131,072 possibilities:

    for k in range(1<<17):
    cand = sha256_hex(bytes(k))
    if sha256_hex(cand.encode()) == server_seed_hash:
    server_seed = cand
    break

  • Result: recover the exact 64-hex server_seed in milliseconds! ⚡


🧙‍♂️ Phase 3: Crafting the Winning Client Seed

  • Game outcome:

    game_hash = sha256(f"{server_seed}:{client_seed}".encode()).hexdigest()
    roulette_number = int(game_hash, 16) % 37

  • Goal: force roulette_number == 13.

  • Method: try client_seed = "0", "1", ... until hit 13:

    for i in itertools.count():
    gh = sha256_hex(f"{server_seed}:{i}".encode())
    if int(gh, 16) % 37 == 13:
    client_seed = str(i)
    break

  • Avg tries: ~37 🎯


🤖 Phase 4: Automation & 37-Strike Run

  1. Precompute Map

    • Build seed_map: server_seed_hash → server_seed for all 2¹⁷ seeds once.
  2. Handle I/O Quirks

    • Use raw recv() or recv_until() because “Place your bet” has no newline.
    • Sync on the newline-terminated "Server seed hash" each round.
  3. High-Level Workflow
    seed_map = build_17bit_map() ← one-time work
    sock = connect("127.0.0.1", 4000)
    wins = 0
    while True:
    srv_hash = recv_until(sock, b"Server seed hash")
    server_seed = seed_map[srv_hash]
    client_seed = find_seed_for_13(server_seed)
    send(sock, client_seed + "\n")
    recv_until(sock, b"Place your bet")
    send(sock, b"13\n")
    buf = recv_until_any(sock, [b"Server seed hash", b"How? How is it possible"])
    if b"How? How is it possible" in buf:
    flag = extract_flag(buf)
    print(flag)
    break
    wins += 1
    log(f"Win #{wins}")

  4. Beginner Tips

    • Precompute the 17-bit map—avoid repeated brute-forces.
    • Beware prompts without \n.
    • Use stderr for logs so flag pops cleanly to stdout.

🏆 Phase 5: Exfiltration & Debrief

  • On Win #37, the server exclaims:

    How? How is it possible? What was the chance?!
    Anyway, here’s your flag, congratulations… BTS{N0_W4Y_Y0U_W1N_13_37}

Lessons Learned

  • Entropy matters: 17 bits ≪ 128 bits → trivially broken.
  • Handle interactive I/O quirks carefully.
  • Automation + protocol insight = swift victory! 🚩

Operation Lucky 13 complete! 🎖️