Engineering Note

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

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

本記事は、オライリージャパンから発行されている「サイバーセキュリティプログラミング ―Pythonで学ぶハッカーの思考(原題:Black Hat Python)」の学習メモとして、書籍ではPython2で書かれていますが、自分なりに解釈した上でPython3に書き直しをしています。

今回はICMPメッセージのデコードについて学んでいきます。

 

 

ICMPについて

ICMP(Internet Control Message Protocol)は、エラー通知や通信状態の調査に用いられるプロトコルです。

疎通確認のテストで用いられるpingや、ルーティングの確認で用いられるtracerouteなどはこのICMPを使ったツールです。

実際はTCPUDPと同じレイヤのプロトコルであり、IPよりも上位層に位置しますが、IPと同じレイヤであるような扱い方をされます。

 

ICMPヘッダについて

現在ではまだIPv4が主流となっているため、ここではIPv4におけるICMPヘッダについて確認していきます。

以下がRFC792で定義されているヘッダフォーマットです。 

 

icmp_header

RFC792より参照

ICMPには用途に応じた様々なタイプを指定できますが、書籍ではタイプ3の"Destination Unreachable(宛先到達不可能)"のものをキャプチャするようにします。

なお、ICMPタイプではコードと呼ばれるフィールドを指定でき、さらに細かい情報の制御が可能となっています。

ICMPタイプ3のものに関しては、主に以下のコードフィールドが良く用いられます。

 

0 = net unreachable

1 = host unreachable

2 = protocol unreachable

3 = port unreachable

4 = fragmentation needed and DF set

5 = source route failed

 

ICMPクラスを作成する

前回作成したスクリプトに以下のICMPクラスを追加し、さらにICMPタイプおよびコードのフィールドを表示する処理をメイン関数に追加します。

 

# sniffer_with_icmp.py
...
class IP(Structure):
...
class ICMP(Structure):
    _fields_ = [
        ("type",            c_uint8),
        ("code",            c_uint8),
        ("checksum",        c_uint16),
        ("unused",          c_uint16),
        ("next_hop_mtu",    c_uint16)
    ]
    def __new__(self, socket_buffer):
        return self.from_buffer_copy(socket_buffer)

    def __init__(self, socket_buffer):
        pass

...
      print("Protocol: {} {} -> {}".format(ip_header.protocol,
                                ip_header.src_address, ip_header.dst_address))
        if ip_header.protocol == "ICMP":
            offset = ip_header.ihl * 4
            buf = raw_buffer[offset:offset + sizeof(ICMP)]
            icmp_header = ICMP(buf)
            print("ICMP -> Type: {} Code: {}".format(icmp_header.type,
                                                            icmp_header.code))
...

 

23行目の"offset"は、ヘッダ長を意味するihlフィールドからIPヘッダのサイズを計算します。

ihlフィールドは、32ビットを一塊として表現されているため、4バイト(32ビット)を掛けることで、IPヘッダのサイズ(バイト単位)がわかります。

今回は確認のために、ICMPタイプとコードをとりあえず表示するだけとしています。

 

動作確認

それでは、上記で作成したスクリプトを起動してみます。

なお、Windowsの場合はプロミスキャスモードを使うには管理者権限が必要なため、コマンドプロンプト(もしくはPowerShell)を管理者権限で起動します。

 

 > python sniffer_with_icmp.py
 Protocol: ICMP 192.168.2.1 -> 192.168.2.1
 ICMP -> Type: 0 Code: 0
 Protocol: ICMP 192.168.2.1 -> 192.168.2.1
 ICMP -> Type: 8 Code: 0
 Protocol: ICMP 192.168.2.1 -> 192.168.2.1
 ICMP -> Type: 3 Code: 1

 

別ターミナルで自分宛にpingをすると、タイプ8のエコー要求とタイプ0のエコー応答が確認でき、存在しないホストに送った場合、タイプ3の宛先到達不可能とコード1の"Host Unreachable"のメッセージがデコードされているのが確認できました。

 

最後に

 

今回はICMPを使い、そのヘッダをデコードする方法について学びました。

次回ではUDPを用いたネットワークスキャナーの作成方法について学んでいきます。

 

参考書籍

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