本系列文章译自thePacketGeek的系列文章。原创翻译,转载请注明出处。

目前在这一系列文章中我们已经了解了如何捕获数据包和使用 capture 对象,我们终于到了有趣的部分,开始对数据包进行操作了!

当我们捕获了数据包后,它们以 packet 对象列表的形式存储在 capture 对象中。这些 packet 对象的方法和属性使我们能够访问数据包头以及包的负载信息。在之前的文章中提到过,我们可以使用 only_summaries 参数来控制每个数据包保存的信息量。

数据包摘要属性

在捕获时将 only_summaries 设置为 True 会使得不管捕获的数据包的内容是何种协议, packet 对象都具有固定的属性集。其中最有用的属性值有:

1
2
3
4
>>> cap = pyshark.FileCapture('test.pcap', only_summaries=True)
>>>
>>> dir(cap[0])
['delta', 'destination', 'info', 'ip id', 'length', 'no', 'protocol', 'source', 'stream', 'summary_line', 'time', 'window']
  • delta : 当前数据包和上一个数据包捕获时间的差值。
  • destination : IP层的目标地址。
  • info :应用层数据的简短摘要(比如”HTTP GET /resource_folder/page.html”)。
  • ip id : IP标识符字段。
  • length : 以字节表示的数据包长度。
  • no : 数据包在列表中的索引值。
  • protocol : 数据包中识别出的最高层级的协议。(译注:HTTP数据包如果是JSON的数据,此处可能是JSON而非HTTP)
  • source : IP层的源地址。
  • stream : 索引值,标识出该数据包属于哪一个TCP流(仅用于TCP数据包)。
  • summary_line : 将所有的摘要属性输出在一个tab分隔的字符串中。
  • time : 当前数据包到达时间与第一个数据包的差值。
  • window : TCP的窗口大小(仅用于TCP数据包)。

利用这些内容可以做很多事情,打印出数据包摘要只是一个开始!利用这些数据可以做出很棒的可视化图表来展示IP会话、带宽使用、协议以及应用的性能指标(比如TCP数据流中的RTT值)。这都是很有用的分析,还有别的吗?

完整的数据包属性

如果你不仅想从捕获的数据包中获取摘要信息,那么好好看看这部分吧。使用Wireshark和tshark内建的解析器,PyShark可以将数据包的所有细节按层次分解。
比如我们先来深入研究一下DNS数据包,看一下数据包所具有的属性。

1
2
3
4
5
6
7
8
9
10
>>> cap = pyshark.LiveCapture(interface='en0', bpf_filter='udp port 53')
>>> cap.sniff(packet_count=50)
>>> dns_1 = cap[0]
>>> dns_2 = cap[1]
>>> dns_1. #(tab auto-complete)
dns_1.captured_length dns_1.highest_layer dns_1.length dns_1.transport_layer
dns_1.dns dns_1.interface_captured dns_1.pretty_print dns_1.udp
dns_1.eth dns_1.ip dns_1.sniff_time
dns_1.frame_info dns_1.layers dns_1.sniff_timestamp

这其中有一些普通的数据包信息属性,比如lengthframe_info,以及time,还有pretty_print()方法用于以可读性较强的方式显示数据包(类似于Wireshark的详细信息视图)。
如果你仔细看的话能够发现直接制定层次名的属性(ethip),还有会根据数据包内的协议而变动的属性(transport_layerhighest_layer)。
如果你要寻找特定类型的数据流量,这些属性可以使得寻找感兴趣的信息变得很简单。
比如下面的脚本会打印出所有的DNS查询和响应:

1
2
3
4
5
6
7
8
9
10
11
12
13
import pyshark
cap = pyshark.LiveCapture(interface='en0', bpf_filter='udp port 53')
cap.sniff(packet_count=10)
def print_dns_info(pkt):
if pkt.dns.qry_name:
print 'DNS Request from %s: %s' % (pkt.ip.src, pkt.dns.qry_name)
elif pkt.dns.resp_name:
print 'DNS Response from %s: %s' % (pkt.ip.src, pkt.dns.resp_name)
cap.apply_on_packets(print_dns_info, timeout=100)

会给出如下的结果:

1
2
3
4
5
6
DNS Request from 10.10.10.40: apple.com
DNS Request from 10.10.10.1: apple.com
DNS Request from 10.10.10.40: ipv6.icanhazip.com
DNS Request from 10.10.10.1: ipv6.icanhazip.com
DNS Request from 10.10.10.40: ipv4.icanhazip.com
DNS Request from 10.10.10.1: ipv4.icanhazip.com

动态的层引用

使用上面提到的动态变化的层属性(比如transport_layerhighest_layer)让我们在分析数据包时更灵活。
如果你对每个数据包都试图访问pkt.dns.qry_resp属性,那么如果这个数据包不是DNS数据包就会返回AttributeError异常。传输层也有类似的问题,因为有TCP和UDP两种可能。我们可以使用动态引用的层属性来获取源地址和目的地址,然后使用try/except来处理既不是TCP也不是UDP数据包的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pyshark
cap = pyshark.FileCapture('test.pcap')
def print_conversation_header(pkt):
try:
protocol = pkt.transport_layer
src_addr = pkt.ip.src
src_port = pkt[pkt.transport_layer].srcport
dst_addr = pkt.ip.dst
dst_port = pkt[pkt.transport_layer].dstport
print '%s %s:%s --> %s:%s' % (protocol, src_addr, src_port, dst_addr, dst_port)
except AttributeError as e:
#ignore packets that aren't TCP/UDP or IPv4
pass
cap.apply_on_packets(print_conversation_header, timeout=100)

该脚本会输出:

1
2
3
4
5
6
7
UDP 10.10.10.12:51554 --> 239.255.255.250:1900
UDP 10.10.10.12:51554 --> 239.255.255.250:1900
UDP 10.10.10.15:58803 --> 8.8.8.8:53
UDP 8.8.8.8:53 --> 10.10.10.15:58803
TCP 10.10.10.15:58632 --> 192.168.20.197:80
TCP 192.168.20.197:80 --> 10.10.10.15:58632
TCP 10.10.10.15:58632 --> 192.168.20.197:80

无限的可能

从这几个简单的例子当中我们可以看出,PyShark使我们能够轻松的访问所有的数据包细节。
在分类和处理多种不同协议的时候,可以使用条件语句来创造动态的逻辑,你也可以寻找具有特定属性的数据包来筛选特定类型的数据流量(当然要注意处理AttributeError)。

希望你喜欢这一系列文章。如果你对PyShark的应用实例感兴趣,你可以看看我的Cloud-Pcap项目


上一篇:PyShark入门(3):capture对象