Python捕获网络报文 socket库、scapy库介绍
在 Python 中,捕获网络报文通常涉及使用一些第三方库来实现网络数据包的捕获、分析和处理。这里我主要介绍scapy库的sniff函数,同时也简单介绍下socket库捕获报文的方法。
注意:由于该操作涉及原始套接字,因此需要root或管理员权限!否则会报出如下错误(下图为windows环境下错误)。
OSError: [WinError 10013] 以一种访问权限不允许的方式做了一个访问套接字的尝试。
1. socket库捕获网络报文
使用 Python 的 socket 库来捕获网络报文,通常需要使用 原始套接字(Raw Socket)。原始套接字允许你直接访问网络层和链路层的数据包,这样你就可以捕获所有通过网络接口传输的数据包,而不只是应用层的数据。
1.1 原始套接字(Raw Socket)
原始套接字是一个底层的网络接口,允许应用程序直接与数据链路层或网络层交互。在 socket 库中,原始套接字由 SOCK_RAW 套接字类型支持。
通过原始套接字,你可以捕获经过网络接口的所有数据包,包括 TCP、UDP、ICMP 等协议的数据包。
1.2 创建原始套接字
首先,你需要使用 socket 库创建一个原始套接字,并将其绑定到特定的网络接口上。
import socket
# 创建原始套接字,捕获所有 IP 数据包
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)
# 绑定到指定的接口
sock.bind(("0.0.0.0", 0)) # "0.0.0.0" 代表监听所有的网络接口
# 捕获数据包
while True:
packet = sock.recvfrom(65565) # 接收最大 65565 字节的数据包
print(packet)
socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)的作用是创建一个原始套接字,捕获IPv4协议下所有IP数据包。
bind() 方法将套接字绑定到本地的某个地址和端口。"0.0.0.0" 表示监听所有网络接口。对于指定的接口,可以使用接口的 IP 地址,如 eth0 等。
recvfrom() 用于接收数据包。65565 是最大接收缓冲区的大小,这表示接收的单个数据包最大为 65565 字节。
1.3 解析数据包
以下是我根据socket捕获的一个数据包
(b'E\x00\x004I\xeb@\x00\x80\x06\x00\x00\x7f\x00\x00\x01\x7f\x00\x00\x01\xd7h$\r\xa3\x12\x14\xa3\x00\x00\x00\x00\x80\x02\xff\xff\xc3\xbe\x00\x00\x02\x04\xff\xd7\x01\x03\x03\x08\x01\x01\x04\x02', ('127.0.0.1', 0))
其结构如下:
(packet_data, (sender_host, sender_port))
由于捕获到的数据包通常是字节流,并没有直接的解析能力。如果要解析数据包中的具体信息(如源 IP、目的 IP、协议类型等),需要使用 struct 库或者其他工具来解析字节流。
比如下面的例子:
import socket
import struct
# 创建原始套接字,捕获所有 IP 数据包
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)
sock.bind(("0.0.0.0", 0))
# 捕获数据包
while True:
packet = sock.recvfrom(65565)
packet_data = packet[0] # 数据包内容
ip_header = packet_data[:20] # 提取 IP 头部(前 20 字节)
# 使用 struct.unpack() 解析 IP 头部
iph = struct.unpack("!BBHHHBBH4s4s", ip_header)
# 获取 IP 头部的版本、协议和源地址、目标地址等
version_ihl = iph[0]
version = version_ihl >> 4
ihl = version_ihl & 0xF
protocol = iph[6]
s_addr = socket.inet_ntoa(iph[8])
d_addr = socket.inet_ntoa(iph[9])
print(f"Version: {version}, Protocol: {protocol}, Source Address: {s_addr}, Destination Address: {d_addr}")
通过挨个解析字符串,可以获得数据包的源地址,目的地址,IP版本、协议等内容,结果如下:
但这种方式要人工过滤源IP地址等内容,比如下面例子:
import socket
import struct
# 设置要过滤的源 IP 和目标 IP
filter_source_ip = "192.168.80.200"
filter_dest_ip = "192.168.80.201"
# 创建原始套接字,捕获所有 IP 数据包
sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_IP)
sock.bind(("0.0.0.0", 0)) # 监听所有接口
# 捕获数据包
while True:
packet = sock.recvfrom(65565)
packet_data = packet[0] # 数据包内容
ip_header = packet_data[:20] # 提取 IP 头部(前 20 字节)
# 使用 struct.unpack() 解析 IP 头部
iph = struct.unpack("!BBHHHBBH4s4s", ip_header)
# 获取源和目标 IP 地址
source_ip = socket.inet_ntoa(iph[8]) # 源 IP 地址
dest_ip = socket.inet_ntoa(iph[9]) # 目标 IP 地址
# 打印源和目标 IP
print(f"Source IP: {source_ip}, Destination IP: {dest_ip}")
# 过滤源 IP 和目标 IP
if source_ip == filter_source_ip and dest_ip == filter_dest_ip:
print("Packet matches the filter criteria!")
# 在这里可以处理符合条件的包
else:
print("Packet does not match the filter criteria.")
所以可以用scapy库等更方便的库通过设置filter进行过滤,下面我将对scapy库进行介绍。
2. scapy库捕获报文
Scapy 是一个强大的 Python 库,用于网络包的创建、发送、捕获和分析。它提供了对网络协议栈的高度控制,允许你构造和解析网络数据包、执行网络扫描、嗅探数据包、执行安全测试等操作。与使用原始 socket 套接字相比,Scapy 提供了更加简洁和高级的接口,特别适合用于网络安全和网络分析工作。
2.0 scapy的特点
由于这里我们主要用scapy捕获网络数据包,所以下面主要介绍sniff函数。
2.1 sniff函数
Scapy 的 sniff() 函数是用于捕获网络报文的核心函数。它可以实时捕获网络接口上经过的数据包,并对这些数据包进行分析和处理。sniff() 是一个非常灵活的函数,支持多种过滤和处理功能,能够满足各种网络分析需求。
2.1.1 基本用法
sniff(count=0, timeout=None, iface=None, filter=None, prn=None, store=True)
例如下面的例子:
filter = f"tcp and src host 192.168.80.200 and dst host 192.168.80.201 and src port 80"
sniff(iface="Intel(R) Ethernet Connection (23) I219-V", count=0, prn=packet_callback, store=0, filter=filter)
这里的意思就是捕获网卡"Intel(R) Ethernet Connection (23) I219-V"的数据包,count为0表示捕获不受限制,packet_callback为捕获到数据包后执行的回调函数,store为0表示不会再内存中存储该网络包,而filter则是规定该捕获的过滤规则,这里的捕获规则就是捕获tcp报文且源地址为80.200端口为80,目的地址为80.201的网络包。
上面的例子可以看到相比于socket方式,scapy能控制捕获时间,指定网络接口,还可以通过BPF语法对数据包进行自动过滤,功能远比socket方式强大。下面简单介绍下回调函数中的packet。
2.1.2 packet
在捕获网络数据包后通常会触发一个prn指定的回调函数,其中会提供一个packet对象,packet 对象是一个 Packet 类实例,它代表了捕获的一个网络数据包。这个对象封装了数据包的所有信息,并提供了多种方法和属性,用于访问和分析数据包中的各个层次(例如:以太网、IP、TCP、UDP 等)。
2.1.2.1 packet对象的结构
Scapy 的数据包是分层的,每一层通常是一个协议,如 Ethernet、IP、TCP 等。Scapy 会根据协议栈为每个数据包创建多个协议层,并将它们组合在一起形成一个层次结构。
常见的协议层
- Ethernet:数据链路层,用于以太网数据帧。
- IP:网络层,处理 IP 地址和路由。
- TCP/UDP:传输层,处理端到端的传输协议。
- ICMP:控制消息协议(用于 ping 等功能)。
- ARP:地址解析协议,用于 IP 地址与 MAC 地址之间的映射。
- DNS、HTTP、FTP 等:应用层协议。
2.1.2.2 packet对象的属性和方法
在 Scapy 中,捕获的数据包(packet)是由一个或多个协议层组成的,每个协议层都有特定的字段,可以通过访问这些字段来获取协议的信息。下面列出了一些常见的属性和方法:
packet.summary()
该函数会打印数据包的摘要信息。它会显示数据包的基本协议层信息,如源 IP、目标 IP 和协议类型。
packet.summary()
Ether / ARP who has 192.168.10.1 says 192.168.10.190 / Padding
Ether / IP / UDP / DNS Qry "b'_companion-link._tcp.local.'"
Ether / IPv6 / UDP / DNS Qry "b'_companion-link._tcp.local.'"
Ether / IP / TCP 111.30.182.254:8080 > 192.168.8.96:64431 PA / Raw
Ether / IP / TCP 192.168.8.96:64431 > 111.30.182.254:8080 A
Ether / IP / TCP 192.168.8.96:64981 > 112.60.24.249:8080 PA / Raw
Ether / IP / TCP 112.60.24.249:8080 > 192.168.8.96:64981 A / Padding
packet.show()
打印数据包的详细信息,包括每个协议层的所有字段及其值。
packet.show()
###[ Ethernet ]###
dst = 01:00:5e:7f:ff:fa
src = 8c:c8:4b:c0:c3:f7
type = IPv4
###[ IP ]###
version = 4
ihl = 5
tos = 0x0
len = 203
id = 50359
flags =
frag = 0
ttl = 1
proto = udp
chksum = 0x3b15
src = 192.168.8.179
dst = 239.255.255.250
\options \
###[ UDP ]###
sport = 64865
dport = ssdp
len = 183
chksum = 0xbdbb
###[ Raw ]###
load = 'M-SEARCH * HTTP/1.1\r\nHOST: 239.255.255.250:1900\r\nMAN: "ssdp:discover"\r\nMX: 1\r\nST: urn:dial-multiscreen-org:service:dial:1\r\nUSER-AGENT: Microsoft Edge/127.0.2651.74 Windows\r\n\r\n'
访问协议层
每一层的字段可以通过 packet[layer] 访问,例如:
访问IP层
ip_src = packet[IP].src # 源 IP 地址
ip_dst = packet[IP].dst # 目标 IP 地址
访问TCP层
tcp_src_port = packet[TCP].sport # 源端口
tcp_dst_port = packet[TCP].dport # 目标端口
访问ARP层
arp_src_ip = packet[ARP].psrc # 源 IP 地址
arp_dst_ip = packet[ARP].pdst # 目标 IP 地址
Scapy 自动根据数据包的结构解析各个协议层,因此你可以直接访问协议字段,而无需自己进行协议解析。
packet.haslayer()
用于检查数据包是否包含特定的协议层。如果数据包包含该层,返回 True,否则返回 False。
if packet.haslayer(IP):
print("This is an IP packet")
if packet.haslayer(TCP):
print("This packet has a TCP layer")
下面是对这些数据包一个结合的例子:
分析TCP数据包的端口信息
from scapy.all import sniff, IP, TCP
def packet_callback(packet):
if packet.haslayer(IP) and packet.haslayer(TCP):
ip_src = packet[IP].src
ip_dst = packet[IP].dst
tcp_src_port = packet[TCP].sport # 源端口
tcp_dst_port = packet[TCP].dport # 目标端口
print(f"Source IP: {ip_src}, Destination IP: {ip_dst}")
print(f"Source Port: {tcp_src_port}, Destination Port: {tcp_dst_port}")
sniff(count=10, prn=packet_callback)
2.1.2.3 Raw层
在 Scapy 中,Raw 层是一个特殊的层,用来表示原始数据(也叫负载数据),即所有协议层之后的直接数据部分。它通常出现在数据包的底层,在数据包的协议层之间没有进一步的解析时,Raw 层便存储了这些“未解析”的原始字节数据。
Raw 层的出现通常包含两种情况:
- 应用层数据:如果是网络应用协议的封装数据,Raw 层包含的是应用层的实际数据内容,例如 HTTP 请求的正文、DNS 查询的原始内容等。
- 没有对应协议层的原始数据:某些数据包可能没有被 Scapy 内部的协议解析器识别或解析(比如自定义协议),这些数据直接被 Scapy 存储为原始字节流,存储在 Raw 层。
比如在使用IEC104协议时,104报文的数据就是存在Raw层,如果我们相对104报文进行解析,则需要先获取Raw层数据,然后在转化成16进制的数据。
if packet.haslayer("Raw"):
raw = packet["Raw"]
iec104_message = raw.load.hex()
作者:Stalker_DAs