scapy模块基础使用

一、安装scapy:

完整安装,会安装ipython和scapy等模块,命令如下:

pip install --pre scapy[complete]

 

python导入scapy使用下面语句:

from scapy.all import *

 

一些工具、方法和用途:
summary() 显示一个关于每个数据包的摘要列表
nsummary() 同上,但规定了数据包数量
conversations() 显示一个会话图表
show() 显示首选表示(通常用nsummary())
filter() 返回一个lambda过滤后的数据包列表
hexdump() 返回所有数据包的一个hexdump
hexraw() 返回所以数据包Raw layer的hexdump
padding() 返回一个带填充的数据包的hexdump
nzpadding() 返回一个具有非零填充的数据包的hexdump
plot() 规划一个应用到数据包列表的lambda函数
make table() 根据lambda函数来显示表格

Route()  实例Route类,返回本机路由表

Route6()  实例化Route6类,返回本机v6路由表

arping 模拟arping函数

 

查看到目标ip的路由,返回出口网卡名、本机ip、网关ip的元组:

>>> help(Route().route)
Help on method route in module scapy.route:

route(dst=None, verbose=2) method of scapy.route.Route instance
Returns the IPv4 routes to a host.
parameters:
- dst: the IPv4 of the destination host

returns: (iface, output_ip, gateway_ip)
- iface: the interface used to connect to the host
- output_ip: the outgoing IP that will be used
- gateway_ip: the gateway IP that will be used

>>>

>>> Route().route('www.baidu.com')
('\\Device\\NPF_{C441126F-2ECC-4D58-A270-68EFE089BB09}',
'192.168.8.14',
'192.168.8.1')
>>>

对于Route类,还有增加、删除路由,以及改变网卡接口地址等功能

 

二、、功能:

1、读写报文:

 rdpcap函数、wrpcap函数

使用wrpcap()函数可以把构造报文写到文件中,

>>> pkt=Ether()/IP(dst='192.168.8.1')/TCP(dport=80)
>>> pkt.summary()
'Ether / IP / TCP 192.168.8.14:ftp_data > 192.168.8.1:http S'
>>> wrpcap(r'f:\pkt.pcap',pkt)
>>>

 

使用rdpcap()函数可以把文件中的报文读到列表中,然后对每个报文可以按协议分层访问各层字段:

>>> newpkt=rdpcap(r'f:\pkt.pcap')
>>> newpkt
<pkt.pcap: TCP:1 UDP:0 ICMP:0 Other:0>

>>> newpkt[0][IP]
<IP version=4 ihl=5 tos=0x0 len=40 id=1 flags= frag=0 ttl=64 proto=tcp chksum=0xe96f src=192.168.8.14 dst=192.168.8.1 |<TCP sport=ftp_data dport=http seq=0 ack=0 dataofs=5 reserved=0 flags=S window=8192 chksum=0xfe1e urgptr=0 |>>
>>> newpkt[0][IP].src

'192.168.8.14'
>>> newpkt[0][IP].dst
'192.168.8.1'

 

2、抓包:

(1) 抓包用sniff函数,函数定义:sniff(filter:str,count:int,iface:str,prn:func)更多更纤细的参数看help(sniff).

其中参数prn为回调函数,每抓到一个符合过滤器规则的包就以其为参数传入执行一次prn函数,通常用lambda写prn;

参数filter,和wireshark的capture filter规则一样。

>>> ping=sniff(filter="icmp",iface='WLAN',count=5,prn=lambda pkt:pkt.summary())
Ether / IP / ICMP 192.168.8.14 > 192.168.8.1 echo-request 0 / Raw
Ether / IP / ICMP 192.168.8.1 > 192.168.8.14 echo-reply 0 / Raw
Ether / IP / ICMP 192.168.8.14 > 192.168.8.1 echo-request 0 / Raw
Ether / IP / ICMP 192.168.8.1 > 192.168.8.14 echo-reply 0 / Raw
Ether / IP / ICMP 192.168.8.14 > 192.168.8.1 echo-request 0 / Raw
>>>
>>> ping[0].summary()
'Ether / IP / ICMP 192.168.8.14 > 192.168.8.1 echo-request 0 / Raw'
>>> ping[4].summary()
'Ether / IP / ICMP 192.168.8.14 > 192.168.8.1 echo-request 0 / Raw'
>>>

 

