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支持全双工模式传输数据,这意味着同一时刻两个方向都可以进行数据的传输。在传输数据之前,TCP通过三次握手建立的实际上是两个方向的连接,因此在传输完毕后,两个方向的连接必须都关闭。
- 主机
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端口扫描
python
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端口扫描
python
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")
通过简单脚本加深对协议工作原理的理解。