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

python + dpkgでpcap解析するチュートリアル。今回はファイルの読み込みからごく単純な解析までを紹介する。

はじめに

以前から小規模ではあるが、pythonでパケット分析をするためのモジュールdpkt http://code.google.com/p/dpkt/ を使用している。
元々dokuwikiの方には少しだけ書いているのだが、整理がてらblogにも書いてみる。

今回は以下の2つの方法について紹介する。

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

ソース見れば分かる人はBitbucketへどうぞ。 https://bitbucket.org/ainoniwa/dpkt-tutorial/

インストール

windows、Linuxのいずれも使い方は変わらないはずだ。
手元の環境は windows XP/7 + Python 2.7.3 だが、好きにすればいいだろう。

dpkt自体は、

1
easy_install dpkt

でインストールする方法と http://code.google.com/p/dpkt/ からダウンロードしてきて

1
2
python setup.py config
python setup.py install

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

dpkt自体には若干信用出来ない部分もあるため、改修を加える必要に迫られるかもしれない。
その場合は、後者の方法で書き換えたほうが実害が少ない…ように思うが、それも個人の好みかもしれない。

pcapファイルの読み込み

さて、丁度いい例題が思い浮かばないが、適当に動くコードを見繕ってみる。
解析対象には wireshark wikiから拝借したpcapファイル を使おう。

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# -*- 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内のパケットの単純な解析

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# -*- 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
2
3
>>>
1.1.23.3 to 1.1.12.1 : 18695 [Byte]
1.1.12.1 to 1.1.23.3 : 92582 [Byte]

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

このような形で、pcapファイルを開き、そのバイナリデータを各プロトコルで扱いやすい形でパースして利用することが出来る。
注意したいのは dpkt.ethernet.Ethernet(buf) としてバイナリデータのパースを開始すると、可能な範囲でデコードが行われる。
その後の処理で、

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

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

終わり

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

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

Hugo で構築されています。
テーマ StackJimmy によって設計されています。