当前位置: 首页 > 工具软件 > Scapy > 使用案例 >

发包利器scapy

黄俊雄
2023-12-01

发包利器scapy

最近在帮助用户调试一个网络问题的时候,发现一个很好用的发包工具scapy,记录一下使用方法。

我们在调试TCP/IP时, 有时需要发送一些报文,用的工具一般有ping, nping, netcat 等。 如果需要再深入微调IP或传输层的域,或发送一些畸型的报文,以上工具可能就不一定能做到了。 此时我们可以自己用socket 编程,不过太麻烦了,迭代周期太长了。 此时 scapy 是一种不错的选择。

./run_scapy_py2

进入交互式Python 界面。 运行一些简单的函数感觉一下 scapy 的功能:

>>> str(IP())
'E\x00\x00\x14\x00\x01\x00\x00@\x00|\xe7\x7f\x00\x00\x01\x7f\x00\x00\x01'
>>> IP(_)

可以看到IP 头结构:

<IP  version=4 ihl=5 tos=0x0 len=20 id=1 flags= frag=0 ttl=64
proto=hopopt chksum=0x7ce7 src=127.0.0.1 dst=127.0.0.1 |>

我们发送一个ICMP 消息:

>>> send(IP(dst="172.25.52.34")/ICMP())
Sent 1 packets.

注意‘/’的使用,它用来连接不同的协议层。 换句话说,‘/’后面的数据是前一层的负载。

  • 一个实例

    用户的问题是这样的:主机A 向主机B发送了一个2千多字节的UDP消息,由于MTU的限制, 该消息在主机A上就被分成了两个IP分片, 经过中间路由器的时候,第一个IP分片又被分成了两片,到达主机B的时候是3个IP分片, 结果上层应用没有收到。 要找到原因在哪里,是在IP层还是UDP层被丢弃的。

因为主机B是我所在公司的产品, 我需要验证,这样的3个IP分片包在主机B上受到了怎样的待遇。它们没被上层应用收到的原因是什么。

直接ping一个大包到主机B说明不了问题,必须要用用户原始的数据。 用户已经抓包了,存放在/tmp/fragments.pcap 里, 我们可以用scapy 的rdpcap()读取抓包。

>>> p = rdpcap("/tmp/fragments.pacp")
>>> p
<fragments.pcap: TCP:0 UDP:1 ICMP:0 Other:2>
>>> len(p)
3

p是一个list, 每个成员是一个IP分片包。 我们可以看一下它们的长度:

 >>> for i in range(len(p)):
...     len(p[i].load)
... 
1448
24
875

和内容:

>>> for i in range(len(p)):
...     p[i].load
... 

p[i].load 就是用户发送的数据(不包括IP头和UDP头)。然后我们可以利用这些数据构造自己的IP分片:

>>> a=IP(dst="172.25.52.34",id=1,flags="MF",frag=0,ttl=255)/UDP(sport=5059,dport=5060,len=2355,chksum=0x8061)/p[0].load


>>> b=IP(dst="172.25.52.34",id=1,flags="MF",frag=182,ttl=255,proto=17)/p[1].load


>>> c=IP(dst="172.25.52.34",id=1,frag=185,ttl=255,proto=17)/p[2].load

根据IP分片的规范, 我们设置了IP头的一些域: dst 指定了目标IP地址, 所有的分片要有一个相同的id,我们在此设置为1, 前两个分片设置了标志MF(more fragments),表示后续还有分片,最后一片不需要设置该标志。 frag 设置的是分片在整个数据包中的偏移量,第一个分片偏移量当然为0, 第二个分片的偏移量是第一个分片中数据的长度,注意frag 在IP头中是一个13位的域,表示的是以8字节为单位的偏移,因此要把实际长度除8, 得到182; 第三个分片的偏移量是第一个和第二个分片是数据的长度相加,再除8, 得到185。 proto为17,表示传输层是UDP。 ttl 设成最大值255,表示IP分片失效的秒数。

另外,第一分片要有一个UDP头,其余分片只要IP头加上数据就可以了, 所以我们构造了一个UDP层:

UDP(sport=5059,dport=5060,len=2355,chksum=0x8061)

这里的源端口、目的端口,长度和 chksum 都是从抓包信息里读出的。

构造好三个分片后,我们就可以发送了:

>>> send(a)
>>> send(b)
>>> send(c)

也可以写一个简单的函数一下子发送:

>>> def send_all():
...    send(a)
...    send(b)
...    send(c)

>>> send_all()

在发送的同时,我们用wireshark 抓包,可以看到IP报文的确发出去了。 但在目的主机 172.25.52.34 上建立的UDP 服务器没有收到这个UDP消息。

由于目标主机上的网络协议就是自己公司的(实验的目的也是为了调试网络协议), 加入调用代码后发现是UDP层检查checksum失败, 把UDP消息丢弃了。 再回过头来看我构造的第一个分片:

>>> a=IP(dst="172.25.52.34",id=1,flags="MF",frag=0,ttl=255)/UDP(sport=5059,dport=5060,len=2355,chksum=0x8061)/p[0].load

这是的 chksum=0x8061 ,我是从用户的抓包里看到的,直接拿来用了。 其实是不对的 。 查资料找到UDP checksum的构成: 它先要构造一个伪IP头,包含目的IP、源IP, UDP长度。 然后这个伪IP头再和UDP头和数据合在一起计算checksum, 由于用户的IP地址和我的环境不同,checksum 当然不同了。 幸好还可以强制让协议栈不计算checksum, 这样至少可以看到一次成功的发收包, 把checksum 设为0:

a=IP(dst="172.25.52.34",id=1,flags="MF",frag=0,ttl=255)/UDP(sport=5059,dport=5060,len=2355,chksum=0)/p[0].load

再发送 a,b,c 这回对方收到UDP消息了。 这说明IP层没有问题,分片都是正确的,问题很可能出在UDP的checksum上。 再参考这个网页https://scapy.readthedocs.io/en/latest/functions.html

计算一下UDP 的checksum, 果然不是0x8061。 很有可能用户的主机在发送UDP的时候,checksum 计算错了。

 类似资料: