Skip to content

Latest commit

 

History

History
357 lines (250 loc) · 12.7 KB

File metadata and controls

357 lines (250 loc) · 12.7 KB

七、Python 网络嗅探

在本章中,我们将介绍以下配方:

  • Python 中的包嗅探器
  • 解析数据包
  • 皮沙克

介绍

嗅探器是一个可以截获网络流量并嗅探数据包以进行分析的程序。当数据流流经网络时,嗅探器可以捕获每个数据包,解码数据包的原始数据以获得数据包头中各个字段的值,并根据适当的规范分析其内容。网络数据包嗅探器可以用 Python 编写。

Python 中的包嗅探器

可以使用 HelpSocket 模块在 Python 中创建一个简单的数据包嗅探器。我们可以使用原始套接字类型来获取数据包。原始套接字提供对支持套接字抽象的底层协议的访问。由于原始套接字是 internet 套接字 API 的一部分,因此它们只能用于生成和接收 IP 数据包。

准备

由于套接字模块的某些行为取决于操作系统套接字 API,并且在不同的操作系统下使用原始套接字没有统一的 API,因此我们需要使用 Linux 操作系统来运行此脚本。因此,如果您使用的是 Windows 或 macOS,请确保在虚拟 Linux 环境中运行此脚本。此外,大多数操作系统需要根访问才能使用原始套接字 API。

怎么做。。。

以下是使用socket模块创建基本数据包嗅探器的步骤:

  1. 创建一个名为basic-packet-sniffer-linux.py的新文件,并在编辑器中打开它。
  2. 导入所需的模块:
import socket 
  1. 现在我们可以创建一个INET原始套接字:
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP) 

读取和写入原始套接字都需要首先创建原始套接字。这里我们使用的是INET系列原始插座。套接字的族参数描述套接字的地址族。以下是地址族常量:

传递的下一个参数是套接字的类型。以下是套接字类型的可能值:

最后一个参数是数据包的协议。该协议编号由互联网分配号码管理局IANA定义)。我们必须意识到插座的家族性;那么我们只能选择一个协议。由于我们选择了AF_INET(IPV4),我们只能选择基于 IP 的协议。

  1. 接下来,启动无限循环以从套接字接收数据:
while True: 
  print(s.recvfrom(65565)) 

socket 模块中的recvfrom方法帮助我们接收来自 socket 的所有数据。传递的参数是缓冲区大小;65565是最大缓冲区大小。

  1. 现在使用 Python 运行程序:
sudo python3 basic-packet-sniffer-linux.py 

结果如下:

解析数据包

现在,我们可以尝试解析我们嗅探到的数据,并解压头。要解析数据包,我们需要了解以太网帧和 IP 的数据包头。

以太网帧结构如下:

前六个字节用于目的 MAC地址,后六个字节用于源 MAC。最后两个字节用于乙醚类型。其余包括数据CRC 校验和。根据 RFC 791,IP 报头如下所示:

IP 标头包括以下部分:

  • 协议版本(四位):前四位。这表示当前的 IP 协议。
  • 报头长度(四位):IP 报头的长度用 32 位字表示。由于该字段为 4 位,因此允许的最大标头长度为 60 字节。通常值为5,表示五个 32 位字:54=20 字节*。
  • 服务类型(八位):前三位为优先位,后四位为服务类型,最后一位未使用。
  • 总长度(16 位):表示 IP 数据报的总长度(字节)。这是一个 16 位字段。IP 数据报的最大大小为 65535 字节。
  • 标志(三位):第二位代表不分段位。当设置此位时,IP 数据报从不分段。第三位代表更多片段位。如果设置了该位,则它表示一个碎片化的 IP 数据报,该数据报后面有更多的碎片。
  • 生存时间(八位):该值表示 IP 数据报在被丢弃之前将经过的跃点数。
  • 协议(八位):表示将数据传递到 IP 层的传输层协议。
  • 报头校验和(16 位):此字段有助于检查 IP 数据报的完整性。
  • 源和目标 IP(每个 32 位):这些字段分别存储源和目标地址。

有关 IP 头的更多详细信息,请参阅 RFC 791 文档:https://tools.ietf.org/html/rfc791

怎么做。。。

以下是解析数据包的步骤:

  1. 创建一个名为basic-parse-packet-packet-linux.py的新文件,并导入解析数据包所需的模块:
from struct import * 
import sys 
  1. 现在我们可以创建一个函数来解析以太网报头:
