前段时间在学网络协议的时候,知道了有 ARP 攻击这么个东西,但是当时对 Linux 环境下的 C 语言编程不是很了解,一直没有机会实践一下。终于在最近学习了网络编程之后,才算是入门了一点点 Linux C 语言编程。于是乎,经过两天的研究,终于亲自试验了一把 ARP 攻击,纸上得来终觉浅,绝知此事要躬行~不废话了,下面就详细介绍一下我在虚拟机上实施 ARP 攻击的过程。
什么是 ARP 协议?
在同一个局域网中, 一个主机要想给另外一个主机发送消息,必须知道目标主机的 MAC 地址,但是在 TCP/IP 协议中,网络层和传输层只关心目标主机的 IP 地址,换言之,数据链路层拿到的报文中只包含目标主机的 IP 地址,他必须根据目标主机的 IP 地址找到相应的 MAC 地址,然后将消息发送出去。这个时候就需要用到 ARP 协议了。
ARP(英文:Address Resolution Protocol),翻译成中文是「地址解析协议」,是根据 IP 地址获取 MAC 地址的网络传输协议。现在我们假设局域网中有三台主机 A、B、C,他们的 IP 地址和 MAC 地址分别如下表所示:
IP 地址
MAC 地址
A
192.168.133.1
00:50:56:C0:00:08
B
192.168.133.140
00:0C:29:B6:1E:5E
C
192.168.133.141
00:0C:29:80:44:D4
现在 A 想给 B 发一条消息,此时 A 只知道 B 的 IP 地址,不知道 B 的 MAC 地址,它就会向局域网中广播一条 ARP 请求消息,询问 B 的 MAC 地址。这相当于 A 在局域网中喊:谁知道 192.168.133.140 的 MAC 地址?知道的话告诉我一声。虽然局域网中其他主机都会收到这条消息,但是其他主机一看自己的 IP 地址不是 192.168.133.140,就不会搭理 A,只有 B 收到了这条消息后发现 A 是在询问自己的 MAC 地址,于是就会给 A 发送一条 ARP 应答消息,告诉 A 自己的 MAC 地址是 00:0C:29:B6:1E:5E。
A 收到 B 的 ARP 应答后,知道了 B 的 MAC 地址,就可以给 B 发送消息了。同时 A 会将 B 的MAC 地址保存下来,以便下次再给 B 发消息的时候使用,这个叫做 ARP 缓存,在 Windows 和 Linux 上都可以通过 arp -a 这条命令查看当前系统的 ARP 缓存。ARP 缓存有一定的有效期,不同的系统有效期不一样,过了有效期之后,当 A 需要再次给 B 发消息的时候,A 会重新广播 ARP 请求,来询问 B 的 MAC 地址。
ARP 攻击
以上讲的是正常的 ARP 请求-应答过程。如果大家都按照这个规则来,就会相安无事,可如果有人不按套路出牌,主动给 A 发送 ARP 应答呢?ARP 协议的漏洞就出现在这里,对于一个主机来说,不管他之前有没有发送过 ARP 请求,只要他收到了 ARP 应答,他就会把收到的 ARP 应答里面的 IP 地址和 MAC 地址的对应关系存到自己的 ARP 缓存里。
假设现在主机 C 向 A 发送了一个 ARP 应答,告诉 A,IP 地址是 192.168.133.140(B 的 IP 地址) 的主机的 MAC 地址是 00:0C:29:80:44:D4(C 的 MAC 地址),那么 A 给 B 发送数据的时候,报文里的 MAC 地址就会写成 C 的 MAC 地址,这样一来,本来应该是发到 B 那里的消息,结果发到了 C 这里。这就是 ARP 攻击的原理。下文中如无特殊说明,A 均指数据发送者,B 均指数据接收者,C 均指攻击者。
ARP 欺骗
实战
前期准备
环境搭建
我的操作系统是 Windows,虚拟机软件用的是 VMware15,上面装了两台操作系统是 CentOS6.5 的虚拟机,虚拟机的网络连接模式选择 NAT 模式。我现在用 Windows 操作系统模拟 A,两台虚拟机分别模拟 B 和 C。
#!/usr/bin/python
#这个是在 CentOS 上运行的,CentOS默认安装了 Python2,没有安装 Python3(我懒得安装了),所以就用 Python2 写了这个 UDP 服务端
import socket
address = ('', 31500)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(address)
while True:
data, addr = s.recvfrom(2048)
if not data:
print "client has exist"
break
print "received:", data, "from", addr
s.close()
在没有运行 send_arp 之前,A 的 ARP 缓存如下:
A 发送给 B 的消息能够正常送达。
在主机 B 上运行 send_arp 之后,A 的 ARP 缓存发生了变化:
而且 A 发送给 B 的消息也无法送达了。
但是如果这个时候你在 C 上运行 UDP 服务端,你会发现 C 也收不到 A 发送的消息。
这是怎么回事呢?其实这个时候 A 发送的消息是到达了 C 的,只不过这个消息在经过网络层的时候被丢弃了。要想把这件事解释清楚,就不得不提到 TCP/IP 的四层网络模型。大家都知道,TCP/IP 的四层网络模型自下而上包括:网络接口层、网际层、传输层、应用层,主机发送消息时,是从上往下发,每经过一层,都要在消息体前面加上当前层的报头信息。比如经过网际层时,要在消息体前面加上源 IP 地址和目标 IP 地址等信息,经过网络接口层时,要在消息体前面加上源 MAC 地址和目标 MAC 地址等信息。
主机接收消息时,正好反过来,是从下往上接收。网络接口层最先收到消息,然后他会验证消息头的目标 MAC 地址是否是本机的 MAC 地址,如果是,就将消息发送给上一层,也就是网际层。网际层收到消息后,会验证目标 IP 地址是否是本机的 IP 地址,如果是,就将消息发送给传输层,否则就丢弃。
C 接受到的 A 发送过来的消息结构大概是这样的:
当这个消息经过网际层的时候,网际层发现这个消息的目标 IP 地址是 192.168.133.140,而当前主机的 IP 地址是 192.168.133.141,所以就把这个消息丢弃了。我们用 Python 写的 UDP 服务端是工作于应用层的程序,应用层的数据来自传输层,但是数据在网际层就被丢弃了,所以没有显示出来。
我在 recv.c 中选择了只接收源 IP 地址是 192.168.133.1,目标 IP 地址是 192.168.133.140 的 UDP 报文,理论上,在实施 ARP 攻击之后,A 发送给 B 的一切数据都会发送到 C 这里,无论是 UDP 还是 TCP,感兴趣的读者可以自行对 recv.c 进行更改,拦截其他数据。
在我写的 recv.c 中,拦截数据之后只是简单地打印出来,实际上这样做的话 B 会很快发现自己收不到 A 的数据了,从而发现隐藏的攻击者。根据不同的目的,我们在拦截数据之后,可以选择不同的处理方式,比如将数据再次转发给 C(甚至可以做一些篡改再转发)。
除了上述的几点简化,本次实验还有有待完善的地方。仔细观察你会发现,A 在局域网中其实是一个网关。在实验过程中,我尝试过把 C 的攻击对象改为 B,也就是让 B 认为 A 的 MAC 地址是 00:0C:29:80:44:D4,然后查看 B 的 ARP 缓存,发现攻击生效了。但是当 B 给 A 发送数据的时候,我发现 A 仍能接收到 B 发送的数据。这一点我始终没想明白是为什么,为什么攻击网关可以生效,而攻击局域网中的其他主机不能生效呢?希望知道真相的读者可以为我指点迷津,谢谢!