【Python】Scapyでの送受信方法まとめ 📡

Python

Scapyを使ってネットワークパケットを送受信する方法を徹底解説!

Scapy は、Pythonで書かれた強力なパケット操作ライブラリです。ネットワークパケットの作成、送受信、キャプチャ、解析などを自由に行うことができます。ネットワークの調査、セキュリティテスト、カスタムプロトコルの実装など、幅広い用途で活躍します。

この記事では、Scapyの基本的な送受信機能に焦点を当て、具体的なコード例とともにその使い方を解説していきます。ネットワークプログラミングの初心者から経験者まで、Scapyを使いこなすための一助となれば幸いです。😊

注意: Scapyを使ったパケット送信やキャプチャは、ネットワーク環境やセキュリティポリシーによっては問題となる可能性があります。特に、許可されていないネットワークに対してスキャンやパケット送信を行うことは絶対に避けてください。実験は必ず自身の管理下にある環境で行ってください。

パケットの送信 ➡️

Scapyでは、主にsend()関数とsendp()関数を使用してパケットを送信します。これらの関数の違いは、どのネットワーク層でパケットを送信するかにあります。

レイヤー3での送信: send()

send()関数は、IP層(レイヤー3)でパケットを送信します。通常、IPパケット(TCP、UDP、ICMPなど)を送信する場合に使用します。Scapyは、指定されたIPパケットに基づいて、適切なレイヤー2ヘッダ(Ethernetヘッダなど)を自動的に生成し、OSのルーティングテーブルに従ってパケットを送信します。

基本的な使い方は非常にシンプルです。送信したいパケットオブジェクトをsend()関数に渡すだけです。

from scapy.all import IP, ICMP, send

# ICMP Echo Requestパケットを作成
packet = IP(dst="8.8.8.8")/ICMP()

# パケットを送信
send(packet)

send()関数にはいくつかの便利なオプションがあります。

  • count: 送信するパケット数を指定します。デフォルトは1です。
  • inter: パケット送信間の待機時間(秒)を指定します。
  • loop: 0以外の値を指定すると、Ctrl+Cで停止するまでパケットを繰り返し送信し続けます。
  • verbose: 詳細な出力表示のレベルを制御します。

例:1秒間隔で5つのICMPパケットを送信する

from scapy.all import IP, ICMP, send

# ICMP Echo Requestパケットを作成
packet = IP(dst="8.8.8.8")/ICMP()

# 1秒間隔で5回送信
send(packet, count=5, inter=1)

複数の宛先に一度に送信することも可能です。dstフィールドにIPアドレスのリストを指定します。

from scapy.all import IP, TCP, send

# 複数の宛先にTCP SYNパケットを送信
send(IP(dst=['8.8.8.8', '1.1.1.1'])/TCP(dport=80, flags='S'))

レイヤー2での送信: sendp()

sendp()関数は、データリンク層(レイヤー2)でパケット(フレーム)を送信します。Ethernetフレームなど、レイヤー2レベルでの制御が必要な場合に使用します。send()とは異なり、sendp()を使用する場合は、Ether()などでレイヤー2ヘッダを明示的に指定する必要があります。

基本的な使い方はsend()と似ていますが、レイヤー2ヘッダを含むパケットオブジェクトを渡します。また、通常はiface引数で送信に使用するネットワークインターフェースを指定する必要があります。

from scapy.all import Ether, IP, UDP, sendp

# 宛先MACアドレスを指定してEthernetフレームを作成 (例: ブロードキャスト)
# 実際の使用時は適切なMACアドレスを指定してください
packet = Ether(dst="ff:ff:ff:ff:ff:ff")/IP(dst="192.168.1.255")/UDP(dport=12345)/"Hello Layer 2!"

# 指定したインターフェースから送信 (例: 'eth0')
# 自身の環境に合わせてインターフェース名を変更してください
sendp(packet, iface="eth0")

sendp()関数もsend()と同様のオプション(count, inter, loop, verbose)を持っています。加えて、以下の重要なオプションがあります。

  • iface: パケットを送信するネットワークインターフェースを指定します。指定しない場合、Scapyのデフォルト設定が使用されますが、明示的に指定することが推奨されます。

例:特定のインターフェースからARPリクエストを送信する

from scapy.all import Ether, ARP, sendp

# ARPリクエストパケットを作成 (192.168.1.1のMACアドレスを問い合わせる)
# pdst: 問い合わせたいIPアドレス
# hwdst: ブロードキャストMACアドレス
# psrc: 送信元IPアドレス (Scapyが自動設定してくれる場合もある)
# hwsrc: 送信元MACアドレス (Scapyが自動設定してくれる場合もある)
arp_request = Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst="192.168.1.1")

