文章目录(Table of Contents)
简介
这一篇主要记录一些关于scapy的内容. 记录一些自己使用到的内容. 逐渐往里面进行更新. 这里会包括Scapy的基础使用, 和一些简单的计算机网络的知识.
参考资料
- Scapy 中文文档
- 这里有很详细的数据包分析: wireshark分析(传输层,网络层,链路层)
关于网络协议
因为Scapy是网络工具, 因此我们需要首先对计算机网络的知识有所了解. 首先一个pacp文件结构如下所示:
接着我们看一下每一层的头部有什么:
- 黄色的是数据链路层的头部,一共14字节
- 蓝色的部分是IP头部,一般是20字节
- 紫色部分是TCP头部,一般是20字节
注意:(源IP地址,目的IP地址,源端口号,目的端口号)这四个字段唯一的确定了一个TCP链接。
数据链路层
- 目的MAC:当前step目的主机的mac地址
- 源MAC:当前step的源主机的mac地址
- 类型:指定网络层所用的协议类型
- 0x0800指IPv4协议
- 0x86dd IPv6协议数据
- 0x809B AppleTalk协议数据
- 0x8138 Novell类型协议
网络层
- 版本:记录数据报属于哪一个版本的协议,如IPv4或IPv6
- 首部长度:指明IP头部长度,单位是字,也就是两个字节。该域的值最小为5,就是标准的头部长度;最大为15,表明有扩展部分。
- 服务类型:用来区分不同服务的需要.
- 数据报总长:包含IP头部的数据报的总长度。注意,这里不包括链路层的头部,目前最大值是65535字节。
- 分组ID:这个域的作用是当一个大的数据报被拆分时,拆分成的小的数据段的这个域都是一样的。
- 标记:共三个bit,第一个未使用;第二个DF(Don't Fragment),设置成1表示这个数据包不能被分割,这个是针对路由器的一条指令;第三个MF(MoreFragment),如果一个数据包被分割了,那么除了最后一个分段以外的所有分段都必须设置为1,用来表示后面还有更多的分段没有到达,最后一个设置为0,用来表示分割的段全部到达。
- 段偏移量:这个域有13bit,也就是每一个数据报最多有8192个分段。每一个分段的长度必须是8字节的倍数,也就是说8字节是分段的基本单位,当然分组的最后一个段不做限制。这样最大的数据报长度为8*8192=65536字节,比目前限制的最大数据报长度还多1,能够满足对网络中所有数据报传送的需求。
- 生存时间:这是一个生存期计数器,最大为255s,但是实际上使用的时候用作跳数计数器,当值为0时数据报被丢弃,用来避免一个数据报过久的逗留在网络中。
- 高层协议:这里和链路层的类型作用相同,用来表示更高层的协议,这个数据报里是TCP
- 首部校验和:IP头部的校验和
- 源IP地址:数据报来源主机的IP地址
- 目的IP地址:数据报目的主机的IP地址
传输层
- 源端口号:数据报来源主机的端口号
- 目的端口号:数据报目的主机的端口号
- TCP序号(sq):发送的TCP的序号,从0开始,实际中这个值就是发送的数据报中内容的字节数,比如我发送的第一个报中sq=0,数据报内容20字节,那么下一个数据报的sq就应该是21。
- 捎带的确认(ack):确认收到上一个数据报,然后act的值是指定自己想要收到的下一个数据报的sq,比如我收到一个数据报的sq=0,数据报内容20字节,那么我的ack就应该是21,用来标明我sq=0,内容为20字节的数据报已经收到,我接下来期望收到的是sq=21的数据报。
- 首部长度:和IP头部的长度域类似,这个域用来标明TCP头部的长度,单位也是字。
- 保留:6bit未使用的域
- Flag:从左到右,[URG|ACK|PSH|RST|SYN|FIN]
- ACK设置为1表示前面的确认(ack)是有效的,否则前面的确认应被忽略。
- PSH表示要求对方在接到数据后立即请求递交给应用程序,而不是缓冲起来直到缓冲区收满为止。
- RST用于重置一个已经混乱的连接。
- SYN用于建立连接的过程。在链接请求中,SYN=1和ACK=0表示该数据段没有使用捎带的确认域。链接应答则捎带了一个确认,即SYN=1和ACK=1.本质上SYN位是用来表示CONNECTION REQUEST和CONNECTION ACCEPTED,然后进一步用ACK来区分是请求还是应答,的确很高明。
- FIN用来释放一个连接。它表示发送方已经没有数据要传输了。然后,在关闭一个连接后,关闭进程可能会在一段不确定的时间内继续接收到数据。SYN和FIN数据段都有TCP序号,从而保证了这两种数据段被按照正确的顺序来进行处理。
- 窗口大小:指定了从被确认的字节算起可以发送多少个字节。要深入理解这个域的含义,可以参看TCP用色控制和慢启动算法
- 校验和:校验范围包括TCP头、数据报内容和概念性伪头部。概念性伪头部又包括源IP,目的IP,TCP协议号。
- 紧急指针:指向数据报中紧急数据最后一个字节的下一个字节。
参考资料: TCP/IP数据包格式详解-包括数据链路层的头部
Scapy的简单使用
- from scapy.all import *
读取和写入pcap文件
读取pcap文件
- packet=rdpcap("/spare/captures/isakmp.pcap")
写入pcap文件
- wrpcap('filtered.pcap', pkt, append=True) #appends packet to output file
获取数据包的常用信息
我们可以使用summary来查看总体的信息. 当一个pacp中有多个会话的时候, 查询的结果如下所示:
- packet.summary()
为了获得更加详细的信息, 我们可以对其中第一个会话进行查看.
- packet[0].show()
- # packet[0].summary()
这里的show可以按层级显示所需要的信息.
当然我们也可以具体显示某一层的IP地址, 或是MAC地址. 首先是查看IP地址.
- packet[0][IP].dst
接下来是查看MAC地址:
- packet[0][Ether].src
依次读取数据包-sniff
sniff是嗅探的意思, 我们可以利用这个函数来依次处理pcap中每一个packet. 例如下面的例子, 我们依次打印出源地址和目的地址.
- def test(packet):
- return "Src{} => Dst:{}".format(packet[0][IP].src, packet[0][IP].dst)
- sniff(offline='64722.pcap', prn=test)
最终的结果如下所示:
如果是想要对原始的 packet 进行修改,我们可以使用 sniff 来修改。下面是抓取 10 个 packet。之后可以对 packets 进行保存。
- from scapy.all import *
- from scapy.layers.inet import IP
- def change_send(pckt):
- actual_src = pckt[IP].src
- pckt[IP].src = "192.168.1.5"
- pckt[IP].tos = 1
- sendp(pckt)
- print("We changed source from " + actual_src + " to " + pckt[IP].src)
- packets = sniff(filter="ip src host 192.168.1.2", prn=change_send, count=10)
这里返回的 packets 的数据类型是 scapy.plist.PacketList,和 list 是十分相似的。我们可以使用 wrpcap 来对上面返回的 packets 进行保存。
- wrpcap(pcapPath, packets)
sniff 中有一个参数是 store,默认情况是 True,这个时候会存储每一个读入的 packet。这样如果遇到比较大的 pcap 文件的话,很可能会出现 Memory Error 的问题。
I managed to find the actual cause to the memory consumption. It was scapy itself. Scapy by default is set to store all packets it captures. But you can disable it. (这个设置为 0,或是直接设置为 False,可以不存储)参考资料,Strange Python memory usage with Scapy
- sniff(iface=interface, prn=sniffmgmt, store=0)
但是需要注意的是,一旦设置了 False 之后就只可以打印一些内容,无法直接对原始 packets 进行修改,这个时候 sniff 返回的会是空。
使用Scapy的一些应用
实现数据匿名化
有的时候, 我们需要将数据包中的IP地址和MAC地址进行匿名化处理, 于是我们可以使用下面的方式进行实现.
首先我们创建一个新的数组packets, 接着使用sniff对逐个packet进行处理, 并将处理的结果保存在packets中, 最后保存packets即可.
- def customAction(packet):
- # 匿名化MAC地址
- packet[Ether].dst='00:00:00:00:00:00'
- packet[Ether].src="00:00:00:00:00:00"
- # 匿名化IP地址(需要判断是IPv4还是IPv6)
- if packet[Ether].type==34525: # 0x86dd
- packet[IPv6].src="0:0:0:0:0:0:0:0"
- packet[IPv6].dst="0:0:0:0:0:0:0:0"
- else:
- packet[IP].src="0.0.0.0"
- packet[IP].dst="0.0.0.0"
- # 创建一个新的packets用来保存
- packets = []
- # 逐个packet进行转换
- for sniffed_packet in sniff(offline='64722.pcap', prn=customAction):
- packets.append(sniffed_packet)
- # 结果保存
- wrpcap('res.pcap', packets)
- 微信公众号
- 关注微信公众号
- QQ群
- 我们的QQ群号
评论