一、说明
之前写DoS程序的时候(见"拒绝服务(DoS)理解、防御与实现"),数据包完全是自己构造的,这其中的难处一是要清楚各层协议的字段、字段长度、字段是数值还是字符、大头还是小头,二是计算校验和。整个过程比较痛苦。
其实构造DoS数据包,我们经常只是想伪造一下源IP等少数字段,而事实上大量时间花在其他大多数不想关心的字段的构造上。
在查找资料过程中发现很多DoS程序直接使用一个“Scapy”的包(开始还以为是Scrapy心想Scrapy什么时候可以用来构造数据包了,后来才注意到少了个“r”)。当时没太在意,现在回头看Scapy确实堪称“神器”,值得记录一番。
本文主要参考官方文档,更多用法也请见:https://scapy.readthedocs.io/en/latest/usage.html
二、安装
直接使用pip安装:
pip install scapy
一些扩展功能安装,可选:
pip install matplotlib pyx cryptography
三、生成数据包
3.1 启动scapy交互shell
切换到Python的Scripts文件夹下,直接使用scapy命令启动(本质是scapy.bat):
scapy
启动界面如下图(由于我没装PyX所以提示不能使用psdump()和pdfdump(),另外还有一些告警,都无关紧要就不处理了):
3.2 生成数据包
在scapy中,应用层之下的数据包,基本使用协议大写对应的方法就可以直接生成。如:
# 使用Ether()方法生成一个以太网层数据包 eth_packet = Ether() # 使用IP()方法生成一个网络层数据包 ip_packet = IP() # 使用TCP()方法生成一个tcp数据包 tcp_packet = TCP() # 使用UDP()方法生成一个udp数据包 udp_packet = UDP() # 使用ICMP()方法生成一个udp数据包 icmp_packet = ICMP()
3.3 查看数据包内容
上一小节中生成数据包,如果就长这样的话完全就是一个黑盒,我们需要一些方法来知道数据包(各字段)是什么内容。
哪个协议的数据包查看都是类似的,我们这里以一个tcp数据包为例,查看其他协议的数据包类似操作即可。
# 使用IP()方法生成一个tcp数据包 ip_packet = IP() # 方法一、使用raw()方法查看 raw(ip_packet) # 方法二、使用hexdump()查看。类似Wireshark hexdump(ip_packet) # 方法三、raw()配合协议类查看。最佳 # 查看该数据包以太网层内容(下层内容不层示,上层内容不解析只以load形式展示) Ether(raw(ip_packet)) # 查看该数据包网络层内容(下层内容不层示,上层内容不解析只以load形式展示) IP(raw(ip_packet))
3.4 自定义字段值
上一小节中我们使用raw配合协议方法的方式清楚地看到了数据包中各字段的值,这些值是根据本机信息自动生成的,我们来看如何自定义各字段的值。
定义字段的值有两个种方法,一种是在构造时直接传递参数,另一种是在生成后重新赋值;至于参数名即是上一小节中打出的那些项。
以修改源IP地址和TTL为例,在上一小节中可以看到默认生成的源IP地址叫“src”值为“10.10.6.91”(这是我本机当前的IP地址),TTL叫ttl默认值64。
# 方法一,在构造时直接传递参数 # 给src传值10.10.6.92,ttl传值128;字段为字符的以字符串形式传,字段为数值的以数值形式传 ip_packet = IP(src="10.10.6.92",ttl=128) # 查看数据包各项值,确认src项是否为10.10.6.92,ttl项是否为128 IP(raw(ip_packet)) # 方法二,在生成后重新赋值 # 给src赋值10.10.6.92,ttl赋值128;字段为字符的以字符串形式赋,字段为数值的以数值形式赋 ip_packet.src = "10.10.6.93" ip_packet.ttl = 200 # 查看数据包各项值,确认src项是否为10.10.6.93,ttl项是否为200 IP(raw(ip_packet))
3.5 同时自定义多层协议的字段
在上一小节中我们自定义了源IP地址,但有一个问题:如果此时我想自定义源MAC地址该如何操作呢。想在构造时直接传递参数使用的是IP()又不是Ether();想在生成后重新赋值,上一小节已试验过了src改的是源ip,源mac没法通过src项改。
构造多层协议,构造函数可使用“/”隔开,低层函数在前高层函数在后;最后使用构造函数中的最低层函数即可查看数据包内容。
# 构造一个IP数据包,源mac设置为"00:00:00:00:00:11",源ip设置为"10.10.6.92" ip_packet = Ether(src="00:00:00:00:00:11")/IP(src="10.10.6.92") # 当前构造函中最低层为Ether Ether(raw(ip_packet)) # 定义一个TCP数据包,源mac设置为"00:00:00:00:00:11",源ip设置为"10.10.6.92",源端口设置为1234 tcp_packet = Ether(src="00:00:00:00:00:11")/IP(src="10.10.6.92")/TCP(sport=1234) # 当前构造函中最低层为Ether Ether(raw(tcp_packet)) # 定义一个应用层数据包,应用层内容为"GET / HTTP/1.0\r\n\r\n" app_packet = IP()/TCP()/"GET / HTTP/1.0\r\n\r\n" # 当前构造函中最低层为IP IP(raw(app_packet))
四、发送和接收数据包
4.1 只发送数据包不接收数据包
send()函数允许自定义网络层,sendp()函数允许自定义以太网层。
不管TCP还是UDP都一样直接使用send()等函数即可,scapy会自己选择socket类型,不我们关心。
send(IP(dst="10.10.6.92")/ICMP()) sendp(Ether()/IP(dst="10.10.6.92")/ICMP())
4.2 发送并接收响应数据包
sr1()函数参许自定义网络层,srp()函数允许自定层数据链路层
rec_packet = sr1(IP(dst="10.10.6.92")/ICMP()/"abcdefg") rec_packet # 直接读取某项值 rec_packet.src # 使用show()方法格式化输出 rec_packet.show()
4.3 syn扫描
所谓syn扫描就是把Flags字段明确指定为SYN
rec_packet = sr1(IP(dst="10.10.6.92")/TCP(dport=80,flags="S")) rec_packet.show()
五、在代码中使用
前面我们都是基于交互shell进行了,如果是在文件中该如何使用scapy呢。
交互shell和文件在代码书写上没有区别,唯一的区别就是交互shell导入好了scapy的所有类,而到文件上导入scapy所有类最简单的方式就是:
from scapy.all import *
(实践发现,这种写法在Pycharm中还是会提示“Unresloved reference”但运行是不报错的;实在不行使用“Alt+Enter”键把使用到的类逐个导入即可。)
参考: