前回 は、hashcat を使って、簡単なパスワードを解析して、hashcat の使い方を詳細に知って、GPU を使って、どれぐらいの時間でパスワードをクラックできるかを考えてみました。
今回は、Python の Scapy を使って、ネットワークプログラミングをやりたいと思います。
それでは、やっていきます。
参考文献
はじめに
「セキュリティ」の記事一覧です。良かったら参考にしてください。
・第2回:Ghidraで始めるリバースエンジニアリング(使い方編)
・第3回:VirtualBoxにParrotOS(OVA)をインストールする
・第4回:tcpdumpを理解して出力を正しく見れるようにする
・第5回:nginx(エンジンエックス)を理解する
・第6回:Python+Flask(WSGI+Werkzeug+Jinja2)を動かしてみる
・第7回:Python+FlaskのファイルをCython化してみる
・第8回:shadowファイルを理解してパスワードを解読してみる
・第9回:安全なWebアプリケーションの作り方(徳丸本)の環境構築
・第10回:Vue.jsの2.xと3.xをVue CLIを使って動かしてみる(ビルドも行う)
・第11回:Vue.jsのソースコードを確認する(ビルド後のソースも見てみる)
・第12回:徳丸本:OWASP ZAPの自動脆弱性スキャンをやってみる
・第13回:徳丸本:セッション管理を理解してセッションID漏洩で成りすましを試す
・第14回:OWASP ZAPの自動スキャン結果の分析と対策:パストラバーサル
・第15回:OWASP ZAPの自動スキャン結果の分析と対策:クロスサイトスクリプティング(XSS)
・第16回:OWASP ZAPの自動スキャン結果の分析と対策:SQLインジェクション
・第17回:OWASP ZAPの自動スキャン結果の分析と対策:オープンリダイレクト
・第18回:OWASP ZAPの自動スキャン結果の分析と対策:リスク中すべて
・第19回:CTF初心者向けのCpawCTFをやってみた
・第20回:hashcatの使い方とGPUで実行したときの時間を見積もってみる
・第21回:Scapyの環境構築とネットワークプログラミング ← 今回
Scapy の公式サイトは以下です。
また、公式のドキュメントは以下にあります。
今回は、この公式ドキュメントを参考にして、出来るだけ多くのプロトコルを実装してみたいと思います。
環境は、Windows10 の VirtualBox で、ParrotOS を使います。通信先は、徳丸本の実習環境の wasbook(VirtualBox で起動した Debian)を使います。wasbook のように、軽い通信相手が手元にあると、とても便利だと思います。
環境構築は以下の記事で行いました。
daisuke20240310.hatenablog.com
Scapyの概要と環境構築
Scapy とは、Python で、柔軟にパケットを送受信することが出来るパッケージで、ハッキングのスクリプト(エクスプロイトコード)で、よく使われている印象です。
Scapy にはインタラクティブシェルという、インストールしなくても試せる機能が提供されています。run_scapy というプログラムで、以下の Scapy の GitHub のツリーに含まれています。なお、pip でインストールしたパッケージには run_scapy は含まれていなくて、代わりに scapy というコマンドで、ほぼ同じことが出来ると思います。ここでは、run_scapy を使ってみます。
クローンして、ディレクトリ移動して、run_scapy を起動するだけです。Scapy の中には、管理者権限がないと実行できないものが存在します(Wiresharkやtcpdumpも管理者権限が必要)ので、sudo を付けて実行します。
なかなか個性的な画面とともに、入力待ち(>>>)になりました。
$ git clone https://github.com/secdev/scapy.git $ cd scapy/ $ sudo ./run_scapy INFO: Can't import PyX. Won't be able to use psdump() or pdfdump(). aSPY//YASa apyyyyCY//////////YCa | sY//////YSpcs scpCY//Pp | Welcome to Scapy ayp ayyyyyyySCP//Pp syY//C | Version 2.6.0rc1.dev79 AYAsAYYYYYYYY///Ps cY//S | pCCCCY//p cSSps y//Y | https://github.com/secdev/scapy SPPPP///a pP///AC//Y | A//A cyP////C | Have fun! p///Ac sC///a | P////YCpc A//A | Craft packets like it is your last scccccp///pSP///p p//Y | day on earth. sY/////////y caa S//P | -- Lao-Tze cayCyayP//Ya pY/Ya | sY/PsY////YCc aC//Yp sc sccaCY//PCypaapyCP//YSs spCPY//////YPSps ccaacs using IPython 8.5.0 >>>
デモにあるように、GitHub に ICMP(ping)アクセスしてみます。
パケットを準備して、sr1(送信して1つの受信を受け取る)を実行します。受信したパケットは、戻り値 r に情報を格納してくれるので、その中を確認できます。ICMP は type で機能が決まります。エコー要求の type は 8、エコー応答の type は 0 です。
>>> p = IP(dst="github.com")/ICMP() >>> p <IP frag=0 proto=icmp dst=Net("github.com/32") |<ICMP |>> >>> r = sr1(p) Begin emission: Finished sending 1 packets. * Received 1 packets, got 1 answers, remaining 0 packets >>> r <IP version=4 ihl=5 tos=0x0 len=28 id=6388 flags= frag=0 ttl=113 proto=icmp chksum=0x5f52 src=20.27.177.113 dst=10.0.2.15 |<ICMP type=echo-reply code=0 chksum=0xffff id=0x0 seq=0x0 unused=b'' |<Padding load=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' |>>> >>> r[IP].src '20.27.177.113' >>> r[ICMP].type 0
このときの送受信を Wireshark でキャプチャしました。

