bettercap 1.2.2 → 1.2.3
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/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
|