def ethernet_head(raw_data): 
    dest, src, prototype = struct.unpack('! 6s 6s H', raw_data[:14])  
    dest_mac = get_mac_addr(dest) 
    src_mac = get_mac_addr(src) 
    proto = socket.htons(prototype) 
    data = raw_data[14:] 
    return dest_mac, src_mac, proto, data  

这里我们使用struct模块中的unpack方法来解包头文件。从以太网帧结构来看,前六个字节用于目标 MAC,后六个字节用于源 MAC,最后一个无符号短字符用于以太类型。最后,剩下的是数据。因此,该函数返回目标 MAC、源 MAC、协议和数据。

  1. 现在我们可以创建一个 main 函数,并在ethernet_head()中解析该函数,获取详细信息:
def main(): 
    s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.ntohs(3))  
    while True: 
        raw_data, addr = s.recvfrom(65535) 
        eth = ethernet(raw_data) 
        print('\nEthernet Frame:') 
        print('Destination: {}, Source: {}, Protocol: {}'.format(eth[0], eth[1], eth[2])) 

main() 
  1. 现在,我们可以检查以太网帧中的数据部分并解析 IP 头。我们可以创建另一个函数来解析ipv4头:
def ipv4_head(raw_data): 
    version_header_length = raw_data[0] 
    version = version_header_length >> 4 
    header_length = (version_header_length & 15) * 4 
    ttl, proto, src, target = struct.unpack('! 8x B B 2x 4s 4s', raw_data[:20]) 
    data = raw_data[header_length:] 
    return version, header_length, ttl, proto, src, target, data 

根据 IP 报头,我们将使用struct中的unpack方法解包报头,并返回versionheader_lentgthttl、协议源和目的 IP。

  1. 现在更新main()以打印 IP 头:
def main(): 
    s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.ntohs(3))  
    while True: 
        raw_data, addr = s.recvfrom(65535) 
        eth = ethernet(raw_data) 
        print('\nEthernet Frame:') 
        print('Destination: {}, Source: {}, Protocol: {}'.format(eth[0], eth[1], eth[2]))  
        if eth[2] == 8: 
            ipv4 = ipv4(ethp[4]) 
            print( '\t - ' + 'IPv4 Packet:') 
            print('\t\t - ' + 'Version: {}, Header Length: {}, TTL:{},'.format(ipv4[1], ipv4[2], ipv4[3])) 
            print('\t\t - ' + 'Protocol: {}, Source: {}, Target: {}'.format(ipv4[4], ipv4[5], ipv4[6])) 
  1. 目前,打印的 IP 地址不是可读格式,因此我们可以编写一个函数对其进行格式化:
def get_ip(addr): 
    return '.'.join(map(str, addr)) 

在返回输出之前,确保通过添加以下行更新ipv4_head函数以格式化 IP 地址:

src = get_ip(src) 
target = get_ip(target) 
  1. 现在我们已经解包了 internet 层,下一个需要解包的层是传输层。我们可以根据 IP 报头中的协议 ID 确定协议。以下是一些协议的协议 ID:

    • TCP:6
    • ICMP:1
    • UDP:17
    • RDP27
  2. 接下来,我们可以创建一个函数来解压缩 TCP 数据包:

def tcp_head( raw_data): 
    (src_port, dest_port, sequence, acknowledgment, offset_reserved_flags) = struct.unpack( 
        '! H H L L H', raw_data[:14]) 
    offset = (offset_reserved_flags >> 12) * 4 
    flag_urg = (offset_reserved_flags & 32) >> 5 
    flag_ack = (offset_reserved_flags & 16) >> 4 
    flag_psh = (offset_reserved_flags & 8) >> 3 
    flag_rst = (offset_reserved_flags & 4) >> 2 
    flag_syn = (offset_reserved_flags & 2) >> 1 
    flag_fin = offset_reserved_flags & 1 
    data = raw_data[offset:] 
    return src_port, dest_port, sequence, acknowledgment, flag_urg, flag_ack, flag_psh, flag_rst, flag_syn, flag_fin, data 

TCP 数据包根据 TCP 数据包头的结构进行解包:

  1. 现在我们可以更新main()来打印 TCP 报头的详细信息。在ipv4部分内添加以下行:
