bettercap 1.2.2 → 1.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/bettercap/discovery/agents/arp.rb +1 -0
- data/lib/bettercap/discovery/agents/base.rb +1 -0
- data/lib/bettercap/discovery/agents/icmp.rb +1 -2
- data/lib/bettercap/discovery/agents/udp.rb +1 -0
- data/lib/bettercap/discovery/thread.rb +3 -1
- data/lib/bettercap/factories/spoofer.rb +1 -0
- data/lib/bettercap/firewalls/base.rb +1 -0
- data/lib/bettercap/logger.rb +3 -0
- data/lib/bettercap/network/arp_reader.rb +3 -0
- data/lib/bettercap/network/network.rb +4 -0
- data/lib/bettercap/network/packet_queue.rb +4 -0
- data/lib/bettercap/network/target.rb +3 -0
- data/lib/bettercap/options.rb +68 -21
- data/lib/bettercap/proxy/module.rb +2 -0
- data/lib/bettercap/proxy/modules/injectcss.rb +20 -12
- data/lib/bettercap/proxy/modules/injectjs.rb +19 -11
- data/lib/bettercap/proxy/proxy.rb +10 -78
- data/lib/bettercap/proxy/request.rb +17 -1
- data/lib/bettercap/proxy/response.rb +9 -15
- data/lib/bettercap/proxy/streamer.rb +54 -123
- data/lib/bettercap/sniffer/sniffer.rb +5 -0
- data/lib/bettercap/spoofers/arp.rb +28 -14
- data/lib/bettercap/spoofers/base.rb +42 -9
- data/lib/bettercap/spoofers/icmp.rb +6 -0
- data/lib/bettercap/spoofers/none.rb +32 -4
- data/lib/bettercap/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a754804a6aed2eab487b2fbe33417c222946953
|
4
|
+
data.tar.gz: 0ba2e64fc445000c10b7dd9d2d3ec94c47108dbe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4bda0263ceae34eee565a7d5b46400f573b623a7a46f343dd9396a95078cdf2a4118fe2580f748d5c77a0174f589a5b505f466d125585824f5d54dd43457dbb3
|
7
|
+
data.tar.gz: 4fa729af4ff2b4a890ab87f53aed5559702e8d82efee9e55986467f28337f652234ca5322b69c20761758d6f6ec0916368f8ae922649100dda50c1d8695e268a
|
@@ -17,8 +17,7 @@ module Agents
|
|
17
17
|
# Class responsible to do a ping-sweep on the network.
|
18
18
|
class Icmp
|
19
19
|
# Create a thread which will perform a ping-sweep on the network in order
|
20
|
-
# to populate the ARP cache with active targets
|
21
|
-
# timeout.
|
20
|
+
# to populate the ARP cache with active targets.
|
22
21
|
def initialize( ctx )
|
23
22
|
Factories::Firewall.get.enable_icmp_bcast(true)
|
24
23
|
|
@@ -40,6 +40,8 @@ class Thread
|
|
40
40
|
|
41
41
|
private
|
42
42
|
|
43
|
+
# This method implements the main discovery logic, it will be executed within
|
44
|
+
# the spawned thread.
|
43
45
|
def worker
|
44
46
|
Logger.debug( 'Network discovery thread started.' ) unless @ctx.options.arpcache
|
45
47
|
|
@@ -48,7 +50,7 @@ class Thread
|
|
48
50
|
@ctx.targets = Network.get_alive_targets(@ctx).sort_by { |t| t.sortable_ip }
|
49
51
|
|
50
52
|
if was_empty and not @ctx.targets.empty?
|
51
|
-
Logger.info "Collected #{@ctx.targets.size} total targets."
|
53
|
+
Logger.info "Collected #{@ctx.targets.size} total target#{if @ctx.targets.size > 1 then "s" else "" end}."
|
52
54
|
|
53
55
|
msg = "\n"
|
54
56
|
@ctx.targets.each do |target|
|
data/lib/bettercap/logger.rb
CHANGED
@@ -73,6 +73,7 @@ module Logger
|
|
73
73
|
|
74
74
|
private
|
75
75
|
|
76
|
+
# Main logger logic.
|
76
77
|
def worker
|
77
78
|
loop do
|
78
79
|
message = @@queue.pop
|
@@ -82,6 +83,7 @@ module Logger
|
|
82
83
|
end
|
83
84
|
end
|
84
85
|
|
86
|
+
# Emit the +message+.
|
85
87
|
def emit(message)
|
86
88
|
puts message
|
87
89
|
unless @@logfile.nil?
|
@@ -91,6 +93,7 @@ module Logger
|
|
91
93
|
end
|
92
94
|
end
|
93
95
|
|
96
|
+
# Format +message+ for the given +message_type+.
|
94
97
|
def formatted_message(message, message_type)
|
95
98
|
"[#{message_type}] #{message}"
|
96
99
|
end
|
@@ -58,6 +58,8 @@ class ArpReader
|
|
58
58
|
|
59
59
|
private
|
60
60
|
|
61
|
+
# Read the computer ARP cache and parse each line, it will yield each
|
62
|
+
# ip and mac address it will be able to extract.
|
61
63
|
def self.parse_cache
|
62
64
|
iface = Context.get.ifconfig[:iface]
|
63
65
|
Shell.arp.split("\n").each do |line|
|
@@ -72,6 +74,7 @@ class ArpReader
|
|
72
74
|
end
|
73
75
|
end
|
74
76
|
|
77
|
+
# Parse a single ARP cache +line+ related to the +iface+ network interface.
|
75
78
|
def self.parse_cache_line( iface, line )
|
76
79
|
/[^\s]+\s+\(([0-9\.]+)\)\s+at\s+([a-f0-9:]+).+#{iface}.*/i.match(line)
|
77
80
|
end
|
@@ -133,6 +133,8 @@ class << self
|
|
133
133
|
|
134
134
|
private
|
135
135
|
|
136
|
+
# Start discovery agents and wait for +ctx.timeout+ seconds for them to
|
137
|
+
# complete their job.
|
136
138
|
def start_agents( ctx )
|
137
139
|
[ 'Icmp', 'Udp', 'Arp' ].each do |name|
|
138
140
|
BetterCap::Loader.load("BetterCap::Discovery::Agents::#{name}").new(ctx)
|
@@ -140,6 +142,8 @@ class << self
|
|
140
142
|
ctx.packets.wait_empty( ctx.timeout )
|
141
143
|
end
|
142
144
|
|
145
|
+
# Search for the MAC address associated to +ip_address+ inside the +cap+
|
146
|
+
# PacketFu::Capture object.
|
143
147
|
def get_mac_from_capture( cap, ip_address )
|
144
148
|
cap.stream.each do |p|
|
145
149
|
arp_response = PacketFu::Packet.parse(p)
|
@@ -52,6 +52,8 @@ class PacketQueue
|
|
52
52
|
|
53
53
|
private
|
54
54
|
|
55
|
+
# Unpack [ ip, port, data ] from +packet+ and send it using the global
|
56
|
+
# UDPSocket instance.
|
55
57
|
def dispatch_udp_packet(packet)
|
56
58
|
ip, port, data = packet
|
57
59
|
@mutex.synchronize {
|
@@ -60,6 +62,7 @@ class PacketQueue
|
|
60
62
|
}
|
61
63
|
end
|
62
64
|
|
65
|
+
# Use the global Pcap injection instance to send the +packet+.
|
63
66
|
def dispatch_raw_packet(packet)
|
64
67
|
@mutex.synchronize {
|
65
68
|
Logger.debug "Sending #{packet.class.name} packet ..."
|
@@ -67,6 +70,7 @@ class PacketQueue
|
|
67
70
|
}
|
68
71
|
end
|
69
72
|
|
73
|
+
# Main PacketQueue logic.
|
70
74
|
def worker
|
71
75
|
Logger.debug "PacketQueue worker started."
|
72
76
|
|
@@ -114,6 +114,7 @@ class Target
|
|
114
114
|
|
115
115
|
private
|
116
116
|
|
117
|
+
# Attempt to perform a NBNS name resolution for this target.
|
117
118
|
def resolve!
|
118
119
|
resp, sock = nil, nil
|
119
120
|
begin
|
@@ -133,10 +134,12 @@ private
|
|
133
134
|
end
|
134
135
|
end
|
135
136
|
|
137
|
+
# Given the +resp+ NBNS response, parse the hostname from it.
|
136
138
|
def parse_nbns_response resp
|
137
139
|
resp[0][57,15].to_s.strip
|
138
140
|
end
|
139
141
|
|
142
|
+
# Lookup the given +mac+ address in order to find its vendor.
|
140
143
|
def self.lookup_vendor( mac )
|
141
144
|
if @@prefixes == nil
|
142
145
|
Logger.debug 'Preloading hardware vendor prefixes ...'
|
data/lib/bettercap/options.rb
CHANGED
@@ -56,8 +56,12 @@ class Options
|
|
56
56
|
attr_accessor :proxy_https
|
57
57
|
# HTTP proxy port.
|
58
58
|
attr_accessor :proxy_port
|
59
|
+
# List of HTTP ports, [ 80 ] by default.
|
60
|
+
attr_accessor :http_ports
|
59
61
|
# HTTPS proxy port.
|
60
62
|
attr_accessor :proxy_https_port
|
63
|
+
# List of HTTPS ports, [ 443 ] by default.
|
64
|
+
attr_accessor :https_ports
|
61
65
|
# File name of the PEM certificate to use for the HTTPS proxy.
|
62
66
|
attr_accessor :proxy_pem_file
|
63
67
|
# File name of the transparent proxy module to load.
|
@@ -100,7 +104,8 @@ class Options
|
|
100
104
|
@no_target_nbns = false
|
101
105
|
@kill = false
|
102
106
|
@packet_throttle = 0.0
|
103
|
-
|
107
|
+
@http_ports = [ 80 ]
|
108
|
+
@https_ports = [ 443 ]
|
104
109
|
@ignore = nil
|
105
110
|
|
106
111
|
@sniffer = false
|
@@ -241,6 +246,14 @@ class Options
|
|
241
246
|
ctx.options.proxy_port = v.to_i
|
242
247
|
end
|
243
248
|
|
249
|
+
opts.on( '--http-ports PORT1,PORT2', 'Comma separated list of HTTP ports to redirect to the proxy, default to ' + ctx.options.http_ports.join(', ') + ' .' ) do |v|
|
250
|
+
ctx.options.http_ports = v
|
251
|
+
end
|
252
|
+
|
253
|
+
opts.on( '--https-ports PORT1,PORT2', 'Comma separated list of HTTPS ports to redirect to the proxy, default to ' + ctx.options.https_ports.join(', ') + ' .' ) do |v|
|
254
|
+
ctx.options.https_ports = v
|
255
|
+
end
|
256
|
+
|
244
257
|
opts.on( '--proxy-https-port PORT', 'Set HTTPS proxy port, default to ' + ctx.options.proxy_https_port.to_s + ' .' ) do |v|
|
245
258
|
ctx.options.proxy = true
|
246
259
|
ctx.options.proxy_https = true
|
@@ -397,6 +410,32 @@ class Options
|
|
397
410
|
raise BetterCap::Error, 'Invalid custom HTTPS upstream proxy address specified.' unless Network.is_ip? @custom_https_proxy
|
398
411
|
end
|
399
412
|
|
413
|
+
# Parse a comma separated list of ports and return an array containing only
|
414
|
+
# valid ports, raise BetterCap::Error if that array is empty.
|
415
|
+
def to_ports(value)
|
416
|
+
ports = []
|
417
|
+
value.split(",").each do |v|
|
418
|
+
v = v.strip.to_i
|
419
|
+
if v > 0 and v <= 65535
|
420
|
+
ports << v
|
421
|
+
end
|
422
|
+
end
|
423
|
+
raise BetterCap::Error, 'Invalid ports specified.' if ports.empty?
|
424
|
+
ports
|
425
|
+
end
|
426
|
+
|
427
|
+
# Setter for the #http_ports attribute, will raise a BetterCap::Error if +value+
|
428
|
+
# is not a valid comma separated list of ports.
|
429
|
+
def http_ports=(value)
|
430
|
+
@http_ports = to_ports(value)
|
431
|
+
end
|
432
|
+
|
433
|
+
# Setter for the #https_ports attribute, will raise a BetterCap::Error if +value+
|
434
|
+
# is not a valid comma separated list of ports.
|
435
|
+
def https_ports=(value)
|
436
|
+
@https_ports = to_ports(value)
|
437
|
+
end
|
438
|
+
|
400
439
|
# Split specified targets and parse them ( either as IP or MAC ), will raise a
|
401
440
|
# BetterCap::Error if one or more invalid addresses are specified.
|
402
441
|
def to_targets
|
@@ -430,35 +469,43 @@ class Options
|
|
430
469
|
redirections = []
|
431
470
|
|
432
471
|
if @proxy
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
472
|
+
@http_ports.each do |port|
|
473
|
+
redirections << Firewalls::Redirection.new( @iface,
|
474
|
+
'TCP',
|
475
|
+
port,
|
476
|
+
ifconfig[:ip_saddr],
|
477
|
+
@proxy_port )
|
478
|
+
end
|
438
479
|
end
|
439
480
|
|
440
481
|
if @proxy_https
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
482
|
+
@https_ports.each do |port|
|
483
|
+
redirections << Firewalls::Redirection.new( @iface,
|
484
|
+
'TCP',
|
485
|
+
port,
|
486
|
+
ifconfig[:ip_saddr],
|
487
|
+
@proxy_https_port )
|
488
|
+
end
|
446
489
|
end
|
447
490
|
|
448
491
|
if @custom_proxy
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
492
|
+
@http_ports.each do |port|
|
493
|
+
redirections << Firewalls::Redirection.new( @iface,
|
494
|
+
'TCP',
|
495
|
+
port,
|
496
|
+
@custom_proxy,
|
497
|
+
@custom_proxy_port )
|
498
|
+
end
|
454
499
|
end
|
455
500
|
|
456
501
|
if @custom_https_proxy
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
502
|
+
@https_ports.each do |port|
|
503
|
+
redirections << Firewalls::Redirection.new( @iface,
|
504
|
+
'TCP',
|
505
|
+
port,
|
506
|
+
@custom_https_proxy,
|
507
|
+
@custom_https_proxy_port )
|
508
|
+
end
|
462
509
|
end
|
463
510
|
|
464
511
|
redirections
|
@@ -9,10 +9,15 @@ Blog : http://www.evilsocket.net/
|
|
9
9
|
This project is released under the GPL 3 license.
|
10
10
|
|
11
11
|
=end
|
12
|
+
|
13
|
+
# This proxy module will take care of CSS code injection.
|
12
14
|
class Injectcss < BetterCap::Proxy::Module
|
15
|
+
# CSS data to be injected.
|
13
16
|
@@cssdata = nil
|
17
|
+
# CSS file URL to be injected.
|
14
18
|
@@cssurl = nil
|
15
19
|
|
20
|
+
# Add custom command line arguments to the +opts+ OptionParser instance.
|
16
21
|
def self.on_options(opts)
|
17
22
|
opts.separator ""
|
18
23
|
opts.separator "Inject CSS Proxy Module Options:"
|
@@ -20,7 +25,7 @@ class Injectcss < BetterCap::Proxy::Module
|
|
20
25
|
|
21
26
|
opts.on( '--css-data STRING', 'CSS code to be injected.' ) do |v|
|
22
27
|
@@cssdata = v
|
23
|
-
unless @@
|
28
|
+
unless @@cssdata.include?("<style>")
|
24
29
|
@@cssdata = "<style>\n#{@@cssdata}\n</style>"
|
25
30
|
end
|
26
31
|
end
|
@@ -39,21 +44,24 @@ class Injectcss < BetterCap::Proxy::Module
|
|
39
44
|
end
|
40
45
|
end
|
41
46
|
|
47
|
+
# Create an instance of this module and raise a BetterCap::Error if command
|
48
|
+
# line arguments weren't correctly specified.
|
49
|
+
def initialize
|
50
|
+
raise BetterCap::Error, "No --css-file, --css-url or --css-data options specified for the proxy module." if @@cssdata.nil? and @@cssurl.nil?
|
51
|
+
end
|
52
|
+
|
53
|
+
# Called by the BetterCap::Proxy::Proxy processor on each HTTP +request+ and
|
54
|
+
# +response+.
|
42
55
|
def on_request( request, response )
|
43
56
|
# is it a html page?
|
44
57
|
if response.content_type =~ /^text\/html.*/
|
45
|
-
#
|
46
|
-
|
47
|
-
|
58
|
+
BetterCap::Logger.info "Injecting CSS #{if @@cssdata.nil? then "URL" else "file" end} into http://#{request.host}#{request.url}"
|
59
|
+
# inject URL
|
60
|
+
if @@cssdata.nil?
|
61
|
+
response.body.sub!( '</head>', " <link rel=\"stylesheet\" href=\"#{@cssurl}\"></script></head>" )
|
62
|
+
# inject data
|
48
63
|
else
|
49
|
-
|
50
|
-
# inject URL
|
51
|
-
if @@cssdata.nil?
|
52
|
-
response.body.sub!( '</head>', " <link rel=\"stylesheet\" href=\"#{@cssurl}\"></script></head>" )
|
53
|
-
# inject data
|
54
|
-
else
|
55
|
-
response.body.sub!( '</head>', "#{@@cssdata}</head>" )
|
56
|
-
end
|
64
|
+
response.body.sub!( '</head>', "#{@@cssdata}</head>" )
|
57
65
|
end
|
58
66
|
end
|
59
67
|
end
|
@@ -9,10 +9,15 @@ Blog : http://www.evilsocket.net/
|
|
9
9
|
This project is released under the GPL 3 license.
|
10
10
|
|
11
11
|
=end
|
12
|
+
|
13
|
+
# This proxy module will take care of Javascript code injection.
|
12
14
|
class Injectjs < BetterCap::Proxy::Module
|
15
|
+
# JS data to be injected.
|
13
16
|
@@jsdata = nil
|
17
|
+
# JS file URL to be injected.
|
14
18
|
@@jsurl = nil
|
15
19
|
|
20
|
+
# Add custom command line arguments to the +opts+ OptionParser instance.
|
16
21
|
def self.on_options(opts)
|
17
22
|
opts.separator ""
|
18
23
|
opts.separator "Inject JS Proxy Module Options:"
|
@@ -39,21 +44,24 @@ class Injectjs < BetterCap::Proxy::Module
|
|
39
44
|
end
|
40
45
|
end
|
41
46
|
|
47
|
+
# Create an instance of this module and raise a BetterCap::Error if command
|
48
|
+
# line arguments weren't correctly specified.
|
49
|
+
def initialize
|
50
|
+
raise BetterCap::Error, "No --js-file, --js-url or --js-data options specified for the proxy module." if @@jsdata.nil? and @@jsurl.nil?
|
51
|
+
end
|
52
|
+
|
53
|
+
# Called by the BetterCap::Proxy::Proxy processor on each HTTP +request+ and
|
54
|
+
# +response+.
|
42
55
|
def on_request( request, response )
|
43
56
|
# is it a html page?
|
44
57
|
if response.content_type =~ /^text\/html.*/
|
45
|
-
#
|
46
|
-
|
47
|
-
|
58
|
+
BetterCap::Logger.info "Injecting javascript #{if @@jsdata.nil? then "URL" else "file" end} into http://#{request.host}#{request.url}"
|
59
|
+
# inject URL
|
60
|
+
if @@jsdata.nil?
|
61
|
+
response.body.sub!( '</head>', "<script src=\"#{@@jsurl}\" type=\"text/javascript\"></script></head>" )
|
62
|
+
# inject data
|
48
63
|
else
|
49
|
-
|
50
|
-
# inject URL
|
51
|
-
if @@jsdata.nil?
|
52
|
-
response.body.sub!( '</head>', "<script src=\"#{@@jsurl}\" type=\"text/javascript\"></script></head>" )
|
53
|
-
# inject data
|
54
|
-
else
|
55
|
-
response.body.sub!( '</head>', "#{@@jsdata}</head>" )
|
56
|
-
end
|
64
|
+
response.body.sub!( '</head>', "#{@@jsdata}</head>" )
|
57
65
|
end
|
58
66
|
end
|
59
67
|
end
|
@@ -87,6 +87,8 @@ class Proxy
|
|
87
87
|
|
88
88
|
private
|
89
89
|
|
90
|
+
# Main server thread, will accept incoming connections and push them to
|
91
|
+
# the thread pool.
|
90
92
|
def server_thread
|
91
93
|
Logger.info "#{@type} Proxy started on #{@address}:#{@port} ...\n"
|
92
94
|
|
@@ -103,100 +105,31 @@ class Proxy
|
|
103
105
|
@socket.close unless @socket.nil?
|
104
106
|
end
|
105
107
|
|
108
|
+
# Return true if the +request+ host header contains one of this computer
|
109
|
+
# ip addresses.
|
106
110
|
def is_self_request?(request)
|
107
111
|
@local_ips.include? IPSocket.getaddress(request.host)
|
108
112
|
end
|
109
113
|
|
110
|
-
|
111
|
-
sock = TCPSocket.new( request.host, request.port )
|
112
|
-
|
113
|
-
if @is_https
|
114
|
-
ctx = OpenSSL::SSL::SSLContext.new
|
115
|
-
# do we need this? :P ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER)
|
116
|
-
|
117
|
-
sock = OpenSSL::SSL::SSLSocket.new(sock, ctx).tap do |socket|
|
118
|
-
sock.sync_close = true
|
119
|
-
sock.connect
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
sock
|
124
|
-
end
|
125
|
-
|
126
|
-
def get_client_details( client )
|
127
|
-
unless @is_https
|
128
|
-
client_port, client_ip = Socket.unpack_sockaddr_in(client.getpeername)
|
129
|
-
else
|
130
|
-
_, client_port, _, client_ip = client.peeraddr
|
131
|
-
end
|
132
|
-
|
133
|
-
[ client_ip, client_port ]
|
134
|
-
end
|
135
|
-
|
114
|
+
# Handle a new +client+.
|
136
115
|
def client_worker( client )
|
137
|
-
client_ip, client_port = get_client_details client
|
138
|
-
|
139
|
-
Logger.debug "New #{@type} connection from #{client_ip}:#{client_port}"
|
140
|
-
|
141
|
-
server = nil
|
142
116
|
request = Request.new @is_https ? 443 : 80
|
143
117
|
|
144
118
|
begin
|
145
119
|
Logger.debug 'Reading request ...'
|
146
120
|
|
147
|
-
request.read
|
121
|
+
request.read(client)
|
148
122
|
|
149
123
|
# someone is having fun with us =)
|
150
124
|
if is_self_request? request
|
151
|
-
|
152
|
-
Logger.warn "#{client_ip} is connecting to us directly."
|
153
|
-
|
154
125
|
@streamer.rickroll client
|
155
|
-
|
156
|
-
elsif request.verb == 'CONNECT'
|
157
|
-
|
158
|
-
Logger.error "You're using bettercap as a normal HTTP(S) proxy, it wasn't designed to handle CONNECT requests:\n\n#{request.to_s}"
|
159
|
-
|
126
|
+
# handle request
|
160
127
|
else
|
161
|
-
|
162
|
-
Logger.debug 'Creating upstream connection ...'
|
163
|
-
|
164
|
-
server = create_upstream_connection request
|
165
|
-
|
166
|
-
sreq = request.to_s
|
167
|
-
|
168
|
-
Logger.debug "Sending request:\n#{sreq}"
|
169
|
-
|
170
|
-
server.write sreq
|
171
|
-
|
172
|
-
# this is probably a POST request, collect incoming data
|
173
|
-
if request.content_length > 0
|
174
|
-
Logger.debug "Getting #{request.content_length} bytes from client"
|
175
|
-
|
176
|
-
@streamer.binary client, server, request: request
|
177
|
-
end
|
178
|
-
|
179
|
-
Logger.debug 'Reading response ...'
|
180
|
-
|
181
|
-
response = Response.from_socket server
|
182
|
-
|
183
|
-
if response.textual?
|
184
|
-
StreamLogger.log_http( @is_https, client_ip, request, response )
|
185
|
-
|
186
|
-
Logger.debug 'Detected textual response'
|
187
|
-
|
188
|
-
@streamer.html request, response, server, client
|
189
|
-
else
|
190
|
-
Logger.debug "[#{client_ip}] -> #{request.host}#{request.url} [#{response.code}]"
|
191
|
-
|
192
|
-
Logger.debug 'Binary streaming'
|
193
|
-
|
194
|
-
@streamer.binary server, client, response: response
|
195
|
-
end
|
196
|
-
|
197
|
-
Logger.debug "#{@type} client served."
|
128
|
+
@streamer.handle( request, client, @is_https )
|
198
129
|
end
|
199
130
|
|
131
|
+
Logger.debug "#{@type} client served."
|
132
|
+
|
200
133
|
rescue Exception => e
|
201
134
|
if request.host
|
202
135
|
Logger.warn "Error while serving #{request.host}#{request.url}: #{e.inspect}"
|
@@ -205,7 +138,6 @@ class Proxy
|
|
205
138
|
end
|
206
139
|
|
207
140
|
client.close
|
208
|
-
server.close unless server.nil?
|
209
141
|
end
|
210
142
|
end
|
211
143
|
|
@@ -24,8 +24,12 @@ class Request
|
|
24
24
|
attr_reader :host
|
25
25
|
# Request port.
|
26
26
|
attr_reader :port
|
27
|
+
# Request headers hash.
|
28
|
+
attr_reader :headers
|
27
29
|
# Content length.
|
28
30
|
attr_reader :content_length
|
31
|
+
# Request body.
|
32
|
+
attr_reader :body
|
29
33
|
|
30
34
|
# Initialize this object setting #port to +default_port+.
|
31
35
|
def initialize( default_port = 80 )
|
@@ -34,7 +38,9 @@ class Request
|
|
34
38
|
@url = nil
|
35
39
|
@host = nil
|
36
40
|
@port = default_port
|
41
|
+
@headers = {}
|
37
42
|
@content_length = 0
|
43
|
+
@body = nil
|
38
44
|
end
|
39
45
|
|
40
46
|
# Read lines from the +sock+ socket and parse them.
|
@@ -53,6 +59,11 @@ class Request
|
|
53
59
|
end
|
54
60
|
|
55
61
|
raise "Couldn't extract host from the request." unless @host
|
62
|
+
|
63
|
+
# keep reading the request body if needed
|
64
|
+
if @content_length > 0
|
65
|
+
@body = sock.read(@content_length)
|
66
|
+
end
|
56
67
|
end
|
57
68
|
|
58
69
|
# Parse a single request line, patch it if needed and append it to #lines.
|
@@ -93,6 +104,11 @@ class Request
|
|
93
104
|
line = 'Accept-Encoding: identity'
|
94
105
|
end
|
95
106
|
|
107
|
+
# collect headers
|
108
|
+
if line =~ /^([^:\s]+)\s*:\s*(.+)$/i
|
109
|
+
@headers[$1] = $2
|
110
|
+
end
|
111
|
+
|
96
112
|
@lines << line
|
97
113
|
end
|
98
114
|
|
@@ -103,7 +119,7 @@ class Request
|
|
103
119
|
|
104
120
|
# Return a string representation of the HTTP request.
|
105
121
|
def to_s
|
106
|
-
@lines.join("\n") + "\n"
|
122
|
+
@lines.join("\n") + "\n" + ( @body || '' )
|
107
123
|
end
|
108
124
|
end
|
109
125
|
end
|
@@ -25,7 +25,7 @@ class Response
|
|
25
25
|
# A list of response headers.
|
26
26
|
attr_reader :headers
|
27
27
|
# Response status code.
|
28
|
-
|
28
|
+
attr_accessor :code
|
29
29
|
# True if the parser finished to parse the headers, otherwise false.
|
30
30
|
attr_reader :headers_done
|
31
31
|
# Response body.
|
@@ -43,21 +43,15 @@ class Response
|
|
43
43
|
@chunked = false
|
44
44
|
end
|
45
45
|
|
46
|
-
#
|
47
|
-
|
48
|
-
|
49
|
-
response
|
50
|
-
|
51
|
-
# read all response headers
|
52
|
-
loop do
|
53
|
-
line = sock.readline
|
54
|
-
|
55
|
-
response << line
|
56
|
-
|
57
|
-
break unless not response.headers_done
|
46
|
+
# Convert a webrick response to this class.
|
47
|
+
def convert_webrick_response!(response)
|
48
|
+
self << "HTTP/#{response.http_version} #{response.code} #{response.msg}"
|
49
|
+
response.each do |key,value|
|
50
|
+
self << "#{key}: #{value}"
|
58
51
|
end
|
59
|
-
|
60
|
-
response
|
52
|
+
self << "\n"
|
53
|
+
@code = response.code
|
54
|
+
@body = response.body || ''
|
61
55
|
end
|
62
56
|
|
63
57
|
# Parse a single response +line+.
|
@@ -15,9 +15,6 @@ module BetterCap
|
|
15
15
|
module Proxy
|
16
16
|
# Handle data streaming between clients and servers for the BetterCap::Proxy::Proxy.
|
17
17
|
class Streamer
|
18
|
-
# Default buffer size for data streaming.
|
19
|
-
BUFSIZE = 1024 * 16
|
20
|
-
|
21
18
|
# Initialize the class with the given +processor+ routine.
|
22
19
|
def initialize( processor )
|
23
20
|
@processor = processor
|
@@ -25,155 +22,89 @@ class Streamer
|
|
25
22
|
|
26
23
|
# Redirect the +client+ to a funny video.
|
27
24
|
def rickroll( client )
|
25
|
+
Logger.warn "#{client_ip} is connecting to us directly."
|
26
|
+
|
28
27
|
client.write "HTTP/1.1 302 Found\n"
|
29
28
|
client.write "Location: https://www.youtube.com/watch?v=dQw4w9WgXcQ\n\n"
|
30
29
|
end
|
31
30
|
|
32
|
-
#
|
33
|
-
#
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
if response.chunked
|
39
|
-
Logger.debug "Reading response body using chunked encoding ..."
|
40
|
-
|
41
|
-
begin
|
42
|
-
len = nil
|
43
|
-
total = 0
|
44
|
-
|
45
|
-
while true
|
46
|
-
line = from.readline
|
47
|
-
hexlen = line.slice(/[0-9a-fA-F]+/) or raise "Wrong chunk size line: #{line}"
|
48
|
-
len = hexlen.hex
|
49
|
-
break if len == 0
|
50
|
-
begin
|
51
|
-
Logger.debug "Reading chunk of size #{len} ..."
|
52
|
-
tmp = read( from, len )
|
53
|
-
Logger.debug "Read #{tmp.bytesize}/#{len} chunk."
|
54
|
-
response << tmp
|
55
|
-
ensure
|
56
|
-
total += len
|
57
|
-
from.read 2
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
rescue Exception => e
|
62
|
-
Logger.debug e
|
63
|
-
end
|
64
|
-
|
65
|
-
elsif response.content_length.nil?
|
66
|
-
Logger.debug "Reading response body using 1024 bytes chunks ..."
|
31
|
+
# Handle the HTTP +request+ from +client+, if +is_https+ is true it will be
|
32
|
+
# forwarded as a HTTPS request.
|
33
|
+
def handle( request, client, is_https )
|
34
|
+
response = Response.new
|
35
|
+
client_ip, client_port = get_client_details( is_https, client )
|
67
36
|
|
68
|
-
|
69
|
-
buff = read( from, 1024 )
|
37
|
+
Logger.debug "Handling #{request.verb} request from #{client_ip}:#{client_port} ..."
|
70
38
|
|
71
|
-
|
39
|
+
begin
|
40
|
+
self.send( "do_#{request.verb}", request, response )
|
72
41
|
|
73
|
-
|
42
|
+
if response.textual?
|
43
|
+
StreamLogger.log_http( is_https, client_ip, request, response )
|
44
|
+
else
|
45
|
+
Logger.debug "[#{client_ip}] -> #{request.host}#{request.url} [#{response.code}]"
|
74
46
|
end
|
75
|
-
else
|
76
|
-
Logger.debug "Reading response body using #{response.content_length} bytes buffer ..."
|
77
|
-
|
78
|
-
buff = read( from, response.content_length )
|
79
47
|
|
80
|
-
|
48
|
+
@processor.call( request, response )
|
81
49
|
|
82
|
-
response
|
50
|
+
client.write response.to_s
|
51
|
+
rescue NoMethodError
|
52
|
+
Logger.warn "Could not handle #{request.verb} request from #{client_ip}:#{client_port} ..."
|
83
53
|
end
|
84
|
-
|
85
|
-
@processor.call( request, response )
|
86
|
-
|
87
|
-
# Response::to_s will patch the headers if needed
|
88
|
-
to.write response.to_s
|
89
54
|
end
|
90
55
|
|
91
|
-
|
92
|
-
# If response|request object is available inside +opts+ and a content length
|
93
|
-
# as well use it to speed up data streaming with precise data size
|
94
|
-
# +from+ and +to+ are the two TCP endpoints.
|
95
|
-
def binary( from, to, opts = {} )
|
96
|
-
total_size = 0
|
97
|
-
|
98
|
-
if not opts[:response].nil?
|
99
|
-
to.write opts[:response].to_s
|
100
|
-
|
101
|
-
total_size = opts[:response].content_length unless opts[:response].content_length.nil?
|
102
|
-
elsif not opts[:request].nil?
|
103
|
-
|
104
|
-
total_size = opts[:request].content_length unless opts[:request].content_length.nil?
|
105
|
-
end
|
106
|
-
|
107
|
-
buff = ''
|
108
|
-
read = 0
|
56
|
+
private
|
109
57
|
|
110
|
-
|
111
|
-
|
58
|
+
# Return the +client+ ip address and port.
|
59
|
+
def get_client_details( is_https, client )
|
60
|
+
unless is_https
|
61
|
+
client_port, client_ip = Socket.unpack_sockaddr_in(client.getpeername)
|
112
62
|
else
|
113
|
-
|
63
|
+
_, client_port, _, client_ip = client.peeraddr
|
114
64
|
end
|
115
65
|
|
116
|
-
|
117
|
-
|
118
|
-
buff = read( from, chunk_size )
|
66
|
+
[ client_ip, client_port ]
|
67
|
+
end
|
119
68
|
|
120
|
-
|
121
|
-
|
69
|
+
# Use a Net::HTTP object in order to perform the +req+ BetterCap::Proxy::Request
|
70
|
+
# object, will return a BetterCap::Proxy::Response object instance.
|
71
|
+
def perform_proxy_request(req, res)
|
72
|
+
path = req.url
|
73
|
+
response = nil
|
74
|
+
http = Net::HTTP.new( req.host, req.port )
|
75
|
+
http.use_ssl = ( req.port == 443 )
|
122
76
|
|
123
|
-
|
77
|
+
http.start do
|
78
|
+
response = yield( http, path, req.headers )
|
79
|
+
end
|
124
80
|
|
125
|
-
|
81
|
+
res.convert_webrick_response!(response)
|
82
|
+
end
|
126
83
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
84
|
+
# Handle a CONNECT request, +req+ is the request object and +res+ the response.
|
85
|
+
def do_CONNECT(req, res)
|
86
|
+
Logger.error "You're using bettercap as a normal HTTP(S) proxy, it wasn't designed to handle CONNECT requests:\n\n#{req.to_s}"
|
87
|
+
end
|
131
88
|
|
132
|
-
|
133
|
-
|
134
|
-
|
89
|
+
# Handle a GET request, +req+ is the request object and +res+ the response.
|
90
|
+
def do_GET(req, res)
|
91
|
+
perform_proxy_request(req, res) do |http, path, header|
|
92
|
+
http.get(path, header)
|
135
93
|
end
|
136
94
|
end
|
137
95
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
dest = ''
|
143
|
-
begin
|
144
|
-
dest << io.read_nonblock(size)
|
145
|
-
rescue IO::WaitReadable
|
146
|
-
if IO.select([io], nil, nil, read_timeout)
|
147
|
-
retry
|
148
|
-
else
|
149
|
-
raise Net::ReadTimeout
|
150
|
-
end
|
151
|
-
rescue IO::WaitWritable
|
152
|
-
# OpenSSL::Buffering#read_nonblock may fail with IO::WaitWritable.
|
153
|
-
# http://www.openssl.org/support/faq.html#PROG10
|
154
|
-
if IO.select(nil, [io], nil, read_timeout)
|
155
|
-
retry
|
156
|
-
else
|
157
|
-
raise Net::ReadTimeout
|
158
|
-
end
|
96
|
+
# Handle a HEAD request, +req+ is the request object and +res+ the response.
|
97
|
+
def do_HEAD(req, res)
|
98
|
+
perform_proxy_request(req, res) do |http, path, header|
|
99
|
+
http.head(path, header)
|
159
100
|
end
|
160
|
-
dest
|
161
101
|
end
|
162
102
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
tmp = consume_stream sd, [ BUFSIZE, size ].min
|
168
|
-
unless tmp.nil? or tmp.bytesize == 0
|
169
|
-
buffer << tmp
|
170
|
-
size -= tmp.bytesize
|
171
|
-
end
|
172
|
-
end
|
173
|
-
rescue EOFError
|
174
|
-
;
|
103
|
+
# Handle a POST request, +req+ is the request object and +res+ the response.
|
104
|
+
def do_POST(req, res)
|
105
|
+
perform_proxy_request(req, res) do |http, path, header|
|
106
|
+
http.post(path, req.body || "", header)
|
175
107
|
end
|
176
|
-
buffer
|
177
108
|
end
|
178
109
|
|
179
110
|
end
|
@@ -52,6 +52,7 @@ class Sniffer
|
|
52
52
|
|
53
53
|
private
|
54
54
|
|
55
|
+
# Return the current PCAP stream.
|
55
56
|
def self.stream
|
56
57
|
if @@ctx.options.sniffer_src.nil?
|
57
58
|
@@cap.stream
|
@@ -62,12 +63,14 @@ class Sniffer
|
|
62
63
|
end
|
63
64
|
end
|
64
65
|
|
66
|
+
# Return true if the +pkt+ packet instance must be skipped.
|
65
67
|
def self.skip_packet?( pkt )
|
66
68
|
!@@ctx.options.local and
|
67
69
|
( pkt.ip_saddr == @@ctx.ifconfig[:ip_saddr] or
|
68
70
|
pkt.ip_daddr == @@ctx.ifconfig[:ip_saddr] )
|
69
71
|
end
|
70
72
|
|
73
|
+
# Apply each parser on the given +parsed+ packet.
|
71
74
|
def self.parse_packet( parsed )
|
72
75
|
@@parsers.each do |parser|
|
73
76
|
begin
|
@@ -78,6 +81,7 @@ class Sniffer
|
|
78
81
|
end
|
79
82
|
end
|
80
83
|
|
84
|
+
# Append the packet +p+ to the current PCAP file.
|
81
85
|
def self.append_packet( p )
|
82
86
|
begin
|
83
87
|
@@pcap.array_to_file(
|
@@ -89,6 +93,7 @@ class Sniffer
|
|
89
93
|
end
|
90
94
|
end
|
91
95
|
|
96
|
+
# Setup all needed objects for the sniffer using the +ctx+ Context instance.
|
92
97
|
def self.setup( ctx )
|
93
98
|
@@ctx = ctx
|
94
99
|
|
@@ -80,37 +80,51 @@ class Arp < Base
|
|
80
80
|
|
81
81
|
@ctx.targets.each do |target|
|
82
82
|
unless target.ip.nil? or target.mac.nil?
|
83
|
-
|
84
|
-
send_spoofed_packet( @gateway.ip, @gateway.mac, target.ip, target.mac )
|
85
|
-
send_spoofed_packet( target.ip, target.mac, @gateway.ip, @gateway.mac ) unless @ctx.options.half_duplex
|
86
|
-
rescue; end
|
83
|
+
spoof(target)
|
87
84
|
end
|
88
85
|
end
|
89
86
|
end
|
90
87
|
|
91
88
|
private
|
92
89
|
|
90
|
+
# Send an ARP spoofing packet to +target+, if +restore+ is true it will
|
91
|
+
# restore its ARP cache instead.
|
92
|
+
def spoof( target, restore = false )
|
93
|
+
if restore
|
94
|
+
send_spoofed_packet( @gateway.ip, @ctx.ifconfig[:eth_saddr], target.ip, target.mac )
|
95
|
+
send_spoofed_packet( target.ip, @ctx.ifconfig[:eth_saddr], @gateway.ip, @gateway.mac ) unless @ctx.options.half_duplex
|
96
|
+
else
|
97
|
+
send_spoofed_packet( @gateway.ip, @gateway.mac, target.ip, target.mac )
|
98
|
+
send_spoofed_packet( target.ip, target.mac, @gateway.ip, @gateway.mac ) unless @ctx.options.half_duplex
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Main spoofer loop.
|
93
103
|
def arp_spoofer
|
94
104
|
spoof_loop(1) { |target|
|
95
105
|
unless target.ip.nil? or target.mac.nil?
|
96
|
-
|
97
|
-
send_spoofed_packet( target.ip, @ctx.ifconfig[:eth_saddr], @gateway.ip, @gateway.mac ) unless @ctx.options.half_duplex
|
106
|
+
spoof(target, true)
|
98
107
|
end
|
99
108
|
}
|
100
109
|
end
|
101
110
|
|
111
|
+
# Return true if the +pkt+ packet is an ARP 'who-has' query coming
|
112
|
+
# from some network endpoint.
|
113
|
+
def is_arp_query?(pkt)
|
114
|
+
# we're only interested in 'who-has' packets
|
115
|
+
pkt.arp_opcode == 1 and \
|
116
|
+
pkt.arp_dst_mac.to_s == '00:00:00:00:00:00' and \
|
117
|
+
pkt.arp_src_ip.to_s != @ctx.ifconfig[:ip_saddr]
|
118
|
+
end
|
119
|
+
|
120
|
+
# Will watch for incoming ARP requests and spoof the source address.
|
102
121
|
def arp_watcher
|
103
122
|
Logger.debug 'ARP watcher started ...'
|
104
123
|
|
105
124
|
sniff_packets('arp') { |pkt|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
unless is_from_us
|
110
|
-
Logger.info "[ARP] #{pkt.arp_src_ip.to_s} is asking who #{pkt.arp_dst_ip.to_s} is."
|
111
|
-
|
112
|
-
send_spoofed_packet pkt.arp_dst_ip.to_s, @ctx.ifconfig[:eth_saddr], pkt.arp_src_ip.to_s, pkt.arp_src_mac.to_s
|
113
|
-
end
|
125
|
+
if is_arp_query?(pkt)
|
126
|
+
Logger.info "[#{'ARP'.green}] #{pkt.arp_src_ip.to_s} is asking who #{pkt.arp_dst_ip.to_s} is."
|
127
|
+
send_spoofed_packet pkt.arp_dst_ip.to_s, @ctx.ifconfig[:eth_saddr], pkt.arp_src_ip.to_s, pkt.arp_src_mac.to_s
|
114
128
|
end
|
115
129
|
}
|
116
130
|
end
|
@@ -28,6 +28,8 @@ class Base
|
|
28
28
|
|
29
29
|
private
|
30
30
|
|
31
|
+
# Will create a PacketFu::Capture object using the specified +filter+ and
|
32
|
+
# will yield every parsed packet to the given code block.
|
31
33
|
def sniff_packets( filter )
|
32
34
|
begin
|
33
35
|
@capture = PacketFu::Capture.new(
|
@@ -57,21 +59,49 @@ private
|
|
57
59
|
end
|
58
60
|
end
|
59
61
|
|
62
|
+
# Print informations about new and lost targets.
|
63
|
+
def print_differences( prev_targets )
|
64
|
+
size = @ctx.targets.size
|
65
|
+
prev_size = prev_targets.size
|
66
|
+
diff = nil
|
67
|
+
label = nil
|
68
|
+
|
69
|
+
if size > prev_size
|
70
|
+
diff = @ctx.targets - prev_targets
|
71
|
+
delta = diff.size
|
72
|
+
label = 'NEW'.green
|
73
|
+
|
74
|
+
Logger.warn "Acquired #{delta} new target#{if delta > 1 then "s" else "" end}."
|
75
|
+
elsif size < prev_size
|
76
|
+
diff = prev_targets - @ctx.targets
|
77
|
+
delta = diff.size
|
78
|
+
label = 'LOST'.red
|
79
|
+
|
80
|
+
Logger.warn "Lost #{delta} target#{if delta > 1 then "s" else "" end}."
|
81
|
+
end
|
82
|
+
|
83
|
+
unless diff.nil?
|
84
|
+
msg = "\n"
|
85
|
+
diff.each do |target|
|
86
|
+
msg += " [#{label}] #{target.to_s(false)}\n"
|
87
|
+
end
|
88
|
+
msg += "\n"
|
89
|
+
Logger.raw msg
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Main spoof loop repeated each +delay+ seconds.
|
60
94
|
def spoof_loop( delay )
|
61
|
-
|
95
|
+
prev_targets = @ctx.targets
|
96
|
+
|
62
97
|
loop do
|
63
|
-
|
98
|
+
unless @running
|
64
99
|
Logger.debug 'Stopping spoofing thread ...'
|
65
100
|
Thread.exit
|
66
101
|
break
|
67
102
|
end
|
68
103
|
|
69
|
-
|
70
|
-
if size > prev_size
|
71
|
-
Logger.warn "Aquired #{size - prev_size} new targets."
|
72
|
-
elsif size < prev_size
|
73
|
-
Logger.warn "Lost #{prev_size - size} targets."
|
74
|
-
end
|
104
|
+
print_differences prev_targets
|
75
105
|
|
76
106
|
Logger.debug "Spoofing #{@ctx.targets.size} targets ..."
|
77
107
|
|
@@ -81,12 +111,13 @@ private
|
|
81
111
|
yield(target)
|
82
112
|
end
|
83
113
|
|
84
|
-
|
114
|
+
prev_targets = @ctx.targets
|
85
115
|
|
86
116
|
sleep(delay)
|
87
117
|
end
|
88
118
|
end
|
89
119
|
|
120
|
+
# Get the MAC address of the gateway and update it.
|
90
121
|
def update_gateway!
|
91
122
|
hw = Network.get_hw_address( @ctx.ifconfig, @ctx.gateway )
|
92
123
|
raise BetterCap::Error, "Couldn't determine router MAC" if hw.nil?
|
@@ -95,6 +126,7 @@ private
|
|
95
126
|
Logger.info "[#{'GATEWAY'.green}] #{@gateway.to_s(false)}"
|
96
127
|
end
|
97
128
|
|
129
|
+
# Update each target that needs to be updated.
|
98
130
|
def update_targets!
|
99
131
|
@ctx.targets.each do |target|
|
100
132
|
# targets could change, update mac addresses if needed
|
@@ -122,6 +154,7 @@ private
|
|
122
154
|
end
|
123
155
|
end
|
124
156
|
|
157
|
+
# Used to raise a NotImplementedError exception.
|
125
158
|
def not_implemented_method!
|
126
159
|
raise NotImplementedError, 'Spoofers::Base: Unimplemented method!'
|
127
160
|
end
|
@@ -30,6 +30,7 @@ class ICMPRedirectPacket < PacketFu::Packet
|
|
30
30
|
|
31
31
|
attr_accessor :eth_header, :ip_header, :icmp_header, :ip_encl_header
|
32
32
|
|
33
|
+
# Create a ICMPRedirectPacket instance.
|
33
34
|
def initialize(args={})
|
34
35
|
@eth_header = PacketFu::EthHeader.new(args).read(args[:eth])
|
35
36
|
|
@@ -54,6 +55,8 @@ class ICMPRedirectPacket < PacketFu::Packet
|
|
54
55
|
super
|
55
56
|
end
|
56
57
|
|
58
|
+
# Update this packet with the correct +gateway+, +target+, +local+ address
|
59
|
+
# and +address2redirect+.
|
57
60
|
def update!( gateway, target, local, address2redirect )
|
58
61
|
@eth_header.eth_src = PacketFu::EthHeader.mac2str(gateway.mac)
|
59
62
|
@ip_header.ip_saddr = gateway.ip
|
@@ -145,6 +148,7 @@ class Icmp < Base
|
|
145
148
|
|
146
149
|
private
|
147
150
|
|
151
|
+
# Return true if the +pkt+ packet comes from one of our targets.
|
148
152
|
def is_interesting_packet?(pkt)
|
149
153
|
return false if pkt.ip_saddr == @local
|
150
154
|
@ctx.targets.each do |target|
|
@@ -155,6 +159,7 @@ class Icmp < Base
|
|
155
159
|
false
|
156
160
|
end
|
157
161
|
|
162
|
+
# DNS watcher logic.
|
158
163
|
def dns_watcher
|
159
164
|
Logger.debug 'DNS watcher started ...'
|
160
165
|
|
@@ -188,6 +193,7 @@ class Icmp < Base
|
|
188
193
|
}
|
189
194
|
end
|
190
195
|
|
196
|
+
# Main spoofer loop.
|
191
197
|
def icmp_spoofer
|
192
198
|
spoof_loop(3) { |target|
|
193
199
|
unless target.ip.nil? or target.mac.nil?
|
@@ -19,13 +19,41 @@ class None < Base
|
|
19
19
|
# Initialize the non-spoofing class.
|
20
20
|
def initialize
|
21
21
|
Logger.warn 'Spoofing disabled.'
|
22
|
+
|
23
|
+
@ctx = Context.get
|
24
|
+
@gateway = nil
|
25
|
+
@thread = nil
|
26
|
+
@running = false
|
27
|
+
|
28
|
+
update_gateway!
|
29
|
+
end
|
30
|
+
|
31
|
+
# Start the "NONE" spoofer.
|
32
|
+
def start
|
33
|
+
stop() if @running
|
34
|
+
@running = true
|
35
|
+
|
36
|
+
@thread = Thread.new { fake_spoofer }
|
22
37
|
end
|
23
38
|
|
24
|
-
#
|
25
|
-
def
|
39
|
+
# Stop the "NONE" spoofer.
|
40
|
+
def stop
|
41
|
+
return unless @running
|
42
|
+
|
43
|
+
@running = false
|
44
|
+
begin
|
45
|
+
@thread.exit
|
46
|
+
rescue
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
# Main fake spoofer loop.
|
53
|
+
def fake_spoofer
|
54
|
+
spoof_loop(1) { |target| }
|
55
|
+
end
|
26
56
|
|
27
|
-
# This does nothing.
|
28
|
-
def stop; end
|
29
57
|
end
|
30
58
|
end
|
31
59
|
end
|
data/lib/bettercap/version.rb
CHANGED
@@ -11,7 +11,7 @@ This project is released under the GPL 3 license.
|
|
11
11
|
=end
|
12
12
|
module BetterCap
|
13
13
|
# Current version of bettercap.
|
14
|
-
VERSION = '1.2.
|
14
|
+
VERSION = '1.2.3'
|
15
15
|
# Program banner.
|
16
16
|
BANNER = File.read( File.dirname(__FILE__) + '/banner' ).gsub( '#VERSION#', "v#{VERSION}")
|
17
17
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: bettercap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.2.
|
4
|
+
version: 1.2.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Simone Margaritelli
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-01-
|
11
|
+
date: 2016-01-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|