Engineering Note

プログラミングなどの技術的なメモ

3.4.2 ICMPのデコード (サイバーセキュリティプログラミング Pythonで学ぶハッカーの思考)

本記事は、オライリージャパンから発行されている「サイバーセキュリティプログラミング ―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アドレスから、その機種を把握することも可能となります。

 

参考書籍

サイバーセキュリティプログラミング ―Pythonで学ぶハッカーの思考