> For the complete documentation index, see [llms.txt](https://writeups.ayweth20.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://writeups.ayweth20.com/2025/404ctf-2025/cryptanalyse/spacemark.md).

# Spacemark

**Catégorie:** Cryptanalyse - **Difficulté:** Moyen

{% file src="/files/c4DaQJ4zsjBmHRxwmPFf" %}

**Description:**

<figure><img src="/files/NVxb99DsLIgJmz0e5GaR" alt=""><figcaption></figcaption></figure>

Solution:

Voici le script pour répondre correctement aux attentes du système tournant derrière la connexion nc :&#x20;

```python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import re, socket, zlib, json, sys, textwrap

HOST, PORT = "challenges.404ctf.fr", 31207
SIZE, BLOCKS = 4096, 128                        # paramètres fixes

def recv_until(sock: socket.socket, sentinel: bytes = b'>>>') -> bytes:
    data = b""
    while sentinel not in data:
        part = sock.recv(4096)
        if not part:
            break
        data += part
    return data

def extract_hex_banner(text: str) -> str:
    """Concatène toutes les lignes composées uniquement d'hexadécimal (≥32 c.)."""
    hex_lines = [l.strip() for l in text.splitlines()
                 if len(l.strip()) >= 32 and re.fullmatch(r"[0-9a-f]+", l.strip())]
    if not hex_lines:
        raise ValueError("Aucun fragment hex trouvé.")
    hx = "".join(hex_lines)
    if len(hx) % 2:                        # robustesse
        hx = hx[:-1]
    return hx

def first_pass_estimate(bits):
    """Reconstruit les 128 bits par le comptage des uns."""
    return ''.join(
        '1' if sum(bits[i*SIZE:(i+1)*SIZE]) != SIZE//2 else '0'
        for i in range(BLOCKS)
    )

def attempt_once(answer: str) -> str:
    """Ouvre une connexion, renvoie `answer`, retourne la réponse complète."""
    with socket.create_connection((HOST, PORT)) as s:
        banner = recv_until(s)             # bannière + hex + prompt
        s.sendall((answer + '\n').encode())
        return banner.decode(errors="ignore") + s.recv(4096).decode(errors="ignore")

def solve_spacemark():
    # --- 1ʳᵉ connexion : récupération du watermark compressé ---
    with socket.create_connection((HOST, PORT)) as s:
        raw = recv_until(s)                # jusqu’au prompt
        text = raw.decode(errors="ignore").lower()
        hex_clean = extract_hex_banner(text)
        bits_json = zlib.decompress(bytes.fromhex(hex_clean))
        bits = json.loads(bits_json.decode())
        if len(bits) != BLOCKS*SIZE:
            print("Watermark inattendu :", len(bits))
            sys.exit(1)

        # --- estimation puis envoi ---
        guess = first_pass_estimate(bits)
        s.sendall((guess + '\n').encode())
        reply = s.recv(4096).decode(errors="ignore")

    # --- analyse de la réponse ---
    if "Congratz" in reply:
        print(reply)                       # flag déjà obtenu
        return

    # Sinon, le serveur imprime : "Nope...\n<watermark>"
    m = re.search(r'\b[01]{128}\b', reply)
    if not m:
        print("Échec mais watermark non trouvé :\n", reply)
        sys.exit(1)

    real_mark = m.group(0)
    # --- 2ᵉ connexion, envoi de la vraie chaîne ---
    final_reply = attempt_once(real_mark)
    print(final_reply)

if __name__ == "__main__":
    solve_spacemark()
```

Le script tourne quelques secondes et nous retourne la réponse directement dans le terminal :&#x20;

<figure><img src="/files/qUKOjF2rghOOZDaZJElC" alt=""><figcaption></figcaption></figure>

<details>

<summary>🚩FLAG</summary>

`404CTF{LGC_4r3_no_G0od}`

</details>