# 'eth0'インターフェースから送信
# 自身の環境に合わせてインターフェース名を変更してください
sendp(arp_request, iface="eth0")

注意: send()sendp()はパケットを送信するだけで、応答を受信する機能はありません。応答を受信したい場合は、後述するsr()系の関数やsniff()関数を使用します。

パケットの送受信 (Send and Receive) ⬅️➡️

Scapyでは、パケットを送信し、その応答を受信するための関数群が用意されています。これにより、対話的なネットワーク通信をシミュレートしたり、特定のプロトコルの応答を確認したりすることが可能です。

レイヤー3での送受信: sr() と sr1()

sr() (Send and Receive) 関数は、レイヤー3パケットを送信し、受信した応答を収集します。この関数は、送信したパケットに対する応答パケットのペアと、応答が得られなかったパケットのリストの2つをタプルとして返します。

sr1() (Send and Receive 1) 関数は、sr()と似ていますが、最初に応答があったパケットのみを返します。応答がない場合はNoneを返します。特定の応答を1つだけ期待する場合に便利です。

例:sr1()を使ってDNSクエリを送信し、応答を取得する

from scapy.all import IP, UDP, DNS, DNSQR, sr1

# DNSクエリパケットを作成 (www.google.comのAレコードを問い合わせる)
dns_query = IP(dst="8.8.8.8")/UDP(dport=53)/DNS(rd=1, qd=DNSQR(qname="www.google.com"))

# パケットを送信し、最初の応答を取得 (タイムアウトを2秒に設定)
# verbose=0 で送信中のメッセージを抑制
response = sr1(dns_query, timeout=2, verbose=0)

# 応答があれば表示
if response:
    print("DNS応答を受信しました:")
    response.show()
    # DNS応答レコード (Answer) を表示
    if response.haslayer(DNS) and response.an:
        print("--- 回答 ---")
        # response.an は DNS Resource Record のリストまたはNone
        for answer in response.an:
             # repr() で詳細表示
             print(repr(answer))
else:
    print("タイムアウトしました。応答がありませんでした。")

例:sr()を使って複数のホストにPing(ICMP Echo Request)を送信し、応答をまとめる

from scapy.all import IP, ICMP, sr

# 複数の宛先にICMP Echo Requestを送信
targets = ["8.8.8.8", "1.1.1.1", "192.168.1.254"] # 最後のIPは応答しない例
ping_requests = IP(dst=targets)/ICMP()

# パケットを送信し、応答を取得 (タイムアウト1秒)
# answeredには (送信パケット, 受信パケット) のタプルリスト
# unansweredには応答のなかった送信パケットのリスト
answered, unanswered = sr(ping_requests, timeout=1, verbose=0)

print("--- 応答があったホスト ---")
if answered:
    # answered.summary() は応答ペアの概要を表示する便利なメソッド
    answered.summary()
else:
    print("応答がありませんでした。")

print("\n--- 応答がなかったホスト ---")
if unanswered:
    unanswered.summary()
else:
    print("すべてのホストから応答がありました。")

sr()およびsr1()の主なオプション:

  • timeout: 応答を待つ最大時間(秒)。
  • verbose: 詳細な出力表示のレベル。0を指定すると送信/受信メッセージが抑制されます。
  • filter: 受信するパケットをフィルタリングするためのBPF(Berkeley Packet Filter)式。
  • iface: 送受信に使用するネットワークインターフェース。
  • retry: 応答がない場合に再送する回数。負数を指定すると、応答があるまで指定回数再試行します。
  • multi: (sr()のみ) Trueに設定すると、1つの送信パケットに対して複数の応答を受信できます。

パケットのキャプチャ (Sniffing) 🔍

Scapyのsniff()関数は、ネットワークインターフェースを流れるパケットをキャプチャ(スニッフィング)するための強力な機能を提供します。ネットワークトラフィックのリアルタイム監視や、特定の条件に一致するパケットの収集に利用できます。

sniff() 関数の基本

sniff()関数は、指定された条件に基づいてパケットをキャプチャし、デフォルトではキャプチャしたパケットのリスト(PacketListオブジェクト)を返します。

基本的な使い方:

from scapy.all import sniff

# パケットを10個キャプチャする
packets = sniff(count=10)

# キャプチャしたパケットの概要を表示
packets.summary()

# 最初のパケットの詳細を表示
if packets:
    packets[0].show()

sniff()関数の主要なパラメータ:

