赞
踩
提到数据包(这里泛指帧、段和报文等)的构造,我们首先需要了解协议和分层这两个概念。在“互联世界的规则一协议”中,我们提到了协议的概念,简单来说协议就是通信时所有参与者必须遵守的规则集合。这些协议各司其职、各尽其能,它们的不同主要体现在产生的数据包的顺序与格式上。
个在网络中的数据包往往会包含多个协议,例如我们所使用的 QQ,它在登录时就会产生数据包。这个数据包的目标地址是腾讯服务器(假设为 1.1.1.1),目标端口是 8000,传输的信息为“我要登录”,那么这个过程产生的数据包就需要包含 IP 部分(用来指明目标地址等信息)UDP 部分(用来指明端口等信息 、QQ 自有协议部分(用来保存传输内容等)。实际情况远比这要复杂,互联网上存在的协议数量已经成千上万了,当多个协议存在于同一个数据包时,为了解析方便,就有必要将它们分成不同的层次。
目前通用的分层方式有两种,我们以相对简单的 TCP/IP 协议族为例,它是一个4层协议模型、自底而上分别是链路层、网络层、传输层和应用层。每一层完成不同的功能,且通过若干协议来实现,上层协议使用下层协议提供的服务。
TCP/IP 协议族常见协议所属层次
这样分层之后来构造数据包就会很简单。但是需要注意,一个数据包并不是必须同时包含这 4层的协议,也不是同一层只能包含1个协议。在后面的具体实现中,我们就会对此有深入的了解。
前面提到 Scapy 是一个可以直接操作到数据包层次的工具。在 Windows 中,我们可以在Python 环境中将Scapy 当作一个库使用;如果是在 Linux中则可以将 Scapy 当作一个独立的工具来使用,它提供了一个和Python相同的交互式命令行环境。在Kali Linux2.0中已经集成了 Scapy 环境,只需要启动一个终端,输入命令“scapy”,就可以启动 Scapy编程环境。
Scapy 提供了和 Python 一样的交互式命令行。这里需要特别强调的是,虽然将 Scapy 模块作为 Python 的一个库,但是 Scapy 本身就是一个可以独立运行的工具,它具备一个独立的运行环境,因而可以不依赖Python。
首先我们先用几个实例来演示 Scapy 的用法。Scapy 使用了“类+属性”的方法来构造数据包,在 Scapy 中每一个网络协议就是一个类,协议中的字段就对应着属性。只需要实例化一个协议类,就可以创建一个该协议类型的数据包。例如我们要构造一个 IP 数据包,可以使用如下方式。
IP()
如果要使用 IP 的话,那么首先需要导人 Scapy 库。考虑到目标模块中的属性非常多,反复输人 Scapy 很不方便,这里我们选择“from 模块 import 类”的形式导人,下面给出了一个使用Scapy构造IP 数据包的演示程序
from scapy.all import IP
pkt=IP()
print(pkt)
这个程序执行之后,可以输出中看到的结果:
对于 IP 来说,最重要的属性就是源地址和目标地址,这两个属性在 Scapy中使用参数 src和 dst来设置。例如我们要构造一个发往“192.168.217.150”的IP 数据包,就可以使用以下语句。
ip=IP(dst="192.168.217.150")
对于 Scapy 的使用者来说,比较困难的一点就是协议类型众多。现在使用IPO来构造数据包的时候,都需要设置哪些参数,这些参数都有什么意义呢?由于网络中协议数量众多,因此Scapy 在内部实现了大量的网络协议 (DNS、ARP、IP、TCP、UDP 等)。人类靠记忆来完成这个工作是很难的。
要想熟练地使用 Scapy,大家需要掌握协议的一些基础知识。另外 Scapy 也提供了一个可以便捷查看数据包格式的函数ls(),当你不了解如何为一个 IP 数据指定目标地址的时候、就可以使用下面的程序。
from scapy.all import IP,ls
pkt=IP()
ls (pkt)
执行该程序,可以看到结果。
Scapy 采用分层的方式来构造数据包,通常最底层的协议为 Ether,然后是 IP,再之后是TCP 或者 UDP。例如我们使用 Ether0,这个类可以设置发送方和接收方的 MAC 地址。那么我们现在来产生一个广播数据包,执行的命令如下。
Ether(dst="ff:ff:ff:ff:ff:ff")
分层是通过符号“/”实现的。如果一个数据包是由多层协议组合而成的,那么这些协议之间就可以使用“/”分开,并按照协议由底而上的顺序从左向右排列。例如我们可以使用Ether()/IP()/TCP()来构造一个TCP数据包。
from scapy.all import
pkt=Ether()/IP()/TCP()
ls(pkt)
这个程序由于需要导入的模块比较多,因此使用了“import *”。在执行这个程序之后,可以看到我们构造了一个包含 Ether、IP 和TCP 这3 种协议的数据包。
如果要构造一个HTTP 数据包,也可以使用以下这种方法。
Scapy中使用频率最高的类要数 Ether IPTCP和UDP了,这些类都具有哪些属性呢?Eth类中显然具有源地址、目标地址和类型。IP类的属性则复杂了许多,除了最重要的源地址和目地址之外,还有版本、长度、协议类型、校验和等。TCP 类中具有源端口和目标端口。这里我可以使用ls()函数来查看一个类所拥有的属性。前面我们已经提过了,这个函数使用属性列表的式来显示一个数据包的详细信息,例如使用ls(Ether0)来查看 Ether 类的属性。
也可以使用同样的方法用ls(IP())来查看 IP 类的属性,可以对属性列表里对应的属性进行设置,例如我们将 ttl 的值设置为 32,就可以使用如下方式。
pkt=IP(src="192.168.1.1",dst="192.168.1.101",ttl=32)
刚开始不熟悉 Scapy 有哪些功能的时候,大家可以使用 lsc()函数列出所有可以使用的函数,下面给出了一些经常使用的函数及其使用方法。首先我们使用 pkt=IP()来构造一个数据包然后利用这个 pkt 来演示各种函数。
如果我们看到了一个数据包,但是不知道如何使用命令来产生相同的数据包时,就可以使用command()函数,它可以显示出构造该数据包的命令。例的就是用 pkt.command()还原 pkt 的构造命令。
有时使用 Scapy 会捕获到大量的数据包,这些数据包需要保存起来,例如在网络取证时就会经常这么做,这时 wrpcap()函数就可以完成这个工作。例如我们在程序中将很多数据包都临时存储在 pkts 中,使用wrpcap("temp.cap",pkts)
就可以将pkts 中的数据包写入文件 temp.cap。
同样 Scapy 也提供了读取数据包文件的功能,rdpcap()函数就可以实现这个功能,例如使用pkts =rdpcap("temp.cap")
读取 temp.cap文件中的数据包。
除了这些对应着协议的类和它们的属性之外,我们还需要一些可以实现各种功能的函数需要注意的一点是,刚才我们使用 IP()的作用是产生了一个 IP 数据包,但是并没有将其发送出去,因此现在首先需要将产生的数据包发送出去。Scapy 提供了多个用来发送数据包的函数先来看其中的 send()函数和 sendp()函数。这两个函数的区别在于 send()数是用来发送IP数据包的,而sendp()函数是用来发送 Ether 数据包的。我们先来构造一个目标地址为 192.168.1.1的ICMP 数据包,并将其发送出去,可以使用如下程序。
from scapy.all import *
pkt=IP(dst="192.168.217.150")/ICMP()
send(pkt)
注意,如果这个数据包发送成功,那么下方会有一个“Sent 1 packets”的显示。sendp()函数的使用方法是相同的,下面给出了一个实例。
sendp(Ether(dst="ff:ff:ff:ff:ff:ff"))
简单来说,当你需要将 MAC 地址作为目标时,就使用 sendp()函数;而当你需要将IP 地址作为目标时,就使用 send()函数。这两个函数的特点是只发不收,也就是说只会将数据包发送出去,但是不会处理该数据包的应答数据包。
在网络的各种应用中,我们需要做的不仅要将创建好的数据包发送出去,也要接收这些数据包的应答数据包,这一点在网络扫描中尤为重要。Scapy 提供了 3 个用来发送和接收数据包的丽数,分别是 sr()函数、sr1()函数和 srp()函数,其中 sr()函数和 sr1()函数主要用于 IP 地址,而srp()丽数用于MAC地址。
这里我们仍然向192.168.217.150发送一个ICMP 数据来了解 sr()函数的使用方法需要注意的是,这里 192.168.217.150 应该是一个可以 ping通的地址。
当产生的数据包被发送出去之后,Scapy 就会监听接收到的数据包,将其中对应的应答数据包筛选出来并显示。为 Reveived 表示收到的数据包个数,answers 表示对应此次发送数据包的应答数据包。
sr()函数是 Scapy 的核心,它的返回值是两个列表,第一个列表包含收到了应答的数据包和对应的应答数据包,第二个列表包含未收到应答的数据包。所以可以使用两个列表来保存sr()函数的返回值。
from scapy.all import *
pkt=IP(dst="192.168.217.150")/ICMP()
ans,uans=sr(pkt)
ans.summary()
这里我们使用ans列表和 uans列表来保存sr()函数的返回值。因为发送出去的是一个ICMP数据包,而且收到了一个应答数据包,所以这个发送的数据包和收到的应答数据包都被保存到了ans列表中使用ans.summary0可以查看两个数据包的内容。unans列表为空。为ans中保存的应答数据包。
srl()雨数跟 sr()丽数作用基本一样,但是只返回一个应答数据包,只需要使用一个列表可以保存这个雨数的返回值。srp()丽数与 srl()函数和 sr()函数的区别在于发送时要使用 MAC地址。
另外,一个十分重要的函数是 snim0。如果你使用过 Tcpdump,那么对这个函数就不会感到陌生。使用这个函数就可以在自己的程序中捕获经过本机网卡的数据包了。
sniff()
这个函数完整的格式为sniff(filter="",iface="any”,prn=function,count=N)
。第1个参数是filter,可以用来对数据包进行过滤。例如我们指定只捕获与 192.168.217.150有关的数据包,就可以使用host 192.168.217.150"
sniff(filter="host 192.168.1.1")
但是这种仅依靠IP地址来过滤的方法有很大的局限性,下面我们介绍一种功能更加完善的方法。1993年史蒂文·麦卡内(Steven McCanne)与范·雅各布森( Van Jacobson)在USENIX93会议上提出了一种机制一伯克利包过滤(Berkeley Packet Filter,BPF)它采用了一种与自然语言很接近的语法,利用这种语法构成的字符串可以确定保留哪些数据包以及忽略哪些数据包。
这种语法很容易理解。例如最简单的空字符串,表示的就是匹配所有数据包,也就是保留所有的数据包。如果这个字符串不为空,那么只有那些使字符串表达式值为“真”的数据包才会被保留。这种字符串通常由一个或者多个原语所组成,每个原语又由一个标识符(名称或者数字)组成,后面跟着一个或者多个限定符。
伯克利包过滤中的限定符有下面3种:
伯克利包过滤中的标识符指的就是那些进行测试的实际内容,例如一个 IP 地址 192.168.217.151,一个子网192.168.217.0/24 或者一个端口号 8080,这些都是常见的标识符。host 192.168.217.151和 port 8080 是两个比较常见的原语,我们还可以用 and、or 和 not 把多个原语组成一个更复杂的过滤语句。例如host 192.168.217.151 and port 8080 就是一个符合规则的过滤语句。
下面给出了一些常见的原语实例。
第2个参数 iface 用来指定要使用的网卡,默认为第一块网卡。
第3个参数 prn表示对捕获到的数据包进行处理的函数,可以使用 Lambda 表达式。例如我们要将获取到的数据包输出,就可以使用以下函数。
sniff(filter="icmp", prn=lambda x:x.summary())
如果这个函数比较长,也可以定义成回调函数。这个回调函数以接收到的数据包对象作为唯一的参数,最后再调用 sniff()函数。
def packet_callback(pkt):
print (pkt,summary)
sniff(prn=packet.callback)
第4个参数 count 用来指定监听到数据包的数量,达到指定的数量就会停止监听。例如我们只希望监听到 10 个数据包就停止。
sniff(count=10)
我们来设计一个综合性的监听器,它会在网卡 eth0 上监听源地址或者目标地址为 192.168.1.1 的1CMP 数据包并输出,当收到了3个这样的数据包之后,就会停止监听。建的监听器如下:
sniff(filter="icmp and host 192,168,1,1",prn=lambda x:x,summary(),count=3)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。