PythonでRawSocketとTCPを使ったネットワークプログラム
今回はPythonでポートスキャンツールを作成したいと思います。
以前はRubyで作りました。
PythonとRubyは似ているため同じような手順で作成することができます。
細かい説明は以前の記事でまとめたので、今回はソースを書きつつさくっと進めていきたいと思います。
ソケット作成
Pythonでrawsocketを使用する時は以下のように記述します。
1 2 3 4 |
# tcpヘッダーを自分で作る def make_socket(): soc = socket.socket(socket.AF_INET,socket.SOCK_RAW,socket.IPPROTO_TCP) return soc |
これでtcpパケットを自分で修正することができます。
受信はipパケットが対象になります。
ちなみにRubyだとこんな感じです。
1 2 3 4 |
def make_socket() soc = Socket::open(Socket::AF_INET,Socket::SOCK_RAW,Socket::IPPROTO_TCP) soc end |
TCPヘッダー作成
こちらもRubyで作った時と同じように作成できます。
細かい部分で違いはありますが、やっていることはRuby版と同じです。
特にpackの部分などがそうですね。
tcpヘッダー構成に合わせて第一引数を指定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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があるかないかですかね。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#チェックサム計算 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を使っています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# スキャン開始 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関数を使って文字列を配列に変換しています。
1 2 3 4 5 6 7 8 9 |
# 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クラスを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# -*- 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 |
以下のように使用します。
1 2 3 |
target_ip = sys.argv[1] scanner = PortScanner() scanner.scan(target_ip) |
実行するときのコマンドと実行結果です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
$ 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はひとつになってしまってもいい気がします。
お互いのライブラリーなどの資産を使えるようにしたらみんな幸せになれると思います。