学习自知乎大佬 ——弈心——网络工程师的Python之路—Scapy基础篇
复现了一波
scapy安装:
pip install scapy
导入scapy方式:
from scapy.all import *
然后是环境配置
kali的IP地址为192.168.43.245,使用的网卡为eth0
物理机的IP地址为192.168.43.1,就是kali的网关
进入scapy,这里我是在kali2020里面进行发送scapy构造的数据包,然后在本机用wireshark进行抓包接收。
物理机监听kali使用的这块网卡
然后是scapy的一些基本用法
除了ls()外,还可以用lsc()函数来查看scapy的指令集(函数)
这里还可以用使用ls()的携带参数模式,比如ls(IP)来查看IP包的各种默认参数。
实验目的:使用IP()函数构造一个目的地址为192.168.2.11(即拓扑中的交换机S1)的IP报文,然后用send()函数将该IP报文发送给S1,在S1上开启debug ip packet以验证是否收到该报文。
首先用IP()函数构造一个目的地址为192.168.43.1的IP报文,将其实例化给ip这个变量
ls(ip)查看这个报文的内容
然后用send()将这个数据包发送给物理机,send()函数只发送不接受
可以看到物理机已经接收到了这个ip报文
实验目的:除了send()外,scapy还有个sendp()函数,两者的区别是前者是发送三层报文,后者则是发送二层报文,实验2将演示如何用sendp()来构造二层报文。
除了send()外,scapy还有个sendp()函数,两者的区别是前者是发送三层报文,后者则是发送二层报文,实验2将演示如何用sendp()来构造二层报文。
先构造一个arp报文,如下
arp = Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(hwsrc="00:0c:29:ef:29:24",psrc="192.168.43.245",pdst="192.168.43.1")/'abc'
这里我们构造了一个源MAC地址为00:0c:29:ef:29:24, 源IP地址为192.168.43.245,,目标IP地址为192.168.43.1,payload为abc的ARP报文。
然后sendp()发送出去
sendp(arp)
可以看到物理机接收到了来自kali的arp数据包,询问192.168.43.1的mac地址,并且物理机也把自己的mac地址返回给了kali
问题:为什么目的MAC地址设置为FF.FF.FF.FF.FF.FF
?
我们以主机A(192.168.1.5)向主机B(192.168.1.1)发送数据为例。当发送数据时,主机A会在自己的ARP缓存表中寻找是否有目标IP地址。如果找到了,也就知道了目标MAC地址,直接把目标MAC地址写入帧里面发送就可以了;如果在ARP缓存表中没有找到相对应的IP地址,主机A就会在网络上发送一个广播,目标MAC地址是“FF.FF.FF.FF.FF.FF”,这表示向同一网段内的所有主机发出这样的询问:“192.168.1.1的MAC地址是什么?”网络上其他主机并不响应ARP询问,只有主机B接收到这个帧时,才向主机A做出这样的回应:“192.168.1.1的MAC地址是00-aa-00-62-c6-09”。这样,主机A就知道了主机B的MAC地址,它就可以向主机B发送信息了。同时它还更新了自己的ARP缓存表,下次再向主机B发送信息时,直接从ARP缓存表里查找就可以了。ARP缓存表采用了老化机制,在一段时间内如果表中的某一行没有使用,就会被删除,这样可以大大减少ARP缓存表的长度,加快查询速度。
在局域网里抓了一会儿包,目的地址果然都是FF.FF.FF.FF.FF.FF
,FF.FF.FF.FF.FF.FF就代表广播broadcast
实验目的:从实验1和实验2的例子可以看出:send()和sendp()函数只能发送报文,而不能接收返回的报文。如果要想查看返回的3层报文,需要用到sr()函数,实验3将演示如何使用sr()函数。
先构造一个目的地址为物理机的ICMP()包。可以看到返回的结果是一个tuple(元组),该元组里的元素是两个列表,其中一个列表叫Results(响应),另一个叫Unanswered(未响应)。
我们可以将sr()函数返回的元组里的两个元素分别赋值给两个变量,第一个变量叫ans,对应Results(响应)这个元素,第二个变量叫unans,对应Unanswered(未响应)这个元素。
wireshark捕获到了发送给物理机的icmp包和物理机的应答包
实验目的:实验3讲到了sr(),它是用来接收返回的3层报文。实验4将使用srp()来接收返回的2层报文。
用srp()配合Ether()和ARP()构造一个arp报文,二层目的地址为ff:ff:ff:ff:ff:ff,三层目的地址为192.168.2.0/24, 因为我们是向整个/24网络发送arp, 耗时会很长,所以这里用timeout = 5,表示将整个过程限制在5秒钟之内完成
ans, unans = srp(Ether(dst = "ff:ff:ff:ff:ff:ff") / ARP(pdst = "192.168.43.0/24"), timeout = 5)
wireshark捕获到了从192.168.43.1到192.168.43.254的整个c段的arp包,因为只有192.168.43.1(物理机)、192.168.43.2(dns服务器)和192.168.43.245(kali),所以只会收到三个应答包
下面用ans.summary()来具体看看到底是哪3个IP响应了我们的’who has’类型的arp报文。
ans.summary()
用unans.summary()来查看那些没有给予我们’who has’类型arp报文回复的IP地址
可以看到询问其他IP的’who has’类型arp报文没有人响应。我们可以用这个方法来写一个python判断存活主机的脚本,向局域网整个段发送arp、icmp请求,如果收到回应即为存活,否则为不存活。可以参考这篇博客:使用python的scapy和nmap模块进行主机存活探测
实验目的:使用tcp()函数构造四层报文,理解和应用RandShort(),RandNum()和Fuzz()函数。
用nmap来扫描一下物理机开启了哪些端口,这里就用8082端口来测试
在scapy上使用ip()和tcp()函数来构造一个目的地IP为192.168.43.1(物理机),源端口为30,目的端口为8082的TCP SYN报文。
ans, unans = sr(IP(dst = "192.168.43.1") / TCP(sport = 30, dport = 8082, flags = "S"))
wireshark捕获到了三次tcp握手
TCP端口号除了手动指定外,还可以使用RandShort(), RandNum()和Fuzz()这几个函数来让scapy帮你自动生成一个随机的端口号,通常可以用作sport(源端口号)。
首先来看RandShort(),RandShort()会在1-65535的范围内随机生成一个TCP端口号,将上面的sport = 30 替换成 sport = RandShort()即可使用。
ans, unans = sr(IP(dst = "192.168.43.1") / TCP(sport = RandShort(), dport = 8082, flags = "S")/"test")
这里可以看到RandShort()替我们随机生成了53780这个TCP源端口号
如果你想指定scapy生成端口号的范围,可以使用RandNum(),比如你只想在1000-1500这个范围内生成端口号,可以使用RandNum(1000,1500)来指定,举例如下:
ans, unans = sr(IP(dst = "192.168.43.1") / TCP(sport = RandNum(666,888), dport = 8082, flags = "S")/"test")
tcp中标记位flags有如下几种
S
(SYN), F
(FIN), P
(PUSH), R
(RST), W
(ECN CWR) , E
(ECN-Echo) , .
(no flags)
我们可以利用TCP三次握手原理进行主机存活的探测,当向目标主机直接发送ACK数据包时,如果目标主机存活就会返回一个RST数据包以终止这个不正常的TCP连接。其工作原理主要依据目标主机响应数据包中flags字段,如果flags字段有值则表示主机存活,该字段通常包括:SYN, FIN, ACK, PSH, RST, URG六种类型。SYN表示建立连接,FIN表示关闭连接,ACK表示应答,PSH表示包含DATA数据传输,RST表示连接重置,USG表示紧急指针。
使用python的scapy和nmap模块进行主机存活探测
扫描端口也是同理,当发送端的TCP SYN包发出后,大致会有下面四种情况发生:
因为用惯了python3所以改成了python3的,并且改为了探测全端口,代码如下
from scapy.all import *
target = '192.168.43.1'
ans, unans = sr(IP(dst=target) / TCP(sport=RandShort(), dport=[i for i in range(1, 65535)], flags="S"), timeout=5)
for sent, received in ans:
if received.haslayer(TCP) and str(received[TCP].flags) == "SA":
print("Port " + str(sent[TCP].dport) + " of " + target + " is OPEN!")
elif received.haslayer(TCP) and str(received[TCP].flags) == "RA":
print("Port " + str(sent[TCP].dport) + " of " + target + " is closed!")
elif received.haslayer(ICMP) and str(received[ICMP].type) == "3":
print("Port " + str(sent[TCP].dport) + " of " + target + " is filtered!")
for sent in unans:
print(str(sent[TCP].dport) + " is filtered!")
物理机用wireshark抓到了kali发送的目的端口为从1到65535的tcp数据包
并且open的端口会返回一个SYN+ACK数据包