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​ 套接字类型支持。

  • ​socket.AF_INET​:表示 IPv4 地址族。
  • ​socket.SOCK_RAW​:表示原始套接字类型。
  • ​socket.IPPROTO_IP​:表示捕获所有 IP 数据包。
  • 通过原始套接字,你可以捕获经过网络接口的所有数据包,包括 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数据包。

  • ​socket.AF_INET​:指定使用 IPv4 协议。
  • ​socket.SOCK_RAW​:指定原始套接字类型。
  • ​socket.IPPROTO_IP​:指定捕获所有 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))
    
  • ​packet_data​:这是捕获到的原始数据包,类型为 bytes​。它包含了所有的数据包内容,包括链路层和网络层的头部。
  • ​(sender_host, sender_port)​:这是发送者的地址信息,通常在捕获原始数据包时,端口信息会是 0​,因为原始套接字捕获的是网络层的所有流量。
  • 由于捕获到的数据包通常是字节流,并没有直接的解析能力。如果要解析数据包中的具体信息(如源 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​ 支持 IP、ARP、TCP、UDP、ICMP 等几乎所有常见的网络协议,甚至可以处理自定义协议。
  • 包构造和解析:你可以创建各种网络数据包,设置其各个字段,甚至修改包中的特定字节。
  • 交互式命令行:Scapy​ 提供了一个交互式的 Python shell,方便快速测试和调试网络包。
  • 支持嗅探与分析:你可以使用 Scapy​ 捕获网络中的数据包,过滤并分析特定协议的包。
  • 支持发送和接收包:Scapy​ 允许你发送和接收网络数据包,支持 TCP/IP 层的各类网络活动。
  • 由于这里我们主要用scapy捕获网络数据包,所以下面主要介绍sniff函数。

    2.1 sniff函数

    ​Scapy​ 的 sniff()​ 函数是用于捕获网络报文的核心函数。它可以实时捕获网络接口上经过的数据包,并对这些数据包进行分析和处理。sniff()​ 是一个非常灵活的函数,支持多种过滤和处理功能,能够满足各种网络分析需求。

    2.1.1 基本用法
    sniff(count=0, timeout=None, iface=None, filter=None, prn=None, store=True)
    
  • ​count​:捕获数据包的数量。如果为 0,则表示捕获无限数量的数据包,直到显式停止(通常通过 Ctrl+C 停止)。
  • ​timeout​:设置捕获数据包的超时时间,单位为秒。如果设置了该参数,捕获将在指定时间后停止。
  • ​iface​:指定要监听的网络接口。可以是设备名(如 "eth0"​、"wlan0"​ 等)。如果不指定,Scapy​ 会尝试自动选择一个接口。
  • ​filter​:用于设置捕获过滤器。可以使用 Berkeley Packet Filter(BPF)语法进行过滤,比如 filter="tcp"​ 只捕获 TCP 数据包,filter="ip" ​捕获所有 IP 数据包等。
  • ​prn​:每捕获一个数据包时调用的回调函数。回调函数接收捕获到的数据包作为参数,通常可以在这里进行数据包分析或处理。
  • ​store​:布尔值,是否保存捕获的包。如果为 True​,捕获的包会被保存在内存中。如果为 False​,则不会保存,适合只需要即时处理数据包而不保存的场景。
  • 例如下面的例子:

    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​ 会根据协议栈为每个数据包创建多个协议层,并将它们组合在一起形成一个层次结构。

    常见的协议层

    1. Ethernet:数据链路层,用于以太网数据帧。
    2. IP:网络层,处理 IP 地址和路由。
    3. TCP/UDP:传输层,处理端到端的传输协议。
    4. ICMP:控制消息协议(用于 ping 等功能)。
    5. ARP:地址解析协议,用于 IP 地址与 MAC 地址之间的映射。
    6. 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​ 层的出现通常包含两种情况:

    1. 应用层数据:如果是网络应用协议的封装数据,Raw​ 层包含的是应用层的实际数据内容,例如 HTTP 请求的正文、DNS 查询的原始内容等。
    2. 没有对应协议层的原始数据:某些数据包可能没有被 Scapy​ 内部的协议解析器识别或解析(比如自定义协议),这些数据直接被 Scapy​ 存储为原始字节流,存储在 Raw​ 层。

    比如在使用IEC104协议时,104报文的数据就是存在Raw层,如果我们相对104报文进行解析,则需要先获取Raw层数据,然后在转化成16进制的数据。

    if packet.haslayer("Raw"):
        raw = packet["Raw"]
        iec104_message = raw.load.hex()
    

    作者:Stalker_DAs

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python捕获网络报文 socket库、scapy库介绍

    发表回复