SKIP TO MAIN CONTENT

[ WRITEUP NODE / FIELD REPORTS ]

SOLVED CHALLENGES & FIELD ANALYSIS

SECURITY RESEARCH KNOWLEDGE TECHNIQUES
B3S/WRITEUPS/GASLIGHTCTF-2026-BADCAT-WRITEUP
← BACK TO ARCHIVE
EVENT: gaslightCTF 2026CATEGORY: PwnPOINTS: 500 PTS

GaslightCTF 2026 - Badcat Writeup

AUTHORED BY:@bealthguy8/15/2026

badcat

Challenge

you've seen batcat, are you ready for badcat?

Login: badcat:b4788c115ea4eb42 Service: ssh -o "ProxyCommand=ncat --ssl %h %p" <instance>.play.gaslightctf.cooking -p 31337

The flag lives at /flag (mode -r-------- root:root). /bin/badcat is SUID root.

Reverse engineering

badcat (not stripped, full RELRO/canary/NX/PIE/FORTIFY) works like a cat:

  • On startup it drops privileges: seteuid(getuid()), setegid(getgid()).
  • Loops over arguments:
    • -slurp(0): read up to 0xfff bytes from stdin, print them, close(fd 0).
    • arg == "/flag" (rodata 0x2004): re-raise euid/egid to root, open("/flag", O_RDONLY|O_NOFOLLOW), read(fd, buf, 8), print psst, here's a sneak peek: %s, then drop privileges again — the fd is never closed.
    • any other arg: __realpath_chk() it, refuse paths resolving to /proc... or /dev..., then open() as the unprivileged user and slurp() the whole file.

So the root-privileged read is limited to the first 8 bytes of /flag.

Exploit: fd reuse via closed stdin

slurp(0) closes fd 0. The subsequent root-branch open("/flag") therefore reuses the lowest free descriptor — fd 0 — pointing at /flag, offset 8 after the peek. A second - makes slurp(0) read from that root-open fd, dumping the rest of the flag.

badcat - /flag - </dev/null

Output:

psst, here's a sneak peek: gaslight
CTF{f3l1n3_d3scr1pt0r_m30www_ee8a0066610c}

Flag

gaslightCTF{f3l1n3_d3scr1pt0r_m30www_ee8a0066610c}