-
Notifications
You must be signed in to change notification settings - Fork 0
/
rubysniff.rb
114 lines (98 loc) · 3.66 KB
/
rubysniff.rb
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
102
103
104
105
106
107
108
109
110
111
112
113
114
require 'pcaprub'
require 'optparse'
require 'ipaddr'
require 'colorize'
require 'terminal-table'
require 'tty-prompt'
require 'tty-spinner'
class WifiSniffer
def initialize(interface, filter, output, verbose)
@interface = interface
@filter = filter
@output = output
@verbose = verbose
@capture = PCAPRUB::Pcap.open_live(@interface, 65535, true, 0)
@capture.setfilter(@filter) if @filter
@rows = []
end
def start
spinner = TTY::Spinner.new("[:spinner] Starting sniffer on interface #{@interface} with filter #{@filter}", format: :pulse_2)
spinner.auto_spin
File.open(@output, 'w') do |file|
@capture.each_packet do |packet|
parse_packet(packet, file)
end
end
spinner.success("(done)")
display_table
end
private
def parse_packet(packet, file)
eth_type = packet[12..13].unpack('H*').first
return unless eth_type == '0800' # IPv4 packets
src_mac = format_mac(packet[6..11])
dst_mac = format_mac(packet[0..5])
ip_header = packet[14..33]
src_ip = IPAddr.new_ntoh(ip_header[12..15])
dst_ip = IPAddr.new_ntoh(ip_header[16..19])
protocol = ip_header[9].unpack('C').first
total_length = ip_header[2..3].unpack('n').first
protocol_name, src_port, dst_port = case protocol
when 1
['ICMP', nil, nil]
when 6
tcp_header = packet[34..53]
src_port = tcp_header[0..1].unpack('n').first
dst_port = tcp_header[2..3].unpack('n').first
['TCP', src_port, dst_port]
when 17
udp_header = packet[34..41]
src_port = udp_header[0..1].unpack('n').first
dst_port = udp_header[2..3].unpack('n').first
['UDP', src_port, dst_port]
else
[protocol.to_s, nil, nil]
end
row = [src_mac, dst_mac, src_ip.to_s, dst_ip.to_s, protocol_name, src_port, dst_port, total_length]
@rows << row
output = row.map { |e| e.nil? ? '' : e.to_s }.join(" | ")
puts output.colorize(:light_blue) if @verbose
file.puts output
end
def format_mac(addr)
addr.unpack('C*').map { |b| format('%02X', b) }.join(':')
end
def display_table
table = Terminal::Table.new :headings => ['Source MAC', 'Destination MAC', 'Source IP', 'Destination IP', 'Protocol', 'Source Port', 'Destination Port', 'Length'], :rows => @rows
puts table
end
end
options = {}
OptionParser.new do |opts|
opts.banner = "Usage: rubysniff.rb [options]"
opts.on("-i", "--interface INTERFACE", "Network interface to sniff") do |v|
options[:interface] = v
end
opts.on("-f", "--filter FILTER", "BPF filter string") do |v|
options[:filter] = v
end
opts.on("-o", "--output FILE", "Output file for captured packets") do |v|
options[:output] = v
end
opts.on("-v", "--verbose", "Run in verbose mode") do |v|
options[:verbose] = true
end
end.parse!
if options[:interface].nil? || options[:output].nil?
puts "Interface and output file are required."
exit 1
end
begin
puts "\nRubySniff - tarsoul"
sniffer = WifiSniffer.new(options[:interface], options[:filter], options[:output], options[:verbose])
sniffer.start
rescue Interrupt
puts "\nSniffer stopped by user."
rescue => e
puts "Error: #{e.message}"
end