Skip to content

Files

Latest commit

 Cannot retrieve latest commit at this time.

History

History
138 lines (110 loc) · 6.27 KB

File metadata and controls

138 lines (110 loc) · 6.27 KB

Solution Analysis for "Chronohack" Challenge

Problem Overview

  • Challenge Name: Chronohack (picoCTF)
  • Objective: Guess a 20-character token generated by a server-side Python script to retrieve a hidden flag.
  • Constraints: You are limited to 50 guesses per connection to the server.
  • Token Generation: The server uses Python’s random module, seeded with the current time in milliseconds (int(time.time() * 1000)), to generate a 20-character token from a 62-character alphabet (0-9, A-Z, a-z).

The key insight is that the token generation relies on a predictable seed (the server’s current time). By estimating the server’s timestamp at the moment of token creation, we can replicate the token locally and guess it within the allowed attempts.

Approach

To solve the challenge, we exploit the time-based seed with the following strategy:

  1. Understand Token Generation: Replicate the server’s token generation logic by using the same seed and alphabet.
  2. Synchronize Timing: Capture our local time just before connecting to the server as a reference point.
  3. Account for Delays: Adjust for network latency and server processing time between our local timestamp and the server’s token generation.
  4. Generate Guesses: Create a range of possible tokens based on timestamps around our captured time, covering a small window (e.g., 50 ms).
  5. Automate Interaction: Use a tool like pwntools to connect to the server, send guesses, and retrieve the flag when the correct token is guessed.

This approach leverages the predictability of the time-based seed and the 50-guess limit to systematically find the token.

Solution Steps

Step 1: Capture Local Time Before Connecting

  • Record the local time in milliseconds just before establishing the server connection using:
    t_start = int(time.time() * 1000)
  • This timestamp serves as the baseline for guessing the server’s token generation time.

Step 2: Connect to the Server

  • Use pwntools to establish a remote connection:
    r = remote('verbal-sleep.picoctf.net', 56648)
  • The server generates the token shortly after the connection is made.

Step 3: Estimate the Delay

  • There’s a small delay between capturing t_start and the server generating the token, due to network latency and server processing.
  • Estimate this delay (e.g., 15 ms) based on typical network conditions:
    estimated_delay = 15
  • This value may need adjustment depending on your network environment.

Step 4: Generate Token Guesses

  • Create 50 token guesses using timestamps ranging from t_start + estimated_delay to t_start + estimated_delay + 49:
    guesses = [get_random(20, t_start + estimated_delay + i) for i in range(50)]
  • Each guess represents a possible server timestamp within a 50 ms window, matching the 50-guess limit.

Step 5: Interact with the Server

  • Read the server’s initial messages until prompted to guess the token.
  • Send each guess and analyze the response:
    • Success: If the response contains "Congratulations" or the flag (e.g., picoCTF{...}), extract and display the flag, then exit.
    • Failure: If the response prompts for another guess, continue to the next one.
    • Exhaustion: If all 50 guesses fail, the server will indicate the attempts are exhausted, and the script exits.

How It Works

  • Timing Synchronization: Capturing t_start just before connecting minimizes the gap between local and server time.
  • Delay Adjustment: The estimated_delay shifts the guess window to account for latency, ensuring the server’s timestamp falls within the 50 ms range.
  • Token Generation: The get_random function replicates the server’s logic, using the same alphabet and seed-based random selection.
  • Automation: pwntools handles the connection and communication, systematically testing guesses until the flag is retrieved.

Notes

  • Delay Tuning: If the script fails, adjust estimated_delay (e.g., 10–20 ms) based on your network latency.
  • Flag Format: The flag will be in the format picoCTF{...}, typically revealed after a correct guess.

This solution efficiently exploits the predictable time-based seed to retrieve the flag within the 50-guess limit, making it a reliable approach to solving "Chronohack."

Complete Solution Code

from pwn import *  # For remote connections
import random
import time

# Function to generate tokens (mimics server logic)
def get_random(length, seed):
    alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    random.seed(seed)
    return "".join(random.choice(alphabet) for _ in range(length))

# Server details
host = 'verbal-sleep.picoctf.net'
port = 56648

# Capture local time just before connecting
t_start = int(time.time() * 1000)
r = remote(host, port)

print(f"Captured local start time (ms): {t_start}")

# Estimate delay (network latency + server processing)
estimated_delay = 15  # Adjust this based on your network

# Generate 50 token guesses
guesses = [get_random(20, t_start + estimated_delay + i) for i in range(50)]

# Read initial server messages until the first prompt
while True:
    line = r.recvline().decode().strip()
    print(line)
    if "Can you guess the token?" in line:
        break

# Send guesses and process responses
for idx, guess in enumerate(guesses):
    print(f"Sending guess {idx + 1}: {guess}")
    r.sendline(guess.encode())
    
    while True:
        line = r.recvline().decode().strip()
        print(line)
        
        # Check for next prompt
        if "Enter your guess for the token (or exit):" in line:
            break
        
        # Check for success
        elif "Congratulations" in line or "flag" in line.lower():
            if "picoCTF" in line:
                print(f"Flag found in line: {line}")
            else:
                flag = r.recvline().decode().strip()
                print(f"Flag: {flag}")
            r.close()
            exit(0)
        
        # Check for failure after all attempts
        elif "exhausted" in line or "too many" in line:
            print("All attempts used up.")
            r.close()
            exit(1)

print("Failed to find the correct token within 50 attempts.")
r.close()