免责声明:以下内容仅供用于研究网络协议,请勿利用其制作黑客工具,如利用其制作黑客工具,本人不代表其任何责任

文字较多,请慢慢看(拒绝一目十行)

协议概述

ARP 协议又称地址解析协议,是网络层协议,负责将某个 IP 地址解析成其对应的 MAC 地址

一台主机和另一台主机通讯,需要知道目标的 IP 地址,但是在局域网中传输数据的网卡却不能直接识别 IP 地址,所以使用 ARP 协议将 IP 地址解析成 MAC 地址。ARP 协议的基本功能就是通过目标设备的 IP 地址,来查询目标设备的 MAC 地址

在局域网的任意一台主机中,都有一个 ARP 缓存表,里面保存着本机已知的此局域网中各主机和路由器的 IP 和 MAC 的对照关系

协议缺陷

ARP 协议是建立在信任局域网内的所有节点的基础上的,他的效率很高,但是不安全。他是无状态协议。他不会检查自己是否发送过请求包,也不知道自己是否发送过请求包。他也不管是否是合法的应答,只要收到目标 MAC 地址是自己的 ARP 返回或者 ARP 广播包(包括 ARP 回复和 ARP 请求),都会接受并缓存

攻击原理

ARP 欺骗建立在局域网主机相互信任的基础上的。当 A 发广播询问:我想知道 IP 是 192.168.1.100 的 MAC 地址是多少?此时 B 当然回话:我是 IP 192.168.1.100,我的 MAC 地址是 mac-b,可是此时的 C 非法回复了:我是 192.168.1.100 的,我的 MAC 地址是 mac-c。而且是大量的,所以 A 就会误信 192.168.1.100 的 MAC 地址是 mac-c,而且动态的更新缓存表,这样主机 C 就劫持了主机 A 发送给主机 B 的数据,这就是 ARP 欺骗的过程

假如 C 直接冒充网关,此时主机 C 会不停的发送 ARP 欺骗广播,大声说:我是 192.168.1.1,我的 MAC 地址是:mac-c,此时局域网内的所有主机都被欺骗,更改自己的缓存表,此时 C 将会监听到整个局域网发送给互联网的数据包

攻击病状

通常表现:

  • 打开网页非常慢,甚至打不开
  • 提示 IP 地址冲突
  • 校园网网络瘫痪

攻击形式

协议内部分析

假冒回复包

向某个目标不停的发送 ARP 回复包

当目标收到后会立刻更新 ARP 缓存表,如果 ARP 缓存表被恶意更改,将会导致数据被传输至一个错误的 MAC 地址上,攻击者可以利用这样来拦截目标 IP 的返回数据,甚至于直接切断其网络连接

假冒请求包

向某个目标不停的发送 ARP 请求包

当目标收到后会做出回复,如果在 ARP 请求包中伪造来源地址的话,可以造成一定程度的 资源消耗/网络瘫痪

中间人攻击

启用包转发并向目标和网关发送假冒的 ARP 回复,由于 ARP 缓存的更新机制,需要做周期性的 ARP 欺骗

这样可以从中拦截目标的 请求/返回 数据,例如:捕获 HTTP 明文账号密码

影响网络连接流畅性

恶意修改网关缓存表

不停的发对网关发送 ARP 回复,让网关更新错误的 MAC 地址,导致网关把数据发送给错误的 MAC 地址

伪造内网网关

通过伪造 ARP 回复冒充网关,局域网内发送的数据无法到达真正的网关,导致内网中的计算机断网

攻击方案

在 Kali Linux 上使用 arpspoof 工具

1
$ arpspoof -i eth0 -t 172.19.28.254 172.19.28.1

以上命令将会在网卡 eth0 上向目标 172.19.28.254 发送 172.19.28.1 位于你 eth0 网卡 MAC 地址的回复包

在 Python 上使用 Scapy 类库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from scapy.all import *

"""
参数解释:
op 标记类型,这里是回复包
hwsrc 来源 MAC 地址
psrc 来源 IP 地址
hwdst 目标 MAC 地址
pdst 目标 IP 地址
"""
# 生成一个 ARP 回复包
pkt = Ether() / ARP(op = 'is-at', hwsrc = 'aa:aa:aa:aa:aa:aa', psrc = '172.19.28.1', hwdst = 'bb:bb:bb:bb:bb:bb', pdst = '172.19.28.254')
# 操作网卡 eth0 在 OSI 第 2 层发送数据包
sendp(pkt, iface = 'eth0')

防御方案

锁定本地上的缓存表

方案一

  1. 将 192.168.10.1 绑定到 00-00-00-00-00-01 上 arp -s 192.168.10.1 00-00-00-00-00-01
  2. 检查目标是被标注为静态:arp -a

方案二

  1. 查看网卡 idx 值 netsh i i show in,这是 netsh interface ipv4 show interfaces 的缩写
  2. 绑定:netsh -c "i i" add ne 18 192.168.10.1 00-00-00-00-00-01,18 是网卡的 idx 值

锁定网关上的缓存表

使用 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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
#define HAVE_REMOTE

