前回 は、hashcat を使って、簡単なパスワードを解析して、hashcat の使い方を詳細に知って、GPU を使って、どれぐらいの時間でパスワードをクラックできるかを考えてみました。
今回は、Python の Scapy を使って、ネットワークプログラミングをやりたいと思います。
それでは、やっていきます。
参考文献
はじめに
「セキュリティ」の記事一覧です。良かったら参考にしてください。
セキュリティの記事一覧
Scapy の公式サイトは以下です。
scapy.net
また、公式のドキュメントは以下にあります。
scapy.readthedocs.io
今回は、この公式ドキュメントを参考にして、出来るだけ多くのプロトコルを実装してみたいと思います。
環境は、Windows10 の VirtualBox で、ParrotOS を使います。通信先は、徳丸本の実習環境の wasbook(VirtualBox で起動した Debian)を使います。wasbook のように、軽い通信相手が手元にあると、とても便利だと思います。
環境構築は以下の記事で行いました。
daisuke20240310.hatenablog.com
Scapyの概要と環境構築
Scapy とは、Python で、柔軟にパケットを送受信することが出来るパッケージで、ハッキングのスクリプト(エクスプロイトコード)で、よく使われている印象です。
Scapy にはインタラクティブシェルという、インストールしなくても試せる機能が提供されています。run_scapy というプログラムで、以下の Scapy の GitHub のツリーに含まれています。なお、pip でインストールしたパッケージには run_scapy は含まれていなくて、代わりに scapy というコマンドで、ほぼ同じことが出来ると思います。ここでは、run_scapy を使ってみます。
github.com
クローンして、ディレクトリ移動して、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 でキャプチャしました。
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 のキャプチャも取ったので貼っておきます。
Wiresharkのキャプチャ(ICMP)
パケットの詳細表示
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 ---
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 \
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 ---
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 \
type = echo-reply
code = 0
chksum = 0xffff
id = 0x0
seq = 0x0
unused = ''
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__':
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 のロゴを使わせていただきました。ありがとうございます。
最後になりましたが、エンジニアグループのランキングに参加中です。
気楽にポチッとよろしくお願いいたします🙇
今回は以上です!
最後までお読みいただき、ありがとうございました。