Scapyって知ってる？

# 概要
ScapyはPythonで記述されたコンピューターネットワーク用の**パケット生成ツール**である。今回はそのScapyを使用してのパケット作成から送信、Wiresharkでの確認までをまとめていく。


# 参考
https://scapy.net/

https://www.wireshark.org/

http://www5e.biglobe.ne.jp/aji/3min/

https://www.infraexpert.com/info/netspecial1.html

ネットワークの勉強をするに当たって、上記の二つにはかなりお世話になった。

# Scapyを触ってみる前にまず、そもそもプロトコルとは？
通信をする上で守らなければいけないお約束のこと。
各種のプロトコルはOSI参照モデルの７レイヤーによって以下のように分類されている。

|層（レイヤー）  　　　　　　　|名称  |規格（プロトコル）  |概要  　　　　　　　|利用例  |
|---|---|---|---|---|
|7  |アプリケーション層 　　　　　　　 |HTTP,FTP,DNS,SMTP,POPなど  |個々のアプリケーション  |www,メール  |
|6  |プレゼンテーション層  　　　　　　　　　　|SMTP,FTP,Telnetなど  |データの表現形式  |HTML  |
|5  |セッション層  |TLS,NetBIOSなど |通信手段 |HTTPS |
|4  |トランスポート層  |TCP,UDP,NetWare/IPなど  |エンド間の通信制御  |TCP,UDP |
|3  |ネットワーク層  |IP,ICMPなど  |データを送る相手を決め最適な経路で送信  |IP  |
|2  |データリンク層  |PPP,ARP,RARP,Ethernetなど  |隣接する機器同士の通信を実現  |Ethernet |
|1  |物理層  |RS-232,UTP,無線 |物理的な接続、電気信号  |UTPケーブル,光ファイバーケーブル |

# Scapyでのパケットの作り方
Scapyでパケットを作る上で、実は上記の７レイヤーが非常に重要になってくる。
実際パケットを作るのは物凄く簡単で、次のように`/`区切りにするだけなのだが順序が大事で、**低いレイヤーから順番に並べる必要がある。**`Ether()/IP()/TCP()`

# パケットの送受信
パケットの送受信についても７レイヤーが重要になっており、ここが少しトリッキーである。

- `send()`
レイヤー3でパケットを送信
- `sendp()`
レイヤー2でパケットを送信
- `sr()`
レイヤー3でパケットを送信し、レスポンスを返す（応答をすべて受信）
- `sr1()`
レイヤー3でパケットを送信し、その応答の1つ目をレスポンスとして返す
- `srp()`
レイヤー2でパケットを送信し、レスポンスを返す（応答をすべて受信）
- `srp1()`
レイヤー2にでパケットを送信し、その応答の1つ目をレスポンスとして返す
- `srflood()`
レイヤー3でパケットを送信し続ける
- `srpflood()`
レイヤー2でパケットを送信し続ける

※`p`がついてる関数はレイヤー2でパケットを送信

例えば、TCPパケットを作って送信したい場合、TCPは**レイヤー３**に位置するので`send`関数を使用して次のように送信することができる。`send(Ether()/IP()/TCP())`

ちなみに、間違ったレイヤーで送信しようとすると以下のような表示になり、いつまでも送信が完了しない。
```
Finished sending 1 packets.
..................................................................................................................................................................
```
成功していれば以下のようなログが出力される。
```
.
Sent 1 packets.
```

# 実装編
実際にパケットを作ってみる。

## 環境構築
```
# Pythonのバージョンを確認
python --version
> Python 3.7.3

# scapyのインストール（macOS）
> pip3 install --pre scapy

# Wiresharkのダウンロード（3.6.5）
https://www.wireshark.org/download.html
```

## ARPパケット

### `ARP`とは？
- Address Resolution Protocolの略であり、IPアドレスからEthernetのMACアドレスの情報を得られるプロトコル。

### 併せて覚えておきたい`ARPテーブル`とは？
- ARPによって割り出したIPアドレスとMACアドレスの対応表。
- 通信を行う度に毎回問い合わせを行うのは非効率なため、一度ARPで調べたデータはOSなどが専用の一時的な記憶領域に保管しておき、次に必要になったらそこから調べる。
- ARPテーブルは、`arp -a`コマンドで確認できる。
```
> arp -a
? (192.168.0.1) at f8:b7:97:f3:38:52 on en0 ifscope [ethernet]
? (192.168.0.3) at 76:88:93:fb:55:c6 on en0 ifscope [ethernet]
? (192.168.0.4) at 8:6d:41:be:6f:7c on en0 ifscope [ethernet]
```

### 実装
「192.168.0.101を持っているのは誰で、MACアドレスは何ですか？」とブロードキャストMACアドレス宛（ff:ff:ff:ff:ff:ff）に送信。　
```python
from scapy.all import *

####################
# IPv4 ARP Request #
####################

# 送信先のIPv4アドレス
DST_IP_V4 = "192.168.0.101"

def arp():
    pkt = Ether(dst="ff:ff:ff:ff:ff:ff") / ARP(op=1, pdst=DST_IP_V4)
    srp1(pkt)
```

## ICMPパケット

### `ICMP`とは？
- Internet Control Message Protocolの略であり、IPプロトコルの「エラー通知」や「制御メッセージ」を転送するためのプロトコル。
### 豆知識
- 皆さんお馴染みの`ping`はこのICMPプロトコルを使用したプログラム。
- `ping`が通らない問題が発生した場合、ファイアウォールがICMPプロトコルの送受信を制限している可能性があるので確認してみると良い。