とても直感的にパケットの送受信を行うことが出来ます。自分が設定、変更したいフィールドだけを設定すれば、あとは、適切なデフォルト値を Scapy の方で設定してくれます。これがとても便利です。
インタラクティブシェルを終了するときは、Python と同じで、exit() で終了できます。
Scapyのインストール
まずは、インストールしていきます。インストールは簡単です。
$ pip install scapy Successfully installed scapy-2.5.0
最新の v2.5.0 が入りました。
Scapyを使ってPythonでネットワークプログラミング
では、Python で書いていきます。
ICMP
まずは、先ほどの ICMP を Python で実装してみます。
やってることは先ほどと同じですが、ICMP の送信先については、コマンドライン引数で渡せるようにしています。
import os, sys from scapy.all import * def icmp( host="localhost" ): pkt = IP(dst=host)/ICMP() print( f"pkt={pkt}" ) res = sr1( pkt ) print( f"res={res}" ) if __name__ == '__main__': print( f"sys.argv={sys.argv}" ) icmp( sys.argv[1] )
では、実行してみます。先ほどと同様に、管理者権限で実行します。
$ sudo python scapy_inet.py github.com sys.argv=['scapy_inet.py', 'github.com'] pkt=IP / ICMP 10.0.2.15 > Net("github.com/32") echo-request 0 Begin emission: Finished sending 1 packets. .* Received 2 packets, got 1 answers, remaining 0 packets res=IP / ICMP 20.27.177.113 > 10.0.2.15 echo-reply 0 / Padding
うまく動いているようです。Wireshark のキャプチャも取ったので貼っておきます。