(2)也可以离线抓包,就是从文件中读取报文,功能和rdpcap相同,不过对匹配的报文可以过滤,调用回调函数,控制更灵活:

sniff(offline = "hw.pcap");

 

(3)sprintf()方法可以控制输出信息,该方法是scapy自己定义的替换控制符,替换变量用%括起来,如下:

sniff(prn=lambda pkt:pkt.sprintf("{IP:%IP.src% -> %IP.dst%\n}{Raw:%Raw.load%\n}"))

 

3、构造报文:

(1)报文根据协议层次构造,使用”/”堆加层次,注意字母大小写,如:

>>> ipPacket = IP()/TCP()
>>> ipPacket
<IP frag=0 proto=tcp |<TCP |>>
>>> ipPacket.summary()
'IP / TCP 127.0.0.1:ftp_data > 127.0.0.1:http S'
>>>

每个层次具有的协议字段可以通过ls命令查看如:

 

构造报文时可以根据自己的需要设置协议的某些字段,如:

 ipPacket = IP(dst='8.8.8.8')/TCP(dport=53)

 

上面是构造单个报文,可以利用通配符、元组和列表等方式一次构造一系列报文,

如果用元组,则表示范围(包含边界值),如下:

>>> ipPackets = IP(dst='192.168.8.1')/TCP(sport=(10000,10005), dport=80)
>>> list(ipPackets)
[<IP frag=0 proto=tcp dst=192.168.8.1 |<TCP sport=10000 dport=http |>>,
<IP frag=0 proto=tcp dst=192.168.8.1 |<TCP sport=10001 dport=http |>>,
<IP frag=0 proto=tcp dst=192.168.8.1 |<TCP sport=10002 dport=http |>>,
<IP frag=0 proto=tcp dst=192.168.8.1 |<TCP sport=10003 dport=http |>>,
<IP frag=0 proto=tcp dst=192.168.8.1 |<TCP sport=10004 dport=http |>>,
<IP frag=0 proto=tcp dst=192.168.8.1 |<TCP sport=10005 dport=http |>>]
>>>

>>> send(ipPackets)
......
Sent 6 packets.
>>>

>>> ipPackets = IP(dst='192.168.8.1')/TCP(sport=range(10000,10005), dport=80)
>>> list(ipPackets)
[<IP frag=0 proto=tcp dst=192.168.8.1 |<TCP sport=10000 dport=http |>>,
<IP frag=0 proto=tcp dst=192.168.8.1 |<TCP sport=10001 dport=http |>>,
<IP frag=0 proto=tcp dst=192.168.8.1 |<TCP sport=10002 dport=http |>>,
<IP frag=0 proto=tcp dst=192.168.8.1 |<TCP sport=10003 dport=http |>>,
<IP frag=0 proto=tcp dst=192.168.8.1 |<TCP sport=10004 dport=http |>>]
>>>

如果用列表,则表示用列表中的每个元素构建,如下:

>>> ipPackets = IP(dst='192.168.8.1')/TCP(sport=[10000,10005], dport=80)
>>> list(ipPackets)
[<IP frag=0 proto=tcp dst=192.168.8.1 |<TCP sport=10000 dport=http |>>,
<IP frag=0 proto=tcp dst=192.168.8.1 |<TCP sport=10005 dport=http |>>]
>>>

>>> send(ipPackets)
..
Sent 2 packets.
>>>

 

也可以混合使用,如:

