Skip to content

Instantly share code, notes, and snippets.

@toby-bro
Last active April 28, 2025 06:45
Show Gist options
  • Save toby-bro/4ae6579d58efde96a4661694094781e8 to your computer and use it in GitHub Desktop.
Save toby-bro/4ae6579d58efde96a4661694094781e8 to your computer and use it in GitHub Desktop.

CocoRiCo

In this challenge, we see that basically each time we connect to the server and input our name we are given a token, which enables us to login with this username. By looking at the code we see that toto seems to be an admin account. Thus we will try to generate his token.

We see that the token is generated by encrypting the usename and his privileges and their CRC32 checksum with AES in OFB mode. Furthermore the iv is provided to us. So as we can see on this wikipedia image, if we possess one valid token then we only need to XOR it to the new username and checksum to get a new valid token.

For simplicity we took bob as a username so as the json dump of his status had exaclty the same length as the one of toto (True has one letter less than False)

So this is what we did

  • Connect to the server and get the token for bob
  • XOR it with format(bob)+CRC32(bob)
  • XOR it with format(toto)+CRC32(toto)
  • Send the new token to the server
  • Get the flag
import json
import socket
from zlib import crc32


def connect_to_server(host: str, port: int) -> socket.socket:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host, port))
    return s


def receive_until(s: socket.socket, text: bytes) -> str:
    buffer = b''
    while text not in buffer:
        data = s.recv(1024)
        if not data:
            break
        buffer += data
    return buffer.decode('utf-8')


def forge_admin_token(normal_token_hex: str) -> str:
    normal_token = bytes.fromhex(normal_token_hex)

    normal_plaintext = json.dumps({'name': 'bob', 'admin': False}).encode()
    normal_crc = crc32(normal_plaintext).to_bytes(4, byteorder='big')

    target_plaintext = json.dumps({'name': 'toto', 'admin': True}).encode()
    target_crc = crc32(target_plaintext).to_bytes(4, byteorder='big')

    if len(normal_plaintext) != len(target_plaintext):
        print("Lengths don't match, adjust your payload!")
        exit(1)

    keystream = bytes(c ^ p for c, p in zip(normal_token, normal_plaintext + normal_crc, strict=False))

    forged_token = bytes(p ^ k for p, k in zip(target_plaintext + target_crc, keystream, strict=False))

    return forged_token.hex()


def main() -> None:
    HOST = 'chall.fcsc.fr'
    PORT = 2150

    print(f'[+] Connecting to {HOST}:{PORT}')
    s = connect_to_server(HOST, PORT)

    receive_until(s, b'>>> ')

    print('[+] Selecting login option')
    s.send(b'1\n')
    receive_until(s, b'(y/n) ')

    print("[+] Registering as 'bob'")
    s.send(b'y\n')
    receive_until(s, b'Name: ')
    s.send(b'bob\n')

    response = receive_until(s, b'>>> ')
    token_lines = response.split('\n')
    for i, line in enumerate(token_lines):
        if 'Here is your token:' in line and i + 1 < len(token_lines):
            normal_token_hex = token_lines[i + 1].strip()
            print(f'[+] Received token: {normal_token_hex}')
            break

    forged_token = forge_admin_token(normal_token_hex)
    print(f"[+] Forged token for 'toto' with admin=true: {forged_token}")

    s.send(b'1\n')
    receive_until(s, b'(y/n) ')
    s.send(b'n\n')
    receive_until(s, b'Token: ')
    s.send(f'{forged_token}\n'.encode())

    result = receive_until(s, b'>>> ')
    print('\n=== RESULT ===\n' + result)

    s.close()


if __name__ == '__main__':
    main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment