Picker I picoGym Exclusive Solution

Published: March 5, 2024

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 1
    Identify callable functions
    Observation
    I noticed that picker-I.py was provided as downloadable source code, which meant reading it would reveal how the service decides what to execute and whether any unintended functions like win() were available for direct invocation.
    The script reads a string and passes it into eval-style logic. Typing getRandomNumber returns 4 exactly as the code shows, so calling 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 accepts a function name as input, looks up that name in its namespace, and calls it. 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 2
    Call win on the remote service
    Observation
    I noticed from the source code that the service performs a direct namespace lookup on whatever string the user submits, which meant sending the bare name 'win' to the remote instance via netcat would invoke 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 51291

    Expected output

    picoCTF{4_d14m0nd_1n_7h3_r0u...5d5b}
    What didn't work first

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

    The service receives the raw string and performs its own lookup and call internally - it does not eval arbitrary Python expressions. Sending 'win()' causes the lookup to fail with a NameError or similar because 'win()' is not a valid identifier in the namespace. The correct input is the bare function name 'win' with no parentheses.

    Tried: Typing the function name interactively in a local python3 session opened on picker-I.py without piping input

    Running python3 picker-I.py locally and typing 'win' at the prompt works for testing, but on the remote service the flag printed is the real one. If you only test locally and the script checks an environment variable or file path to load the flag, you may see a placeholder or an error instead of the real picoCTF flag. Always send 'win' to the remote netcat address to get the actual flag.

    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 3
    Convert from hex
    Observation
    I noticed the remote service printed the flag as a sequence of 0x-prefixed hex byte values rather than readable ASCII, which indicated one final decoding step was required before the flag would be usable.
    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 becomes a security vulnerability when user-supplied input determines which function gets called without a strict allowlist. Any server that looks up and invokes a name from its own namespace based on untrusted input is effectively handing attackers a remote function call primitive. This class of bug appears in plugin systems, scripting interpreters, and RPC frameworks wherever the callable registry is broader than the intended public API.

Related reading

Want more picoGym Exclusive writeups?

Useful tools for Reverse Engineering

What to try next