pcap⽂件的python解析实例
分享⼀下我⽼师⼤神的⼈⼯智能教程!零基础,通俗易懂!
也欢迎⼤家转载本篇⽂章。分享知识,造福⼈民,实现我们中华民族伟⼤复兴!
最近⼀直在分析数据包。
同时也⼀直想学python。
凑⼀块⼉了...于是,便开⼯了。座椅爆炸!
正⽂
⾸先要说的是,我知道python有很多解析pcap⽂件的库,这⾥不使⽤它们的原因是为了理解pcap⽂件的格式细节。使⽤tcpdump你可以很容易抓取到⼀系列的数据包,然⽽tcpdump并没有分析数据包的功能,如果想从这个抓包⽂件中分析出⼀些端倪,⽐如重传情况,你必须使⽤wireshark之类的软件,⽤wireshark打开tcpdump抓取的pcap⽂件,如果你看到了⼀堆堆的深红⾊(类似静脉⾎管⾥流出的猪⾎的颜⾊)的数据包,那么这些包⼀定是“在协议层看来”异常的数据包,包括但不限于重传,乱序等等,欲知详情,请在wireshark的过滤器中敲进去“tcp.analysis.”然后就会⾃动补全,这⼀切简直⽅便到极点。如果你还想看⼀
些全局的统计数据,那么请点击”统计“菜单的第⼀个”捕获⽂件属性“,你会看到更多的信息。虽然数据包早就已经过去,但是雁过留声,我们通过抓取的数据包,还是可以得到更多的信息,多谢有wireshark/tshark(⼀个字符界⾯的pcap⽂件分析⼯具,类似wireshark,但更适合玩机械键盘的命令⾏粉们使
⽤)/shookshark(...)这些⼯具,使得我们真实能够分析pcap⽂件以获取信息。
然⽽,这些我觉得还不够。
有⼀个简单的需求,我想得到在⼀个TCP连接中,⼀个端节点⼀共发送了多少字节的TCP载荷数据,包括正常发送以及重传。我没有在wireshark中到得到这个数据的功能,于是我迫不及待⾃⼰写⼀个。厨师还怕没⾁吃吗?
但有个前提,那就是我必须搞明⽩pcap⽂件的格式,因为我想裸分析pcap⽂件,试图出每⼀个感兴趣数据包的TCP载荷(不包括TCP头和IP头)长度,然后将其累加。这样我必须知道pcap⽂件的格式细节才⾏。
幸运的是,pcap⽂件⾮常简单,就像我⼏乎10年前分析Windows PE⽂件⼀样,如今依然循着⽼路做着同样的事情。
如果你不善于查⽂档,那么作为⼀个编程者,看libpcap的源码也是个不错的选择,⼏乎和任何⽂件格式⼀样,pcap也是⼀个⾃描述的格式(这个⾃描述设计的不够优雅,以⾄于后来出现了pcapng⽂件格式,后⾯我会写⼀篇⽂章单独论述之),整体包括⽂件头和数据载荷,这⾥所谓的数据载荷就是⽹络数据包。在libpcap的pcap.h⽂件中,结构体pcap_file_header描述了⽂件头:
struct pcap_file_header { bpf_u_int32 magic; u_short version_major; u_short version_minor; bpf_int32 thiszone; /* gmt to local correction */ b pf_u_int32 sigfigs; /* accuracy of timestamps */ bpf_u_int32 snaplen; /* max length saved portion of each pkt */ bpf_u_int32 linktype; /* data link type (LINKTYPE_*) */};
具体我就不解释了,待会⼉我会⽤⼀个实例来解析。紧接着这个⽂件头,后⾯就是⼀个个数据包了,为了描述每⼀个数据包的元信息,每⼀个数据包都会有⼀个描述头:
struct pcap_pkthdr { struct timeval ts; /* time stamp */ bpf_u_int32 caplen; /* length of portion present 由于tcpdump可以设置-s参数指定抓取的长度,这个字段表⽰实际抓取的数据包长度 */ bpf_u_int32 len; /* length this packet (off wire) 这个字段表⽰数据包的⾃然长度 */};
这个结构体描述了数据包抓取的时间信息以及长度信息,在这个结构之后才会是数据包,因此⼀个典型的pcap⽂件应该是如下所⽰:
这简直清晰⾄极啊,再次看我的那个需求,我想统计的两个量怎么得到呢?
⼀个TCP连接实际发送的字节数:每⼀个数据包的TCP载荷长度的加和。
⼀个TCP理论上应该发送的字节数:结束的TCP序列号与初始序列号之差。
有了上⾯的论述,我觉得这个需求超级简单就实现了,为了展⽰⼀下学习python的出⾎效果,给出以下的代码:
#!/usr/bin/python import sys import socket import struct filename = sys.argv[0] filename = sys.argv[1] ip
addr = sys.argv[2] direction = sys.argv[3] packed = socket.inet_aton(ipaddr) ip32 = struct.unpack("!L", packed)[0] file = open(filename, "rb") pcaphdrlen = 24 pkthdrlen=16 pkthdrlen1=14 iphdrlen=20 tcp hdrlen=20 stdtcp = 20 total = 0 pos = 0 start_seq = 0 end_seq = 0 cnt = 0# Read 24-bytes pcap header data = ad(pcaphdrlen) (tag, maj, min, tzon e, ts, ppsize, lt) = struct.unpack("=L2p2pLLLL", data) # 具体的LinkType细节,请看:# /ntar/draft/PCAP-DumpFileFormat.html#a ppendixBlockCodes if lt == 0x71: pkthdrlen1 = 16else: pkthdrlen1 = 14 ipcmp = 0# Read 16-bytes packet header data = ad(pkthdrlen) while data: (sec, microsec, iplensave, origlen) = struct.unpack("=LLLL", data) # read link link = ad(pkthdrlen1) # read IP header data = ad(iphdrlen) (vl, tos, tot_len, id, frag_off, ttl, protocol, check, saddr, daddr) = struct.unpack(">ssHHHssHLL", data) iphdrlen = ord(vl) & 0x0F iphdrlen *= 4# read TC P standard header tcpdata = ad(stdtcp) (sport, dport, seq, ack_seq, pad1, win, check, urgp) = struct.unpack(">HHLLHHHH", tcpdata) tcphdrlen = pad1 & 0xF000 tcphdrlen = tcphdrlen >> 12 tcphdrlen = tcphdrlen*4if direction == 'out': ipcmp = saddr else: ipcmp = daddr if ipcmp == ip32: cn t += 1 total += tot_len total -= iphdrlen + tcphdrlen if start_seq == 0: # BUG? start_seq = seq end_seq = seq # skip data skip = ad(iplensav e-pkthdrlen1-iphdrlen-stdtcp) # read next packet pos += 1 data = ad(pkthdrlen) # 打印出实际传输的字节数,以及本应该传输的字节数print pos, c nt, 'Actual:'+str(total), 'ideal:'+str(end_seq-start_seq)
很简单吧!懂python的⼈都会嘲笑我!
其实,在我看pcap⽂件格式之前,我曾经⼀直以为pcap⽂件是由类似ASN.1组织的,但是看了以后却发现不是,也是挺失望的。我之所以失望是因为,看起来以上描述的这种pcap不能描述除了数据包之外的更多东西,它事实上并不是⾃描述的,它是⼀种固定长度格式的⽂件结构,虽然处理起来很快,但是却⼗分不灵活不易扩展!彻底的⾃描述结构就是ASN.1!
......
我们来看⼀个例⼦。随便抓⼀个TCP包获得test.pcap⽂件,⽤UE打开这个pcap,请⾃⾏脑补!如果你真的理解了pcap的⽂件组织形式,那么请认真分析,如果不,请理解透彻⽽不要脑补!
执⾏python脚本pcap-parser.py,我们⼀⽆所获,因为这只是包含⼀个纯ACK包的pcap,没有携带任何数据,⽽python脚本旨在得到TCP数据流实际传输的数据量,因此我们不得不抓取⼀个携带TCP流量的pcap⽂件,⽽这⾮常简单。
两台虚拟机A,B互联,A启动httpd,B上执⾏wget下载⼀个⽂件,同时设置丢包率以获得额外的重传数据量。执⾏:
pcap-parser.py ./testTCP.pcap 192.168.44.129 out
我们得到了以下结果:
...请⾃⾏执⾏获取
python是做什么的通俗易懂的⾁眼计算后发现结果是⼀致的,我认为这个脚本可⽤了。然⽽...
然⽽当我⽤我写的python脚本去分析⼀个tshark抓取的数据包的时候,发现解析错了,这个时候魔术字就起作⽤了,我⽤UE残忍地打开了这个pcap⽂件,结果呢?
魔术字都是错的!于是上wireshark⽹站,知道了这是⼀个pcapng这个⽂件格式,同时,也知道了pcapng不能向下兼容。这是令⼈悲伤的⼀件事,但是幸运的是,pcapng⽂件格式要⽐pcap简单的多,⽽且,它基本就是类似ASN.1的组织办法。
我们发现pcap的⽂件格式中,⼤部分的元描述结构都是固定数量且定长的,以LinkType为例,⼀次抓包我只能指定⼀个LinkType,它被记录在pcap⽂件开始的pcap_file_header中,这意味着,我⽆法同时在以太⽹卡和⾮以太的PPP⽹卡上抓包并同时得到详细的链路层信息!⽽pcapng解决了这个问题。
欲知pcapng如何,且看下篇⽂字。
附录
细节1:Cooked Capture与Ethernet
如果说使⽤tcpdump -i any参数,我们不会看到标准的以太头信息,我们看到的是Cooked Capture,⽽不是Ethernet!关键的
是,Cooked Capture描述的元信息长度是16字节⽽不是Ethernet的14字节。以下是Cooked Capture的头⽰例:
这个信息可以通过LinkType来获取。为什么会有这种Cooked Capture类型的数据包?因为抓包⼯具在-i any的情况下,⽆法⽤⼀种统⼀的⽅式来处理链路层的长度,⽐如很多协议,内部⼜区分了很多的⼦协议,其协议头的长度根据应⽤层⽽定,这是内核在抓包层⾯所处理不了的。pcap⽂件中,只能在⼀处指定LinkType,那就是⽂件头之后的pcap_pkthdr,如果说我指定-i eth0 -i lo -i ppp0 -i tun0,那就彻底没辙了!幸运的是,如果使⽤pcapng格式来存储抓包⽂件,那就可以针对这些⽹卡区别对待了,每⼀个⽹卡抓到的包都会关到⼀个LinkType,你会更轻松处理链路层,不过,⼤多数⼈不在乎链路层,也不在乎IP,更多⼈在乎的是TCP。
细节2:时钟跳变
作为⼀个pcap⽂件写的练习,这⾥有⼀个关于时钟跳变现象重现的例⼦。
我们抓包发现了⼀个奇怪的现象,那就是从客户端抓包上看,中间间隔了⼏⼗秒没有收到任何数据,在服务端抓包看来⼀切正常,总共的数据传输时间也就⼗⼏秒,这是怎么回事呢?
当即判断是客户端抓包时发⽣了时钟的跳变,⽐如时钟突然后跳了40秒,为了重现这个现象,我拿⼀个真实的普通正常的TCP下载为例,在收发第900个数据包的时候以及以后,数据包描述头⾥的时间戳字段统⼀加上40秒,看看是什么效果...代码⾮常简单:
if pos > 900: data = struct.pack("=LLLL", sec+40, microsec, iplensave, origlen) file_out.write(data)
然后就重现了这个现象:
给我⽼师的⼈⼯智能教程打call!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论