Description
A PIE-protected binary leaks the address of `main` each time you connect. Use that leak to compute the absolute address of `win` and jump there instead of returning to `main`.
Setup
Fetch both the binary and its source so you can inspect the control flow (`win` simply prints the flag).
Connect to `nc rescued-float.picoctf.net 59193` and note the leaked address of `main`.
Use objdump or radare2 locally to record the offsets of `main` and `win` inside the binary.
wget https://challenge-files.picoctf.net/c_rescued_float/2736a730340dbe9969fe3104da0cca0c60eddaf1fedb0e220b5df5a3f3cf015f/vuln.cwget https://challenge-files.picoctf.net/c_rescued_float/2736a730340dbe9969fe3104da0cca0c60eddaf1fedb0e220b5df5a3f3cf015f/vulnobjdump -D vuln | grep -E "<win>|<main>"Solution
- Step 1Derive the PIE baseWrite down the leaked `main` address from the remote service. Subtract the local `main` offset (0x133d) to recover the PIE base address for that run.
pie_base = leaked_main - 0x133dLearn more
Position Independent Executables (PIE) are binaries compiled with
-fPIE -pieso that every instruction and data reference uses relative addressing. At load time, the OS kernel maps the binary to a random base address chosen by ASLR (Address Space Layout Randomization). Every subsequent address inside the binary is that base plus the static offset from the compiled binary - somain's runtime address equalspie_base + 0x133d.By inverting this arithmetic - subtracting the known offset of
mainfrom its leaked runtime address - you recover thepie_basefor that specific execution. This value remains constant throughout the process lifetime, so once known, you can compute the absolute address of any other symbol. This is exactly how return-oriented programming (ROP) and other advanced exploits work after obtaining a single leak.objdump -Ddisassembles all sections of the binary and prints symbol addresses as offsets from zero (since the binary isn't loaded yet).readelf -sandnmprovide cleaner symbol table output. The key insight is that these offsets are fixed at compile time - ASLR only randomizes the base, not the relative layout. - Step 2Compute the win addressAdd the known `win` offset (0x12a7) to the PIE base to get the absolute address of `win`. A three-line Python helper that does `win_address = leaked_main - main_offset + win_offset` keeps the math simple.
win_address = leaked_main - 0x133d + 0x12a7Learn more
The address arithmetic
win_address = leaked_main - main_offset + win_offsetis the fundamental formula for all PIE-defeat exploits. It generalizes to any two symbols in the binary: knowing one runtime address and both static offsets lets you compute any other runtime address. CTF players often write a small Python script using pwntools (from pwn import *) which automates connecting to the service, parsing the leaked address, computing the target address, and sending the exploit.Pwntools'
ELFclass can parse the binary automatically:elf = ELF('./vuln'); main_offset = elf.symbols['main']; win_offset = elf.symbols['win']. This avoids manual offset extraction fromobjdumpand keeps the exploit script portable across different binary versions. Thecontext.archandcontext.log_levelsettings further simplify address packing and debugging output.Real-world exploitation frequently involves leaking a libc address (via a
putscall or format string) rather than a binary address, computing the libc base, then jumping tosystem('/bin/sh')or using a ROP gadget chain. The arithmetic is identical - only the target binary (libc vs. the vuln binary) changes. - Step 3Send the target addressReconnect (or keep the connection open), paste the computed `0x...` value when prompted, and the binary jumps straight into `win`, printing the flag.
Learn more
The mechanism that lets you "send an address" and have the binary jump there is almost always a stack buffer overflow. The vulnerable program reads more input than the buffer can hold, overwriting the saved return address on the stack. When the current function executes its
retinstruction, it pops your supplied address into the instruction pointer and execution continues from there.The
winfunction pattern - a function that prints the flag but is never called by normal program flow - is a classic CTF teaching device. In real-world binary exploitation, there is nowinfunction; attackers typically chain ROP gadgets to callexecve('/bin/sh', NULL, NULL)or use a one-gadget (a single libc address that directly spawns a shell under the right register conditions).Keeping the connection alive (rather than reconnecting) is important because ASLR generates a new random base on each process execution. If the service forks (a common CTF server pattern), the child inherits the parent's address space layout, so the leak from one connection is valid for subsequent requests to the same child process. New connections that spawn new processes get fresh ASLR randomization.
Flag
picoCTF{b4s1c_p051t10n_1nd3p3nd3nc3_31cc...}
Keep one terminal open with nc to grab the current leak and a second to run the helper script so the values stay in sync.