PythonでRawSocketとTCPを使ったネットワークプログラム
今回はPythonでポートスキャンツールを作成したいと思います。
以前はRubyで作りました。
PythonとRubyは似ているため同じような手順で作成することができます。
細かい説明は以前の記事でまとめたので、今回はソースを書きつつさくっと進めていきたいと思います。
ソケット作成
Pythonでrawsocketを使用する時は以下のように記述します。
# tcpヘッダーを自分で作る
def make_socket():
soc = socket.socket(socket.AF_INET,socket.SOCK_RAW,socket.IPPROTO_TCP)
return soc
これでtcpパケットを自分で修正することができます。
受信はipパケットが対象になります。
ちなみにRubyだとこんな感じです。
def make_socket()
soc = Socket::open(Socket::AF_INET,Socket::SOCK_RAW,Socket::IPPROTO_TCP)
soc
end
TCPヘッダー作成
こちらもRubyで作った時と同じように作成できます。
細かい部分で違いはありますが、やっていることはRuby版と同じです。
特にpackの部分などがそうですね。
tcpヘッダー構成に合わせて第一引数を指定します。
def make_tcp(src='127.0.0.1', dst='127.0.0.1', sport=9999, dport=31337, seq=1, ack=1, doff=5, flags=1<<1, win=300, check=0, urg=0):
tcp_header = struct.pack("!HHLLBBHHH", sport, dport, seq, ack, doff<<4, flags, win, check, urg)
# 擬似ipヘッダー
pip_saddr = socket.inet_aton(src)
pip_daddr = socket.inet_aton(dst)
pip_reserved = 0
pip_protocol = socket.IPPROTO_TCP
pip_len = 20
pip_header = struct.pack("!4s4sBBH",pip_saddr,pip_daddr,pip_reserved,pip_protocol,pip_len)
# チェックサム取得
check = checksum(pip_header+tcp_header)
tcp_header = struct.pack("!HHLLBBHHH",sport,dport,seq,ack,doff<<4,flags,win,check,urg)
return tcp_header
チェックサム
これに関しては単にPythonで書きなおしただけですね。
Rubyとの違いはendがあるかないかですかね。
#チェックサム計算
def checksum(data):
data_len = len(data) // 2*2
sum = 0
for i in range(0,data_len,2):
sum += (ord(data[i+1]) << 8) + ord(data[i])
if len(data) % 2 != 0:
sum += ord(data[-1])
while sum >> 16:
sum = (sum >> 16) + (sum & 0xffff)
sum = sum >> 8 | (sum << 8 & 0xff00)
return ~sum&0xffff
スキャン
スキャン処理についても流れは同じです。
Python版でもI/Oのreadyの確認にselectを使っています。
# スキャン開始
def scan(target_ip):
print("target host: {}".format(target_ip))
# port 1~65535
for i in range(1,65536):
# srcは自分のipアドレス
packet = make_tcp(src='192.168.11.6', dst=target_ip, dport=i)
$ 10回ずつ
for _ in range(10):
soc.sendto(packet,(target_ip,0))
sel_res = select.select([soc],[],[],10)
if len(sel_res[0]) > 0:
packet = sel_res[0][0].recv(1024)
ip = extract_ip(packet[0:20])
offset = (ip[0]&0x0f)*4
tcp = extract_tcp(packet[offset:offset+20])
# 送信元ip,ポート,フラグ確認
if ip[8] == socket.inet_aton(target_ip):
if tcp[0] == i:
if ( (1<<1) + (1<<4) ) == tcp[5]:
print("- port {} open".format(i))
break
受信したパケットらヘッダーを取得する処理も似ていて、 unpack関数を使って文字列を配列に変換しています。
# ipヘッダーを取得
def extract_ip(packet):
ip = struct.unpack('!BBHHHBBH4s4s', packet)
return ip
# tcpヘッダーを取得
def extract_tcp(packet):
tcp = struct.unpack('!HHLLBBHHH', packet)
return tcp
全体
Rubyで作成した時と同様に今回もPortScannerクラスを作成します。
# -*- coding: utf-8 -*-
import socket
import struct
import sys
import select
class PortScanner:
def __init__(self):
self.soc = self.make_socket()
def __del__(self):
self.soc.close()
# tcpヘッダーを自分で作る
def make_socket(self):
soc = socket.socket(socket.AF_INET,socket.SOCK_RAW,socket.IPPROTO_TCP)
return soc
# ヘッダー作成
def make_tcp(self, src='127.0.0.1', dst='127.0.0.1', sport=9999, dport=31337, seq=1, ack=1, doff=5, flags=1<<1, win=300, check=0, urg=0):
tcp_header = struct.pack("!HHLLBBHHH", sport, dport, seq, ack, doff<<4, flags, win, check, urg)
# 擬似ipヘッダー
pip_saddr = socket.inet_aton(src)
pip_daddr = socket.inet_aton(dst)
pip_reserved = 0
pip_protocol = socket.IPPROTO_TCP
pip_len = 20
pip_header = struct.pack("!4s4sBBH",pip_saddr,pip_daddr,pip_reserved,pip_protocol,pip_len)
# チェックサム 擬似ip+tcpヘッダー
check = self.checksum(pip_header+tcp_header)
tcp_header = struct.pack("!HHLLBBHHH",sport,dport,seq,ack,doff<<4,flags,win,check,urg)
return tcp_header
def checksum(self, data):
data_len = len(data) // 2*2
sum = 0
for i in range(0,data_len,2):
sum += (ord(data[i+1]) << 8) + ord(data[i])
#Python3
#sum += (ord(chr(data[i+1])) << 8) + ord(chr(data[i]))
if len(data) % 2 != 0:
sum += ord(data[-1])
#Python3
#sum += ord(chr(data[-1]))
while sum >> 16:
sum = (sum >> 16) + (sum & 0xffff)
sum = sum >> 8 | (sum << 8 & 0xff00)
return ~sum&0xffff
# ipヘッダー取得
def extract_ip(self, packet):
#packで配列に変換 第一引数はipヘッダーに合うように指定
ip = struct.unpack('!BBHHHBBH4s4s', packet)
return ip
# tcpヘッダー取得
def extract_tcp(self, packet):
#packで配列に変換 第一引数はtcpヘッダーに合うように指定
tcp = struct.unpack('!HHLLBBHHH', packet)
return tcp
def scan(self, target_ip):
print("target host: {}".format(target_ip))
# port 1~65535
for i in range(1,65535):
# srcは自分のipをいい具合に指定
packet = self.make_tcp(src='192.168.11.6', dst=target_ip, dport=i)
# 10回ずつ
for _ in range(10):
self.soc.sendto(packet,(target_ip,0))
sel_res = select.select([self.soc],[],[],10)
if len(sel_res[0]) > 0:
packet = sel_res[0][0].recv(1024)
ip = self.extract_ip(packet[0:20])
offset = (ip[0]&0x0f)*4
tcp = self.extract_tcp(packet[offset:offset+20])
# 送信元ip,ポート,フラグを確認
if ip[8] == socket.inet_aton(target_ip):
if tcp[0] == i:
if ( (1<<1) + (1<<4) ) == tcp[5]:
print("- port {} open".format(i))
else:
break
以下のように使用します。
target_ip = sys.argv[1]
scanner = PortScanner()
scanner.scan(target_ip)
実行するときのコマンドと実行結果です。
$ sudo python port_scan.py 192.168.11.38
target host: 192.168.11.38
- port 21 open
- port 22 open
- port 23 open
- port 25 open
- port 53 open
- port 80 open
- port 111 open
- port 139 open
- port 445 open
- port 512 open
- port 513 open
- port 514 open
- port 1099 open
- port 1524 open
- port 2049 open
- port 2121 open
- port 3306 open
- port 3632 open
- port 5432 open
- port 5900 open
- port 6000 open
- port 6667 open
- port 6697 open
- port 8009 open
- port 8180 open
- port 8787 open
- port 33774 open
- port 43909 open
- port 54497 open
- port 58138 open
感想
もうPythonとRubyはひとつになってしまってもいい気がします。
お互いのライブラリーなどの資産を使えるようにしたらみんな幸せになれると思います。