【Ruby】RawSocketを使ったネットワークプログラム
RubyでRawSocketを使いポートスキャンツールを作ります。
Pythonにはscapyという便利なライブラリーがあります。
Rubyにそのようなものがあるのか調べてませんしわからないので、スクラッチで作ることになります。
ソケット作成
synスキャンを行うためrawsocketを使う必要があります。
Rubyでは以下のようにしてraw socketを作成します。
c言語どほとんど変わりはありませんでした。
アドレスファミリー、ソケットタイプ、プロトコルを指定します。
1 2 3 4 |
def make_socket() soc = Socket::open(Socket::AF_INET,Socket::SOCK_RAW,Socket::IPPROTO_TCP) soc end |
tcpヘッダを自分で編集するための設定として
ソケットタイプにSOCK_RAW
プロトコルにIPPROTO_TCP
を指定します。
TCPヘッダーの作成
ヘッダーの作成方法に関しては僕が知らないだけでもっといいやり方があるかもしれません。
今回は以下の方法で作成しました。
TCPはパケットの内容が壊れていないかを判定するためチェックサムを計算しますが、その時に擬似ipヘッダーというものを加えます。
これは正しい通信相手からのパケットかどうかを判断するためだです。
諸々のデータを決定してpackで文字列にします。
packに渡している「n2N2C2n3」は値の型と数を表しています。
「n2」は2byteの値を2個、
「N2」は4byteの値を2個、
「C2」は1byteの値を1個、
「n3」は2byteの値を3個といった具合に配列の値を文字列に変換してくれます。
ですのでtcpヘッダーの構成を考えると「n2N2C2n3」をpackに渡すことになります。
特に難しいことはしていません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
def make_tcp(sport: 9999,dport: 31337,seq: 0,ackseq: 0,doff:5, fin: 0,syn: 0,rst: 0,psh: 0,ack: 0,urg: 0,window:300,urp:0, src: "127.0.0.1",dst: "127.0.0.1",dlen: 20) # 擬似ヘッダ pseudo = [IPAddr.new(src).to_i,IPAddr.new(dst).to_i,0,Socket::IPPROTO_TCP,dlen] pseudo_packet = pseudo.pack("N2C2n1") flags = fin + (syn << 1) + (rst << 2) + (psh << 3) + (ack << 4) + (urg << 5) doff = (doff << 4) cksum = 0 data = [sport,dport,seq,ackseq,doff,flags,window,cksum,urp] tcp_packet = data.pack("n2N2C2n3") cksum = checksum(pseudo_packet+tcp_packet) data = [sport,dport,seq,ackseq,doff,flags,window,cksum,urp] data.pack("n2N2C2n3") end |
チェックサムの計算
パケットのチェックサムを計算する関数です。
2byte単位で計算していきます
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# チェックサム計算 def checksum(data) len = data.size / 2 * 2 sum = 0 0.step(len-1,2) do |i| sum += (data[i+1].ord << 8) + data[i].ord end if data.size % 2 != 0 sum += data[-1].ord end while (sum >> 16) != 0 sum = (sum >> 16) + (sum & 0xffff) end sum = sum >> 8 | (sum << 8 & 0xff00) return ~sum & 0xffff end |
スキャン
1から65535までのポートを対象にスキャンを開始します。
ひとつのポートにつき10回までスキャンを試みます。
以下の終了条件を満たすまでパケットを送り続けます。
- 10回パケットの送受信をする
- syn,ackブラグがたったパケットを受信する
- フラグ上記の②以外の状態のパケットを受信する
項目として十分ではないかもしれませんが、ポートスキャナーとして最低限の動きはしてくれると思います。
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 |
def scan(target_ip) puts "target host: #{target_ip}" for i in 1..65536 packet = make_tcp(sport:9999, dport: i, src: {自分のip}, dst: target_ip, syn: 1) addr = Socket.pack_sockaddr_in(i, target_ip) for _ in 0..10 @soc.send(packet, 0, addr) sel = IO.select([@soc],nil,nil,10) if sel != nil && sel_soc = sel[0] packet = sel_soc[0].recv(1024) # パケットを解析 ip_data = extract_ip(packet[0...20]) hl = ip_data[0] & 0x0f start = hl*4 tcp_data = extract_tcp(packet[start...start+20]) # ターゲットからのパケットか確認 if ip_data[8] == IPAddr.new(target_ip).to_i # ターゲットポートからのデータ && syn,ackフラグが立っている if tcp_data[0] == i if ( (1 << 1) + (1 << 4) ) == tcp_data[5] puts "- port: #{i} open" end break end end end end end end |
パケットからヘッダーを抽出
ターゲットからパケットを受信したあと、そこからipヘッダーとtcpヘッダーを取得します。
パケットを送信する際にpackを使用して文字列にして送信しました。
今回は文字列に対しunpackを使用して配列を取得します。
1 2 3 4 5 6 7 8 9 |
# 受信パケットからipヘッダ取得 def extract_ip(packet) packet.unpack("C2n3C2n1N2") end # 受信パケットからtcpヘッダ取得 def extract_tcp(packet) packet.unpack("n2N2C2n3") end |
全体
ソースコード全体はこうなりました。
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 91 92 93 94 95 96 97 98 99 100 101 |
require 'socket' require 'ipaddr' class PortScanner def initialize() @soc = make_socket() end # ソケット作成 rawsocket def make_socket() Socket::open(Socket::AF_INET,Socket::SOCK_RAW,Socket::IPPROTO_TCP) end # チェックサム計算 def checksum(data) len = data.size / 2 * 2 sum = 0 0.step(len-1,2) do |i| sum += (data[i+1].ord << 8) + data[i].ord end if data.size % 2 != 0 sum += data[-1].ord end while (sum >> 16) != 0 sum = (sum >> 16) + (sum & 0xffff) end sum = sum >> 8 | (sum << 8 & 0xff00) return ~sum & 0xffff end # 受信パケットからipヘッダ取得 def extract_ip(packet) packet.unpack("C2n3C2n1N2") end # 受信パケットからtcpヘッダ取得 def extract_tcp(packet) packet.unpack("n2N2C2n3") end # スキャン開始 def scan(target_ip) puts "target host: #{target_ip}" for i in 1..65535 packet = make_tcp(sport:9999, dport: i, src: {自分のip}, dst: target_ip, syn: 1) addr = Socket.pack_sockaddr_in(i, target_ip) for _ in 0..10 @soc.send(packet, 0, addr) sel = IO.select([@soc],nil,nil,10) if sel != nil && sel_soc = sel[0] packet = sel_soc[0].recv(1024) # パケットを解析 ip_data = extract_ip(packet[0...20]) hl = ip_data[0] & 0x0f start = hl*4 tcp_data = extract_tcp(packet[start...start+20]) # ターゲットからのパケットか確認 if ip_data[8] == IPAddr.new(target_ip).to_i # ターゲットポートからのデータ && syn,ackフラグが立っている if tcp_data[0] == i if ( (1 << 1) + (1 << 4) ) == tcp_data[5] puts "- port: #{i} open" else break end end end end end end end # tcpヘッダ作成 def make_tcp(sport: 9999,dport: 31337,seq: 0,ackseq: 0,doff:5, fin: 0,syn: 0,rst: 0,psh: 0,ack: 0,urg: 0,window:300,urp:0, src: "127.0.0.1",dst: "127.0.0.1",dlen: 20) # 擬似ヘッダ pseudo = [IPAddr.new(src).to_i,IPAddr.new(dst).to_i,0,Socket::IPPROTO_TCP,dlen] pseudo_packet = pseudo.pack("N2C2n1") flags = fin + (syn << 1) + (rst << 2) + (psh << 3) + (ack << 4) + (urg << 5) doff = (doff << 4) cksum = 0 data = [sport,dport,seq,ackseq,doff,flags,window,cksum,urp] tcp_packet = data.pack("n2N2C2n3") cksum = checksum(pseudo_packet+tcp_packet) data = [sport,dport,seq,ackseq,doff,flags,window,cksum,urp] data.pack("n2N2C2n3") end end |
動作確認
実行してみましょう
1 2 3 4 5 |
target_ip = ARGV[0] scanner = PortScanner.new() 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 ruby port_scan.rb 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 |