scapy 是一个python编写的功能强大的网络数据包操作库,可以仿造,捕获和解析大量不同协议类型的数据包。
可参考 http://scapy.readthedocs.io/en/latest/usage.html
注意事项:
安装 graphviz 和 ImageMagick
>>> yum install graphviz
>>> pip install graphviz
>>> yum install ImageMagick
p = rdpcap('test.cap') # 手册中使用了 readpcap 接口没有定义;
p.conversations(type='jpg', target="> test.jpg")
详细请参考 http://blog.csdn.net/chirebingxue/article/details/50393755
使用 scapy,可以很容易的捕获特定数据包,达到 tcpdump 和 tshark 在数据捕获方面的效果。可以按照端口,捕获一个或多个端口的数据包。如果不给定端口,默认会捕获所有端口的数据。
sniff(count=0, store=1, offline=None, prn=None, lfilter=None, L2socket=None, timeout=None, opened_socket=None, stop_filter=None, iface=None, *arg, **karg)
返回PacketList类型的数据包对象;
count: 要捕获数据包的总数. 0 表示无限制;
store: 是否要保存捕获的数据包;
prn: 回调函数,会作用于每个数据包
ex: prn = lambda x: x.summary()
lfilter: 过滤函数,不满足条件的数据包会被丢弃;
ex: lfilter = lambda x: x.haslayer(Padding)
offline: 从pcap文件中读取数据包;
timeout: 捕获指定时间内的数据包;
L2socket: 通过给定的 L2socket 进行数据捕获;
opened_socket: 通过给定的 socket 进行数据捕获;
stop_filter: 过滤函数,满足条件后将结束数据捕获;
ex: stop_filter = lambda x: x.haslayer(TCP)
iface: 指定端口或端口数组
注意: lfiter 是回调函数,filter 是BPF 字符串,不要混淆!
sniff 接口源码: /python/site-packages/scapy/sendrecv.py
可阅读源码理解sniff的工作逻辑。
sniff 使用示例:
>>> sniff(filter="icmp and host 66.35.250.151",count=2)
>>> sniff(iface="wifi0",prn=lambdax:x.summary())
>>> sniff(iface="eth1",prn=lambdax:x.show())
>>> sniff(iface=["eth1","eth2"],prn=lambdax:x.sniffed_on+": "+x.summary())
>>> sniff(filter="icmp and host 66.35.250.151", count=2)
>>> pkts = sniff(offline="temp.cap")
使用自定义回调函数;
#! /usr/bin/env python
from scapy.all import *
def arp_monitor_callback(pkt):
if ARP in pkt and pkt[ARP].op in (1,2): #who-has or is-at
return pkt.sprintf("%ARP.hwsrc% %ARP.psrc%")
sniff(prn=arp_monitor_callback, filter="arp", store=0)
filter 的规则使用 Berkeley Packet Filter(BPF)语法!
过滤规则有三种类型的限定词,分别为 type,dir,和proto
1 type: 可以是host,net,port
host foo
net 128.3
port 20
用以限定——主机,网络,端口
2 dir 方向限定词:src,dst
’src foo’, ’dst net 128.3’, ’src or dst port ftp-data’
限定数据流的方向;src 192.168.10.11,表示所有从主机192.168.10.11发出的数据包。
3 proto 协议限定词:ether,fddi,ip,arp,rarp,decnet,tcp,udp等等
’ether src foo’, ’arp net 128.3’, ’tcp port 21’
4 逻辑连接符: and(&&), or(|), not(!)
详细请参考 http://www.cnblogs.com/JohnABC/p/5914543.html
PacketList类源码: /python/site-packages/scapy/plist.py
>>> pkts = sniff(filter='ip src host 200.200.200.44', count=10)
>>> pkts.summary()
Ether / IP / UDP / DNS Qry "15.9.0.20.in-addr.arpa."
Ether / IP / UDP / DNS Qry "15.9.0.20.in-addr.arpa."
Ether / IP / UDP / DNS Qry "15.9.0.20.in-addr.arpa."
Ether / IP / UDP / DNS Qry "15.9.0.20.in-addr.arpa."
Ether / IP / UDP / DNS Qry "15.9.0.20.in-addr.arpa."
Ether / IP / UDP / DNS Qry "15.9.0.20.in-addr.arpa."
Ether / IP / UDP / DNS Qry "www.baidu.com."
Ether / IP / ICMP 200.200.200.44 > 61.135.169.125 echo-request 0 / Raw
Ether / IP / UDP / DNS Qry "125.169.135.61.in-addr.arpa."
Ether / IP / ICMP 200.200.200.44 > 61.135.169.125 echo-request 0 / Raw
如上所示,pkts是一个PacketList对象,pkts.res 是一个由packet组成的list。在对数据包组进行遍历时,会用到。
summary 接口:
91 def summary(self, prn=None, lfilter=None):
92 """prints a summary of each packet
93 prn: function to apply to each packet instead of lambda x:x.summary()
94 lfilter: truth function to apply to each packet to decide whether it will be displayed"""
95 for r in self.res:
96 if lfilter is not None:
97 if not lfilter(r):
98 continue
99 if prn is None:
100 print self._elt2sum(r)
101 else:
102 print prn(r)
默认会调用私有方法 _elt2sum() 打印出包信息,也可以自定义回调处理函数,summary会打印自定义函数的返回结果,也可以添加过滤函数。
filter() : 根据返回过滤后的数据包 list。
make_table(): 将数据包信息按照定义的格式打印为数据表;
>>> ans.make_table( lambda (s,r): (s.dst, s.ttl, r.src) )
216.15.189.192 216.15.189.193 216.15.189.194 216.15.189.195
1 192.168.8.1 192.168.8.1 192.168.8.1 192.168.8.1
2 81.57.239.254 81.57.239.254 81.57.239.254 81.57.239.254
3 213.228.4.254 213.228.4.254 213.228.4.254 213.228.4.254
4 213.228.3.3 213.228.3.3 213.228.3.3 213.228.3.3
5 193.251.254.1 193.251.251.69 193.251.254.1 193.251.251.69
6 193.251.241.174 193.251.241.178 193.251.241.174 193.251.241.178
conversations():绘制捕获后数据的会话图;
284 def conversations(self, getsrcdst=None,**kargs):
285 """Graphes a conversations between sources and destinations and display it
286 (using graphviz and imagemagick)
287 getsrcdst: a function that takes an element of the list and
288 returns the source, the destination and optionally
289 a label. By default, returns the IP source and
290 destination from IP and ARP layers
291 type: output type (svg, ps, gif, jpg, etc.), passed to dot's "-T" option
292 target: filename or redirect. Defaults pipe to Imagemagick's display program
293 prog: which graphviz program to use"""
294 if getsrcdst is None:
295 def getsrcdst(pkt):
296 if 'IP' in pkt:
297 return (pkt['IP'].src, pkt['IP'].dst)
298 if 'ARP' in pkt:
299 return (pkt['ARP'].psrc, pkt['ARP'].pdst)
300 raise TypeError()
301 conv = {}
302 for p in self.res:
303 p = self._elt2pkt(p)
304 try:
305 c = getsrcdst(p)
306 except:
307 # No warning here: it's OK that getsrcdst() raises an
308 # exception, since it might be, for example, a
309 # function that expects a specific layer in each
310 # packet. The try/except approach is faster and
311 # considered more Pythonic than adding tests.
312 continue
313 if len(c) == 3:
314 conv.setdefault(c[:2], set()).add(c[2])
315 else:
316 conv[c] = conv.get(c, 0) + 1
317 gr = 'digraph "conv" {\n'
318 for (s, d), l in conv.iteritems():
319 gr += '\t "%s" -> "%s" [label="%s"]\n' % (
320 s, d, ', '.join(str(x) for x in l) if isinstance(l, set) else l
321 )
322 gr += "}\n"
323 return do_graph(gr, **kargs)
getsrcdst 是提取会话的源点与目的点的函数,如上所示默认情况是使用以下函数:
294 if getsrcdst is None:
295 def getsrcdst(pkt):
296 if 'IP' in pkt:
297 return (pkt['IP'].src, pkt['IP'].dst)
298 if 'ARP' in pkt:
299 return (pkt['ARP'].psrc, pkt['ARP'].pdst)
300 raise TypeError()
以 IP 源地址与目的地址为结果!graphviz的详细介绍请参考:
http://blog.csdn.net/chirebingxue/article/details/50393755