if ipv4[4] == 6:  
    tcp = tcp_head(ipv4[7]) 
    print(TAB_1 + 'TCP Segment:') 
    print(TAB_2 + 'Source Port: {}, Destination Port: {}'.format(tcp[0], tcp[1])) 
    print(TAB_2 + 'Sequence: {}, Acknowledgment: {}'.format(tcp[2], tcp[3])) 
    print(TAB_2 + 'Flags:') 
    print(TAB_3 + 'URG: {}, ACK: {}, PSH:{}'.format(tcp[4], tcp[5], tcp[6])) 
    print(TAB_3 + 'RST: {}, SYN: {}, FIN:{}'.format(tcp[7], tcp[8], tcp[9]))  
    if len(tcp[10]) > 0: 
         # HTTP 
        if tcp[0] == 80 or tcp[1] == 80: 
             print(TAB_2 + 'HTTP Data:') 
                 try: 
                    http = HTTP(tcp[10]) 
                    http_info = str(http[10]).split('\n') 
                    for line in http_info: 
                       print(DATA_TAB_3 + str(line)) 
                 except: 
                       print(format_multi_line(DATA_TAB_3, tcp[10])) 
                 else: 
                      print(TAB_2 + 'TCP Data:') 
                      print(format_multi_line(DATA_TAB_3, tcp[10])) 
  1. 同样,更新函数以解压缩 UDP 和 ICMP 数据包。

根据分组报头结构对分组进行解包。以下是 ICMP 的数据包头结构:

根据图表,我们可以使用以下代码解包:

elif ipv4[4] == 1: 
    icmp = icmp_head(ipv4[7]) 
    print('\t -' + 'ICMP Packet:') 
    print('\t\t -' + 'Type: {}, Code: {}, Checksum:{},'.format(icmp[0], icmp[1], icmp[2])) 
    print('\t\t -' + 'ICMP Data:') 
    print(format_multi_line('\t\t\t', icmp[3])) 

以下是 UDP 的数据包头结构:

正如我们对 ICMP 所做的那样,我们可以按如下方式解压缩 UDP 数据包头:

elif ipv4[4] == 17: 
    udp = udp_head(ipv4[7]) 
    print('\t -' + 'UDP Segment:') 
    print('\t\t -' + 'Source Port: {}, Destination Port: {}, Length: {}'.format(udp[0], udp[1], udp[2])) 

现在以所需权限保存并运行脚本:

sudo python3 basic-parse-packet-linux.py  

输出将打印嗅探到的所有数据包。因此,它将继续打印,直到我们通过键盘中断停止它。输出结果如下:

皮沙克

PyShark 是 Wireshark CLI(TShark)的包装器,因此我们可以在 PyShark 中使用所有 Wireshark 解码器。我们可以使用 PyShark 嗅探接口,也可以分析pcap文件。

准备

使用此模块时,请确保在您的系统上安装 Wireshark,并使用pip命令安装pyshark

pip3 install pyshark  

另外,请确保您的计算机上安装了 TShark。TShark 是基于终端的 Wireshark,PyShark 将其用于数据包捕获功能。

在这里了解更多关于 TShark 的信息:https://www.wireshark.org/docs/wsug_html_chunked/AppToolstshark.html

怎么做。。。

让我们用一些例子试试 PyShark。确保在系统中安装 TShark。

  1. 为了更好地理解,我们可以使用 Python 交互终端并浏览 PyShark 的函数。请注意,这些命令也可以包含在脚本中。唯一的依赖项是 TShark。
  2. 导入pyshark模块:
>>> import pyshark 
  1. 现在将pcap文件加载到pyshark
>>> cap = pyshark.FileCapture('sample.pcap') 

我们可以使用以下命令从实时界面进行嗅探:

 >>> cap = pyshark.LiveCapture(interface='wlp3s0b1')
                  >>> cap.sniff(timeout=3)

这将在接下来的 3 秒钟内嗅探接口

  1. 现在您可以从cap变量获取数据包详细信息。

要打印出第一个数据包的详细信息,我们可以使用以下命令:

>>> print(cap[0]) 

输出结果如下:

您可以通过dir()查看所有可能的选项:

>>> print(dir(cap[0])) 

我们可以使用pprint模块以漂亮的格式查看它们:

>>> import pprint 
>>> pprint.pprint(dir(cap[0])) 

这将在 PyShark 中打印数据包的所有可能选项。输出结果如下:

  1. 您可以按如下方式遍历每个数据包:
for pkt in cap: print(pkt.highest_layer)

  1. 我们可以得到过滤后的数据包流到pyshark,如下所示:
cap = pyshark.LiveCapture(interface='en0', bpf_filter='ip and tcp port 80')  
cap.sniff(timeout=5) 

这将过滤掉除 TCP/IP 到端口80之外的数据包