>>> ipPackets = IP(dst='192.168.8.1')/TCP(sport=[10000,(2000,2002),10005], dport=80)
>>> list(ipPackets)
[<IP frag=0 proto=tcp dst=192.168.8.1 |<TCP sport=10000 dport=http |>>,
<IP frag=0 proto=tcp dst=192.168.8.1 |<TCP sport=2000 dport=http |>>,
<IP frag=0 proto=tcp dst=192.168.8.1 |<TCP sport=2001 dport=http |>>,
<IP frag=0 proto=tcp dst=192.168.8.1 |<TCP sport=2002 dport=http |>>,
<IP frag=0 proto=tcp dst=192.168.8.1 |<TCP sport=10005 dport=http |>>]
>>>

 

对于ip地址访问,可以使用列表方式指定,也可以指定掩码方式构造一系列不同报文,这对于构造扫描报文很有用,如:

>>> ipPackets = IP(dst=['192.168.8.1','192.168.8.2'])/TCP(sport=10000, dport=80)
>>> list(ipPackets)
[<IP frag=0 proto=tcp dst=192.168.8.1 |<TCP sport=10000 dport=http |>>,
<IP frag=0 proto=tcp dst=192.168.8.2 |<TCP sport=10000 dport=http |>>]
>>>

>>> ipPackets = IP(dst='192.168.8.1/30')/TCP(sport=10000, dport=80)
>>> list(ipPackets)
[<IP frag=0 proto=tcp dst=192.168.8.0 |<TCP sport=10000 dport=http |>>,
<IP frag=0 proto=tcp dst=192.168.8.1 |<TCP sport=10000 dport=http |>>,
<IP frag=0 proto=tcp dst=192.168.8.2 |<TCP sport=10000 dport=http |>>,
<IP frag=0 proto=tcp dst=192.168.8.3 |<TCP sport=10000 dport=http |>>]
>>>

ip地址也可以使用域名,scapy会自动通过Net函数进行地址解析:

>>> ipPackets = IP(dst='www.sina.com.cn/30')/TCP(sport=10000, dport=80)

>>> ipPackets
<IP frag=0 proto=tcp dst=Net("www.sina.com.cn/30") |<TCP sport=10000 dport=http |>>
>>> list(ipPackets)
[<IP frag=0 proto=tcp dst=175.153.178.216 |<TCP sport=10000 dport=http |>>,
<IP frag=0 proto=tcp dst=175.153.178.217 |<TCP sport=10000 dport=http |>>,
<IP frag=0 proto=tcp dst=175.153.178.218 |<TCP sport=10000 dport=http |>>,
<IP frag=0 proto=tcp dst=175.153.178.219 |<TCP sport=10000 dport=http |>>]
>>>

 注意:Net函数不在接收通配符*和范围操作符-,因此IP的dst和src不能再用dst=‘192.168.8.1-254’或dst=‘192.168.1.*’进行表示了。

 

堆叠数据层,构建系列数据包:

>>> a=IP(ttl=[1,2,3])
>>> b=TCP(dport=(20,21))
>>> ab=a/b
>>> list(ab)
[<IP frag=0 ttl=1 proto=tcp |<TCP dport=ftp_data |>>,
<IP frag=0 ttl=1 proto=tcp |<TCP dport=ftp |>>,
<IP frag=0 ttl=2 proto=tcp |<TCP dport=ftp_data |>>,
<IP frag=0 ttl=2 proto=tcp |<TCP dport=ftp |>>,
<IP frag=0 ttl=3 proto=tcp |<TCP dport=ftp_data |>>,
<IP frag=0 ttl=3 proto=tcp |<TCP dport=ftp |>>]
>>>

 

(2)报文的查看:

针对报文,可以查看概要信息,分层字段信息,16进制偏移格式等,

summary(),show(),hexdump()

 

 

 

