Scapy Tips

# 概要

scapyのTips集(順次追加)

## 公式ドキュメント

https://scapy.readthedocs.io/en/latest/

Scapy API reference
https://scapy.readthedocs.io/en/latest/api/scapy.html


# Tips
## Ethernetアダプタのインターフェース名を知りたい

`get_windows_if_list()`を使います。

各APIのパラメータにあるifaceに設定可能です。


**参考**

[Scapy can't see_use some Ethernet interfaces on Windows · Issue #1542 · secdev_scapy · GitHub](
https://webcache.googleusercontent.com/search?q=cache:qS0tKu17dBoJ:https://github.com/secdev/scapy/issues/1542)の gpotter2 commented on 7 Aug 2018dで書かれている


**コード**

```python
from scapy.all import *
get_windows_if_list()
```

**結果**

nameの値が各APIの引数ifaceに設定できる。
下記の例でいうと、`'イーサネット 6'`, `'イーサネット 2'`など
MACやIPアドレスから所望のEthernetアダプタを見つけます。
nameをifaceに設定することができます。

```
In [6]: from scapy.all import *
   ...: get_windows_if_list()
Out[6]: 
[{'name': 'イーサネット 6',
  'win_index': 15,
  'description': 'ASIX AX88179 USB 3.0 to Gigabit Ethernet Adapter #2',
  'guid': '{93E3CFB5-11A7-43F4-9BE0-E42AD69529A3}',
  'mac': '74:03:bd:7f:83:21',
  'ipv4_metric': 5,
  'ipv6_metric': 5,
  'ips': ['fe80::b964:9105:342:b0e1', '192.168.51.123']},
 ...
 {'name': 'イーサネット 2',
  'win_index': 11,
  'description': 'Intel(R) Ethernet Connection (5) I219-LM',
  'guid': '{6091155D-A8EA-491C-BA3D-CD5CB22B29BE}',
  'mac': '40:b0:34:1a:78:6f',
  'ipv4_metric': 25,
  'ipv6_metric': 0,
  'ips': ['10.168.38.67']},
   ...
 ```

### メモ

~~Nmap Loopback Adapter (127.0.0.1同士の通信を確認するための仮想的なアダプタ)もここに出てくるが、sniff()で取得したpacketをwrpcap()でpcap化しようとすると失敗する。調査中。~~ 

実インターフェースでも発生した。
実インターフェースで発生したのはsniff()で取得したpacketをリストに入れて格納してしまっており、
それをwrpcap()に渡していたためであった。
イメージでいうと以下のようになっていた。

```py
[<Sniffed: TCP:0 UDP:5 ICMP:0 Other:5>, <Sniffed: TCP:0 UDP:5 ICMP:0 Other:5>, ...]
```

(scapy.plist.PacketListのインスタンスのリストを作成していた)

append()などがサポートされていなかったが`+=`が使えたので、以下の書き方ができた。

```py
all_packets = None
# ループ
...
    pkts = scpy.sniff(iface="イーサネット 4", count=10)
    if all_packets :
        all_packets += pkts # 初回以降は+=で追加していく。
    else:
        all_packets = pkts # 初回
wrpcap(fname, all_packets )
```

ループバック("127.0.0.1")を見たいときにLoopback Pseudo-Interface 1を選択してしまうとダメな様子。

```py
  File "C:\Python37\lib\site-packages\scapy\arch\windows\__init__.py", line 706, in dev_from_pcapname
    raise ValueError("Unknown pypcap network interface %r" % pcap_name)
ValueError: Unknown pypcap network interface 'Loopback Pseudo-Interface 1'
```

ifcaceに "Npcap Loopback Adapter" を設定すると"127.0.0.1"のpcapを取得することができた。
ただ、wiresharkで直接"Npcap Loopback Adapter"を測定するとパケットを適切に解析できるが、
取得したpcapを開くと、データ的には同じにも関わらず、N/Aが表示されてしまう・・・。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34777/3c0b986b-f2de-7809-455b-02b56d5bc224.png)

実際のインターフェースなら取得できるので、問題なしとするか。。。githubかstack overflowでQAしてみるか。。。

https://github.com/nmap/nmap/issues/200
によって、
https://wiki.wireshark.org/NullLoopback
になっている？


## 受信したパケットのサマリ(IPアドレス、ポート)をとりあえず簡単にコンソールに出したい

