$ cat writeup.md…
$ cat writeup.md…
hackthebox
Task: sed-like string replacement utility with read() not adding null terminator and contiguous BSS buffers. Solution: 3-pass ret2libc exploiting strlen inflation across buffer boundary, partial return address overwrite for PIE leak, GOT read for libc leak.
$ cat /etc/rate-limit
Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.
"This useful interactive SED-like utility was shared with me to use, can you make sure it is safe?"
64-bit ELF PIE binary, dynamically linked, not stripped. Implements an interactive string replacement utility in sed format (s/old/new/). Takes an input string and a replacement pattern, performs substitution, and outputs the result.
Remote target: 154.57.164.68:32476.
Files: replaceme (binary), libc.so.6 (Ubuntu GLIBC 2.31-0ubuntu9.14), Dockerfile (Ubuntu 20.04, socat), flag.txt.
| Protection | Status |
|---|---|
| PIE | Enabled |
| NX | Enabled |
| Canary | No |
| RELRO | Full (GOT not writable, but readable) |
main()Sequentially reads two inputs into BSS:
input (BSS 0x4040, 128 bytes) — string to processreplacement (BSS 0x40c0, 128 bytes) — replacement pattern in s/old/new/ formatThen calls do_replacement().
ask_input(prompt, buffer, size)Uses read(0, buffer, size) — does not add null terminator. This is critical: if all 128 bytes of input are filled, strlen() will continue reading into replacement.
do_replacement() — vulnerable functionParses s/old/new/, finds old in input, builds result: prefix + new + suffix into stack buffer result[128] via memcpy without bounds checking.
rbp-0xc0: result[128] ← buffer start
rbp-0x40: padding
rbp-0x3c: after_len ← suffix length (int)
rbp-0x38: wp ← write pointer
rbp-0x30: match ← match pointer
rbp-0x24: new_len
rbp-0x20: end_slash
rbp-0x14: old_len
rbp-0x10: mid_slash
rbp-0x08: old_start
rbp+0x00: saved RBP
rbp+0x08: return address ← result + 0xc8 = result[200]
input (0x4040) and replacement (0x40c0) are located contiguously in BSS. When input is filled with all 128 bytes without null terminator, strlen() continues counting bytes from replacement, inflating after_len.
...
$ grep --similar