Description
Can you control your overflow?
Setup
Connect to the remote instance at tethys.picoctf.net <PORT_FROM_INSTANCE>.
Explore the menu options; they mirror heap0 (print/write/inspect safe_var/print flag).
nc tethys.picoctf.net <PORT_FROM_INSTANCE>Solution
Walk me through it- Step 1Measure the offset to safe_varSet a breakpoint after the malloc and check pwndbg's heap output: the input chunk and the safe_var chunk are adjacent, with safe_var sitting 32 bytes after the buffer's start.bash
gdb ./challbashpwndbg> b *main+offset_of_fgetsbashpwndbg> rbashpwndbg> heapbashpwndbg> p &safe_var - input_bufLearn more
This step advances beyond heap-0 by requiring a controlled overwrite rather than just a null byte. The challenge now checks that
safe_varequals a specific string value ("pico"), not just that it's zero, so you need to write exactly the right bytes at exactly the right position.pwndbg> heap Allocated chunk | PREV_INUSE Addr: 0x55555556a2a0 Size: 0x21 (with flag bits: 0x21) <- input buffer (0x20 = 32 user bytes) Allocated chunk | PREV_INUSE Addr: 0x55555556a2c0 Size: 0x21 (with flag bits: 0x21) <- safe_var chunk pwndbg> p (char *)0x55555556a2c0 - (char *)0x55555556a2a0 $1 = 0x20 <- 32 bytes between themPrecision matters: one byte too few and
safe_varis untouched; one byte too many with the wrong content corrupts it to the wrong value. In real exploits targeting allocator metadata (free-list pointers, size fields), the offset from the overflow buffer to the target comes from this same chunk-layout inspection. - Step 2Append the magic stringAppend pico immediately after the 32-byte filler when using menu option 2. The null terminator follows pico, leaving safe_var == "pico".bash
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAApicoLearn more
The string
"pico"is 4 bytes:p=0x70,i=0x69,c=0x63,o=0x6f. These 4 bytes, written starting at the 33rd position, overwritesafe_varwith exactly the right value. The null terminator fromfgetsfollows at position 37, which either lands in the next heap chunk (harmlessly) or terminates the string naturally.This illustrates controlled heap overflow: you write a chosen value (not just zeros or garbage) to an adjacent heap object. In real exploits, controlled overwrites target function pointers (to redirect execution), heap freelist pointers (to enable arbitrary allocation), or security-critical flags (as here). The technique scales from simple guard variables to complex heap management structures.
The 36-character total payload (
A*32 + "pico") fits within a reasonable input buffer size. If the input function's size limit were only 32 bytes, this attack would be impossible, which is why proper input length validation must account for all adjacent data that could be affected, not just the buffer itself.Precision overwrite is what works here because the offset to
safe_varis fixed and known. Heap spraying (filling the heap with many copies of a payload to land somewhere by chance) only earns its keep when the offset is unknown or jitter exists between runs, neither of which applies to this challenge. - Step 3Print the flagOnce safe_var contains pico, selecting option 4 prints the flag without further tricks.Use option 3 first if you want to confirm safe_var now shows pico, then call option 4.
Learn more
Using option 3 to verify the write before triggering the reward is good exploit development practice. Confirming intermediate state before proceeding helps isolate failures: if the flag doesn't print, you know whether the problem is the overflow payload or the flag-printing logic itself.
This two-step approach (write → verify → trigger) mirrors professional exploit development, where each stage is tested independently. In complex exploit chains with multiple vulnerabilities chained together (info leak → bypass ASLR → overflow → code execution), verifying each step prevents wasted time debugging the wrong stage.
The progression from heap-0 (zero any value), to heap-1 (write a specific value), to heap-2 (write a function pointer), to heap-3 (use-after-free) represents the learning ladder of heap exploitation. Each challenge adds one new concept: precision control, address knowledge, memory lifecycle awareness. Professional heap exploitation combines all of these plus allocator internals, making it one of the most technically demanding areas of binary exploitation.
Flag
picoCTF{starting_to_get_the_hang_c58...}
As soon as safe_var == pico, option 4 prints the full flag (truncated above; the trailing 8-character random tail is per-instance).
How to prevent this
How to prevent this
Same root cause as heap-0; the attacker just chose a specific value to write instead of zero.
- Bounds-check every write into a heap buffer.
strncpy,snprintf, and explicit length parameters are the minimum bar. - Do not place security-relevant variables adjacent to user-controlled buffers. The compiler often does this for you with
-fstack-protector-strongand modern allocators (mimalloc, jemalloc) but explicit struct layout is more reliable. - Run with AddressSanitizer (
-fsanitize=address) and HWASAN in CI. Both catch out-of-bounds writes the instant they happen, not three frames later when corruption manifests.