2013年01月10日
Python(Scapy)を使ってGateway Finderを作ってみる
ネットワークへ接続する為にIPアドレスとネットマスクは知っているけれど、ゲートウェイのアドレスは分からない、隠れているゲートウェイを見つけたい。 たまーにですけど、こんな状況ありますよね。

いざという時に手作業で調べていては大変。
今日はPython + ScapyでGateway Finderを作ってみます。

いざという時に手作業で調べていては大変。
今日はPython + ScapyでGateway Finderを作ってみます。
Scapyのインストール
何時もの通りapt-getでScapyネットワーク・ライブラリを用意します。
$ sudo apt-get install python-scapy
apt-getは使わずにsetup.pyでインストールするならこちら。
$ wget http://www.secdev.org/projects/scapy/files/scapy-latest.tar.gz$ tar xzf scapy-latest.tar.gz$ cd scapy-.x.x/$ sudo python setup.py install
Gateway Finderの考え方
Gatewayを見つけるロジックは次の通り。
- ARPリクエストを送信して同一ネットワーク上のノード(IP,MACアドレス)情報をスキャンする
- 存在するノードに対してTTLを1にしたICMPパケットを送信する
- TTL値の減算が行われ時間超過(TTL=0,Time Exceeded )メッセージ(11)を返信したノードをルーターと考える
- ルーターに対し、外部アドレス宛のICMPパケットを送信し、応答があればGatewayと判断する
気の利く人なら直ぐに思いつくかもしれませんね。
時間超過メッセージを返信するノードなんてそう簡単にはいないから3番目までで判断しても良いんじゃ無い?、と思うかもしれませんが最近はVMwareのような仮想化製品(その性質上、ルーティングやDHCPサーバー機能を持っている)を使っているなんてこともありますから、怪しい候補としてはリストしつつ、外部サーバーからICMP Replyを受け取るまでを組込み・判断します。

Code of Gateway Finder
コードはこちら。
import sys import threading import time from scapy.all import * import socket import fcntl import struct ICMP_TYPE_ECHO_REPLY = 0 ICMP_TYPE_TTL_EXCEEDED = 11 END_POINT_ADDR = "8.8.8.8" conf.iface = "eth0" conf.verb = 0 if len(sys.argv) != 2: print "Usage: gateway.py eg: sudo python gateway-finder.py 192.168.1.0/24" sys.exit(1) def get_ip_addr(ifname): s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) return socket.inet_ntoa(fcntl.ioctl(s.fileno(), 0x8915,struct.pack('256s', ifname[:15]))[20:24]) iface_ip = get_ip_addr(conf.iface) iface_hw = get_if_hwaddr(conf.iface) # arp ans,unans=srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=sys.argv[1]),timeout=3) mac2ip = {} for snd,rcv in ans: mac2ip[rcv.src] = rcv.psrc # sniff & handle icmp packet class ICMPSniffer(threading.Thread): def __init__(self, mac2ip): threading.Thread.__init__(self) self.mac2ip = mac2ip def callbak(self, pkt): global iface_hw global iface_ip if ICMP in pkt: if pkt[ICMP].type == ICMP_TYPE_TTL_EXCEEDED: print "ROUTER?: %s\t%s" % (pkt[IP].src, pkt.src) icmp = Ether(src=iface_hw,dst=pkt.src)/IP(src=iface_ip,dst=END_POINT_ADDR)/ICMP() sendp(icmp) elif pkt[ICMP].type == ICMP_TYPE_ECHO_REPLY: print "GATEWAY: %s\t%s" % (self.mac2ip[pkt.src], pkt.src) def run(self): sniff(prn=self.callbak, filter="icmp", store=0) icmpsniff = ICMPSniffer(mac2ip) icmpsniff.daemon = True icmpsniff.start() time.sleep(1) for mac,ip in mac2ip.iteritems(): #print "IP NODE: %s\t%s" % (ip, mac) icmp = Ether(src=iface_hw,dst=mac)/IP(src=iface_ip,dst=END_POINT_ADDR,ttl=1)/ICMP() sendp(icmp)
このコードではインタフェースはeth0、外部への疎通確認用IPアドレス(END_POINT_ADDR)には8.8.8.8 (Google DNS)を指定しています。
最初に自身のIPアドレス(iface_ip)、MACアドレス(iface_hw)を取得しておき、ローカル・ネットワーク上にarpリクエストを送信、MACアドレスとIPアドレスとのマップを作成しておきます。
(インタフェース名からそのIPアドレスを取得する簡単な方法って無いのかしら・・・)
続いてインタフェースをsniffし、ICMPパケットのハンドリングを行うスレッドを用意・開始しておきます。
このスレッドは受信したICMPパケットのメッセージタイプを見てTime Exceeded (11)であれば、ルーティング機能を持つノードだと判断して宛先アドレスをEND_POINT_ADDR (8.8.8.8)としたICMP Echo Request (8)を送信、ICMP Echo Reply (0)ならばGatewayだと判断します。
(書いていて思ったんですが、Echo Replyの場合に送信元IPアドレスがEND_POINT_ADDRか否かもチェックした方が確実ですね!)
最後にARPスキャンして集めた同一ネットワーク上にあるノードのMACアドレスを宛先にしたEtherオブジェクトに宛先IPアドレスをEND_POINT_ADDR (8.8.8.8)としたICMPパケット、つまりルーティング・リクエストを一斉に送信しています。
このパケットはTTLの値を1としているのでルーターであれば、値を1減算して0になり、パケット破棄及び送信元へのTime Exceeded (11)メッセージ送信が行われるので、先程用意しておいたスレッドで検知されるという訳なんです。
このコードをgateway-finder.pyとして保存、引数にローカルのネットワーク・アドレスを指定して起動してみます。
$ sudo python gateway-finder.py 192.168.1.0/24ROUTER?: 192.168.1.120 00:90:c1:1a:a6:f9ROUTER?: 192.168.1.213 00:de:0b:a2:33:1eROUTER?: 192.168.1.254 00:a0:de:16:ff:30GATEWAY: 192.168.1.254 00:a0:de:16:ff:30
よーし、ルーター候補を3ノード、実際に外部に接続できるゲートウェイを1ノード無事に見つけてくれました。
それでは、また今度!
ハッカーと画家 コンピュータ時代の創造者たち
posted with amazlet at 13.01.10
ポール グレアム
オーム社
売り上げランキング: 21,582
オーム社
売り上げランキング: 21,582
この記事へのトラックバックURL
http://trackback.blogsys.jp/livedoor/netbuffalo/4335463