Scapy是一个 Python程序,它允许用户发送、嗅探、分析和伪造网络包。这种能力允许构建能够探测、扫描或攻击网络的工具。
换句话说,Scapy是一个强大的交互式包操作程序。它能够伪造或解码大量协议的数据包,在网络上发送它们,捕获它们,匹配请求和响应,等等。Scapy可以轻松地处理大多数经典任务,如扫描、跟踪、探测、单元测试、攻击或网络发现。它可以代替hping、arpsoof、arp-sk、arping、p0f甚至Nmap、tcpdump和tshark的某些部分。

scapy还可以很好地执行许多其他工具无法处理的其他特定任务,例如发送无效帧、注入自己的802.11帧、结合技术(VLAN跳跃+ARP缓存中毒、WEP加密通道上的VoIP解码等)。
这个想法很简单。scapy主要做两件事:发送数据包和接收答案。定义一组数据包,发送它们,接收答案,将请求与答案匹配,并返回数据包对(请求、应答)列表和不匹配数据包列表。与nmap或hping等工具相比,这有一个很大的优势,即答案不会减少到(打开/关闭/过滤),而是整个包。
除此之外,还可以构建更多的高级功能,例如,跟踪路由并仅给出请求的起始TTL和应答的源IP。一种能发出整个网络信号并给出应答机列表的设备。一个执行portscan并返回一个 Latex 报告的函数。
参考文档:介绍 — Scapy 2.4.4. 文档
pip3 install scapy
send
- 在第3层发送数据包(Scapy创建第2层标头),不接收任何数据包。
>>> send(IP(dst='8.8.8.8')/TCP(dport=53, flags='S')) . Sent 1 packets. >>> >>> send(IP(dst='8.8.8.8')/TCP(dport=53, flags='S'), count=10) .......... Sent 10 packets. >>> >>> send(IP(dst='8.8.8.8')/TCP(dport=53, flags='S'), loop=1) ......................... [... snipped ...] Sent 1503 packets.
sendp
>>> sendp(Ether()/IP(dst="1.2.3.4",ttl=(1,4)), iface="eth0") .... Sent 4 packets. >>> sendp("I’m travelling on Ethernet", iface="eth0", loop=1, inter=0.2) >>> sendp(rdpcap("/tmp/pcapfile")) # tcpreplay ........... Sent 11 packets.
sr
>>> sr(IP(dst="60.205.177.168")/TCP(dport=[21,22,23]))
Begin emission: Finished sending 3 packets. ...**...............................^C Received 36 packets, got 2 answers, remaining 1 packets (
sr1
>>> p=sr1(IP(dst="www.baidu.com")/ICMP()/"asdqwe")
Begin emission: Finished sending 1 packets. .* Received 2 packets, got 1 answers, remaining 0 packets
srloop
>>> packet = IP(dst='60.205.177.168')/ICMP()
>>> srloop(packet)
RECV 1: IP / ICMP 60.205.177.168 > 172.17.51.80 echo-reply 0 RECV 1: IP / ICMP 60.205.177.168 > 172.17.51.80 echo-reply 0 RECV 1: IP / ICMP 60.205.177.168 > 172.17.51.80 echo-reply 0 RECV 1: IP / ICMP 60.205.177.168 > 172.17.51.80 echo-reply 0 ^C Sent 4 packets, received 4 packets. 100.0% hits. (
在一行中创建数据包
>>> packet = Ether()/IP(dst='8.8.8.8')/TCP(dport=53,flags='S')
分别创建每个图层并使用'/'运算符将它们堆叠
>>> l2 = Ether()
>>> l3 = IP(dst='8.8.8.8/30')
>>> l4 = TCP(dport=53, flags = 'S')
>>> packet = l2/l3/l4
Scapy IP表示法
Scapy接受普通的IP表示法,CIDR表示法,主机名。
>>> packet = IP(dst = '8.8.8.8')
>>> packet = IP(dst = 'scanme.nmap.org')
>>> packet = IP(dst = '8.8.8.8/30')
>>> [a for a in packet] [
创建一组数据包
我们可以使用Scapy创建一组数据包
>>> pkts = IP(ttl=[1,3,5,(7,10)])/TCP() >>> [pkt for pkt in pkts] [>, >, >, >, >, >, >] >>> packet=IP(dst="192.168.*.1-10")/TCP(dport=(0,100)) >>> [a for a in packet] [ >, >, >, >, >, >, >, >, >, >, >, >, >, >, >, >, >, >, >, >, >, >, >, >, >, >, >, >, >, >, >, ...
获取数据包的详细说明以及数据类型
>>> packet = IP()/TCP()
>>> ls(packet)
version : BitField = 4 (4)
ihl : BitField = None (None)
tos : XByteField = 0 (0)
len : ShortField = None (None)
id : ShortField = 1 (1)
flags : FlagsField = 0 (0)
frag : BitField = 0 (0)
ttl : ByteField = 64 (64)
proto : ByteEnumField = 6 (0)
chksum : XShortField = None (None)
src : Emph = '127.0.0.1' (None)
dst : Emph = '127.0.0.1' ('127.0.0.1')
options : PacketListField = [] ([])
[-- snipped --]
show
显示详细的包头
>>> packet.show()
###[ IP ]###
version= 4
ihl= None
tos= 0x0
len= None
id= 1
flags=
frag= 0
ttl= 64
proto= tcp
chksum= None
src= 127.0.0.1
dst= 127.0.0.1
\options\
###[ TCP ]###
sport= ftp_data
dport= http
seq= 0
ack= 0
dataofs= None
reserved= 0
flags= S
window= 8192
chksum= None
urgptr= 0
options= []
show2
与show()类似,但可以组装数据包并计算校验和和IHL(报头长度,最小值是5)。
>>> packet.show2()
###[ IP ]###
version= 4
ihl= 5
tos= 0x0
len= 40
id= 1
flags=
frag= 0
ttl= 64
proto= tcp
chksum= 0x7ccd
src= 127.0.0.1
dst= 127.0.0.1
\options\
###[ TCP ]###
sport= ftp_data
dport= http
seq= 0
ack= 0
dataofs= 5
reserved= 0
flags= S
window= 8192
chksum= 0x917c
urgptr= 0
options= []
summary
显示数据包的简短的摘要
>>> packet.summary() 'IP / TCP 127.0.0.1:ftp_data > 127.0.0.1:http S'
与数据包内部的字段进行交互
>>> Ether(dst="d8:55:a3:fe:80:78")/IP(dst="8.8.8.8")> >>> packet=_ >>> packet.dst 'd8:55:a3:fe:80:78' >>> packet[IP].dst '8.8.8.8'
检查数据包中是否存在层
haslayer方法
>>> if packet.haslayer(IP): ...: print (packet[IP].dst) ...: 8.8.8.8
使用in构造
>>> pkt = IP()/TCP()/DNS() >>> DNS in pkt True
Scapy的sprintf
>>> packet.sprintf("Ethernet source is %Ether.src% and IP proto is %IP.proto%")
'Ethernet source is 00:16:3e:0c:d1:ad and IP proto is tcp'
>>> a.sprintf("%dst% %IP.dst% vlan=%Dot1Q.vlan%")
'00:00:d4:ae:3f:71 192.168.0.1 vlan=42'
>>>
>>>a.sprintf(" %TCP.flags% | %5s,TCP.flags% | %#05xr,TCP.flags%")
' RA | RA | 0x014'
数据包处理程序
我们可以使用lambda函数编写处理TCP数据包的数据包处理程序,但该功能仅适用于TCP数据包。
>>> f=lambda x:x.sprintf("%IP.dst%:%TCP.dport%")
>>> f(IP(dst="8.8.8.8")/TCP())
'8.8.8.8:http'
>>> f(IP(dst="8.8.8.8")/UDP())
'8.8.8.8:??'
还可以使用sprintf()中的条件子字符串来实现处理其它层的目的。条件子字符串仅在数据包中存在某个层时才触发,否则将被忽略。还可以!用于检查是否缺少图层。条件子字符串格式: {[!]层:子字符串}
>>> f=lambda x: x.sprintf("=> {IP:ip=%IP.dst% {UDP:dport=%UDP.dport%}\
...: ... {TCP:%TCP.dport%/%TCP.flags%}{ICMP:type=%r,ICMP.type%}}\
...: ... {!IP:not an IP packet}")
>>> f(IP()/TCP())
'=> ip=127.0.0.1 http/S'
>>> f(IP()/UDP())
'=> ip=127.0.0.1 dport=domain'
>>> f(IP()/ICMP())
'=> ip=127.0.0.1 type=8'
>>> f(Ether()/ARP())
'=> not an IP packet'
PCAP格式
从PCAP文件导入数据包。
pkts = rdpcap("temp.cap")
pkts = sniff(offline="temp.cap")
将数据包导出到pcap文件。
wrpcap("temp.cap",pkts)
十六进制转储格式
十六进制字符串
还可以使用str()函数将整个数据包转换为十六进制字符串
Base64
>>> export_object(s) b'eNprYEouTk4sqNTLSaxMLSrWyzHici3JSC3iKmTQDCpk1EiOT85PSU0u5krNAzG4Cpki7BkYGA7PCD20+PC+Qw0VDGJ2PIcnHlrLweDKwKDBwMjA4MB2qDvu0BpB4wAOIGAQYQhggIIAJgWGQwt4GRgKmSPYgPycxJLMPMNClrZC1qBCNnfHGxoeDcsdkv2AoKSQPUkPALURLMU=' >>> new_pkt = import_object
Sniff()
>>> sniff(count=4, iface='eth0')
可以添加过滤以捕获需要的数据包,使用标准的tcpdump / libpcap语法:
>>> pkts = sniff(count=1,filter="tcp and host 60.205.177.168 and port 80") >>> pkts.summary() Ether / IP / TCP 172.17.51.80:54578 > 60.205.177.168:http S
>>> pkts = sniff(count=5,filter="host 60.205.177.168",prn=lambda x:x.summary()) Ether / IP / TCP 172.17.51.80:54624 > 60.205.177.168:http S Ether / IP / TCP 60.205.177.168:54624 > 172.17.51.80:http S Ether / IP / TCP 172.17.51.80:http > 60.205.177.168:54624 SA Ether / IP / TCP 60.205.177.168:http > 172.17.51.80:54624 SA Ether / IP / TCP 172.17.51.80:54624 > 60.205.177.168:http A
pkts = sniff(offline='test.pcap') >>> pkts.nsummary() 0000 Ether / IP / TCP 172.16.16.128:1606 > 74.125.95.104:http S 0001 Ether / IP / TCP 74.125.95.104:http > 172.16.16.128:1606 SA 0002 Ether / IP / TCP 172.16.16.128:1606 > 74.125.95.104:http A 0003 Ether / IP / TCP 172.16.16.128:1606 > 74.125.95.104:http PA / Raw 0004 Ether / IP / TCP 74.125.95.104:http > 172.16.16.128:1606 A / Padding >>> sniff(offline='test.pcap', lfilter = lambda s: s[TCP].flags == 18, prn = lambda x: x[IP].dst) 192.168.1.1
# encoding: utf-8
from scapy.all import *
import threading
import sys
def dealwith():
print '开始抓包'
# 下面的iface是电脑网卡的名称 count是捕获报文的数目
dpkt = sniff(iface="Qualcomm Atheros AR956x Wireless Network Adapter", count=1000) # 抓包 # prn=lambda x: x.show(),
# pkts = sniff(prn=lambda x : x.sprintf("{IP:%IP.src% -> %IP.dst%\n}{Raw:%Raw.load%\n}"))
print ( '抓包成功')
wrpcap("c://demo.pcap", dpkt)
print ('所抓的包已经保存')
pcks = rdpcap('c://demo.pcap')
print ('开始解析pcap包')
# 输出重定向 讲在控制台的输出重定向到 txt文本文件中
output = sys.stdout
outputfile = open('c://capture.txt', 'w')
sys.stdout = outputfile
zArp = 0
zIcmp = 0
ipNum = set()
for p in pcks:
status1 = p.payload.name # 可能是ARP的报文
status2 = p.payload.payload.name # 可能是TCP报文 也可能是ICMP的报文
# p.show() 输出报文, 在符合的情况下
if status1 == 'IP':
ipNum.add(p.payload.src) # 将ip报文的源地址,和目的地址存在set集合里面(set去重)
ipNum.add(p.payload.dst)
p.show()
print ('')
else:
if status1 == 'ARP':
p.show()
print ('')
zArp += 1
if status2 == 'ICMP':
p.show()
print ('')
zIcmp += 1
print ('IP:' + str(len(ipNum)) + ' ARP:' + str(zArp) + ' ICMP:' + str(zIcmp) )# 报文数量的输出
outputfile.close()
sys.stdout = output # 恢复到控制台输出
print ( '输出结束')
print( dpkt)
dealwith() # 运行报文捕获函数