$ cat writeup.md…
$ cat writeup.md…
fortid2026
Task: C++ store with an unchecked index into a global std::string[8]: get leaks OOB, put writes OOB, FULL RELRO blocks GOT. Solution: leak libc/ctype via get -2, overwrite the std::ctype facet vtable via put -1, redirect do_widen to setcontext+61 and pivot into ROP system("/bin/sh").
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
Strings, strings everywhere...
Handout handout.zip contains chall, chall.cpp, libc-2.39.so,
libstdc++.so.6.0.33. The remote service is reachable over TLS
(ncat --ssl <host> 1337). Category pwn, 245 points, 20 solves, author ante.
English summary: a tiny C++ REPL lets you store and print std::string notes in a
fixed 8-element global array. Both the store and the print take an integer index
with no bounds check, giving an out-of-bounds read (get) and an
out-of-bounds write (put) on an array of std::string objects. The binary is
PIE + NX + canary + FULL RELRO, so a plain GOT overwrite is off the table. The
goal is a shell on the remote box, then read the flag.
The entire program is 42 lines:
const int CAP = 8; string notes[CAP]; int main() { ... if (command == "put") { int idx; iss >> idx; string val; iss >> val; notes[idx] = val; // OOB write, no bounds check } else if (command == "get") { int idx; iss >> idx; cout << notes[idx] << endl; // OOB read, no bounds check } ... }
idx is a signed int and is used directly as an array subscript. Because
sizeof(std::string) == 32 on x86-64, element idx lives at
notes + idx*32 and arbitrary signed indices reach far outside the array.
Two useful primitives fall out:
...
$ grep --similar