#include <iostream>
#include <sstream>
#include <string>
#include <vector>
#include <WinSock2.h>
#include <pcap.h>
#include <remote-ext.h>

#pragma comment(lib, "ws2_32.lib")

using namespace std;

vector<char *> adapter;
u_char packet[] = {
// 以太网
/* 0 - 5 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // 目标 MAC 地址:00:00:00:00:00:01
/* 6 - 11 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // 来源 MAC 地址:00:00:00:00:00:02
/* 12 - 13 */ 0x08, 0x06, // 类型:ARP

// ARP
/* 14 - 15 */ 0x00, 0x01, // 硬件类型:以太网
/* 16 - 17 */ 0x08, 0x00, // 协议类型:IPv4
/* 18 */ 0x06, // 硬件大小:6
/* 19 */ 0x04, // 协议大小:4
/* 20 - 21 */ 0x00, 0x02, // 操作:回复
/* 22 - 27 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, // 发送者 MAC 地址:00:00:00:00:00:02
/* 28 - 31 */ 0xc0, 0xa8, 0x0a, 0x01, // 发送者 IP 地址:192.168.10.1
/* 32 - 37 */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, // 目标 MAC 地址:00:00:00:00:00:01
/* 38 - 41 */ 0xc0, 0xa8, 0x0a, 0xfd, // 目标 IP 地址:192.168.10.253
};

int print_device()
{
char errorBuffer[PCAP_ERRBUF_SIZE];

pcap_if_t *allDevice;
if (pcap_findalldevs_ex((char *)PCAP_SRC_IF_STRING, NULL, &allDevice, errorBuffer) == -1)
{
cout << "在 pcap_findalldevs_ex 中发生错误:" << endl << errorBuffer;
return 0;
}

int i = 0;
pcap_if_t *device;
for (device = allDevice; device != NULL; device = device->next)
{
try
{
string deviceDescription = device->description;
deviceDescription = deviceDescription.substr(17, deviceDescription.length() - 17);
deviceDescription = deviceDescription.substr(0, deviceDescription.find('\''));

adapter.push_back(device->name);

cout << ++i << ". " << deviceDescription.c_str() << endl;
}
catch (exception)
{
cout << ++i << ". 发生错误,无法获取适配器详情" << endl;
}
}

pcap_freealldevs(allDevice);
return i;
}

void init_packet()
{
// 初始化目标 MAC 地址
packet[0] = packet[32] = 0x00;
packet[1] = packet[33] = 0x00;
packet[2] = packet[34] = 0x00;
packet[3] = packet[35] = 0x00;
packet[4] = packet[36] = 0x00;
packet[5] = packet[37] = 0x00;

// 初始化来源 MAC 地址
packet[6] = packet[22] = 0x00;
packet[7] = packet[23] = 0x00;
packet[8] = packet[24] = 0x00;
packet[9] = packet[25] = 0x00;
packet[10] = packet[26] = 0x00;
packet[11] = packet[27] = 0x02;

// 初始化来源 IP 地址
packet[28] = 0xc0; // 192
packet[29] = 0xa8; // 168
packet[30] = 0x0a; // 10
packet[31] = 0x01; // 1

// 初始化目标 IP 地址
packet[38] = 0xc0; // 192
packet[39] = 0xa8; // 168
packet[40] = 0x0a; // 10
packet[41] = 0xfd; // 253
}

int main(int argc, char *argv[])
{
system("chcp 936 > nul 2>&1");

if (print_device() > 0)
{
int adapterNumber;
char adapterNumberChar[1024];
cout << "请选择网卡(一般选择这样的:Realtek PCIe GbE Family Controller) => ";
cin.getline(adapterNumberChar, 1024);
adapterNumber = atoi(adapterNumberChar) - 1;

char errorBuffer[PCAP_ERRBUF_SIZE];
pcap_t *adapterSock;
if ((adapterSock = pcap_open(adapter[adapterNumber], 100, PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, errorBuffer)) == NULL)
{
cout << "请检查您是否安装了 WinPcap 依赖库" << endl;
}
else
{
init_packet();

int i = 0;
while (true)
{
if (pcap_sendpacket(adapterSock, packet, sizeof(packet)) != 0)
{
cout << "\r在发送第 " << ++i << " 个数据包时发生错误:" << endl << pcap_geterr(adapterSock);
break;
}
else
{
cout << "\r" << ++i << " 个数据包已发送";
}
}
}
}
else
{
cout << "请检查您是否安装了 WinPcap 依赖库" << endl;
}

cin.get();
return 0;
}

使用 Python 实现

原理同上,Python 的玩意吃多点 CPU,有条件建议上 C++ 的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from scapy.all import *

try:
input = raw_input
except:
pass

try:
show_interfaces()
except:
pass

ethernet = input('请选择网卡 => ')

pkt = Ether() / ARP(op = 'is-at', hwsrc = '00:00:00:00:00:02', psrc = '192.168.10.253', hwdst = '00:00:00:00:00:01', pdst = '192.168.10.1')
while 1:
sendp(pkt, iface = ethernet, count = 100, verbose = False)