Skip to main content

Picker I picoGym Exclusive Solution

Interact with a Python script and figure out how to invoke the function that prints the flag.

Published: March 5, 2024Updated: August 25, 2026

Description

This supposedly random-number service exposes helper functions inside its Python source. If you can trigger win(), it prints the flag instead of a number.

Remote Python serviceDownload picker-I.py

Grab the Python source so you can see helper functions like getRandomNumber() and win().

Test it locally with python3 to understand which identifiers the service accepts.

bash
wget https://artifacts.picoctf.net/c/515/picker-I.py
python
python3 picker-I.py

Solution

Want to try it yourself first?

The guided walkthrough reveals hints one step at a time.

Walk me through it
  1. Step 1Identify callable functions
    Observation
    The service ships its own source, so reading it shows how it decides what to execute and whether anything like a win() function is callable.
    The main loop reads a line and runs eval(user_input + '()'), so whatever name you type is called as a function. Typing getRandomNumber prints 4 exactly as the code shows, so typing win should trigger the flag routine.
    Learn more

    Code review is always the first step when source code is available. Reading picker-I.py reveals the program's logic: it reads a function name, appends () to it, and hands the whole string to eval(). This is a form of dynamic dispatch - the program decides at runtime which function to execute based on user input.

    The win() function is a backdoor left intentionally in the code for CTF purposes, but it mirrors a real vulnerability class. In production systems, developers sometimes leave debug functions, admin backdoors, or test endpoints in deployed code without realizing they are accessible. Auditing code for unintended callable functions is part of standard security review.

    The hint that getRandomNumber() returns 4 is a reference to the famous xkcd comic about "random" numbers chosen by fair dice roll - chosen by a human rather than being truly random. The joke underscores a real principle: functions named "getRandomNumber" may not be cryptographically secure, and relying on predictable "random" values for security decisions (session tokens, CSRF tokens, one-time passwords) is a significant vulnerability.

  2. Step 2Call win on the remote service
    Observation
    The loop evaluates your line with () appended, so sending the bare name win makes it evaluate win() and run the flag-printing function.
    Connect to the deployed instance with netcat and send win. The program prints the flag as a sequence of hex bytes instead of ASCII.
    bash
    printf "win\n" | nc saturn.picoctf.net <PORT_FROM_INSTANCE>

    Expected output

    0x70 0x69 0x63 0x6f 0x43 0x54 0x46 0x7b ...
    What didn't work first

    Tried: Sending 'win()' with parentheses instead of just 'win'

    The service appends its own parentheses, so eval sees win()(). The flag still prints, but then Python tries to call win's None return value, raises "'NoneType' object is not callable", and the loop breaks and drops you. Send the bare name and the session stays alive.

    Tried: Typing win into a local run of picker-I.py and expecting the real flag

    win() reads flag.txt from the script's own directory, and locally that file does not exist, so you get a FileNotFoundError instead of a flag. Only the deployed instance has the real flag.txt beside the script, so send the input there.

    Learn more

    Netcat (nc) is a networking utility that reads from standard input and sends it over a TCP or UDP connection, printing whatever the server responds. It is called the "Swiss Army knife of networking" because it can act as a client, server, port scanner, or data relay. In CTF exploitation, piping a Python payload into netcat (python3 -c '...' | nc host port) is the standard way to send crafted input to a remote service.

    The printf "win\n" command sends the string win followed by a newline character (\n). The newline is important - most line-oriented network services (including this Python script) wait for a newline to know the user has finished their input. Without the newline, the server might wait indefinitely for more input instead of processing the command.

    The fact that the flag is printed as hex bytes rather than ASCII is an intentional design choice in the Picker series: it adds a small extra step (hex decoding) to prevent the flag from appearing directly in plain text. This mirrors real scenarios where sensitive output is encoded to prevent casual interception, though proper protection requires encryption rather than simple encoding.

  3. Step 3Convert from hex
    Observation
    The output is a run of hex byte values rather than readable text, so one decode remains.
    Copy the 0x-prefixed values into CyberChef (From Hex) or pipe them through xxd -r -p locally to reveal the picoCTF flag.
    Learn more

    CyberChef is a web-based tool developed by GCHQ that chains data transformation operations ("recipes"). The "From Hex" operation accepts space-separated or 0x-prefixed hex values and produces the decoded bytes. CyberChef is particularly useful when you need to apply multiple transformations in sequence (e.g., From Hex, then Base64 Decode, then XOR) without writing code.

    For command-line decoding, xxd -r -p (reverse, plain) reads hex digits and outputs the corresponding bytes. The slight complication here is the 0x prefix on each byte - you would need to strip those before piping into xxd, or use Python: bytes.fromhex("70 69 63 6f".replace(" ", "")).

    The Picker series (I through IV) is designed as a progressive introduction to code injection and binary exploitation. Each version adds restrictions that require a more sophisticated bypass. Understanding each level's mechanism - and why its defenses fail - builds the mental model needed for real vulnerability research where defenses are layered and imperfect.

Interactive tools
  • Strings ExtractorPull printable text from any binary, library, or image. ASCII and UTF-16 detection, configurable minimum length, flag-like highlight, no command line needed.
  • Hex ViewerView text or raw hex bytes as a xxd-style hex dump with byte offset, hex columns, and ASCII sidebar. Highlights printable characters and null bytes.

Flag

Reveal flag

picoCTF{4_d14m0nd_1n_7h3_r0u...5d5b}

The remote program never validates user input beyond evaluating tokens, so calling win outputs the flag immediately.

Key takeaway

Dynamic dispatch turns into a vulnerability when user input picks which function runs and nothing constrains the set. A server that looks up a name in its own namespace from untrusted input has handed out a remote function call primitive. Plugin systems, scripting interpreters, and RPC frameworks all hit this wherever the callable registry is wider than the intended public API.

Related reading

Useful tools for Reverse Engineering

Where to go next