bettercap 1.1.8 → 1.1.9
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/TODO.md +2 -0
- data/bin/bettercap +17 -186
- data/lib/bettercap.rb +2 -0
- data/lib/bettercap/base/ifirewall.rb +2 -2
- data/lib/bettercap/context.rb +46 -126
- data/lib/bettercap/discovery.rb +57 -0
- data/lib/bettercap/firewalls/linux.rb +4 -4
- data/lib/bettercap/firewalls/osx.rb +3 -3
- data/lib/bettercap/logger.rb +23 -17
- data/lib/bettercap/network.rb +0 -5
- data/lib/bettercap/options.rb +182 -1
- data/lib/bettercap/proxy/stream_logger.rb +1 -1
- data/lib/bettercap/shell.rb +14 -5
- data/lib/bettercap/sniffer/parsers/base.rb +1 -1
- data/lib/bettercap/sniffer/parsers/httpauth.rb +2 -2
- data/lib/bettercap/sniffer/parsers/https.rb +1 -1
- data/lib/bettercap/sniffer/parsers/ntlmss.rb +1 -1
- data/lib/bettercap/sniffer/parsers/post.rb +1 -1
- data/lib/bettercap/sniffer/parsers/url.rb +1 -1
- data/lib/bettercap/sniffer/sniffer.rb +18 -15
- data/lib/bettercap/spoofers/arp.rb +14 -13
- data/lib/bettercap/target.rb +18 -7
- data/lib/bettercap/update_checker.rb +53 -0
- data/lib/bettercap/version.rb +1 -1
- data/test/logger_test.rb +1 -1
- metadata +7 -9
- data/.gitignore +0 -12
- data/Gemfile +0 -2
- data/Gemfile.lock +0 -25
- data/bettercap.gemspec +0 -28
@@ -0,0 +1,57 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
BETTERCAP
|
4
|
+
|
5
|
+
Author : Simone 'evilsocket' Margaritelli
|
6
|
+
Email : evilsocket@gmail.com
|
7
|
+
Blog : http://www.evilsocket.net/
|
8
|
+
|
9
|
+
This project is released under the GPL 3 license.
|
10
|
+
|
11
|
+
=end
|
12
|
+
class Discovery
|
13
|
+
def initialize( ctx )
|
14
|
+
@ctx = ctx
|
15
|
+
@running = false
|
16
|
+
@thread = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def start
|
20
|
+
@running = true
|
21
|
+
@thread = Thread.new { worker }
|
22
|
+
end
|
23
|
+
|
24
|
+
def stop
|
25
|
+
@running = false
|
26
|
+
if @thread != nil
|
27
|
+
Logger.info( 'Stopping network discovery thread ...' ) unless @ctx.options.arpcache
|
28
|
+
begin
|
29
|
+
@thread.exit
|
30
|
+
rescue
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def worker
|
38
|
+
Logger.debug( 'Network discovery thread started.' ) unless @ctx.options.arpcache
|
39
|
+
|
40
|
+
while @running
|
41
|
+
empty_list = @ctx.targets.empty?
|
42
|
+
|
43
|
+
if @ctx.options.should_discover_hosts?
|
44
|
+
Logger.info 'Searching for targets ...' if empty_list
|
45
|
+
end
|
46
|
+
|
47
|
+
@ctx.targets = Network.get_alive_targets(@ctx).sort_by { |t| t.sortable_ip }
|
48
|
+
|
49
|
+
if empty_list and not @ctx.targets.empty?
|
50
|
+
Logger.info "Collected #{@ctx.targets.size} total targets."
|
51
|
+
@ctx.targets.each do |target|
|
52
|
+
Logger.info " #{target}"
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -25,20 +25,20 @@ class LinuxFirewall < IFirewall
|
|
25
25
|
shell.execute("echo #{enabled ? 0 : 1} > /proc/sys/net/ipv4/icmp_echo_ignore_broadcasts")
|
26
26
|
end
|
27
27
|
|
28
|
-
def add_port_redirection(
|
28
|
+
def add_port_redirection( r )
|
29
29
|
# post route
|
30
30
|
shell.execute('iptables -t nat -I POSTROUTING -s 0/0 -j MASQUERADE')
|
31
31
|
# accept all
|
32
32
|
shell.execute('iptables -P FORWARD ACCEPT')
|
33
33
|
# add redirection
|
34
|
-
shell.execute("iptables -t nat -A PREROUTING -i #{
|
34
|
+
shell.execute("iptables -t nat -A PREROUTING -i #{r.interface} -p #{r.protocol} --dport #{r.src_port} -j DNAT --to #{r.dst_address}:#{r.dst_port}")
|
35
35
|
end
|
36
36
|
|
37
|
-
def del_port_redirection(
|
37
|
+
def del_port_redirection( r )
|
38
38
|
# remove post route
|
39
39
|
shell.execute('iptables -t nat -D POSTROUTING -s 0/0 -j MASQUERADE')
|
40
40
|
# remove redirection
|
41
|
-
shell.execute("iptables -t nat -D PREROUTING -i #{
|
41
|
+
shell.execute("iptables -t nat -D PREROUTING -i #{r.interface} -p #{r.protocol} --dport #{r.src_port} -j DNAT --to #{r.dst_address}:#{r.dst_port}")
|
42
42
|
end
|
43
43
|
|
44
44
|
private
|
@@ -31,12 +31,12 @@ class OSXFirewall < IFirewall
|
|
31
31
|
rescue; end
|
32
32
|
end
|
33
33
|
|
34
|
-
def add_port_redirection(
|
34
|
+
def add_port_redirection( r )
|
35
35
|
# create the pf config file
|
36
36
|
config_file = "/tmp/bettercap_pf_#{Process.pid}.conf"
|
37
37
|
|
38
38
|
File.open( config_file, 'a+t' ) do |f|
|
39
|
-
f.write "rdr pass on #{
|
39
|
+
f.write "rdr pass on #{r.interface} proto #{r.protocol} from any to any port #{r.src_port} -> #{r.dst_address} port #{r.dst_port}\n"
|
40
40
|
end
|
41
41
|
|
42
42
|
# load the rule
|
@@ -45,7 +45,7 @@ class OSXFirewall < IFirewall
|
|
45
45
|
enable true
|
46
46
|
end
|
47
47
|
|
48
|
-
def del_port_redirection(
|
48
|
+
def del_port_redirection( r )
|
49
49
|
# FIXME: This should search for multiple rules inside the
|
50
50
|
# file and remove only this one.
|
51
51
|
|
data/lib/bettercap/logger.rb
CHANGED
@@ -11,43 +11,49 @@ This project is released under the GPL 3 license.
|
|
11
11
|
=end
|
12
12
|
module Logger
|
13
13
|
class << self
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
def init( debug, logfile )
|
15
|
+
@@debug = debug
|
16
|
+
@@logfile = logfile
|
17
|
+
@@queue = Queue.new
|
18
|
+
@@thread = Thread.new { worker }
|
19
|
+
end
|
17
20
|
|
18
21
|
def error(message)
|
19
|
-
|
22
|
+
@@queue.push formatted_message(message, 'E').red
|
20
23
|
end
|
21
24
|
|
22
25
|
def info(message)
|
23
|
-
|
26
|
+
@@queue.push formatted_message(message, 'I')
|
24
27
|
end
|
25
28
|
|
26
29
|
def warn(message)
|
27
|
-
|
30
|
+
@@queue.push formatted_message(message, 'W').yellow
|
28
31
|
end
|
29
32
|
|
30
33
|
def debug(message)
|
31
|
-
if
|
32
|
-
|
34
|
+
if @@debug
|
35
|
+
@@queue.push formatted_message(message, 'D').light_black
|
33
36
|
end
|
34
37
|
end
|
35
38
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
39
|
-
|
39
|
+
def raw(message)
|
40
|
+
@@queue.push message
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
40
44
|
|
41
|
-
|
42
|
-
|
45
|
+
def worker
|
46
|
+
loop do
|
47
|
+
message = @@queue.pop
|
48
|
+
puts message
|
49
|
+
unless @@logfile.nil?
|
50
|
+
f = File.open( @@logfile, 'a+t' )
|
43
51
|
f.puts( message.gsub( /\e\[(\d+)(;\d+)*m/, '') + "\n")
|
44
52
|
f.close
|
45
53
|
end
|
46
|
-
|
54
|
+
end
|
47
55
|
end
|
48
56
|
|
49
|
-
private
|
50
|
-
|
51
57
|
def formatted_message(message, message_type)
|
52
58
|
"[#{message_type}] #{message}"
|
53
59
|
end
|
data/lib/bettercap/network.rb
CHANGED
@@ -45,11 +45,6 @@ class Network
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
end
|
48
|
-
|
49
|
-
raise BetterCap::Error, "Could not detect the gateway address for interface #{Context.get.options.iface}, "\
|
50
|
-
'make sure you\'ve specified the correct network interface to use and to have the '\
|
51
|
-
'correct network configuration, this could also happen if bettercap '\
|
52
|
-
'is launched from a virtual environment.' unless !gw.nil? and is_ip?(gw)
|
53
48
|
gw
|
54
49
|
end
|
55
50
|
|
data/lib/bettercap/options.rb
CHANGED
@@ -80,6 +80,187 @@ class Options
|
|
80
80
|
@check_updates = false
|
81
81
|
end
|
82
82
|
|
83
|
+
def self.parse!
|
84
|
+
ctx = Context.get
|
85
|
+
|
86
|
+
OptionParser.new do |opts|
|
87
|
+
opts.banner = "Usage: #{$0} [options]"
|
88
|
+
opts.version = BetterCap::VERSION
|
89
|
+
|
90
|
+
opts.on( '-G', '--gateway ADDRESS', 'Manually specify the gateway address, if not specified the current gateway will be retrieved and used. ' ) do |v|
|
91
|
+
ctx.options.gateway = v
|
92
|
+
end
|
93
|
+
|
94
|
+
opts.on( '-I', '--interface IFACE', 'Network interface name - default: ' + ctx.options.iface.to_s ) do |v|
|
95
|
+
ctx.options.iface = v
|
96
|
+
end
|
97
|
+
|
98
|
+
opts.on( '-S', '--spoofer NAME', 'Spoofer module to use, available: ' + SpooferFactory.available.join(', ') + ' - default: ' + ctx.options.spoofer ) do |v|
|
99
|
+
ctx.options.spoofer = v
|
100
|
+
end
|
101
|
+
|
102
|
+
opts.on( '-T', '--target ADDRESS1,ADDRESS2', 'Target IP addresses, if not specified the whole subnet will be targeted.' ) do |v|
|
103
|
+
ctx.options.target = v
|
104
|
+
end
|
105
|
+
|
106
|
+
opts.on( '--ignore ADDRESS1,ADDRESS2', 'Ignore these addresses if found while searching for targets.' ) do |v|
|
107
|
+
ctx.options.ignore = v
|
108
|
+
end
|
109
|
+
|
110
|
+
opts.on( '-O', '--log LOG_FILE', 'Log all messages into a file, if not specified the log messages will be only print into the shell.' ) do |v|
|
111
|
+
ctx.options.logfile = v
|
112
|
+
end
|
113
|
+
|
114
|
+
opts.on( '-D', '--debug', 'Enable debug logging.' ) do
|
115
|
+
ctx.options.debug = true
|
116
|
+
end
|
117
|
+
|
118
|
+
opts.on( '-L', '--local', 'Parse packets coming from/to the address of this computer ( NOTE: Will set -X to true ), default to false.' ) do
|
119
|
+
ctx.options.local = true
|
120
|
+
ctx.options.sniffer = true
|
121
|
+
end
|
122
|
+
|
123
|
+
opts.on( '-X', '--sniffer', 'Enable sniffer.' ) do
|
124
|
+
ctx.options.sniffer = true
|
125
|
+
end
|
126
|
+
|
127
|
+
opts.on( '--sniffer-source FILE', 'Load packets from the specified PCAP file instead of the interface ( will enable sniffer ).' ) do |v|
|
128
|
+
ctx.options.sniffer = true
|
129
|
+
ctx.options.sniffer_src = File.expand_path v
|
130
|
+
end
|
131
|
+
|
132
|
+
opts.on( '--sniffer-pcap FILE', 'Save all packets to the specified PCAP file ( will enable sniffer ).' ) do |v|
|
133
|
+
ctx.options.sniffer = true
|
134
|
+
ctx.options.sniffer_pcap = File.expand_path v
|
135
|
+
end
|
136
|
+
|
137
|
+
opts.on( '--sniffer-filter EXPRESSION', 'Configure the sniffer to use this BPF filter ( will enable sniffer ).' ) do |v|
|
138
|
+
ctx.options.sniffer = true
|
139
|
+
ctx.options.sniffer_filter = v
|
140
|
+
end
|
141
|
+
|
142
|
+
opts.on( '-P', '--parsers PARSERS', 'Comma separated list of packet parsers to enable, "*" for all ( NOTE: Will set -X to true ), available: ' + ParserFactory.available.join(', ') + ' - default: *' ) do |v|
|
143
|
+
ctx.options.sniffer = true
|
144
|
+
ctx.options.parsers = ParserFactory.from_cmdline(v)
|
145
|
+
end
|
146
|
+
|
147
|
+
opts.on( '--no-discovery', 'Do not actively search for hosts, just use the current ARP cache, default to false.' ) do
|
148
|
+
ctx.options.arpcache = true
|
149
|
+
end
|
150
|
+
|
151
|
+
opts.on( '--no-spoofing', 'Disable spoofing, alias for --spoofer NONE.' ) do
|
152
|
+
ctx.options.spoofer = 'NONE'
|
153
|
+
end
|
154
|
+
|
155
|
+
opts.on( '--half-duplex', 'Enable half-duplex MITM, this will make bettercap work in those cases when the router is not vulnerable.' ) do
|
156
|
+
ctx.options.half_duplex = true
|
157
|
+
end
|
158
|
+
|
159
|
+
opts.on( '--proxy', 'Enable HTTP proxy and redirects all HTTP requests to it, default to false.' ) do
|
160
|
+
ctx.options.proxy = true
|
161
|
+
end
|
162
|
+
|
163
|
+
opts.on( '--proxy-https', 'Enable HTTPS proxy and redirects all HTTPS requests to it, default to false.' ) do
|
164
|
+
ctx.options.proxy = true
|
165
|
+
ctx.options.proxy_https = true
|
166
|
+
end
|
167
|
+
|
168
|
+
opts.on( '--proxy-port PORT', 'Set HTTP proxy port, default to ' + ctx.options.proxy_port.to_s + ' .' ) do |v|
|
169
|
+
ctx.options.proxy = true
|
170
|
+
ctx.options.proxy_port = v.to_i
|
171
|
+
end
|
172
|
+
|
173
|
+
opts.on( '--proxy-https-port PORT', 'Set HTTPS proxy port, default to ' + ctx.options.proxy_https_port.to_s + ' .' ) do |v|
|
174
|
+
ctx.options.proxy = true
|
175
|
+
ctx.options.proxy_https = true
|
176
|
+
ctx.options.proxy_https_port = v.to_i
|
177
|
+
end
|
178
|
+
|
179
|
+
opts.on( '--proxy-pem FILE', 'Use a custom PEM certificate file for the HTTPS proxy.' ) do |v|
|
180
|
+
ctx.options.proxy = true
|
181
|
+
ctx.options.proxy_https = true
|
182
|
+
ctx.options.proxy_pem_file = File.expand_path v
|
183
|
+
end
|
184
|
+
|
185
|
+
opts.on( '--proxy-module MODULE', 'Ruby proxy module to load.' ) do |v|
|
186
|
+
ctx.options.proxy = true
|
187
|
+
ctx.options.proxy_module = File.expand_path v
|
188
|
+
end
|
189
|
+
|
190
|
+
opts.on( '--custom-proxy ADDRESS', 'Use a custom HTTP upstream proxy instead of the builtin one.' ) do |v|
|
191
|
+
ctx.options.custom_proxy = v
|
192
|
+
end
|
193
|
+
|
194
|
+
opts.on( '--custom-proxy-port PORT', 'Specify a port for the custom HTTP upstream proxy, default to ' + ctx.options.custom_proxy_port.to_s + ' .' ) do |v|
|
195
|
+
ctx.options.custom_proxy_port = v.to_i
|
196
|
+
end
|
197
|
+
|
198
|
+
opts.on( '--custom-https-proxy ADDRESS', 'Use a custom HTTPS upstream proxy instead of the builtin one.' ) do |v|
|
199
|
+
ctx.options.custom_https_proxy = v
|
200
|
+
end
|
201
|
+
|
202
|
+
opts.on( '--custom-https-proxy-port PORT', 'Specify a port for the custom HTTPS upstream proxy, default to ' + ctx.options.custom_https_proxy_port.to_s + ' .' ) do |v|
|
203
|
+
ctx.options.custom_https_proxy_port = v.to_i
|
204
|
+
end
|
205
|
+
|
206
|
+
opts.on( '--httpd', 'Enable HTTP server, default to false.' ) do
|
207
|
+
ctx.options.httpd = true
|
208
|
+
end
|
209
|
+
|
210
|
+
opts.on( '--httpd-port PORT', 'Set HTTP server port, default to ' + ctx.options.httpd_port.to_s + '.' ) do |v|
|
211
|
+
ctx.options.httpd = true
|
212
|
+
ctx.options.httpd_port = v.to_i
|
213
|
+
end
|
214
|
+
|
215
|
+
opts.on( '--httpd-path PATH', 'Set HTTP server path, default to ' + ctx.options.httpd_path + '.' ) do |v|
|
216
|
+
ctx.options.httpd = true
|
217
|
+
ctx.options.httpd_path = v
|
218
|
+
end
|
219
|
+
|
220
|
+
opts.on( '--check-updates', 'Will check if any update is available and then exit.' ) do
|
221
|
+
ctx.options.check_updates = true
|
222
|
+
end
|
223
|
+
|
224
|
+
opts.on('-h', '--help', 'Display the available options.') do
|
225
|
+
puts opts
|
226
|
+
puts "\nFor examples & docs please visit " + "http://bettercap.org/docs/".bold
|
227
|
+
exit
|
228
|
+
end
|
229
|
+
end.parse!
|
230
|
+
|
231
|
+
Logger.init( ctx.options.debug, ctx.options.logfile )
|
232
|
+
|
233
|
+
Logger.warn "You are running an unstable/beta version of this software, please" \
|
234
|
+
" update to a stable one if available." if BetterCap::VERSION =~ /[\d\.+]b/
|
235
|
+
|
236
|
+
if ctx.options.check_updates
|
237
|
+
UpdateChecker.check
|
238
|
+
exit
|
239
|
+
end
|
240
|
+
|
241
|
+
raise BetterCap::Error, 'This software must run as root.' unless Process.uid == 0
|
242
|
+
raise BetterCap::Error, 'No default interface found, please specify one with the -I argument.' if ctx.options.iface.nil?
|
243
|
+
|
244
|
+
unless ctx.options.gateway.nil?
|
245
|
+
raise BetterCap::Error, "The specified gateway '#{ctx.options.gateway}' is not a valid IPv4 address." unless Network.is_ip?(ctx.options.gateway)
|
246
|
+
ctx.gateway = ctx.options.gateway
|
247
|
+
Logger.debug("Targetting manually specified gateway #{ctx.gateway}")
|
248
|
+
end
|
249
|
+
|
250
|
+
unless ctx.options.target.nil?
|
251
|
+
ctx.targets = ctx.options.to_targets
|
252
|
+
end
|
253
|
+
|
254
|
+
# Load firewall instance, network interface informations and detect the
|
255
|
+
# gateway address.
|
256
|
+
ctx.update!
|
257
|
+
|
258
|
+
# Spoofers need the context network data to be initialized.
|
259
|
+
ctx.spoofer = ctx.options.to_spoofers
|
260
|
+
|
261
|
+
ctx
|
262
|
+
end
|
263
|
+
|
83
264
|
def should_discover_hosts?
|
84
265
|
!@arpcache
|
85
266
|
end
|
@@ -89,7 +270,7 @@ class Options
|
|
89
270
|
end
|
90
271
|
|
91
272
|
def has_spoofer?
|
92
|
-
@spoofer
|
273
|
+
@spoofer != 'NONE' and @spoofer != 'none'
|
93
274
|
end
|
94
275
|
|
95
276
|
def has_http_sniffer_enabled?
|
@@ -40,7 +40,7 @@ class StreamLogger
|
|
40
40
|
response_s += " [#{response.code}]"
|
41
41
|
end
|
42
42
|
|
43
|
-
Logger.
|
43
|
+
Logger.raw "[#{self.addr2s(client)}] #{request.verb.light_blue} #{request_s} #{response_s}"
|
44
44
|
end
|
45
45
|
end
|
46
46
|
end
|
data/lib/bettercap/shell.rb
CHANGED
@@ -12,13 +12,22 @@ module Shell
|
|
12
12
|
|
13
13
|
#return the output of command
|
14
14
|
def execute(command)
|
15
|
-
r
|
16
|
-
|
17
|
-
|
15
|
+
r = ''
|
16
|
+
10.times do
|
17
|
+
begin
|
18
|
+
r=%x(#{command})
|
19
|
+
if $? != 0
|
20
|
+
raise BetterCap::Error, "Error, executing #{command}"
|
21
|
+
end
|
22
|
+
break
|
23
|
+
rescue Errno::EMFILE => e
|
24
|
+
Logger.debug "Retrying command '#{command}' due to Errno::EMFILE error ..."
|
25
|
+
sleep 1
|
26
|
+
end
|
18
27
|
end
|
19
|
-
|
28
|
+
r
|
20
29
|
end
|
21
|
-
|
30
|
+
|
22
31
|
def ifconfig(iface = '')
|
23
32
|
self.execute( "LANG=en && ifconfig #{iface}" )
|
24
33
|
end
|
@@ -28,7 +28,7 @@ class BaseParser
|
|
28
28
|
s = pkt.to_s
|
29
29
|
@filters.each do |filter|
|
30
30
|
if s =~ filter
|
31
|
-
Logger.
|
31
|
+
Logger.raw "[#{addr2s(pkt.ip_saddr)}:#{pkt.tcp_src} > #{addr2s(pkt.ip_daddr)}:#{pkt.tcp_dst} #{pkt.proto.last}] " +
|
32
32
|
"[#{@name}] ".green +
|
33
33
|
pkt.payload.strip.yellow
|
34
34
|
end
|
@@ -31,11 +31,11 @@ class HttpauthParser < BaseParser
|
|
31
31
|
decoded = Base64.decode64(encoded)
|
32
32
|
user, pass = decoded.split(':')
|
33
33
|
|
34
|
-
Logger.
|
34
|
+
Logger.raw "[#{addr2s(pkt.ip_saddr)}:#{pkt.tcp_src} > #{addr2s(pkt.ip_daddr)}:#{pkt.tcp_dst} #{pkt.proto.last}] " +
|
35
35
|
'[HTTP BASIC AUTH]'.green + " http://#{hostname}#{path} - username=#{user} password=#{pass}".yellow
|
36
36
|
|
37
37
|
elsif line =~ /Authorization:\s*Digest\s+(.+)/i
|
38
|
-
Logger.
|
38
|
+
Logger.raw "[#{addr2s(pkt.ip_saddr)}:#{pkt.tcp_src} > #{addr2s(pkt.ip_daddr)}:#{pkt.tcp_dst} #{pkt.proto.last}] " +
|
39
39
|
'[HTTP DIGEST AUTH]'.green + " http://#{hostname}#{path}\n#{$1}".yellow
|
40
40
|
|
41
41
|
end
|