在本章中,我们将介绍以下配方:
- Python 中的包嗅探器
- 解析数据包
- 皮沙克
嗅探器是一个可以截获网络流量并嗅探数据包以进行分析的程序。当数据流流经网络时,嗅探器可以捕获每个数据包,解码数据包的原始数据以获得数据包头中各个字段的值,并根据适当的规范分析其内容。网络数据包嗅探器可以用 Python 编写。
可以使用 HelpSocket 模块在 Python 中创建一个简单的数据包嗅探器。我们可以使用原始套接字类型来获取数据包。原始套接字提供对支持套接字抽象的底层协议的访问。由于原始套接字是 internet 套接字 API 的一部分,因此它们只能用于生成和接收 IP 数据包。
由于套接字模块的某些行为取决于操作系统套接字 API,并且在不同的操作系统下使用原始套接字没有统一的 API,因此我们需要使用 Linux 操作系统来运行此脚本。因此,如果您使用的是 Windows 或 macOS,请确保在虚拟 Linux 环境中运行此脚本。此外,大多数操作系统需要根访问才能使用原始套接字 API。
以下是使用socket
模块创建基本数据包嗅探器的步骤:
- 创建一个名为
basic-packet-sniffer-linux.py
的新文件,并在编辑器中打开它。 - 导入所需的模块:
import socket
- 现在我们可以创建一个
INET
原始套接字:
s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)
读取和写入原始套接字都需要首先创建原始套接字。这里我们使用的是INET
系列原始插座。套接字的族参数描述套接字的地址族。以下是地址族常量:
传递的下一个参数是套接字的类型。以下是套接字类型的可能值:
最后一个参数是数据包的协议。该协议编号由互联网分配号码管理局(IANA定义)。我们必须意识到插座的家族性;那么我们只能选择一个协议。由于我们选择了AF_INET
(IPV4),我们只能选择基于 IP 的协议。
- 接下来,启动无限循环以从套接字接收数据:
while True:
print(s.recvfrom(65565))
socket 模块中的recvfrom
方法帮助我们接收来自 socket 的所有数据。传递的参数是缓冲区大小;65565
是最大缓冲区大小。
- 现在使用 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
以下是解析数据包的步骤:
- 创建一个名为
basic-parse-packet-packet-linux.py
的新文件,并导入解析数据包所需的模块:
from struct import *
import sys
- 现在我们可以创建一个函数来解析以太网报头:
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、协议和数据。
- 现在我们可以创建一个 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()
- 现在,我们可以检查以太网帧中的数据部分并解析 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
方法解包报头,并返回version
、header_lentgth
、ttl
、协议源和目的 IP。
- 现在更新
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]))
- 目前,打印的 IP 地址不是可读格式,因此我们可以编写一个函数对其进行格式化:
def get_ip(addr):
return '.'.join(map(str, addr))
在返回输出之前,确保通过添加以下行更新ipv4_head
函数以格式化 IP 地址:
src = get_ip(src)
target = get_ip(target)
-
现在我们已经解包了 internet 层,下一个需要解包的层是传输层。我们可以根据 IP 报头中的协议 ID 确定协议。以下是一些协议的协议 ID:
- TCP:6
- ICMP:1
- UDP:17
- RDP27
-
接下来,我们可以创建一个函数来解压缩 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 数据包头的结构进行解包:
- 现在我们可以更新
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]))
- 同样,更新函数以解压缩 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。
- 为了更好地理解,我们可以使用 Python 交互终端并浏览 PyShark 的函数。请注意,这些命令也可以包含在脚本中。唯一的依赖项是 TShark。
- 导入
pyshark
模块:
>>> import pyshark
- 现在将
pcap
文件加载到pyshark
:
>>> cap = pyshark.FileCapture('sample.pcap')
我们可以使用以下命令从实时界面进行嗅探:
>>> cap = pyshark.LiveCapture(interface='wlp3s0b1')
>>> cap.sniff(timeout=3)
这将在接下来的 3 秒钟内嗅探接口
- 现在您可以从
cap
变量获取数据包详细信息。
要打印出第一个数据包的详细信息,我们可以使用以下命令:
>>> print(cap[0])
输出结果如下:
您可以通过dir()
查看所有可能的选项:
>>> print(dir(cap[0]))
我们可以使用pprint
模块以漂亮的格式查看它们:
>>> import pprint
>>> pprint.pprint(dir(cap[0]))
这将在 PyShark 中打印数据包的所有可能选项。输出结果如下:
- 您可以按如下方式遍历每个数据包:
for pkt in cap: print(pkt.highest_layer)
- 我们可以得到过滤后的数据包流到
pyshark
,如下所示:
cap = pyshark.LiveCapture(interface='en0', bpf_filter='ip and tcp port 80')
cap.sniff(timeout=5)
这将过滤掉除 TCP/IP 到端口80
之外的数据包