python + dpkt で pcap解析 - 1.読み込みから解析開始まで

Posted on 2012/05/11(Fri) 00:24 in technical

以前から小規模ではあるが、pythonでパケット分析をするためのモジュールdpkt(http://code.google.com/p/dpkt/)を使用している。

元々dokuwikiの方には少しだけ書いているのだが、整理がてらblogにも書いてみる。

まずはこの2つ。

  • *.pcapファイルの読み込み
  • 読み込んだバイナリデータの簡単な解析

ソース見れば分かる人はBitbucketへどうぞ。

windows、Linuxのいずれも使い方は変わらないはずだ。

手元の環境は windows XP/7 + Python 2.7.3 だが、好きにすればいいだろう。

dpkt自体は、

easy_install dpkt

でインストールする方法と、

http://code.google.com/p/dpkt/ からダウンロードしてきて

python setup.py config
python setup.py install

でインストールする方法がある。

dpkt自体には若干信用出来ない部分もあるため、改修を加える必要に迫られるかもしれない。

その場合は、後者の方法で書き換えたほうが実害が少ない...ように思うが、それも個人の好みかもしれない。

さて、丁度いい例題が思い浮かばないが、適当に動くコードを見繕ってみる。

解析対象には、 wireshark wikiから拝借したpcapファイル を使おう。

pcapファイルを読み込んで、先頭パケットから最後のパケットまで、「フレームNo」「時間(1970/01/01 00:00:00.00からの経過時間)」「フレーム長(FCSの有無はpcapファイルに依存する)」を表示するだけのコード。

# -*- coding: utf-8 -*-
#!/usr/bin/env python

import dpkt

def main():
    filename = u'C:\\dev\\pcap\\tcp-ecn-sample.pcap'
    pcr = dpkt.pcap.Reader(open(filename,'rb'))
    packet_count = 0

    for ts,buf in pcr:
        packet_count += 1
        print packet_count, '. time: ', ts, 'Length:', len(buf)

if __name__ == '__main__':
    main()

非常に短いが、その役割は果たされているはず。

同じように、同pcapファイルに含まれるIP Flow(EthernetType/SrcAddress/DstAddressの3-tuple)を分類して、その転送量を表示するように書いてみる。

# -*- coding: utf-8 -*-
#!/usr/bin/env python

import dpkt
import socket

def main():
    filename = u'C:\\dev\\pcap\\tcp-ecn-sample.pcap'
    pcr = dpkt.pcap.Reader(open(filename,'rb'))

    packet_count = 0
    flow_list = {}

    for ts,buf in pcr:
        packet_count += 1
        try:
            eth = dpkt.ethernet.Ethernet(buf)
        except:
            print 'Fail parse FrameNo:', packet_count, '. skipped.'
            continue

        if type(eth.data) == dpkt.ip.IP:
            ip = eth.data
            src = socket.inet_ntoa(ip.src)
            dst = socket.inet_ntoa(ip.dst)
            flow_word = src + " to " + dst
            if flow_list.has_key(flow_word):
                flow_list[flow_word] += len(str(buf))
            else:
                flow_list[flow_word] = len(str(buf))

    for k,v in flow_list.iteritems():
        print k, ':', v, '[Byte]'

if __name__ == '__main__':
    main()

実行結果

>>>
1.1.23.3 to 1.1.12.1 : 18695 [Byte]
1.1.12.1 to 1.1.23.3 : 92582 [Byte]

この結果は、wiresharkのConversationsと一致することが確認できるはずだ。

image0

このような形で、pcapファイルを開き、そのバイナリデータを各プロトコルで扱いやすい形でパースして利用することが出来る。

注意したいのは、 dpkt.ethernet.Ethernet(buf) としてバイナリデータのパースを開始すると、可能な範囲でデコードが行われる。

その後の処理で、

ip = eth.data
src = socket.inet_ntoa(ip.src)

のようにして、 ip.src に即座にアクセス出来るのは、既にパースが完了しているからで、この場合の ip.src は、 eth.data.src のようにしてもアクセス出来る。

とりあえず読み込みから初歩解析まで。

一からのパケット生成、ファイルへの書き出しまでは後日。