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.
Setup
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.
wget https://artifacts.picoctf.net/c/515/picker-I.pypython3 picker-I.pySolution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1Identify callable functions
ObservationThe 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.pyreveals the program's logic: it reads a function name, appends()to it, and hands the whole string toeval(). 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.Step 2Call win on the remote service
ObservationThe 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.bashprintf "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 stringwinfollowed 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.
Step 3Convert from hex
ObservationThe 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 the0xprefix 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.