**参考**

scapy.sendrecv.sniff(*args, **kwargs)
https://scapy.readthedocs.io/en/latest/api/scapy.sendrecv.html#scapy.sendrecv.sniff


**コード**

以下は同期のメソッドなのでCtrl+Cで止める必要あり。
受信パケット数やタイムアウトなどで停止条件を設定することもできる。

```python
from scapy.all import *
sniff(iface='イーサネット 4', prn=lambda x: x.summary())
```

**結果**

```
In [7]: from scapy.all import *
   ...: sniff(iface='イーサネット 4', prn=lambda x: x.summary())
Ether / IP / UDP 192.168.50.12:30490 > 237.50.0.1:30490 / Raw
Ether / IP / UDP 192.168.50.12:30490 > 237.50.0.1:30490 / Raw
Ether / IP / UDP 192.168.50.12:30490 > 237.50.0.1:30490 / Raw
Ether / IP / UDP 192.168.50.12:30490 > 237.50.0.1:30490 / Raw
Ether / IP / UDP 192.168.50.12:30490 > 237.50.0.1:30490 / Raw
Ether / IP / UDP 192.168.50.12:30490 > 237.50.0.1:30490 / Raw
Ether / IP / UDP 192.168.50.12:30490 > 237.50.0.1:30490 / Raw
Ether / ARP who has 192.168.51.2 says 192.168.51.1 / Padding
Ether / IP / UDP 192.168.50.12:30490 > 237.50.0.1:30490 / Raw
Ether / ARP who has 192.168.51.2 says 192.168.51.1 / Padding
Ether / ARP who has 192.168.51.2 says 192.168.51.1 / Padding
Ether / IP / UDP 192.168.50.12:30490 > 237.50.0.1:30490 / Raw
Ether / ARP who has 192.168.51.2 says 192.168.51.1 / Padding
Ether / ARP who has 192.168.51.2 says 192.168.51.1 / Padding
Ether / IP / UDP 192.168.50.12:30490 > 237.50.0.1:30490 / Raw
Ether / ARP who has 192.168.51.2 says 192.168.51.1 / Padding
Ether / ARP who has 192.168.51.2 says 192.168.51.1 / Padding
Out[7]: <Sniffed: TCP:0 UDP:10 ICMP:0 Other:7>
```

## 別スレッドでパケット受信して、ログに出力したい＋pcapにしたい。

[AsyncSniffer](https://stackoverflow.com/questions/19929326/scapy-sniff-in-non-blocking-way)については別で書く。

ここでは[Python Scapyを使ったネットワークプログラミング - Qiita](https://qiita.com/tansokun920/items/ec9ef64cd71c959c20d9)の記事をベースにpcapにするメソッドを追加したクラス例。

**コード**

```python
import threading
import scapy.all as scpy
import datetime
from logging import getLogger, basicConfig, NullHandler,DEBUG, INFO, WARNING
mylogger = getLogger(__name__)
mylogger.addHandler(NullHandler())

class Sniffer(threading.Thread):
    def __init__(self, iface, count=20, timeout=3, logger=None):
        self.stop_event = threading.Event() #停止させるかのフラグ
        self.thread = threading.Thread(target = self._sniffer_main_loop)
        self.iface = iface
        self.count = count
        self.timeout = timeout
        self.packets = None
        if logger:
            self.log = logger
        else:
            self.log = mylogger
        
    def start(self):
        self.log.debug(f"try to start thread.")
        self.packets = None
        self.stop_event.clear()
        self.thread.start()

    def stop(self):
        self.log.debug(f"try to stop thread.")
        self.stop_event.set()
        self.thread.join()

    def _sniffer_main_loop(self):
        self.log.debug(f"start thread.")

        try:
            while not self.stop_event.is_set():
                pkts = scpy.sniff(iface=self.iface, count=self.count, timeout=self.timeout)
                if self.packets:
                    self.packets += pkts
                else:
                    self.packets = pkts
        except Exception as e:
            self.log.error(f"exception occurred. stop sniffer main loop. {e}")
            self.stop_event.set()
        
        self.log.debug(f"end thread.")

    def create_pcap(self, fname=datetime.datetime.now().strftime("doip_test_%H%M%S.cap")):
        self.stop() if not self.stop_event.is_set() else None
        scpy.wrpcap(fname, self.packets)
```

**結果**

使い方は別途追記。