RawSocketで簡単なパケットモニタを作る方法
たまにはハッカーっぽく、Binary 2.0っぽい記事を書こう。
Windows 2000/XPでは、RawSocketという仕組みが用意されていて、パケットをかなり自由に受信したり・送信することができます。
RawSocketでは、いわゆる「生パケット」が扱えるのです。
悪い言い方をすると、パケットを覗きみたり、パケットを偽装することも可能ということになります。
以前までは、ドライバレベルでコーディングしないとできなかったようなのですが、WinSock 2.2以降では、これがとても簡単にできます。
今日は、これを利用して簡単なパケットモニタツールを作ってみようと思います。
ビルドは cygwin+mingwです。
大きな流れは以下のとおりです。
- Socketの初期化
- IPアドレスの取得(必須ではありません)
- バインド
- プロミスキャスモードに設定
- 受信
では1つずつ見ていきましょう
1.Socketの初期化
// Socket初期化 if (INVALID_SOCKET == (this->socket = ::socket(AF_INET, SOCK_RAW, IPPROTO_IP))) { TRACE("INVALID_SOCKET\n"); return false; }
2. IPアドレスの取得
Windowsは、複数のIPアドレスを持っている可能性があるので、そのIPアドレスを列挙します。
なお今回は、1個目に見つかったアドレスを使用するようにしています。
GUIであれば、IPアドレスを選択できるようにするのもよいでしょう。
SOCKET_ADDRESS_LIST* addressList; DWORD d; char buffer[1024]; // アダプタの列挙 if (WSAIoctl(this->socket, SIO_ADDRESS_LIST_QUERY, NULL, 0, buffer, 1024, &d, NULL, NULL) != 0) { TRACE("INVALID_SOCKET\n"); return 0; } addressList = (SOCKET_ADDRESS_LIST*)buffer; if (addressList->iAddressCount == 0) { TRACE("address not found\n"); return 0; } // 1番目のアドレスを使う(手抜き) return ((SOCKADDR_IN*)addressList->Address[0].lpSockaddr)->sin_addr.s_addr;
3.バインド
2.で取得したアドレスを元にbind()
// bind SOCKADDR_IN addr_in; addr_in.sin_addr.s_addr = this->GetIPAddress(); // IPアドレス addr_in.sin_family = AF_INET; // IPv4 addr_in.sin_port = htons(0); // 0で初期化らしい if (SOCKET_ERROR == bind(this->socket, (SOCKADDR*)&addr_in, sizeof(addr_in))) { TRACE("bind error\n"); return false; }
4.プロミスキャスモードに設定
NIC(ネットワークインターフェースカード)は、通常自分宛でないパケットは破棄するようになっていますが、プロミスキャスモードという動作モードに設定可能で、これをすると自分宛以外のパケットも受信できるようになります。
今回はパケットモニターを作っているのでこのモードに設定します。
// プロミスキャスモードの設定 #define SIO_RCVALL _WSAIOW(IOC_VENDOR, 1) unsigned long optval=1; //PROMISC if (SOCKET_ERROR != WSAIoctl(this->socket, SIO_RCVALL, &optval, sizeof(optval), NULL, 0, &d, NULL, NULL)) { TRACE("SIO_RCVALL\n"); return false; }
5.受信
いよいよ受信です。WSARecv関数を使用して受信します。
なお今回はブロックモードで受信しています。
ブロックモードとは、パケットがくるまでスレッドがブロックされるモードのことです。
パケット到着まで他の作業はできません。
unsigned long length; unsigned long flags = 0; WSABUF wsb; wsb.buf = (PTCHAR)this->readBuffer; wsb.len = MAX_RECV_SIZE; ZeroMemory(wsb.buf, wsb.len); if (SOCKET_ERROR == WSARecv(this->socket, &wsb, 1, &length, &flags, NULL, NULL)) { TRACE("RECIEVE ERROR\n"); return false; }
パケットモニタ
上記手順を踏めば、ほんの100行足らずでパケットを受信することができてしまいます。
今回は、この処理を抽象化して以下のように使えるようにしてみました。
RawSocketクラスのインスタンスに対して、Open/Read/Closeするだけです。
int main(int argc, char *argv[]) { byte buffer[1024]; dword length; RawSocket socket; if (!socket.Open()) { printf("Socket Open Error\n"); return -1; } for (int i = 0; i < 3; i++) { printf("\nWaiting for Packet...\n"); socket.Read(buffer, 1024, &length); IP::Header* header = (IP::Header*)buffer; printf("protocol number %x\n", header->prot); printf("source address "); printAddress(header->srcip); printf("dest address "); printAddress(header->dstip); printf("data "); for (int j = 0; j < 10; j++) { printf("%02x ", header->data[j]); } printf("\n"); } if (!socket.Close()) { printf("Socket Close Error\n"); return -1; } return 0; }
実際に動作させると
Waiting for Packet... protocol number 1 source address 192.168.11.2 dest address 192.168.11.2 data 00 00 61 ffffffce 6c 0e 05 00 3c ffffff86
こんな感じでパケットの中身を読むことができます。
プロトコルごとにさらに詳しく解析するといろいろと楽しいことができそうですね。
ソースとか
ソースと実行ファイルを置いておきます。
使用は自己責任でお願いします。実行ファイルは管理者権限でないと動作しないと思います。
rawsocket.zip
資料
はてなブックマーク - winsockに関するhigeponのブックマーク
- 作者: 竹下隆史,村山公保,荒井透,苅田幸雄
- 出版社/メーカー: オーム社
- 発売日: 2002/02/26
- メディア: 単行本
- 購入: 4人 クリック: 76回
- この商品を含むブログ (136件) を見る