Chronohack

Published: April 2, 2025

Description

You intercepted ABC School's token generator. It seeds Python's Mersenne Twister with `int(time.time()*1000)` and gives you 50 guesses. Synchronize on the server time, brute-force the nearby millisecond range, and submit the matching token.

Study token_generator.py to learn the alphabet, seed, and token length (20 characters).

Connect to `nc verbal-sleep.picoctf.net 64704` and synchronize with the server time when sending guesses.

pip install pwntools
python3 script.py # see snippet below

Solution

  1. Step 1Replicate get_random
    Reimplement the provided `get_random()` locally but accept a custom time argument. Seed with `int(t*1000)` to mimic the challenge.
    Learn more

    Python's built-in random module uses the Mersenne Twister algorithm (MT19937), a pseudo-random number generator (PRNG) designed for statistical quality, not cryptographic security. Its critical property here is that it is deterministic: given the same seed, it will always produce the exact same sequence of numbers. This means anyone who knows the seed can reproduce all past and future outputs.

    When the seed is derived from time.time() - the current Unix timestamp - the seed space is severely limited. Using millisecond precision (int(time.time() * 1000)) gives only about 1,000 possible seeds per second. An attacker who can estimate when the token was generated (for example, by measuring round-trip time to the server) can enumerate all plausible seeds in a fraction of a second.

    The correct approach for token generation is to use cryptographically secure random number generators: secrets.token_hex() or os.urandom() in Python, which draw from the operating system's entropy pool (e.g., /dev/urandom on Linux). These are non-deterministic and cannot be predicted even with knowledge of the current time.

  2. Step 2Brute-force nearby timestamps
    When the socket connects, record `time.time()` and iterate ± a few milliseconds (increments of 0.001). For each candidate seed, generate the token and submit it. Within 50 tries one guess matches and the server prints the flag.
    from pwn import *
    import random, time
    
    def get_random(length, t):
        alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
        random.seed(int(t*1000))
        return "".join(random.choice(alphabet) for _ in range(length))
    
    p = remote("verbal-sleep.picoctf.net", 64704)
    base = time.time()
    for i in range(50):
        guess = get_random(20, base + i*0.001)
        p.recvuntil(b"):")
        p.sendline(guess.encode())
        line = p.recvline()
        if b"Congratulations" in line:
            print(p.recvline().decode())
            break
    Learn more

    Pwntools is a Python library built for CTF exploit development and binary exploitation. Its remote() function creates a socket connection and exposes convenient methods like recvuntil() (block until a specific byte sequence is received), sendline() (send data followed by a newline), and interactive() (hand the connection to the user for manual interaction). This allows complex multi-round protocol interactions to be scripted precisely.

    The attack works because network latency introduces only a small, bounded clock difference between client and server. By recording the client's timestamp at connection time and scanning a small window of milliseconds around it, the attacker covers the range of seeds the server could have used. With 50 guesses allowed and a roughly 50 ms window to search, the probability of success is very high.

    This class of vulnerability is called a time-based seed attack or temporal PRNG attack. Real-world examples include PHP's early mt_rand(time()) usage for session tokens, early versions of WordPress generating password reset tokens from the current time, and various Java applications using new Random(System.currentTimeMillis()). All were vulnerable to the same prediction technique demonstrated here.

Flag

picoCTF{UseSecure#$_Random@j3n3r@T0rsd...}

Network jitter means you may need to rerun the script, but scanning a 50 ms window is enough.

Want more picoCTF 2025 writeups?

Useful tools for Reverse Engineering

Related reading

What to try next