今回は、Windows上で、EthernetのRawソケットでパケット送信してみたいなと思い、Python で送信する方法について挑戦してみたので、その方法についてシェアします。
まずは、今回必要となるライブラリです。
scapyライブラリは、Pythonで有名なEthernetのパケット生成・送受信・解析用のライブラリです。
scapyライブラリのインストール方法は以下です。
pip install scapy
上記をコマンドプロンプトで実行すると以下のようになり、successfully installedと表示されればインストール完了となります。
今回のプログラムの全体は以下の通りです。
# 参考
# https://scapy.readthedocs.io/en/latest/usage.html
from scapy.all import *
def main():
IFACES.show() # IFACE 確認
iface = IFACES.dev_from_index(4) # Iface名取得
socket = conf.L2socket(iface=iface) # ソケット取得
### SEND
print("Ether")
ls(Ether)
pcakt = Ether(dst='11:22:33:44:55:66',src='66:55:44:33:22:11',type=2048)
socket.send(pcakt) # send raw data
print("\n")
print("ARP")
ls(ARP)
pcakt = ARP()
socket.send(pcakt) # send raw data
print("\n")
print("ICMP")
ls(ICMP)
pcakt = ICMP()
socket.send(pcakt) # send raw data
print("\n")
print("IP")
ls(IP)
pcakt = Ether()/IP()
socket.send(pcakt) # send raw data
print("\n")
print("TCP")
ls(TCP)
pcakt = Ether()/IP()/TCP()
socket.send(pcakt) # send raw data
print("\n")
print("Dot1Q")
ls(Dot1Q)
pcakt = Ether()/Dot1Q(vlan=1)/IP()/TCP()
socket.send(pcakt) # send raw data
print("\n")
print("Dot1Q")
ls(Dot1Q)
pcakt = Ether()/Dot1Q(vlan=1)/Dot1Q(vlan=2)/IP()/TCP()
socket.send(pcakt) # send raw data
socket.close()
if __name__ == '__main__':
main()
実行結果は、ネットワークパケット解析ツール wiresharkのログを見てもらえればと思います。
ちなみに、ネットワークパケット解析ツール wiresharkというのは、パケットキャプチャを行うためのツールで、最も一般的に使用されているフリーソフトのツールになります。
以下からダウンロードできます。
ここからは、プログラムの解説になりますが、次ページにて記載します。
ここからは、プログラムの解説です。
このプログラムの流れは以下のような感じになっています。
では順に解説していきます。
まずはPC上のインターフェースを確認する方法です。
from scapy.all import *
IFACES.show() # IFACE 確認
上記2行をプログラムにいれて実行すれば、PC内のインターフェースを確認できます。
IPアドレス等はぼかしていますが、以下のような感じでインターフェスのインデックスを取得できます。
Index列を確認してください。
Note
毎回呼び出ししても問題にはなりませんが、この処理は、一番最初に使用したインターフェースを確認するときのみ実行すれば大丈夫です。
インターフェースのインデックスをステップ1で取得できれば、次はインターフェース名を取得します。
以下がその処理にあたります。
今回はインデックス4になりますので、VirtualBox Host-Only Ethernet Adapterのインターフェース名を取得しています。
注意
認識が間違っている方もいるかと思いますが、「VirtualBox Host-Only Ethernet Adapter」というのはDescriptionという項目にあたり、インターフェース名にはなりません。(私もそうでした。。。)
インターフェース名は、「\Device\NPF_{XXXXXXXXXXXX}」というような形になります。
iface = IFACES.dev_from_index(4) # Iface名取得
続いてソケット生成です。
ソケットの生成では、先ほどのインターフェース名を指定して、 conf.L2socketというAPIを呼び出すだけとなります。
socket = conf.L2socket(iface=iface) # ソケット取得
続いてデータ送信です。
scapyでは、基本的に以下のように、①パケットを生成②で送信という手順になります。
①pcakt = Ether(dst='11:22:33:44:55:66',src='66:55:44:33:22:11',type=2048)
②socket.send(pcakt) # send raw data
送信自体は、②のsendだけで問題ありませんが、パケットの生成はやり方がありますので、こちらについて簡単に説明します。
scapyでは、レイヤー事にヘッダをつなぎ合わせることでパケットを生成します。
順番にEther()/IP()/TCP()という順にレイヤーをそれぞれ”/”でつなぎ合わせることでパケットを作成します。
ちなみに、ARP()や、ICMP()といったものも生成できます。
それぞれのプロトコルの意味については、各自で調べてみてください。
また、それぞれのエイヤーのヘッダの内容を設定できるようになっていて、以下のように、それぞれのレイヤーのAPIの中身の引数を設定すれば、それぞれのヘッダの内容を変更できます。
Ether(dst='11:22:33:44:55:66',src='66:55:44:33:22:11',type=2048)
また、それぞれのレイヤの中身が知りたい場合は”ls()”関数呼び出せば中身が確認できます。
以下のような感じで、知りたいAPIを”ls”関数で呼び出せば中身が確認できます。
ls(Ether) <- Etherの設定内容が知りたいとき
ls(ARP) <- ARPの設定内容が知りたいとき
ちなみに、Etherをlsで呼び出すと以下のような感じで確認できます。
最後に、今回私がもっとも試してみたかったものとしてVLANタグをつけたパケットを送信することができましたのでその方法を記載します。
基本的にはこれまでと同様の方法で、設定するのですが、VALNを付けるために、”Dot1Q”という関数を呼び出すことで、VLANタグをつけたパケットを生成できました。
ちなみに、以下のように記述すれば、Vlanタグを複数つけることも可能です。
pcakt = Ether()/Dot1Q(vlan=1)/Dot1Q(vlan=2)/IP()/TCP()
最後に、ソケットをクローズして処理終了です。
socket.close()
今回は、Ethernetのパケットを自身で生成して、好きに投げる方法が知りたいなと思って、やってみたのですが、通常のsocket通信の方法よりも簡単にできてしまいました。
ただ、各レイヤのヘッダの意味を理解していないと使いこなせないので、使い道はかなり限定されるのかなというのが印象で、普通にTCPやUDPの通信がしたいだけなら、一般的なsocket通信を使うのが特殊な知識等が不要でいいのではないかと思いました。
ちなみに、pythonで一般的なsocket通信をする方法については、以下記事でまとめていますので、気になった方は参考にしてみてください。
最後に、Pythonの基礎を学びたい方は以下がおすすめです。私も持っていてたまに眺めて勉強していますものですのでぜひ購入して学習してみてください。