scapy – python 可自由组包
参考学习:初识Scapy–Python的Scapy/Kamene模块学习之路
scapy 介绍
Scapy是基于Python语言的网络报文处理程序,它可以让用户发送、嗅探、解析、以及伪造网络报文,运用Scapy可以进行网路侦测、端口扫描、路由追踪、以及网络攻击。Kamene是Scapy的分支,一开始的Scapy并不支持Python 3,为了支持Python 3就诞生了Scapy3k这个分支,为了方便区分就把Scapy3k更名为了Kamene。注意:最新的Scapy是支持Python 3的(Scapy 2.4.0+)。
Scapy 官网: https://scapy.net/
Scapy Pypi包主页: https://pypi.org/project/scapy/
:::info
官方指导文档:https://scapy.readthedocs.io/en/latest/
:::
如何选择Python和Scapy的版本?
这是Scapy版本的兼容列表:
scapy 安装
sudo apt install python3-scapy
运行 scapy 后,会进入到 python 的环境
sudo scapy
这就是一个Python shell,所以可以用exit()退出它。
scapy 的使用
ping = IP(dst="http://www.baidu.com") / ICMP()
Scapy中的斜杠’ / ‘就是用来区分网络层次的,一般来说把底层的协议写在左边,把高层的协议写在右边,就像下面这样:
数据链路层协议 / 网络层协议 / 传输层协议 / 应用层协议及数据
下面是TCP/IP协议族关系表:
一般来说我们不需要关心数据链路层的协议Scapy会为我们自动配置。
Scapy定义了很多函数和类,通过这些函数和类我们就可以构造数据包。这里的IP可以用来生成一个IP的报文头。
IP()
ping = IP(dst="http://www.baidu.com")
有基础的朋友可能会说:“这明明是域名,不是IP地址!“。对,这确实是域名,但是Scapy功能非常强大,当你把dst赋值为域名时Scapy会自动调用一个名为Net的函数来解析域名。
list()
还可以指定掩码位数:
list(Net('http://www.baidu.com/30'))
然后我们再来看看ICMP协议:
ICMP(互联网控制消息协议)是互联网协议族的核心协议之一。它用于TCP/IP网络中发送控制消息,提供可能发生在通信环境中的各种问题反馈,通过这些信息,使管理者可以对所发生的问题作出诊断,然后采取适当的措施解决(维基百科)。ping就是基于ICMP协议的。
ICMP()
生成一个ICMP报文头:
ICMP只有5个字段这些字段分别的意思是:
- type:ICMP的类型,标识生成的错误报文。
- code:进一步划分ICMP的类型,该字段用来查找产生错误的原因。例如,ICMP的目标不可达类型可以把这个位设为1至15等来表示不同的意思。
- chksum:校验码部分,这个字段包含有从ICMP报头和数据部分计算得来的,用于检查错误的数据,其中此校验码字段的值视为0。
- id:这个字段包含了ID值,在Echo Reply类型的消息中要返回这个字段。
- Seq:这个字段包含一个序号,同样要在Echo Reply类型的消息中要返回这个字段。
以下是ICMP type code的对应表:
一般来说只用记住这2个,type为8、code为0是ping的请求,type为0、code为0是ping的响应。
知道这些之后我们就可以构造一个ICMP请求报文头。注意Scapy会为每个报文设定默认值,调用ICMP函数默认就是构造一个ICMP请求,所以下面两条是等价的:
ICMP()
ICMP(type=8, code=0)
也可以查看对象的type,code属性值
ls(<协议名>)
通过ls函数就可以查看默认值
也可以同时查看实际值和默认值
ls(IP(dst="http://www.baidu.com"))
没有括号的是实际值,有括号的是默认值
_.show()
show()方法获取报文字段信息(在Python shell中下划线’_’表示上条语句执行结果)
长度(len),校验和(chksum)、协议(proto)等字段Scapy都会自动计算,所以我们只有3个字段需要注意一下,ttl、src、dst。
- ttl: 生存时间值(Time To Live),当数据包每进行进行一次转发时TLL值就会减一,TTL为0时数据包就会被丢弃,TTL的主要作用是避免IP包在网络中的无限循环和收发,节省了网络资源,并能使IP包的发送者能收到告警消息(路由追踪原理)。
- src: 源地址(Source Address)数据发送者的地址
- dst: 目的地址(Destination Address)数据接收者的地址
现在我们再回头看刚刚构造的ping请求包就应该可以明白其中的意思了:
ping = IP(dst="192.168.40.1") / ICMP()
注意: 为了方便演示我把地址换成了自己的网关
ICMP函数首先构造一个ping请求报文头,然后IP函数构造一个IP报文头设置目标为’192.168.40.1’,最后赋值给变量ping。
构造完数据包之后那我们就来发送数据包。
resultpkt = sr1(ping)
sr1函数发送一个数据包,再接收一个数据包,然后返回响应的数据包。发送成功会显示形如下面信息。
Received 3 packets意思是: 收到了3个包
got 1 answers意思是: 得到了一个应答。也就是我们需要的包。
rermaining 0 packets意思是: 剩余没有应答得包。
直接在命令行输入变量名就可以输出接收到的包信息了。
好了得到回显数据之后就可以通过一些方法来访问其中的数据了。通过下标访问每个报文数据:
这里可能有一点难理解,简单解释一下就是:对于IP协议来说ICMP报文头和Padding实际上就是一堆数据,当我们获取IP报文时它会把IP报文头和数据一起输出。对于获取ICMP报文也是如此,输出ICMP报文头和Padding数据**(报文实际上就是报文头加数据)**。
通过属性的形式获取字段
resultpkt[0].src
resultpkt[1].type
可以加下标也可以不加,但是推荐加上。例如我们想访问ICMP的校验和(chksum),不加标访问的是IP的校验和加下标[1]才可以访问ICMP的校验和
通过fields属性以字典的形式获取报文头
获取IP报文头:
resultpkt[0].fields
获取ICMP报文头 :
resultpkt[1].fields
获取Padding数据:
resultpkt[2].fields
既然是字典就可以通过键获取对应的值。
获取IP报文头的源地址:
resultpkt[0].fields[‘src’]
获取IP报文头的目标地址:
resultpkt[0].fields[‘dst’]
获取ICMP报文头的type值和code值:
resultpkt[0].fields[‘type’]
resultpkt[0].fields[‘code’]
还有一种高级的方法,通过协议下标来访问报文头:
就是把数字下标改为了对应协议的下标功能和数字下标基本一样,但是用协议下标可以增加程序的灵活性和可读性。
Scapy数据包收发机制
Scapy数据包发送函数:
- sr1: 发送一个数据包并接收一个相匹配的数据包
- srp1: 与sr1一样但是关注数据链路层(以后讲ARP协议时会用)
- sr: 发送数据包并接收相匹配的数据包
- srp: 与sr一样但是关注数据链路层(以后讲ARP协议时会用)
- send: 只发送数据包不接收数据包
- sendp: 与send一样但是关注数据链路层(以后讲ARP协议时会用)
下图就是Scapy sr函数发收流程图:
通过实例理解一下
生成一组数据包然后通过sr函数发送,timeout参数设置超时时间默认单位为秒,这里设置超时时间为10秒钟。
result = sr(IP(dst="10.72.1.0/24") / ICMP(), timeout=10)
直接输入变量名或用type函数就可以看到返回的数据是个元组
通过下标访问results中的数据
上图通过result[0],获取有响应的包的列表,用len函数获取长度,用display方法来展示数据。
result[0]里的每个素都是元组,每个元组都是由发送的包和它相匹配的应答包组成。
分别访问发送的包和接收的包
对于单个数据包的操作就和就前面的一样了
通过result[1]访问没有相应的请求数据表列表,里面的元素就是单个数据包。
scapy 实例
发送一个 RA 包
在Linux中,发送RA(Router Advertisement)包通常需要使用 scapy 这样的工具,它是一个强大的Python库,用于构建和发送网络数据包。以下是使用 scapy 发送RA包的命令示例:
sudo scapy
然后在 scapy 命令行中,你可以使用以下代码来构造和发送RA包:
from scapy.all import *
# 构造RA包
ra_packet = Ether(dst="00:C0:02:12:35:8A") / IPv6(dst="ff02::1") / ICMPv6ND_RA()
# 发送RA包
sendp(ra_packet, iface="你的网卡接口名")
在上述代码中:
- Ether(dst=“00:C0:02:12:35:8A”) 指定了目的MAC地址。
- IPv6(dst=“ff02::1”) 指定了IPv6的多播地址。
- ICMPv6ND_RA() 构造了一个RA包。
请确保替换代码中的 “00:C0:02:12:35:8A” 为你的目的MAC地址,以及 “你的网卡接口名” 为你要发送数据包的网络接口名称(如 eth0、wlan0 等)。
发送二层数据链路层包【MAC】
from scapy.all import Ether, sendp
# 构造以太网帧
eth_frame = Ether(dst="00:C0:02:12:35:89")
data = "hello mantic"
# 发送以太网帧
sendp(eth_frame/data, iface="你的网络接口名")
发送 IP 包【IP】
使用scapy发包工具,从192.168.3.2 的电脑发送一个二层协议包到IP是192.168.3.1,mac地址是00:C0:02:12:35:89的开发板,包的内容是hello mantic
from scapy.all import Ether, IP, sendp
# 构造以太网帧,指定目的 MAC 地址和源 MAC 地址
eth_frame = Ether(dst="00:C0:02:12:35:89")
# 构造 IP 数据包,指定目的 IP 地址和源 IP 地址
ip_packet = IP(dst="192.168.3.1", src="192.168.3.2")
# 自定义内容
data = "hello mantic"
# 构造数据帧
packet = eth_frame/ip_packet/data
# 发送数据帧
sendp(packet, iface="你的网络接口名")
开发一个ping扫描工具
下面代码的Git仓库地址: https://github.com/starunity/Sc
#!/usr/bin/env python3
from scapy.all import *
def pingscan(ip):
"""
pingscan(ip)
Ping the incoming IP.
ip: Pass in an IP like 192.168.1.0 or 192.168.1.0/24
"""
answer, uanswer = sr( \
IP(dst=ip) / ICMP(), \
timeout=10, verbose=False \
)
alive = []
for send, recv in answer:
if recv[ICMP].type == recv[ICMP].code == 0:
alive.append(recv[IP].src)
return alive
if __name__ == '__main__':
ip = input("Enter IP address:")
result = pingscan(ip)
for i in result:
print("{} is alive.".format(i))
第13行的sr函数里面的verbose参数是用来开启或关闭发收包详情的
verbose默认开启的效果:
verbose关闭的效果:
执行结果如下:
总结
scapy是一个很好用的发包工具,不过在使用中,是比较麻烦的,要自己组包需要写代码完成,不如hping3,下期介绍。