パラメータ説明
countキャプチャするパケット数。0を指定すると無制限(Ctrl+Cで停止)。count=50
ifaceキャプチャ対象のネットワークインターフェース。指定しない場合はデフォルトインターフェース。リストで複数指定も可能。iface="eth0", iface=["eth0", "wlan0"]
filterキャプチャするパケットを絞り込むためのBPF (Berkeley Packet Filter) 式。tcpdumpと同じ構文。filter="tcp port 80", filter="udp and host 192.168.1.1"
prnキャプチャした各パケットに対して実行されるコールバック関数。関数の戻り値が表示される。prn=lambda pkt: pkt.summary()
lfilter各パケットに適用されるPythonの関数。Trueを返すパケットのみが処理される。BPFより複雑なフィルタリングが可能。lfilter=lambda pkt: pkt.haslayer(TCP) and pkt[TCP].dport == 443
timeout指定した秒数が経過するとキャプチャを停止する。timeout=60
storeキャプチャしたパケットをメモリに保存するかどうか (True/False)。prnで処理するだけで保存不要な場合はFalseにするとメモリ効率が良い。store=False
offlineネットワークインターフェースの代わりに、指定したpcapファイルからパケットを読み込む。offline="capture.pcap"
stop_filter各パケットに適用されるPythonの関数。Trueを返した時点でキャプチャを停止する。stop_filter=lambda pkt: pkt.haslayer(TCP) and pkt[TCP].flags == 'F'
sessionTCPセッションなどを扱うためのセッションクラスを指定する。TCPストリームの再構成などに利用。session=TCPSession

例:特定のインターフェースでHTTP (TCPポート80) トラフィックのみをキャプチャし、概要を表示する

from scapy.all import sniff

def print_summary(packet):
    """パケットの概要を表示するコールバック関数"""
    print(packet.summary())

print("HTTPトラフィックをキャプチャ中 (Ctrl+Cで停止)...")
# 'eth0' インターフェースでTCPポート80のトラフィックをフィルタリング
# 各パケットに対して print_summary 関数を実行
# パケットはメモリに保存しない (store=False)
# 自身の環境に合わせてインターフェース名を変更してください
sniff(iface="eth0", filter="tcp port 80", prn=print_summary, store=False)

print("キャプチャを終了しました。")

例:pcapファイルからICMPパケットのみを読み込む

from scapy.all import sniff, ICMP

try:
    # 'network_traffic.pcap' ファイルからICMPパケットのみをフィルタリングして読み込む
    icmp_packets = sniff(offline="network_traffic.pcap", filter="icmp")
    print(f"{len(icmp_packets)} 個のICMPパケットを読み込みました。")
    if icmp_packets:
        icmp_packets.summary(prn=lambda pkt: f"{pkt.time:.6f} {pkt.summary()}") # タイムスタンプ付きで表示
except FileNotFoundError:
    print("エラー: pcapファイル 'network_traffic.pcap' が見つかりません。")
except Exception as e:
    print(f"エラーが発生しました: {e}")

sniff()関数は非常に柔軟性が高く、リアルタイムのネットワーク分析や特定のイベントの監視、オフラインでのpcapファイル分析など、様々なシナリオで活用できます。🕵️‍♀️

まとめ ✅

この記事では、Pythonの強力なパケット操作ライブラリであるScapyを使用して、ネットワークパケットを送受信およびキャプチャする方法について解説しました。

関数レイヤー機能主な用途
send()L3 (IP)パケット送信 (応答受信なし)IP, TCP, UDP, ICMPパケットの送信
sendp()L2 (Ethernet)フレーム送信 (応答受信なし)Ethernet, ARPフレームの送信、インターフェース指定必須
sr()L3 (IP)パケット送受信 (複数応答可)Pingスイープ、ポートスキャン(TCP/UDP)、プロトコル応答確認
sr1()L3 (IP)パケット送受信 (最初の応答のみ)単一応答期待 (DNSクエリ、特定のPing応答など)
srp()L2 (Ethernet)フレーム送受信 (複数応答可)ARPスキャン、LLDP/CDP確認、インターフェース指定必須
srp1()L2 (Ethernet)フレーム送受信 (最初の応答のみ)単一ARP応答確認、インターフェース指定必須
sniff()L2/L3パケットキャプチャトラフィック監視、特定パケット収集、pcapファイル分析、コールバック処理

これらの関数を組み合わせることで、ネットワークの挙動を深く理解したり、カスタムツールを作成したりすることが可能になります。Scapyは非常に多機能なライブラリであり、ここで紹介したのはその一部に過ぎません。ぜひ公式ドキュメントなどを参照し、さらなる活用方法を探求してみてください。🚀

参考情報 📚

コメント

タイトルとURLをコピーしました