传输层协议分析
0x01 TCP
协议
传输层也称主机到主机的传输层,位于TCP/IP
栈的第3
层,它为应用层提供可靠的或不可靠的端到端服务。
传输层中最为常见的两个协议:
- 传输控制协议
TCP(Transmission Control Protocol)
- 用户数据包协议
UDP(User Datagram Protocol)
TCP
是一种面向连接的传输层协议,可提供可靠的传输服务。
TCP
通常使用IP
作为网络层协议,这时TCP
数据段被封装在IP
数据包内。
TCP
数据段由TCP Header
和TCP Data
组成。TCP
最多可以有60
个字节的头部,如果没有Options
字段,正常的长度是20
字节。
TCP Header
是由如上图标识的一些字段组成,这里列出几个常用字段。
16
位源端口号:源主机的应用程序使用的端口号。16
位目的端口号:目的主机的应用程序使用的端口号。每个TCP
头部都包含源和目的端的端口号,这两个值加上IP
头部中的源IP
地址和目的IP
地址可以唯一确定一个TCP
连接。32
位序列号:用于标识从发送端发出的不同的TCP
数据段的序号。数据段在网络中传输时,它们的顺序可能会发生变化;接收端依据此序列号,便可按照正确的顺序重组数据。32
位确认序列号:用于标识接收端确认收到的数据段。确认序列号为成功收到的数据序列号加1
。4
位头部长度:表示头部占32bit
字的数目,它能表达的TCP
头部最大长度为60
字节。6
位控制位 :在TCP
报头中有6
个标志位,它们中的多个可同时被设置为1
。依次为:- ①
URG
: 为1
表示紧急指针有效,为0
则忽略紧急指针值。 - ②
ACK
: 为1
表示确认号有效,为0
表示报文中不包含确认信息,忽略确认号字段。 - ③
PSH
: 为1
表示是带有PUSH
标志的数据,指示接收方应该尽快将这个报文段交给应用层而不用等待缓冲区装满。 - ④
RST
: 用于复位由于主机崩捷或其他原因而出现错误的连接。它还可以用于拒绝非法的报文段和拒绝连接请求。一般情况下,如果收到一个RST
为1
的报文,那么一定发生了某些问题。 - ⑤
SYN
: 同步序号,为1
表示连接请求,用于建立连接和使顺序号同步(synchronize
)。 - ⑥
FIN
: 用于释放连接,为1
表示发送方已经没有数据发送了,即关闭本方数据流。
- ①
16
位窗口大小:表示接收端期望通过单次确认而收到的数据的大小。由于该字段为16
位,所以窗口大小的最大值为65535
字节,该机制通常用来进行流量控制。16
位校验和:校验整个TCP
报文段,包括TCP
头部和TCP
数据。该值由发送端计算和记录并由接收端进行验证。16
位紧急指针: 只有当URG
标志置1
时紧急指针才有效。紧急指针是一个正的偏移量,和顺序号宇段中的值相加表示紧急数据最后一个字节的序号。TCP
的紧急方式是发送端向另一端发送紧急数据的一种方式,很少有实现的应用。- 选项: 最常见的可选字段是最长报文大小,又称为
MSS (Maximum Segment Size )
。每个连接方通常都在通信的第一个报文段(为建立连接而设置SYN
标志的那个段)中指明这个选项,它指明本端所能接收的最大长度的报文段。选项长度不一定是32
的整数倍,所以要加填充位,使得报头长度成为整字数。
三次握手
这个都挺熟悉了,每次TCP
应用最先发起的就是三次握手连接了
- 主机
A
(通常也称为客户端)发送一个标识了SYN
的数据段,表示期望与服务器A
建立连接,此数据段的序列号(seq)
为a
。 - 服务器
A
回复标识了SYN+ACK
的数据段,此数据段的序列号为b
,确认序列号为主机A
的序列号加1
即a+1
,以此作为对主机A
的SYN
报文的确认。 - 主机
A
发送一个标识了ACK
的数据段,此数据段的序列号为a+1
,确认序列号为服务器A的序列号加1
即b+1
,以此作为对服务器A
的SYN
报文的确认。
传输过程
序列号配合确认号可以完成支持重传的可靠性功能
TCP
滑动窗口技术通过动态改变窗口大小来实现对端到端设备之间的数据传输进行流量控制。
四次分手
TCP
支持全双工模式传输数据,这意味着同一时刻两个方向都可以进行数据的传输。在传输数据之前,TC
P通过三次握手建立的实际上是两个方向的连接,因此在传输完毕后,两个方向的连接必须都关闭。
- 主机
A
想终止连接,于是发送一个标识了FIN
,ACK
的数据段,序列号为a
,确认序列号为b
。 - 服务器
A
回应一个标识了ACK
的数据段,序列号为b
,确认序号为a+1
,作为对主机A
的FIN
报文的确认。 - 服务器
A
想终止连接,于是向主机A
发送一个标识了FIN
,ACK
的数据段,序列号为b
,确认序列号为a+1
。 - 主机
A
回应一个标识了ACK
的数据段,序列号为a+1
,确认序号为b+1
,作为对服务器A的FIN
报文的确认。
以上四次交互便完成了两个方向连接的关闭。这里就非常有意思了,因为除了四次分手外,生活中还经常见到三次分手的情况
三次分手其实也是正常拆除连接的情况,只需要在拆除连接请求时,被请求的那一端恰好也传输完了数据,就可以直接回复FIN
置位的ACK
消息了
当然了还有一种特殊的拆除连接,就是RST
置位的消息
当收到RST
置位的消息时,会直接断开连接,应用场景挺多的,比如重定向之前、劫持报文前都会使用到这个消息
0x02 UDP
协议
UDP
是一种面向无连接的传输层协议,传输可靠性没有保证。
UDP
报文分为UDP
报文头和UDP
数据区域两部分。报头由源端口、目的端口、报文长度以及校验和组成。UDP
头部的标识如下:
16
位源端口号:源主机的应用程序使用的端口号。16
位目的端口号:目的主机的应用程序使用的端口号。16
位UDP
长度:是指UDP
头部和UDP
数据的字节长度。因为UDP
头部长度为8
字节,所以该字段的最小值为8
。16
位UDP
校验和:该字段提供了与TCP
校验字段同样的功能;该字段是可选的。
使用UDP
传输数据时,由应用程序根据需要提供报文到达确认、排序、流量控制等功能。
UDP
适合传输对时延敏感的流量,如语音和视频。
在使用TCP
协议传输数据时,如果一个数据段丢失或者接收端对某个数据段没有确认,发送端会重新发送该数据段。TCP
重新发送数据会带来传输延迟和重复数据,降低了用户的体验。对于时延敏感的应用,少量的数据丢失一般可以被忽略,这时使用UDP
传输将能够提升用户的体验。
另外,使用UDP
流程也相对安全
一些,可以避免类型TCP
协议握手时才会发生的半开连接攻击Syn Flooding
0x03 python
编写tcp
端口扫描
from scapy.all import *
import random
dip = '172.16.2.13'
sport = random.randint(40000,60000)
dport = [80,3389,445,139,20,21,22,23]
pkt = IP(dst=dip)/TCP(sport=sport,dport=dport,flags='S') #以FIN标记位来进行扫描测试ans,unans = sr(pkt,verbose=0,timeout=2)
for req,rep in ans:if (rep[TCP].flags == 'SA'):print(TCP_SERVICES[req[TCP].dport])print(req[TCP].dport)print(req.sprintf("%IP.dst%的(%TCP.dport%)端口是开放的"))elif (rep[TCP].flags == 'RA'):print(req.sprintf("%IP.dst%的(%TCP.dport%)端口是未开放的"))else:print(req.sprintf("%IP.dst%的(%TCP.dport%)端口是未知的"))
for req in unans:print("主机未存活")
0x04 python
编写udp
端口扫描
from scapy.all import *
import random
dip = '172.16.2.34'
sport = random.randint(40000,60000)
dport = [123,230,500]pkt = IP(dst=dip)/UDP(sport=sport,dport=dport)
ans,unans = sr(pkt,verbose=2,timeout=2)
for req in unans:print("OK")
for req,rep in ans:print("no")
通过简单脚本加深对协议工作原理的理解。