本記事は、オライリージャパンから発行されている「サイバーセキュリティプログラミング ―Pythonで学ぶハッカーの思考(原題:Black Hat Python)」の学習メモとして、書籍ではPython2で書かれていますが、自分なりに解釈した上でPython3に書き直しをしています。
前回はICMPのデコードをする方法を学び、そのメッセージからリモートホストの状態を知ることを学びました。
今回はこの仕組みを利用した、ネットワークスキャンツールを作成します。
ネットワークスキャンについて
今回作成するネットワークスキャンツールは、UDPを使ったものになります。
リモートホストの使っていないであろうUDPポート番号に向けてパケットを送信した際に、リモートホストが起動していれば、ICMPメッセージとしてタイプ3(Destination Unreachable)およびコード3(Port Unreachable)を送信します。
このICMPメッセージを解析し、サブネット上に存在するホストをスキャンしていきます。
なお、前回使用したスクリプトを拡張する形で作成していきます。
ライブラリのインポート
今回は以下のライブラリを使用します。
# scanner.py import socket import struct import os from ctypes import * import threading import time from ipaddress import ip_address, ip_network
なお、書籍では別途netaddrモジュールをインストールして、サブネットやIPアドレスの処理を行っていますが、ここでは標準のipaddressモジュールを使います。
udp_sender関数の実装
UDPの送信処理を行う関数を追加します。
def udp_sender(subnet, magic_message): time.sleep(3) sender = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) for ip in ip_network(subnet): try: sender.sendto(magic_message, (str(ip), 65212)) except: pass
引数である"magic_message"とは、任意のバイト文字列を渡します。
これはポート到達不能として、リモートホストからICMPメッセージを受信する際に、Dataとしてこのバイト文字列も送り返すため、このバイト文字列を含んでいるかどうかでパケットの真正性を判断しているようです。
メイン関数の作成
今回はメイン関数を以下に書き換えます。
def main(): if os.name == "nt": socket_protocol = socket.IPPROTO_IP else: socket_protocol = socket.IPPROTO_ICMP sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket_protocol) sniffer.bind((host, 0)) sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) if os.name == "nt": sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON) t = threading.Thread(target=udp_sender, args=[subnet, magic_message]) t.start() try: while True: raw_buffer = sniffer.recvfrom(65565)[0] ip_header = IP(raw_buffer[0:20]) if ip_header.protocol == "ICMP": offset = ip_header.ihl * 4 buf = raw_buffer[offset:offset + sizeof(ICMP)] icmp_header = ICMP(buf) if icmp_header.code == 3 and icmp_header.type == 3: if ip_address(ip_header.src_address) in ip_network(subnet): if raw_buffer[len(raw_buffer) - len(magic_message):] == magic_message: print("Host Up: {}".format(ip_header.src_address)) except KeyboardInterrupt: if os.name == "nt": sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF) if __name__ == '__main__': host = "192.168.2.1" subnet = "192.168.2.0/24" magic_message = b"PYTHONRULES!" main()
動作確認
それでは、上記で作成したスクリプトを起動してみます。
なお、Windowsの場合はプロミスキャスモードを使うには管理者権限が必要なため、コマンドプロンプト(もしくはPowerShell)を管理者権限で起動します。
> python scanner.py Host Up: 192.168.2.1 Host Up: 192.168.2.5 Host Up: 192.168.2.7
上記のように起動しているホストをスキャンすることができました。
最後に
今回はUDPとICMPを使ったネットワークスキャンツールを作成しました。
またARPなどの低レイヤのプロトコルを使用すれば、リモートホストのMACアドレスから、その機種を把握することも可能となります。