$ cat writeup.md…
$ cat writeup.md…
hackthebox
Task: PIE/no-canary glibc 2.31 menu pwn with a scanf(\"%16s\") off-by-null that zeroes main's saved RBP and an OOB-read scanner that leaks the stack. Solution: leak via the OOB oracle, derive the pivot delta directly from a leaked stack qword (buf=leak-0x1100, main_rbp=leak-0xF0), pivot main's frame into the controlled buffer, then overwrite the option-1 fgets's own saved return address (offset delta-8) with a ret2libc chain.
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
In the spirit of optimisation, I wanted a fast memory searching algorithm, and thanks to this program I think I've finally found one. Not only is it fast, it's also completely secure.
We are given an archive (password hackthebox) containing the scanner binary, libc.so.6 (glibc 2.31), ld-2.31.so, and a fake local flag.txt. The goal is remote code execution against 154.57.164.64:31193 to read the real flag.
This is a multi-stage stack-pivot challenge. The crux is not finding the bugs (an off-by-null in scanf("%16s") and an out-of-bounds read in the scanner) but precisely deriving the stack geometry so the pivot lands deterministically with no ASLR brute force.
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
libc: glibc 2.31 (provided)
PIE + no canary means: leak something, then a single return-address overwrite is enough to redirect control. No canary removes any need to leak/repair a stack cookie.
main() runs a menu loop. Its prologue is:
push rbp mov rbp, rsp sub rsp, 0x1010 ; 0x1000 buffer + locals
Frame layout (relative to main's rbp):
| Location | Meaning |
|---|---|
[rbp-0x1010] | buf — 0x1000-byte working buffer |
[rbp-0x10] | ptr — pointer returned by malloc |
[rbp-0x8] | size — fread size |
[rbp-0x4] | idx — selected scanner index |
Menu options:
fgets(buf, 0x1000, stdin) into [rbp-0x1010]. No scanner-name validation. This fgets is the control-flow hijack primitive.read_parameters() → loops run_scanner() → free(ptr).read_parameters() → run_scanner() → print_scanner_output() → free(ptr)....
$ grep --similar