SharpPcapを使ってパケットキャプチャ

なんかWinPcapが流行っているようで(チト違う(;゚Д゚))。
そういえばSharpPcapとかあったなということで、.NETからSharpPcapを使ってパケットレベルでの操作を行う方法について。


まずは必要なものと言うことで、下記をダウンロード(´∀`)

WinPcap
http://www.winpcap.org/install/default.htm
SharpPcap
http://sourceforge.net/projects/sharppcap/

WinPcapはインストールしておきます。
SharpPcapは解凍して、Visual Studioで作ったプロジェクトでSharpPcap.dllを参照設定。
SharpPcap.dllはwpcap.dllで公開されるAPIを使って、.NETから簡単にWinPcapを使用できるようにしたものです。


っということで、SharpPcapの使い方の基本について(・∀・)

デバイス/インターフェースの列挙

まずは情報の取得から。
デバイス/インターフェースの列挙方法はこんな感じ(・ω・)

using SharpPcap;

...
public static void Main(string[] args)
{
    // デバイス一覧列挙
    foreach( LivePcapDevice device in LivePcapDeviceList.Instance )
    {
        Debug.WriteLine( "FriendlyName: " + device.Interface.FriendlyName );
        Debug.WriteLine( "Name: " + device.Interface.Name );

        // インターフェース
        foreach( PcapAddress addr in device.Interface.Addresses )
        {
            Debug.WriteLine( "Addr     : " + addr.Addr );
            Debug.WriteLine( "Netmask  : " + addr.Netmask );
            Debug.WriteLine( "Broadaddr: " + addr.Broadaddr );
            Debug.WriteLine( "Dstaddr  : " + addr.Dstaddr );
        }

        Debug.WriteLine( "MacAddress: " + device.Interface.MacAddress );
        Debug.WriteLine( "Flags: " + device.Interface.Flags );
        Debug.WriteLine( "Description: " + device.Interface.Description );
    }
}

出力はこんな風になりりんこ(・ω・)

FriendlyName: ローカル エリア接続
Name: \Device\NPF_{9B6016DC-22F0-41A7-BAB4-9E8C68D8F403}
Addr     : 192.168.1.3
Netmask  : 255.255.255.0
Broadaddr: 255.255.255.255
Dstaddr  : 
Addr     : HW addr: 00219B19CCA3
Netmask  : 
Broadaddr: 
Dstaddr  : 
MacAddress: 00219B19CCA3
Flags: 0
Description: Realtek PCIe GBE Family Controller

っで、以降の処理では主にLivePcapDeviceクラスを使用します。

パケットキャプチャ

次にパケットのキャプチャ方法について。
以下のような処理だけで簡単にパケットのキャプチャが行えます(・∀・)

public static void Main(string[] args)
{
    LivePcapDevice device = LivePcapDeviceList.Instance[ 0 ];

    // ハンドラ設定
    device.OnPacketArrival += OnPacketArrival;

    // デバイスオープン
    int readTimeoutMilliseconds = 1000;
    device.Open( DeviceMode.Promiscuous, readTimeoutMilliseconds );

    // キャプチャ開始
    device.StartCapture();

    // 終了待ち
    Console.ReadLine();

    // キャプチャ停止
    device.StopCapture();

    // 統計情報
    Debug.WriteLine( device.Statistics().ToString() );

    device.Close();
}

// イベントハンドラ
private static void OnPacketArrival(object sender, CaptureEventArgs e)
{
    DateTime time = e.Packet.PcapHeader.Date;
    uint len = e.Packet.PcapHeader.PacketLength;
    Debug.WriteLine( String.Format( "{0}:{1}:{2},{3} Len={4}",
                                    time.Hour, time.Minute, time.Second, time.Millisecond, len ) );
    Debug.WriteLine( e.Packet.ToString() );
}

まずはキャプチャ対象のデバイスを決定して、パケットキャプチャのイベントハンドラを設定します。
後はデバイスをオープンしてStartCapture()でキャプチャを開始すれば、キャプチャを行うバックグラウンドスレッドからパケット毎にハンドラがコールバックされます。
ここでは単純にデバッグ出力するだけのイベントハンドラを使用しています。


っで、出力結果はこんな風になります。

9:49:23,183 Len=1031
[TCPPacket: 192.168.1.3.4158 -> 66.249.89.103.Http ack[0x933062ef] psh l=20,977][IPv4Packet: 192.168.1.3 -> 66.249.89.103 proto=TCP l=20,1017][EthernetPacket: 00219B19CCA3 -> 000D0241A65E proto=IpV4 (0x800) l=14]
9:49:23,245 Len=60
[TCPPacket: 66.249.89.103.Http -> 192.168.1.3.4158 ack[0x32351ae4] l=20,0][IPv4Packet: 66.249.89.103 -> 192.168.1.3 proto=TCP l=20,40][EthernetPacket: 000D0241A65E -> 00219B19CCA3 proto=IpV4 (0x800) l=14]
9:49:23,248 Len=1468
[TCPPacket: 66.249.89.103.Http -> 192.168.1.3.4158 ack[0x32351ae4] l=20,1414][IPv4Packet: 66.249.89.103 -> 192.168.1.3 proto=TCP l=20,1454][EthernetPacket: 000D0241A65E -> 00219B19CCA3 proto=IpV4 (0x800) l=14]
9:49:23,249 Len=1468
[TCPPacket: 66.249.89.103.Http -> 192.168.1.3.4158 ack[0x32351ae4] l=20,1414][IPv4Packet: 66.249.89.103 -> 192.168.1.3 proto=TCP l=20,1454][EthernetPacket: 000D0241A65E -> 00219B19CCA3 proto=IpV4 (0x800) l=14]
9:49:23,249 Len=54
[TCPPacket: 192.168.1.3.4158 -> 66.249.89.103.Http ack[0x93306dfb] l=20,0][IPv4Packet: 192.168.1.3 -> 66.249.89.103 proto=TCP l=20,40][EthernetPacket: 00219B19CCA3 -> 000D0241A65E proto=IpV4 (0x800) l=14]
9:49:23,249 Len=1468
...

また、キャプチャするパケットはフィルタを設定することが可能です(・ω・)
例えば次のようにフィルタを設定すれば、キャプチャ対象はIPとTCPのみとなり、ICMPなんかは拾わなくなります。

...
string filter = "ip and tcp";
device.SetFilter( filter );

// キャプチャ開始
device.StartCapture();
...

後は、LivePcapDevice.DumpOpen()を使用してキャプチャ内容のファイルへのダンプや、OfflinePcapDeviceクラスを使用してダンプ内容の再生なんかも行えます(・∀・)

パケットの種類毎の処理

パケットキャプチャでは単純なキャプチャ結果の出力だけを行いましたが、パケットの種類(TCPUDPIPv4等)に応じて処理を行う場合、CaptureEventArgsのPacketメンバの型をチェックします。
例えばTCPパケットに対して処理を行う場合、以下のようにします(・ω・)

if( e.Packet is TCPPacket )
{
    TCPPacket tcp = (TCPPacket)e.Packet;
    System.Net.IPAddress srcIp = tcp.SourceAddress;
    System.Net.IPAddress dstIp = tcp.DestinationAddress;
    int srcPort = tcp.SourcePort;
    int dstPort = tcp.DestinationPort;
...
}

SharpPcap.Packets名前空間にはTCPPacket、UDPPacket、IPv4Packet、ICMPPacket等が定義されているので、パケットの種類に応じた処理ではそれらを使用します(・∀・)

パケットの送信

パケットのキャプチャは出来たので、次は偽造^h^hパケットの送信方法について。
パケットの送信は以下のようになります。

public static void Main(string[] args)
{
    LivePcapDevice device = LivePcapDeviceList.Instance[0];

    device.Open();

    // パケット生成
    byte[] bytes = GetPacketBytes();

    // パケット送信
    device.SendPacket( bytes );

    device.Close();
}

パケットの中身をバイト配列として作成し、LivePcapDevice.SendPacket()に送信します。
なお、SendPacket()メソッドにはSendPacket(byte[] p)の他にSendPacket(Packet p)等の定義もあるので、バイト配列ではなくTCPPacketなんかを指定することも可能です。



っということで、これで誰でもパケットキャプチャ・偽造パケット送信ツールが作れますね(`・ω・´)
悪いことしちゃダメよ(´д`;)


まあ、今こういうことを書くのは若干どうなのよと思わなくもないですが(゚Д゚;)
もっとも、ネットワーク弄っている人なら普通の話だし、一般常識として知っておいて損はない内容なので書いてみました(・ω・)


最近は、お金を出すのでFTP専用のPCを買って置いてくれませんかみたいなことをお客さんから言ってきたりしてね(・ω・)
それよりFTPを止めろよと思うんだけど(´д`;)>某所