Scapyでpcapファイルからデータを抽出する

scapyの勉強をしていた時にpcapファイルに含まれるファイルを全て抽出できないかと思い立ち、とりあえず作ってみました。その際の知識もを含めてメモとして残します。

#インストール

pipを使ってインストールが可能です。

```python:
pip3 install scapy
```

#基本編

下の構文でライブラリを読み込みます

```python:
from scapy.all import *
```

ファイルの読み込みは、rdpcapを使います。

```python:
p = rdpcap('test.pcap')
```

pcapデータを全て見る時は、showメソッドを使います。

```python:
p.show()
```

```:出力例
0000 Ether / IP / UDP / DNS Qry "b'www.qiita.com.'" 
0001 Ether / IP / UDP / DNS Ans "13.230.63.181" 
```

パケット1個1個の内容を見る時は、パケット番号を指定してshowメソッドを使います。

```python:
p[0].show()
```

```:出力例
###[ Ethernet ]### 
  dst       = xx:xx:xx:xx:xx:xx
  src       = xx:xx:xx:xx:xx:xx
  type      = IPv4
###[ IP ]### 
     version   = 4
     ihl       = 5
     ........
###[ UDP ]### 
        sport     = 59228
        dport     = domain
        ........
###[ DNS ]### 
           id        = 55997
           qr        = 0
           opcode    = QUERY
           ........
```

指定したプロトコルがパケットに入っているかを判別します。プロトコルが入っていれば`true`を返します。

```python:
'DNS' in p[0]
```

パケットから指定したプロトコルの内容を取り出します。

```python:
p[0]['DNS']
```

```:出力例
<DNS  id=55997 qr=0 opcode=QUERY aa=0 tc=0 rd=1 ra=0 z=0 ad=0 cd=0 rcode=ok qdcount=1 ancount=0 nscount=0 arcount=1 qd=<DNSQR  qname='www.qiita.com.' qtype=A qclass=IN |> an=None ns=None ar=<DNSRROPT  rrname='.' type=OPT rclass=512 extrcode=0 version=0 z=0 rdlen=None |> |>
```

パラメータは、下のような構文で取り出せます。

```:
p[0]['DNS'].qr
```

#応用編

DNS応答から名前解決した結果を取り出します。シノニムも元のFQDNにします。

```python:
p = rdpcap('test.pcap')
cnv = lambda x:x if type(x) == str else x.decode()
dnsdata = { cnv(x['DNSRR'][y].rdata):cnv(x['DNSRR'][y].rrname) for x in p if 'DNSRR' in x for y in range(x['DNS'].ancount) }
dns = lambda x:x if not x in dnsdata else dns(dnsdata[x])
print({ r:dns(a) for r,a in dnsdata.items() })
```

```python:出力例
{'13.230.63.181': 'www.qiita.com.', '54.92.35.84': 'www.qiita.com.', '13.113.37.17': 'www.qiita.com.'}
```

パケットのバイト数を、(送信元のIPアドレス,送信先のIPアドレス)毎に集計します。

```python:
p = rdpcap('test.pcap')
t = {}
for y in [ ((x['IP'].src,x['IP'].dst),len(x)) for x in p if 'IP' in x ]:
    t[y[0]] = y[1] if not y[0] in t else t[y[0]] + y[1]
print(t)
```

```python:出力例
{('192.168.99.99', '54.92.35.84'): 10984, ('54.92.35.84', '192.168.99.99'): 130283}
```

TCPのペイロードを、(送信元のIPアドレス,送信元のポート番号,送信先のIPアドレス,送信先のポート番号)毎にファイルとして取り出します。1つのTCPセッションで複数のファイルを送っていた場合は、1つのファイルに全てつながって入りますので、binwalkなどでファイル分割をしてください。

```python:
p = rdpcap('test.pcap')
cnv = lambda x:x if type(x) == str else x.decode()
dnsdata = { cnv(x['DNSRR'][y].rdata):cnv(x['DNSRR'][y].rrname) for x in p if 'DNSRR' in x for y in range(x['DNS'].ancount) }
dns = lambda x:x if not x in dnsdata else dns(dnsdata[x])
t = {}
for y in [ ((x['IP'].src,x['TCP'].sport,x['IP'].dst,x['TCP'].dport),[i,x['Raw'].load]) for i,x in enumerate(p) if ('TCP' in x) and ('Raw' in x) ]:
    t[y[0]] = y[1] if not y[0] in t else [t[y[0]][0],t[y[0]][1] + y[1][1]]
    with open('summary.txt','w') as f1:
        for k,v in t.items():
            n = str(v[0])+'.bin'
            f1.write(n+'  --  '+dns(k[0])+':'+str(k[1])+' -> '+dns(k[2])+':'+str(k[3])+'\n')
            with open(n,'wb') as f2:
                f2.write(v[1]) 
```

#さいごに

本当にとりあえず作ったものなので、使い方によっては望みのデータが取り出せない場合があるかもしれません。