>>> hexdump(ipPacket)
0000 45 00 00 28 00 01 00 00 40 06 E9 6F C0 A8 08 0E E..(....@..o....
0010 C0 A8 08 01 27 10 00 50 00 00 00 00 00 00 00 00 ....'..P........
0020 50 02 20 00 D7 22 00 00 P. .."..
>>>

 

(3)查看报文有指定的协议层使用haslayer函数,获取指定层使用getlayer函数,如下:

>>> ipPacket.haslayer(TCP)
True
>>> tcp=ipPacket.getlayer(TCP)
>>> tcp
<TCP sport=10000 dport=http |>
>>> tcp.sport
10000
>>>

 

4、发送和接收报文:

sendp,send,srp,sr,srp1,sr1,srloop等函数

数字1表示只发送1个报文,带有字母p表示发送构造了二层帧的报文,没有字母p的函数表示发送三层报文,有字母r表示发送报文后还会接收报文。

没有数字1表示可以发送1个或多个报文(报文列表)

>>> pkt=IP(dst='192.168.8.1')/TCP(sport=RandNum(30000,30010),dport=80,flags='S')

>>> send(pkt)
.
Sent 1 packets.
>>>
>>> send([pkt,pkt])
..
Sent 2 packets.


>>> send(pkt *10)
..........
Sent 10 packets.
>>>

 

 

发送tcp/udp报文:

>>>sr1( IP(dst='192.168.8.1')/TCP(sport=50000,dst=80,flags='S'))

报文构造中,很多地方都要用到随机数,scapy也定义了相应随机函数,如RandInt(),RandShort(), RandNum()和Fuzz()等用来生成随机数。

RandInt(),顾名思义,产生一个32位随机整数,通常用在tcp的seq或ack中:

>>> int(RandInt())
3023505333

 

RandShort(),产生一个16位随机整数,范围1~65535,通常用在tcp/udp的sport或dport中。

>>> int(RandShort())

53905

 

 

RandNum(),可以生成指定范围内的一个随机数

>>> int(RandNum(3000,4000))
3709

pkt=IP(dst='192.168.8.1')/TCP(sport=RandNum(30000,30010),dport=80,flags='S')

 

 

fuzz(),通常情况下,如果你构建tcp层没有指定sport或dport时,发包时会自动使用知名端口如20,80。这可能并不是你想要的。

而fuzz()函数能够在忘记写sport或dport情况下,自动帮你随机生成sport或dport

如下:

 

 

 

循环发送报文: 

srloop(pkts, *args, **kargs)
Send a packet at layer 3 in loop and print the answer each time
srloop(pkts, [prn], [inter], [count], ...) --> None

srloop(IP(dst="192.168.8.1")/TCP(),count=2,inter=0.5)

 

发送dns查询报文,注意里面的RR等构造需要借助DNSQR等方法:

>>> p = sr1(IP(dst="8.8.8.8") / UDP() / DNS(qdcount=1,qd=DNSQR(qclass=1, qtype=1, qname='www.maipu.com')))
Begin emission:
Finished sending 1 packets.
...*
Received 4 packets, got 1 answers, remaining 0 packets
>>>

>>> qr=DNSQR(qclass=1, qtype=1, qname='www.baidu.com')
>>> qr
<DNSQR qname='www.maipu.com' qtype=A qclass=IN |>
>>> qr.show()
###[ DNS Question Record ]###
qname = 'www.baidu.com'
qtype = A
qclass = IN

>>>

 

注意:对于sr之类的同时收发报文的函数,返回的一个2元组,结构类似为 ( ((s,r),(s,r),...),(p1,p2,...) ),其第一个元素是由每个发送报文和响应报文一对一构成元组的元组,第2个报文为剩下的发送报文(没有收到响应报文)构成的元组。因此,获取收到的报文通常由ans,unans=sr();s,r=ans方式进行解包获取到列表或进行遍历。




 

posted @ 2023-04-02 16:03  梦想与现实边缘  阅读(2101)  评论(0编辑  收藏  举报