### 実装
```python
from scapy.all import *

##########################
# IPv4 ICMP Echo Request #
##########################

# 送信先のIPv4アドレス
DST_IP_V4 = "192.168.0.101"

def icmp():
    pkt = Ether() / IP(dst=DST_IP_V4) / ICMP()
    srp1(pkt)
```

## SNMPパケット

### `SNMP`とは？
- Simple Network Management Protocolの略であり、ルータ、スイッチ、サーバなどTCP/IPネットワークに接続された通信機器に対し、ネットワーク経由で監視、制御するためのアプリケーション層プロトコル。

### 実装
```python
from scapy.all import *

#############
# IPv4 SNMP #
#############

# 送信先のIPv4アドレス
DST_IP_V4 = "192.168.0.101"

#dportは 161 で固定
def snmp():
    pkt = Ether() / IP(dst=DST_IP_V4)/UDP(dport=161)/SNMP(version=0,community="public",PDU=SNMPnext(varbindlist=[SNMPvarbind(oid=ASN1_OID("1.3.6.1.4.1.2699.1.2"))]))
    sendp(pkt)
```

## SSDPパケット
### `SSDP`とは？
- Simple Service Discovery Protocolの略であり、ネットワーク上の機器を自動的に発見・接続するUPnP（Universal Plug and Play）で用いられるプロトコル。

このパケットをScapyで作るには、payload部分を自作する必要がある。
### 実装
```python
from scapy.all import *

#############
# IPv4 SSDP #
#############

#送信元のIPv4アドレス
SRC_IP_V4 = "192.168.0.10" 

#dstは 239.255.255.250 で固定
#sport、dportは 1900 で固定
def ssdp():
    payload = "M-SEARCH * HTTP/1.1\r\n" \
    "Host: 239.255.255.250:1900\r\n" \
    "ST: upnp:rootdevice\r\n" \
    "Man:\"ssdp:discover\"\r\n" \
    "MX:1\r\n\r\n" \

    pkt = IP(src=SRC_IP_V4,dst="239.255.255.250") / UDP(sport=1900, dport=1900) / payload
    send(pkt)
```

## DNSパケット
### `DNS`とは？
- Domain Name Systemの略であり、ドメイン名（コンピュータを識別する名称）をIPアドレスに自動的に変換してくれるアプリケーション層プロトコル。

### 実装
```python
from scapy.all import *

############
# IPv4 DNS #
############

def dns():
    pkt = IP(dst="8.8.8.8") / UDP() / DNS(rd=1,qd=DNSQR(qname="www.google.com"))
    sr1(pkt)
```


## mDNS（Bonjour）パケット

### mDNSとは？
- Multicast Domain Name Serviceの略であり、ローカルネットワーク内でホスト名からIPアドレスを割り出すために用いられるプロトコル。

### 実装
```python
from scapy.all import *

######################
# IPv4 mDNS(Bonjour) #
######################

#送信先のNetBIOS名
NAME = "RNP583879001982"

#dstは 224.0.0.251 で固定
#sport、dportは 5353 で固定
def mdns():
    pkt = IP(dst="224.0.0.251") / UDP(sport=5353,dport=5353) / DNS(rd=1,qd=DNSQR(qname=NAME+".local", qtype='A'))
    send(pkt)
```

# Wiresharkでパケットキャプチャ
せっかくScapyでパケットを作ったので、ちゃんとそのパケットが飛んでいるのか確認してみる。

試しに上記で作成した`DNS`のパケットをキャプチャしてみると、ちゃんと飛んでいて更に応答が返ってきているのが分かる。面白い。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2684448/ed6290d4-968f-be86-dcdc-5717f518e4d1.png)



## 表示フィルター

以下の赤枠の小窓に、条件構文を直接入力することによってディスプレイをフィルタリングすることができる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2684448/0b30d212-f95d-a8da-2d54-1bf6cbfe37b5.png)

- **プロトコル**（以下など）
    - `tcp`
    - `udp`
    - `icmp`
    - `arp`
- **IPアドレス**
    -  送信元IP: `ip.src==xxx.xxx.xxx.xxx`
    -  送信先IP: `ip.dst==xxx.xxx.xxx.xxx`
    -  送信元/送信先両方: `ip.addr==xxx.xxx.xxx.xxx`
- **ポート番号**
    - TCP: `tcp.port==xxx`
    - UDP: `udp.port==xxx`
- **組み合わせ**
    - 例）`mdns&&ip.src==xxx.xxx.xxx.xxx`


### 豆知識
パケット解析などに用いられるWiresharkだが、**無線LAN**のキャプチャには実はWindowsは向かない。Macを使用すべき。
Windowsで無線LANをキャプチャする場合、米国Riverbed Technology社の`AirPcap`という製品が必要になるのだが、2017年に製造・販売が終了したので中古市場でも入手困難＋高額。一方でMacはAirPcap使用の必要がなく、Wiresharkのみで無線LANのキャプチャが可能だ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2684448/e803e646-55ed-07a3-9aed-c4f1f4a4642d.png)

https://support.riverbed.com/content/support/software/steelcentral-npm/airpcap.html

# 最後に
今回はScapyを使ってIPv4のパケットをいくつか作ってみたが、IPv6のパケットも作れるので今後そちらについても書いてみたいと思う。ネットワークモジュールなどのテスト（異常系パケットをわざと作って送ったり、大量のパケットを送りつけるなど）にもScapyは使用できるのでとても便利だ。