パケットの詳細表示
ICMP のプログラムに、パケットを詳細に表示するものを追加してみます。detail 関数を追加しました。見やすいように少し改行を追加しています。
import os, sys from scapy.all import * def icmp( host="localhost" ): pkt = IP(dst=host)/ICMP() print( f"pkt={pkt}" ) detail( pkt ) res = sr1( pkt ) print( f"res={res}" ) detail( res ) def detail( pkt ): print() print( "--- show ---\n" ) pkt.show() print( "--- ls ---\n" ) ls( pkt ) print( "\n--- hexdump ---\n" ) hexdump( pkt ) if __name__ == '__main__': print( f"sys.argv={sys.argv}" ) icmp( sys.argv[1] )
では、実行してみます。
$ sudo python scapy_inet.py github.com sys.argv=['scapy_inet.py', 'github.com'] pkt=IP / ICMP 10.0.2.15 > Net("github.com/32") echo-request 0 --- show --- ###[ IP ]### version = 4 ihl = None tos = 0x0 len = None id = 1 flags = frag = 0 ttl = 64 proto = icmp chksum = None src = 10.0.2.15 dst = Net("github.com/32") \options \ ###[ ICMP ]### type = echo-request code = 0 chksum = None id = 0x0 seq = 0x0 unused = '' --- ls --- version : BitField (4 bits) = 4 ('4') ihl : BitField (4 bits) = None ('None') tos : XByteField = 0 ('0') len : ShortField = None ('None') id : ShortField = 1 ('1') flags : FlagsField = <Flag 0 ()> ('<Flag 0 ()>') frag : BitField (13 bits) = 0 ('0') ttl : ByteField = 64 ('64') proto : ByteEnumField = 1 ('0') chksum : XShortField = None ('None') src : SourceIPField = '10.0.2.15' ('None') dst : DestIPField = Net("github.com/32") ('None') options : PacketListField = [] ('[]') -- type : ByteEnumField = 8 ('8') code : MultiEnumField (Depends on 8) = 0 ('0') chksum : XShortField = None ('None') id : XShortField (Cond) = 0 ('0') seq : XShortField (Cond) = 0 ('0') ts_ori : ICMPTimeStampField (Cond) = None ('31606452') ts_rx : ICMPTimeStampField (Cond) = None ('31606452') ts_tx : ICMPTimeStampField (Cond) = None ('31606452') gw : IPField (Cond) = None ("'0.0.0.0'") ptr : ByteField (Cond) = None ('0') reserved : ByteField (Cond) = None ('0') length : ByteField (Cond) = None ('0') addr_mask : IPField (Cond) = None ("'0.0.0.0'") nexthopmtu : ShortField (Cond) = None ('0') unused : MultipleTypeField (ShortField, IntField, StrFixedLenField) = b'' ("b''") --- hexdump --- 0000 45 00 00 1C 00 01 00 00 40 01 A9 45 0A 00 02 0F E.......@..E.... 0010 14 1B B1 71 08 00 F7 FF 00 00 00 00 ...q........ Begin emission: Finished sending 1 packets. .* Received 2 packets, got 1 answers, remaining 0 packets res=IP / ICMP 20.27.177.113 > 10.0.2.15 echo-reply 0 / Padding --- show --- ###[ IP ]### version = 4 ihl = 5 tos = 0x0 len = 28 id = 6529 flags = frag = 0 ttl = 113 proto = icmp chksum = 0x5ec5 src = 20.27.177.113 dst = 10.0.2.15 \options \ ###[ ICMP ]### type = echo-reply code = 0 chksum = 0xffff id = 0x0 seq = 0x0 unused = '' ###[ Padding ]### load = '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' --- ls --- version : BitField (4 bits) = 4 ('4') ihl : BitField (4 bits) = 5 ('None') tos : XByteField = 0 ('0') len : ShortField = 28 ('None') id : ShortField = 6529 ('1') flags : FlagsField = <Flag 0 ()> ('<Flag 0 ()>') frag : BitField (13 bits) = 0 ('0') ttl : ByteField = 113 ('64') proto : ByteEnumField = 1 ('0') chksum : XShortField = 24261 ('None') src : SourceIPField = '20.27.177.113' ('None') dst : DestIPField = '10.0.2.15' ('None') options : PacketListField = [] ('[]') -- type : ByteEnumField = 0 ('8') code : MultiEnumField (Depends on 0) = 0 ('0') chksum : XShortField = 65535 ('None') id : XShortField (Cond) = 0 ('0') seq : XShortField (Cond) = 0 ('0') ts_ori : ICMPTimeStampField (Cond) = None ('31606452') ts_rx : ICMPTimeStampField (Cond) = None ('31606452') ts_tx : ICMPTimeStampField (Cond) = None ('31606452') gw : IPField (Cond) = None ("'0.0.0.0'") ptr : ByteField (Cond) = None ('0') reserved : ByteField (Cond) = None ('0') length : ByteField (Cond) = None ('0') addr_mask : IPField (Cond) = None ("'0.0.0.0'") nexthopmtu : ShortField (Cond) = None ('0') unused : MultipleTypeField (ShortField, IntField, StrFixedLenField) = b'' ("b''") -- load : StrField = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ("b''") --- hexdump --- 0000 45 00 00 1C 19 81 00 00 71 01 5E C5 14 1B B1 71 E.......q.^....q 0010 0A 00 02 0F 00 00 FF FF 00 00 00 00 00 00 00 00 ................ 0020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ..............
それぞれ、だいぶ細かい表示ですね。実装時は、各パケットの特定のフィールドにアクセスすることが多いと思うので、あまり使わないかもしれませんが、Wireshark が使えない環境とかではいいかもです。
情報取得
次は、自分の情報(IPアドレスとか、MACアドレスとか)や、指定したホストの情報を取得してみたいと思います。Scapy には、たくさんの情報を取得できる関数が用意されています。
import os, sys from scapy.all import * from scapy_inet import icmp def myinfo(): lst_if = get_if_list() print( f"get_if_list()={lst_if}" ) lst_info = [] for iface in lst_if: dic = { 'if': None, 'ip': None, 'mac': None, } dic['if'] = iface dic['ip'] = get_if_addr( iface ) dic['mac'] = get_if_hwaddr( iface ) lst_info.append( dic ) for dic in lst_info: print( f"iface={dic['if']}" ) print( f" ip_addr={dic['ip']}" ) print( f" mac={dic['mac']}" ) def hostinfo( host ): res = icmp( host ) mac = getmacbyip( res[IP].src ) print( f"host({host}) mac={mac}" ) if __name__ == '__main__': print( f"sys.argv={sys.argv}" ) myinfo() print() hostinfo( sys.argv[1] )
では、実行してみます。最初は、自分の情報を取得してログ出力し、次は、指定したホスト(wasbook)について、ICMP で、IPアドレスを取得した後、MACアドレスを取得しています。
$ sudo python scapy_info.py example.jp sys.argv=['scapy_info.py', 'example.jp'] get_if_list()=['lo', 'enp0s3', 'enp0s8'] iface=lo ip_addr=127.0.0.1 mac=00:00:00:00:00:00 iface=enp0s3 ip_addr=10.0.2.15 mac=08:00:27:38:2a:ed iface=enp0s8 ip_addr=192.168.56.105 mac=08:00:27:b7:97:75 pkt=IP / ICMP 192.168.56.105 > Net("example.jp/32") echo-request 0 Begin emission: Finished sending 1 packets. ..* Received 3 packets, got 1 answers, remaining 0 packets res=IP / ICMP 192.168.56.101 > 192.168.56.105 echo-reply 0 / Padding host(example.jp) mac=08:00:27:b3:d8:93
ARP
次は、ARP を送信してみます。
ARP は、Ether の上に乗ってるので、そのように実装します。IPアドレスを送信すると、MACアドレスを返してくれます。
import os, sys from scapy.all import * from scapy_common import detail def arp( ipaddr="127.0.0.1", debug=False ): pkt = Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=ipaddr) print( f"pkt={pkt}" ) if debug: detail( pkt ) res = srp1( pkt ) print( f"res={res}" ) if debug: detail( res ) return res if __name__ == '__main__': # 実行例 # $ sudo python scapy_arp.py 10.0.2.2 print( f"sys.argv={sys.argv}" ) arp( sys.argv[1] )
では、実行してみます。
$ sudo python scapy_arp.py 10.0.2.2 sys.argv=['scapy_arp.py', '10.0.2.2'] pkt=Ether / ARP who has 10.0.2.2 says 10.0.2.15 Begin emission: Finished sending 1 packets. * Received 1 packets, got 1 answers, remaining 0 packets res=Ether / ARP is at 52:54:00:12:35:02 says 10.0.2.2 / Padding
同じネットワーク内のノードを対象とする必要があるので、VirtualBox が準備してくれるルーターに送信しました。MACアドレスが取得できています。
おわりに
今回は、Scapy を使って、低レイヤなネットワークの実装をしてみました。
必要になったら、DNS、DHCPクライアントなどを実装して、ここに追記したいと思います。HTTP もやってみたかったのですが、Scapy よりも requests を使った方が実装しやすいようなので、別の記事で書きたいと思います。
今回は、Scapy のロゴを使わせていただきました。ありがとうございます。
最後になりましたが、エンジニアグループのランキングに参加中です。
気楽にポチッとよろしくお願いいたします🙇
今回は以上です!
最後までお読みいただき、ありがとうございました。