bettercap 1.1.1 → 1.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +5 -0
- data/LICENSE.md +596 -0
- data/README.md +18 -15
- data/TODO.md +22 -0
- data/bettercap.gemspec +1 -6
- data/bin/bettercap +35 -46
- data/lib/bettercap.rb +39 -0
- data/lib/bettercap/base/ifirewall.rb +11 -5
- data/lib/bettercap/base/ispoofer.rb +9 -3
- data/lib/bettercap/context.rb +48 -38
- data/lib/bettercap/discovery/arp.rb +18 -2
- data/lib/bettercap/discovery/base.rb +64 -0
- data/lib/bettercap/discovery/icmp.rb +3 -3
- data/lib/bettercap/discovery/syn.rb +6 -49
- data/lib/bettercap/discovery/udp.rb +23 -58
- data/lib/bettercap/factories/firewall_factory.rb +1 -1
- data/lib/bettercap/factories/parser_factory.rb +27 -25
- data/lib/bettercap/factories/spoofer_factory.rb +19 -13
- data/lib/bettercap/firewalls/linux.rb +3 -11
- data/lib/bettercap/firewalls/osx.rb +5 -18
- data/lib/bettercap/httpd/server.rb +4 -4
- data/lib/bettercap/network.rb +87 -74
- data/lib/bettercap/proxy/module.rb +1 -1
- data/lib/bettercap/proxy/proxy.rb +3 -3
- data/lib/bettercap/proxy/request.rb +5 -5
- data/lib/bettercap/proxy/response.rb +2 -2
- data/lib/bettercap/sniffer/parsers/base.rb +13 -13
- data/lib/bettercap/sniffer/sniffer.rb +19 -2
- data/lib/bettercap/spoofers/arp.rb +5 -5
- data/lib/bettercap/target.rb +18 -18
- data/lib/bettercap/version.rb +1 -1
- metadata +10 -7
- data/LICENSE +0 -225
- data/example_proxy_module.rb +0 -21
@@ -20,10 +20,10 @@ class Server
|
|
20
20
|
@port = port
|
21
21
|
@path = path
|
22
22
|
@server = WEBrick::HTTPServer.new(
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
23
|
+
:Port => @port,
|
24
|
+
:DocumentRoot => @path,
|
25
|
+
:Logger => WEBrick::Log.new("/dev/null"),
|
26
|
+
:AccessLog => []
|
27
27
|
)
|
28
28
|
end
|
29
29
|
|
data/lib/bettercap/network.rb
CHANGED
@@ -22,53 +22,60 @@ require 'bettercap/discovery/syn'
|
|
22
22
|
require 'bettercap/discovery/arp'
|
23
23
|
|
24
24
|
class Network
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
25
|
+
class << self
|
26
|
+
def is_ip?(ip)
|
27
|
+
if /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\Z/ =~ ip
|
28
|
+
return $~.captures.all? {|i| i.to_i < 256}
|
29
|
+
end
|
30
|
+
false
|
29
31
|
end
|
30
|
-
false
|
31
|
-
end
|
32
32
|
|
33
|
-
|
34
|
-
|
33
|
+
def get_gateway
|
34
|
+
nstat = Shell.execute('netstat -nr')
|
35
35
|
|
36
|
-
|
37
|
-
|
38
|
-
out = nstat.split(/\n/).select {|n| n =~ /UG/ }
|
39
|
-
gw = out.first.split[1]
|
40
|
-
|
41
|
-
raise BetterCap::Error, 'Could not detect gateway address' unless is_ip?(gw)
|
42
|
-
gw
|
43
|
-
end
|
36
|
+
Logger.debug "NETSTAT:\n#{nstat}"
|
44
37
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
38
|
+
out = nstat.split(/\n/).select {|n| n =~ /UG/ }
|
39
|
+
gw = nil
|
40
|
+
out.each do |line|
|
41
|
+
if line.include?( Context.get.options[:iface] )
|
42
|
+
gw = line.split[1]
|
43
|
+
end
|
51
44
|
end
|
45
|
+
|
46
|
+
raise BetterCap::Error, 'Could not detect gateway address' unless gw.nil? or is_ip?(gw)
|
47
|
+
gw
|
52
48
|
end
|
53
49
|
|
54
|
-
|
55
|
-
|
50
|
+
def get_local_ips
|
51
|
+
ips = []
|
52
|
+
|
53
|
+
Shell.ifconfig.split("\n").each do |line|
|
54
|
+
if line =~ /inet [adr:]*([\d\.]+)/
|
55
|
+
ips << $1
|
56
|
+
end
|
57
|
+
end
|
56
58
|
|
57
|
-
|
58
|
-
if ctx.options[:arpcache] == false
|
59
|
-
icmp = IcmpAgent.new timeout
|
60
|
-
udp = UdpAgent.new ctx.ifconfig, ctx.gateway, ctx.iface[:ip_saddr]
|
61
|
-
syn = SynAgent.new ctx.ifconfig, ctx.gateway, ctx.iface[:ip_saddr]
|
62
|
-
|
63
|
-
syn.wait
|
64
|
-
icmp.wait
|
65
|
-
udp.wait
|
66
|
-
else
|
67
|
-
Logger.debug 'Using current ARP cache.'
|
59
|
+
ips
|
68
60
|
end
|
69
61
|
|
70
|
-
|
71
|
-
|
62
|
+
def get_alive_targets( ctx, timeout = 5 )
|
63
|
+
if ctx.options[:arpcache] == false
|
64
|
+
icmp = IcmpAgent.new timeout
|
65
|
+
udp = UdpAgent.new ctx.ifconfig, ctx.gateway, ctx.ifconfig[:ip_saddr]
|
66
|
+
syn = SynAgent.new ctx.ifconfig, ctx.gateway, ctx.ifconfig[:ip_saddr]
|
67
|
+
arp = ArpAgent.new ctx.ifconfig, ctx.gateway, ctx.ifconfig[:ip_saddr]
|
68
|
+
|
69
|
+
syn.wait
|
70
|
+
icmp.wait
|
71
|
+
arp.wait
|
72
|
+
udp.wait
|
73
|
+
else
|
74
|
+
Logger.debug 'Using current ARP cache.'
|
75
|
+
end
|
76
|
+
|
77
|
+
ArpAgent.parse ctx
|
78
|
+
end
|
72
79
|
|
73
80
|
=begin
|
74
81
|
FIXME:
|
@@ -83,49 +90,55 @@ class Network
|
|
83
90
|
|
84
91
|
won't catch anything, instead we're using cap.stream.each.
|
85
92
|
=end
|
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
|
-
|
93
|
+
def get_hw_address( iface, ip_address, attempts = 2 )
|
94
|
+
hw_address = nil
|
95
|
+
|
96
|
+
attempts.times do
|
97
|
+
arp_pkt = PacketFu::ARPPacket.new
|
98
|
+
|
99
|
+
arp_pkt.eth_saddr = arp_pkt.arp_saddr_mac = iface[:eth_saddr]
|
100
|
+
arp_pkt.eth_daddr = 'ff:ff:ff:ff:ff:ff'
|
101
|
+
arp_pkt.arp_daddr_mac = '00:00:00:00:00:00'
|
102
|
+
arp_pkt.arp_saddr_ip = iface[:ip_saddr]
|
103
|
+
arp_pkt.arp_daddr_ip = ip_address
|
104
|
+
|
105
|
+
cap_thread = Thread.new do
|
106
|
+
target_mac = nil
|
107
|
+
timeout = 0
|
108
|
+
|
109
|
+
cap = PacketFu::Capture.new(
|
110
|
+
:iface => iface[:iface],
|
111
|
+
:start => true,
|
112
|
+
:filter => "arp src #{ip_address} and ether dst #{arp_pkt.eth_saddr}"
|
113
|
+
)
|
114
|
+
arp_pkt.to_w(iface[:iface])
|
115
|
+
|
116
|
+
begin
|
117
|
+
Logger.debug 'Attempting to get MAC from packet capture ...'
|
118
|
+
target_mac = Timeout::timeout(0.5) { get_mac_from_capture(cap, ip_address) }
|
119
|
+
rescue Timeout::Error
|
120
|
+
timeout += 0.1
|
121
|
+
retry if target_mac.nil? && timeout <= 5
|
115
122
|
end
|
116
123
|
|
117
|
-
|
118
|
-
|
119
|
-
Logger.debug 'Retrying ...'
|
120
|
-
sleep 0.1
|
124
|
+
target_mac
|
121
125
|
end
|
122
|
-
|
126
|
+
hw_address = cap_thread.value
|
127
|
+
|
128
|
+
break unless hw_address.nil?
|
123
129
|
end
|
124
|
-
hw_address = cap_thread.value
|
125
130
|
|
126
|
-
|
131
|
+
hw_address
|
127
132
|
end
|
128
133
|
|
129
|
-
|
134
|
+
private
|
135
|
+
|
136
|
+
def get_mac_from_capture( cap, ip_address )
|
137
|
+
cap.stream.each do |p|
|
138
|
+
arp_response = PacketFu::Packet.parse(p)
|
139
|
+
target_mac = arp_response.arp_saddr_mac if arp_response.arp_saddr_ip == ip_address
|
140
|
+
break target_mac unless target_mac.nil?
|
141
|
+
end
|
142
|
+
end
|
130
143
|
end
|
131
144
|
end
|
@@ -52,7 +52,7 @@ class Proxy
|
|
52
52
|
Logger.info 'Stopping proxy ...'
|
53
53
|
|
54
54
|
if @socket and @running
|
55
|
-
@running = false
|
55
|
+
@running = false
|
56
56
|
@socket.close
|
57
57
|
end
|
58
58
|
rescue
|
@@ -115,7 +115,7 @@ class Proxy
|
|
115
115
|
read += buff.size
|
116
116
|
|
117
117
|
# collect into the proper object
|
118
|
-
if not opts[:request].nil? and opts[:request].
|
118
|
+
if not opts[:request].nil? and opts[:request].post?
|
119
119
|
opts[:request] << buff
|
120
120
|
end
|
121
121
|
|
@@ -229,7 +229,7 @@ class Proxy
|
|
229
229
|
break unless not response.headers_done
|
230
230
|
end
|
231
231
|
|
232
|
-
if response.
|
232
|
+
if response.textual?
|
233
233
|
log_stream client_ip, request, response
|
234
234
|
|
235
235
|
Logger.debug 'Detected textual response'
|
@@ -40,7 +40,7 @@ class Request
|
|
40
40
|
|
41
41
|
line = "#{@verb} #{url} HTTP/1.0"
|
42
42
|
|
43
|
-
|
43
|
+
# get the host header value
|
44
44
|
elsif line =~ /^Host: (.*)$/
|
45
45
|
@host = $1
|
46
46
|
if host =~ /([^:]*):([0-9]*)$/
|
@@ -48,7 +48,7 @@ class Request
|
|
48
48
|
@port = $2.to_i
|
49
49
|
end
|
50
50
|
|
51
|
-
|
51
|
+
# parse content length, this will speed up data streaming
|
52
52
|
elsif line =~ /^Content-Length:\s+(\d+)\s*$/i
|
53
53
|
@content_length = $1.to_i
|
54
54
|
|
@@ -65,12 +65,12 @@ class Request
|
|
65
65
|
@lines << line
|
66
66
|
end
|
67
67
|
|
68
|
-
def
|
69
|
-
|
68
|
+
def post?
|
69
|
+
@verb == 'POST'
|
70
70
|
end
|
71
71
|
|
72
72
|
def to_s
|
73
|
-
|
73
|
+
@lines.join("\n") + "\n"
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
@@ -52,12 +52,12 @@ class Response
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
-
def
|
55
|
+
def textual? # textual?
|
56
56
|
@content_type and ( @content_type =~ /^text\/.+/ or @content_type =~ /^application\/.+/ )
|
57
57
|
end
|
58
58
|
|
59
59
|
def to_s
|
60
|
-
if
|
60
|
+
if textual?
|
61
61
|
@headers.map! do |header|
|
62
62
|
# update content length in case the body was
|
63
63
|
# modified
|
@@ -13,19 +13,19 @@ require 'bettercap/logger'
|
|
13
13
|
require 'colorize'
|
14
14
|
|
15
15
|
class BaseParser
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
16
|
+
def initialize
|
17
|
+
@filters = []
|
18
|
+
@name = 'BASE'
|
19
|
+
end
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
21
|
+
def on_packet( pkt )
|
22
|
+
s = pkt.to_s
|
23
|
+
@filters.each do |filter|
|
24
|
+
if s =~ filter
|
25
|
+
Logger.write "[#{pkt.ip_saddr}:#{pkt.tcp_src} > #{pkt.ip_daddr}:#{pkt.tcp_dst} #{pkt.proto.last}] " +
|
26
|
+
"[#{@name}] ".green +
|
27
|
+
pkt.payload.strip.yellow
|
28
|
+
end
|
30
29
|
end
|
30
|
+
end
|
31
31
|
end
|
@@ -22,13 +22,30 @@ class Sniffer
|
|
22
22
|
def self.start( ctx )
|
23
23
|
Logger.info 'Starting sniffer ...'
|
24
24
|
|
25
|
+
pcap = nil
|
26
|
+
|
27
|
+
if !ctx.options[:sniffer_pcap].nil?
|
28
|
+
pcap = PcapFile.new
|
29
|
+
Logger.warn "Saving packets to #{ctx.options[:sniffer_pcap]} ."
|
30
|
+
end
|
31
|
+
|
25
32
|
@@parsers = ParserFactory.load_by_names ctx.options[:parsers]
|
26
33
|
|
27
|
-
cap = Capture.new(
|
34
|
+
cap = Capture.new(
|
35
|
+
:iface => ctx.options[:iface],
|
36
|
+
:filter => ctx.options[:sniffer_filter],
|
37
|
+
:start => true
|
38
|
+
)
|
28
39
|
cap.stream.each do |p|
|
40
|
+
begin
|
41
|
+
pcap.array_to_file( :filename => ctx.options[:sniffer_pcap], :array => [p], :append => true) unless pcap.nil?
|
42
|
+
rescue Exception => e
|
43
|
+
Logger.warn e.message
|
44
|
+
end
|
45
|
+
|
29
46
|
pkt = Packet.parse p
|
30
47
|
if not pkt.nil? and pkt.is_ip?
|
31
|
-
next if ( pkt.ip_saddr == ctx.
|
48
|
+
next if ( pkt.ip_saddr == ctx.ifconfig[:ip_saddr] or pkt.ip_daddr == ctx.ifconfig[:ip_saddr] ) and !ctx.options[:local]
|
32
49
|
|
33
50
|
@@parsers.each do |parser|
|
34
51
|
begin
|
@@ -27,7 +27,7 @@ class ArpSpoofer < ISpoofer
|
|
27
27
|
Logger.debug 'ARP SPOOFER SELECTED'
|
28
28
|
|
29
29
|
Logger.info "Getting gateway #{@ctx.gateway} MAC address ..."
|
30
|
-
@gw_hw = Network.get_hw_address( @ctx.
|
30
|
+
@gw_hw = Network.get_hw_address( @ctx.ifconfig, @ctx.gateway )
|
31
31
|
if @gw_hw.nil?
|
32
32
|
raise BetterCap::Error, "Couldn't determine router MAC"
|
33
33
|
end
|
@@ -45,7 +45,7 @@ class ArpSpoofer < ISpoofer
|
|
45
45
|
pkt.arp_daddr_ip = daddr
|
46
46
|
pkt.arp_opcode = 2
|
47
47
|
|
48
|
-
pkt.to_w(@ctx.
|
48
|
+
pkt.to_w(@ctx.ifconfig[:iface])
|
49
49
|
end
|
50
50
|
|
51
51
|
def start
|
@@ -84,7 +84,7 @@ class ArpSpoofer < ISpoofer
|
|
84
84
|
if target.mac.nil?
|
85
85
|
Logger.warn "Getting target #{target.ip} MAC address ..."
|
86
86
|
|
87
|
-
hw = Network.get_hw_address( @ctx.
|
87
|
+
hw = Network.get_hw_address( @ctx.ifconfig, target.ip, 1 )
|
88
88
|
if hw.nil?
|
89
89
|
Logger.warn "Couldn't determine target MAC"
|
90
90
|
next
|
@@ -95,8 +95,8 @@ class ArpSpoofer < ISpoofer
|
|
95
95
|
end
|
96
96
|
end
|
97
97
|
|
98
|
-
send_spoofed_packed @ctx.gateway, @ctx.
|
99
|
-
send_spoofed_packed target.ip, @ctx.
|
98
|
+
send_spoofed_packed @ctx.gateway, @ctx.ifconfig[:eth_saddr], target.ip, target.mac
|
99
|
+
send_spoofed_packed target.ip, @ctx.ifconfig[:eth_saddr], @ctx.gateway, @gw_hw
|
100
100
|
end
|
101
101
|
|
102
102
|
prev_size = @ctx.targets.size
|
data/lib/bettercap/target.rb
CHANGED
@@ -17,36 +17,36 @@ class Target
|
|
17
17
|
@@prefixes = nil
|
18
18
|
|
19
19
|
def initialize( ip, mac )
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
20
|
+
@ip = ip
|
21
|
+
@mac = mac
|
22
|
+
@vendor = Target.lookup_vendor(mac) if not mac.nil?
|
23
|
+
@hostname = nil # for future use
|
24
24
|
end
|
25
25
|
|
26
26
|
def mac=(value)
|
27
|
-
|
28
|
-
|
27
|
+
@mac = value
|
28
|
+
@vendor = Target.lookup_vendor(@mac) if not @mac.nil?
|
29
29
|
end
|
30
30
|
|
31
31
|
def to_s
|
32
|
-
|
32
|
+
"#{@ip} : #{@mac}" + ( @vendor ? " ( #{@vendor} )" : "" )
|
33
33
|
end
|
34
34
|
|
35
35
|
private
|
36
36
|
|
37
37
|
def self.lookup_vendor( mac )
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
38
|
+
if @@prefixes == nil
|
39
|
+
Logger.debug 'Preloading hardware vendor prefixes ...'
|
40
|
+
|
41
|
+
@@prefixes = {}
|
42
|
+
filename = File.dirname(__FILE__) + '/hw-prefixes'
|
43
|
+
File.open( filename ).each do |line|
|
44
|
+
if line =~ /^([A-F0-9]{6})\s(.+)$/
|
45
|
+
@@prefixes[$1] = $2
|
46
|
+
end
|
48
47
|
end
|
48
|
+
end
|
49
49
|
|
50
|
-
|
50
|
+
@@prefixes[ mac.split(':')[0,3].join('').upcase ]
|
51
51
|
end
|
52
52
|
end
|