========================================== The Shadow Penguin Documents. No. 19 - Snifferの原理 - Written by UNYUN Apr.14, 1999 ========================================== 1. はじめに パケットモニタリングとは、ネットワーク内を流れるパケットを監視することであり、ネットワークエンジ ニアがネットワークの診断を行う場合などは有効な手段です。しかし、このパケットモニタリングは使い方次 第では、不正進入者にも非常に有効な代物となります。このパケットモニタリングを利用した攻撃Sniffingの 原理を具体的に解説します。また、PenguinToolboxにある「GreedyDog」の使用法も解説します。なお、このド キュメントに掲載されているプログラム、およびsimpleSniffは、TurboLINUX3.0で動作確認しています。 2. パケットモニタリングの基本 スイッチで切られていない同一ネットワーク内は、各ホストが吐き出す色々なパケットがだらだらの流れてい ます。通常は、NICがパケットのヘッダを監視して、自分のホスト宛以外のパケットは片っ端からデータリンク 層レベルで落として行くので、ネットワーク層、トランスポート層、アプリケーション層などにはパケットは 渡ってきません。つまり、自分のホスト宛以外のパケットを覗くことはできないのです。しかし、NICをいわゆ る「プロミスキャスモード」で動作させると、すべてのパケットをアプリで拾うことができます。TCP-DUMPや sniffer等は、この基本原理で動作しています。 現在、通常のFTP、pop、telnetなどのプロトコルは、クリアテキストのパケットがそのままネットワーク内を 流れています。つまりネットワーク内の一台のマシンでパケットモニタリングを行うだけで、そのネットワー ク内で誰かがtelnetなんかを使うと、そのIDやパスワード、実行コマンド等を捕まえることができるわけで す。もちろん、他人のメールを勝手に読んだり、チャットを覗き見したりできますね。暗号化が施されていな いパケットは何でも覗き放題となってしまいます。パケットモニタリングソフトでも、こういったアングラな 用途に特化して作られた(?)のがsnifferです。 このsnifferの原理を理解するキーワードは、先ほど述べたNICの「プロミスキャスモード」、および「RAWソ ケット」です。これらの詳細な解説は、PenguinAcademyに掲載予定です。ここでは、snifferの動作原理の理 解に重点を置いて解説します。 3. 非プロミスキャスモード・sniffer とりあえず、最も簡単なsnifferを紹介します。 /*------------------------< non-promisc-sniffer.c Programmed by UNYUN >-----------------------*/ #include #include #include #include #include #define SNIF_PORT 21 /* モニタリング対象ポート */ void printable_fwrite(data,len,fp) char *data; int len; FILE *fp; { int i; for (i=0;idest) == SNIF_PORT){ length-=sizeof(struct ip)-sizeof(struct tcphdr); memcpy(&ia,&(ip_header->ip_src),sizeof(struct in_addr)); strcpy(sourceIP,(char *)inet_ntoa(ia)); printf("¥n¥n>¥n"); printf("From %s¥n",sourceIP); printable_fwrite(recvbuf+sizeof(struct ip)+sizeof(struct tcphdr), length,stdout); } } } return(0); } /*--------------------------------------------------------< END >-----------------------------*/ このsnifferは、プロミスキャスモードをサポートしていませんので、このsnifferを動作させているホスト 宛に送られてきたパケットのみを捕まえます。この例では、ポート21(FTP)に送られてくる表示可能なTCPパケ ットを画面表示しています。通常、ポート21のTCPパケットは、ftpのデーモンが拾って処理するので、 sock=socket(AF_INET,SOCK_STREAM,0); でソケットを作ってポート21でbindしようとすると、ftpdが動いている状態だとbind()が失敗します。つま り、STREAM型ソケットだとftpdにナイショでパケットを捕まえることはできません。そこで、RAWソケットを 使うわけですが、RAWソケットはその性質上、root権限でしか扱うことができません(ここで解説する各種プロ グラムはroot権限で実行してください)。RAWソケットは次のような方法で作成します。 sock=socket(AF_INET,SOCK_RAW,IPPROTO_TCP); このような方法で作成されたソケットsockから読み出したデータは、IPヘッダ、TCPヘッダを含みます。デー タの読み出しは、 FD_ZERO(&read_fd); FD_SET(sock,&read_fd); select(FD_SETSIZE,&read_fd,NULL,NULL,NULL); for (;;){ if (FD_ISSET(sock,&read_fd)){ target_info_len = sizeof(target_info); length=recvfrom(sock,recvbuf,5000,0,(struct sockaddr *)&target_info,&target_info_len); } } といった方法が一般的でしょう。 この方法で読み込んだデータは、以下のようになっています。 オフセット データ長 内容 -------------------------------------------------------- (IPヘッダ) 0 4bit IPのバージョン(通常4:Version4) 4bit IPヘッダ長(通常5:20byte) 1 3bit データ優先順位(通常0:ルーチン) 5bit サービスタイプ(通常0) 2 2byte 全IP長 4 2byte データグラムID番号 6 2byte フラグメント領域 8 1byte 生存時間 9 1byte 運ぶプロトコル(TCPの場合:6) 10 2byte ヘッダチェックサム 12 2byte 送信元アドレス 14 2byte 宛先アドレス (TCPヘッダ) 16 2byte 送信元ポート 18 2byte 宛先ポート 20 4byte 送信元シーケンス番号 24 4byte 確認応答シーケンス番号 28 4bit TCPヘッダ長(通常:5) 4bit 予約 29 1byte セッションフラグ 30 2byte ウインドウサイズ 32 2byte チェックサム 34 2byte 緊急データポインタ (データ) 36〜 n byte TCPデータ Table 1. RAWソケットより取り込んだデータフォーマット このデータ構造を簡単に扱うため、UNIXではip.h、tcp.h等のヘッダファイルが提供されており、そのヘッダ ファイル内には、struct ip、struct tcp等、これらフォーマットを表す構造体が定義されています。 recvbufに読み込んだパケットの先頭はipヘッダの先頭であるため、 struct ip *ip_header; struct tcphdr *tcp_header; ip_header = (struct ip *)recvbuf; tcp_header = (struct tcphdr *)(recvbuf+sizeof(struct iphdr)); として、IPヘッダip_header、TCPヘッダtcp_headerを定義します。 拾ったパケットの中で、FTPパケットだけを抽出したい場合は、宛先ポートが21になっていないパケットを弾 くようにすればいいので、 if (ntohs(tcp_header->dest) == 21){ /* 拾ったパケットはFTPパケット */ } のようにすれば、ポート21でlistenしているftpdへのパケットを抽出することができます。 このパケットで、アプリケーション層に渡るべきデータは、recvbufの先頭アドレスに、IPヘッダ長、TCPヘッ ダ長をオフセットとして加算したアドレスとなります。あとは、そこからのデータを画面表示すればいいわけ です。 4. プロミスキャスモードの簡単sniffer プロミスキャスモードをサポートした、簡単なsniffer、SimpleSniffの原理を紹介します。 PenguinToolboxにSimpleSniffのソースがあるのでダウンロードしてください。 基本的には、先ほどのnon-promisc-sniffer.cと原理は同じですが、プロミスキャスモードで動作するので、 同一ネットワーク内を流れている、自ホスト宛でないパケットも拾ってくることができます。 NICをプロミスキャスモードで動作させるためには、NICに対してその旨を通知する必要があります。 int sock; struct ifreq f; sock=socket(AF_INET,SOCK_PACKET,htons(ETH_P_ALL)); strcpy(f.ifr_name, "eth0"); ioctl(sock,SIOCGIFFLAGS,&f); f.ifr_flags |= IFF_PROMISC; ioctl(sock,SIOCSIFFLAGS,&f); これで、NIC"eth0"はプロミスキャスモードで動作します。 /sbin/ifconfig eth0 で確認すると、 eth0 Link encap:Ethernet HWaddr 00:60:61:62:63:64 inet addr:100.101.102.103 Bcast:100.101.102.255 Mask:255.255.255.0 UP BROADCAST RUNNING PROMISC MULTICAST MTU:1500 Metric:1 RX packets:18000000 errors:48 dropped:0 overruns:0 frame:48 TX packets:2800000 errors:28 dropped:0 overruns:0 carrier:1 coll:100000 Interrupt:5 Base address:0x7000 のように、PROMISCと表示されます。 sock=socket(AF_INET,SOCK_PACKET,htons(ETH_P_ALL)); の、SOCK_PACKETですが、これはデバイスレベルでパケットを取得するというソケットタイプの指定であり、LINUX 固有(他にもあるかもしれませんけど)の方法です。socket()のプロトコル指定、htons(ETH_P_ALL)は、「すべて のパケット」という指定です。他のSOCK_PACKETで指定できるプロトコルタイプは、 /usr/include/linux/if_ether.hに記載されていますので、興味のある方は参照してください。 NICの制御は、 ifreq構造体とioctl()を用いて行います。ifreq構造体は、インタフェースリクエスト構造体であり、ifr_name にNICの名前を入れ、フラグ状態の取得を示すSIOCGIFFLAGSとifreqデータのポインタを引数として、デバイ スsockに対してデバイスコントロールをioctl()により行います。これで、現在NICで設定されているインタ フェースフラグを取得することができます。このインタフェースフラグは色々なものが用意されており、 /usr/include/linux/if.hに詳細が記載されています。ここで重要なのはIFF_PROMISC(0x100)であり、PROMISC ビットがセットされてると、NICはプロミスキャスモードで動作します。取得したインタフェースフラグの PROMISCビットをセットし、今度はフラグセットを示すSIOCSIFFLAGSでioctl()を行い、NICをプロミスキャス モードにスイッチさせます。 あとは、 read(sock,packet,sizeof(packet)); 等で、デバイスsockからデータポインタpacketへパケットを拾ってくることができます。拾ってきたパケッ トはイーサーネットヘッダが先頭についていますので、IPヘッダのポインタは、packet+14となります。 5. GreedyDog telnet、rlogin、pop、ftp等の全パケットをロギングできるLINUX、SunOS4用snifferです。全部ロギングす るので、踏み台なサーバに仕掛けると、ずっと先のサーバのアドレスやパスワード、rootshellの在処やクラッ ク過程等も全てロギングできます。管理者のヒトも、侵入者にローカルのログを消されても証拠をばっちり確 保できますんで、いいかもしれませんね。 何はともあれ、コンパイルです。 一応、TurboLINUX3.0と、SunOS4.1.3 for Sparcでコンパイル&動作確認しています。 LINUXは、 gcc gdd.c -DLINUX (Tested on TURBO-LINUX 3) SunOS4は、 cc gdd.c -DSUNOS4 (Tested on SunOS 4.1.3) のようにコンパイルしてください。 コンパイルが正常に通れば、rootになって、 #./a.out & で常駐させます。 これで、カレントディレクトリにsnif.logというファイルが作成され、このファイルにログが残ります。ログ ファイルの名前やディレクトリを変更したい場合は、ソース中の #define LOGFILE "./snif.log" を変更してください。 また、正常に動作しない場合は、NICのデバイス名が通常と異なる可能性があります。ifconfigコマンドでNIC の名前を確認し、 LINUXの場合 #define DEFAULT_NIC "eth0" SunOSの場合 #define DEFAULT_NIC "le0" を変更してください。 6. さいごに 以上、snifferの基本的な原理について解説しました。一応、Stealthスキャンに続くパケットネタの学習教材 の第2版(笑)ということですが、バッファリング関連などは次回(?)にします。一応、パケットネタはシリーズ としてしばらく続ける予定です。 7. 参考文献 [1] Thamer Al-Herbish, "Raw IP Networking FAQ", Whitefang (http://www.whitefang.com/). [2] Humble, "LinSniffer 0.666", rhino9