bettercap 1.5.1 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/bettercap/context.rb +22 -26
- data/lib/bettercap/discovery/agents/arp.rb +2 -2
- data/lib/bettercap/discovery/agents/base.rb +4 -6
- data/lib/bettercap/discovery/agents/icmp.rb +2 -2
- data/lib/bettercap/discovery/thread.rb +8 -4
- data/lib/bettercap/monkey/celluloid/actor.rb +23 -0
- data/lib/bettercap/network/arp_reader.rb +2 -2
- data/lib/bettercap/network/network.rb +0 -2
- data/lib/bettercap/network/packet_queue.rb +10 -1
- data/lib/bettercap/network/servers/dnsd.rb +18 -14
- data/lib/bettercap/network/servers/httpd.rb +10 -6
- data/lib/bettercap/network/target.rb +14 -10
- data/lib/bettercap/options/core_options.rb +5 -5
- data/lib/bettercap/options/options.rb +7 -7
- data/lib/bettercap/options/proxy_options.rb +7 -3
- data/lib/bettercap/pluggable.rb +37 -0
- data/lib/bettercap/proxy/http/module.rb +1 -1
- data/lib/bettercap/proxy/http/modules/injectcss.rb +8 -0
- data/lib/bettercap/proxy/http/modules/injecthtml.rb +8 -0
- data/lib/bettercap/proxy/http/modules/injectjs.rb +8 -0
- data/lib/bettercap/proxy/http/proxy.rb +2 -1
- data/lib/bettercap/proxy/http/sslstrip/strip.rb +1 -1
- data/lib/bettercap/proxy/http/streamer.rb +7 -0
- data/lib/bettercap/proxy/stream_logger.rb +1 -1
- data/lib/bettercap/proxy/tcp/module.rb +1 -1
- data/lib/bettercap/proxy/tcp/proxy.rb +1 -1
- data/lib/bettercap/sniffer/sniffer.rb +1 -1
- data/lib/bettercap/spoofers/arp.rb +5 -5
- data/lib/bettercap/spoofers/icmp.rb +2 -3
- data/lib/bettercap/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 182a02bf76fdd35026f7807807c63c0f27f2dae2
|
4
|
+
data.tar.gz: 231c55ddf54d61c4fd50bc7aa66e39e825f99fd8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 17d11b9a0f810509cde47dc5e236ea22e0d18abd8fb5ec74fec50abab8e0b65f2ee6d2a91490d1d4e861cfb0e52d6f4febc383a30f4e89bd98a0f4280443b6de
|
7
|
+
data.tar.gz: 0c465a3405d311a59323be9b976951c6e670353af20ccb57e6a7189de89d463162a56b0f9d5c5dc6283b467830ff4e4677b4ee1d4db6fd0749bb699ea8747ed8
|
data/lib/bettercap/context.rb
CHANGED
@@ -17,10 +17,10 @@ module BetterCap
|
|
17
17
|
class Context
|
18
18
|
# Instance of BetterCap::Options class.
|
19
19
|
attr_accessor :options
|
20
|
-
# A dictionary containing information about the selected network interface.
|
21
|
-
attr_accessor :ifconfig
|
22
20
|
# Instance of the current BetterCap::Firewalls class.
|
23
21
|
attr_accessor :firewall
|
22
|
+
# Local interface ( as an instance of BetterCap::Network::Target ).
|
23
|
+
attr_accessor :iface
|
24
24
|
# Network gateway ( as an instance of BetterCap::Network::Target ).
|
25
25
|
attr_accessor :gateway
|
26
26
|
# A list of BetterCap::Target objects which is periodically updated.
|
@@ -63,8 +63,10 @@ class Context
|
|
63
63
|
@running = true
|
64
64
|
@timeout = 5
|
65
65
|
@options = Options.new iface
|
66
|
-
@
|
67
|
-
@firewall =
|
66
|
+
@discovery = Discovery::Thread.new self
|
67
|
+
@firewall = Firewalls::Base.get
|
68
|
+
@memory = Memory.new
|
69
|
+
@iface = nil
|
68
70
|
@gateway = nil
|
69
71
|
@targets = []
|
70
72
|
@spoofer = nil
|
@@ -72,10 +74,7 @@ class Context
|
|
72
74
|
@dnsd = nil
|
73
75
|
@proxies = []
|
74
76
|
@redirections = []
|
75
|
-
@discovery = Discovery::Thread.new self
|
76
|
-
@firewall = Firewalls::Base.get
|
77
77
|
@packets = nil
|
78
|
-
@memory = Memory.new
|
79
78
|
end
|
80
79
|
|
81
80
|
# Update the Context state parsing network related informations.
|
@@ -86,23 +85,24 @@ class Context
|
|
86
85
|
'correct network configuration, this could also happen if bettercap '\
|
87
86
|
'is launched from a virtual environment.' unless Network::Validator.is_ip?(gw)
|
88
87
|
|
89
|
-
@gateway = Network::Target.new gw
|
90
|
-
@targets = @options.core.targets unless @options.core.targets.nil?
|
91
|
-
@ifconfig = PacketFu::Utils.ifconfig @options.core.iface
|
92
88
|
|
89
|
+
cfg = PacketFu::Utils.ifconfig @options.core.iface
|
93
90
|
raise BetterCap::Error, "Could not determine IPv4 address of '#{@options.core.iface}', make sure this interface "\
|
94
|
-
'is active and connected.' if
|
91
|
+
'is active and connected.' if cfg[:ip4_obj].nil?
|
92
|
+
|
93
|
+
@gateway = Network::Target.new gw
|
94
|
+
@targets = @options.core.targets unless @options.core.targets.nil?
|
95
|
+
@iface = Network::Target.new( cfg[:ip_saddr], cfg[:eth_saddr], cfg[:ip4_obj], cfg[:iface] )
|
96
|
+
|
97
|
+
Logger.info "[#{@iface.name.green}] #{@iface.to_s(false)}"
|
95
98
|
|
96
99
|
Logger.debug '----- NETWORK INFORMATIONS -----'
|
97
|
-
Logger.debug " network = #{@
|
100
|
+
Logger.debug " network = #{@iface.network} ( #{@iface.network.to_range.to_s.split('..').join( ' -> ')} )"
|
98
101
|
Logger.debug " gateway = #{@gateway.ip}"
|
99
|
-
Logger.debug " local_ip = #{@
|
100
|
-
@ifconfig.each do |key,value|
|
101
|
-
Logger.debug " ifconfig[:#{key}] = #{value}"
|
102
|
-
end
|
102
|
+
Logger.debug " local_ip = #{@iface.ip}"
|
103
103
|
Logger.debug "--------------------------------\n"
|
104
104
|
|
105
|
-
@packets = Network::PacketQueue.new( @
|
105
|
+
@packets = Network::PacketQueue.new( @iface.name, @options.core.packet_throttle, 4 )
|
106
106
|
# Spoofers need the context network data to be initialized.
|
107
107
|
@spoofer = @options.spoof.parse_spoofers
|
108
108
|
end
|
@@ -122,10 +122,6 @@ class Context
|
|
122
122
|
def start!
|
123
123
|
# Start targets auto discovery.
|
124
124
|
@discovery.start
|
125
|
-
# give some time to the discovery thread to spawn its workers,
|
126
|
-
# this will prevent 'Too many open files' errors to delay host
|
127
|
-
# discovery.
|
128
|
-
sleep(1.5) if @options.core.discovery?
|
129
125
|
|
130
126
|
# Start network spoofers if any.
|
131
127
|
@spoofer.each do |spoofer|
|
@@ -193,7 +189,7 @@ class Context
|
|
193
189
|
|
194
190
|
# Apply needed BetterCap::Firewalls::Redirection objects.
|
195
191
|
def enable_port_redirection!
|
196
|
-
@redirections = @options.get_redirections(@
|
192
|
+
@redirections = @options.get_redirections(@iface)
|
197
193
|
@redirections.each do |r|
|
198
194
|
Logger.debug "Redirecting #{r.protocol} traffic from #{r.src_address.nil? ? '*' : r.src_address}:#{r.src_port} to #{r.dst_address}:#{r.dst_port}"
|
199
195
|
@firewall.add_port_redirection( r )
|
@@ -210,17 +206,17 @@ class Context
|
|
210
206
|
|
211
207
|
# create HTTP proxy
|
212
208
|
if @options.proxies.proxy
|
213
|
-
@proxies << Proxy::HTTP::Proxy.new( @
|
209
|
+
@proxies << Proxy::HTTP::Proxy.new( @iface.ip, @options.proxies.proxy_port, false )
|
214
210
|
end
|
215
211
|
|
216
212
|
# create HTTPS proxy
|
217
213
|
if @options.proxies.proxy_https
|
218
|
-
@proxies << Proxy::HTTP::Proxy.new( @
|
214
|
+
@proxies << Proxy::HTTP::Proxy.new( @iface.ip, @options.proxies.proxy_https_port, true )
|
219
215
|
end
|
220
216
|
|
221
217
|
# create TCP proxy
|
222
218
|
if @options.proxies.tcp_proxy
|
223
|
-
@proxies << Proxy::TCP::Proxy.new( @
|
219
|
+
@proxies << Proxy::TCP::Proxy.new( @iface.ip, @options.proxies.tcp_proxy_port, @options.proxies.tcp_proxy_upstream_address, @options.proxies.tcp_proxy_upstream_port )
|
224
220
|
end
|
225
221
|
|
226
222
|
@proxies.each do |proxy|
|
@@ -234,7 +230,7 @@ class Context
|
|
234
230
|
if @options.servers.dnsd
|
235
231
|
Logger.warn "Starting DNS server with spoofing disabled, bettercap will only reply to local DNS queries." unless @options.spoof.enabled?
|
236
232
|
|
237
|
-
@dnsd = Network::Servers::DNSD.new( @options.servers.dnsd_file, @
|
233
|
+
@dnsd = Network::Servers::DNSD.new( @options.servers.dnsd_file, @iface.ip, @options.servers.dnsd_port )
|
238
234
|
@dnsd.start
|
239
235
|
end
|
240
236
|
|
@@ -23,10 +23,10 @@ class Arp < Discovery::Agents::Base
|
|
23
23
|
def get_probe( ip )
|
24
24
|
pkt = PacketFu::ARPPacket.new
|
25
25
|
|
26
|
-
pkt.eth_saddr = pkt.arp_saddr_mac = @
|
26
|
+
pkt.eth_saddr = pkt.arp_saddr_mac = @ctx.iface.mac
|
27
27
|
pkt.eth_daddr = 'ff:ff:ff:ff:ff:ff'
|
28
28
|
pkt.arp_daddr_mac = '00:00:00:00:00:00'
|
29
|
-
pkt.arp_saddr_ip = @
|
29
|
+
pkt.arp_saddr_ip = @ctx.iface.ip
|
30
30
|
pkt.arp_daddr_ip = ip.to_s
|
31
31
|
|
32
32
|
pkt
|
@@ -20,13 +20,11 @@ class Base
|
|
20
20
|
# Initialize the agent using the +ctx+ BetterCap::Context instance.
|
21
21
|
# If +address+ is not nil only that ip will be probed.
|
22
22
|
def initialize( ctx, address = nil )
|
23
|
-
@ctx
|
24
|
-
@
|
25
|
-
@local_ip = @ifconfig[:ip_saddr]
|
26
|
-
@address = address
|
23
|
+
@ctx = ctx
|
24
|
+
@address = address
|
27
25
|
|
28
26
|
if @address.nil?
|
29
|
-
net = ip = @
|
27
|
+
net = ip = @ctx.iface.network
|
30
28
|
# loop each ip in our subnet and push it to the queue
|
31
29
|
while net.include?ip
|
32
30
|
unless skip_address?(ip)
|
@@ -52,7 +50,7 @@ class Base
|
|
52
50
|
if ip == @ctx.gateway.ip
|
53
51
|
return !@ctx.gateway.mac.nil?
|
54
52
|
# don't send probes to our device
|
55
|
-
elsif ip == @
|
53
|
+
elsif ip == @ctx.iface.ip
|
56
54
|
return true
|
57
55
|
# don't stress endpoints we already discovered
|
58
56
|
else
|
@@ -26,9 +26,9 @@ class Icmp
|
|
26
26
|
3.times do
|
27
27
|
pkt = PacketFu::ICMPPacket.new
|
28
28
|
|
29
|
-
pkt.eth_saddr = ctx.
|
29
|
+
pkt.eth_saddr = ctx.iface.mac
|
30
30
|
pkt.eth_daddr = 'ff:ff:ff:ff:ff:ff'
|
31
|
-
pkt.ip_saddr = ctx.
|
31
|
+
pkt.ip_saddr = ctx.iface.ip
|
32
32
|
pkt.ip_daddr = '255.255.255.255'
|
33
33
|
pkt.icmp_type = 8
|
34
34
|
pkt.icmp_code = 0
|
@@ -23,12 +23,16 @@ class Thread
|
|
23
23
|
|
24
24
|
# Start the active network discovery thread.
|
25
25
|
def start
|
26
|
-
if @ctx.options.core.discovery?
|
27
|
-
Logger.info "[#{'DISCOVERY'.green}] Targeting the whole subnet #{@ctx.ifconfig[:ip4_obj].to_range} ..."
|
28
|
-
end
|
29
|
-
|
30
26
|
@running = true
|
31
27
|
@thread = ::Thread.new { worker }
|
28
|
+
|
29
|
+
if @ctx.options.core.discovery?
|
30
|
+
Logger.info "[#{'DISCOVERY'.green}] Targeting the whole subnet #{@ctx.iface.network.to_range} ..."
|
31
|
+
# give some time to the discovery thread to spawn its workers,
|
32
|
+
# this will prevent 'Too many open files' errors to delay host
|
33
|
+
# discovery.
|
34
|
+
sleep(1.5)
|
35
|
+
end
|
32
36
|
end
|
33
37
|
|
34
38
|
# Stop the active network discovery thread.
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
=begin
|
3
|
+
|
4
|
+
BETTERCAP
|
5
|
+
|
6
|
+
Author : Simone 'evilsocket' Margaritelli
|
7
|
+
Email : evilsocket@gmail.com
|
8
|
+
Blog : http://www.evilsocket.net/
|
9
|
+
|
10
|
+
This project is released under the GPL 3 license.
|
11
|
+
|
12
|
+
=end
|
13
|
+
|
14
|
+
# Monkey patching to remove Internals::Logger.crash calls.
|
15
|
+
module Celluloid
|
16
|
+
class Actor
|
17
|
+
# Handle any exceptions that occur within a running actor
|
18
|
+
def handle_crash(exception)
|
19
|
+
shutdown ExitEvent.new(behavior_proxy, exception)
|
20
|
+
rescue => ex
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -21,7 +21,7 @@ class ArpReader
|
|
21
21
|
def self.parse( ctx )
|
22
22
|
targets = []
|
23
23
|
self.parse_cache do |ip,mac|
|
24
|
-
if ip != ctx.gateway.ip and ip != ctx.
|
24
|
+
if ip != ctx.gateway.ip and ip != ctx.iface.ip
|
25
25
|
if ctx.options.core.ignore_ip?(ip)
|
26
26
|
Logger.debug "Ignoring #{ip} ..."
|
27
27
|
else
|
@@ -65,7 +65,7 @@ class ArpReader
|
|
65
65
|
# Read the computer ARP cache and parse each line, it will yield each
|
66
66
|
# ip and mac address it will be able to extract.
|
67
67
|
def self.parse_cache
|
68
|
-
iface = Context.get.
|
68
|
+
iface = Context.get.iface.name
|
69
69
|
Shell.arp.split("\n").each do |line|
|
70
70
|
m = self.parse_cache_line(iface,line)
|
71
71
|
unless m.nil?
|
@@ -26,10 +26,19 @@ class PacketQueue
|
|
26
26
|
@running = true
|
27
27
|
@stream = PCAPRUB::Pcap.open_live( iface, 0xffff, false , 1 )
|
28
28
|
@mutex = Mutex.new
|
29
|
-
@udp = UDPSocket.new
|
30
29
|
@queue = Queue.new
|
31
30
|
@workers = (0...nworkers).map { ::Thread.new { worker } }
|
32
31
|
@ctx = Context.get
|
32
|
+
|
33
|
+
begin
|
34
|
+
@udp = UDPSocket.new
|
35
|
+
rescue Errno::EMFILE
|
36
|
+
Logger.warn "It looks like another process is using a lot of UDP file descriptors" \
|
37
|
+
"and the operating system is denying resources to me, I'll try again in one second."
|
38
|
+
|
39
|
+
sleep 1.0
|
40
|
+
retry
|
41
|
+
end
|
33
42
|
end
|
34
43
|
|
35
44
|
# Push a packet to the queue.
|
@@ -78,22 +78,26 @@ class DNSD
|
|
78
78
|
:server_class => DnsWrapper
|
79
79
|
}
|
80
80
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
81
|
+
begin
|
82
|
+
RubyDNS::run_server( options ) do
|
83
|
+
# Suppress RubyDNS logging.
|
84
|
+
@logger.level = ::Logger::ERROR
|
85
|
+
@upstream ||= RubyDNS::Resolver.new([[:udp, "8.8.8.8", 53], [:tcp, "8.8.8.8", 53]])
|
86
|
+
|
87
|
+
# Default DNS handler
|
88
|
+
otherwise do |transaction|
|
89
|
+
Logger.debug "[#{transaction.options[:peer]} > #{'DNS'.green}] Received request for '#{transaction.question.to_s.yellow}' -> upstream DNS"
|
90
|
+
transaction.passthrough!(@upstream)
|
91
|
+
end
|
90
92
|
end
|
91
|
-
end
|
92
93
|
|
93
|
-
|
94
|
-
|
95
|
-
|
94
|
+
unless @hosts.nil?
|
95
|
+
DNSD.parse_hosts( @hosts ).each do |exp,addr|
|
96
|
+
add_rule!( exp, addr )
|
97
|
+
end
|
96
98
|
end
|
99
|
+
rescue Errno::EADDRINUSE
|
100
|
+
raise BetterCap::Error, "[DNS] It looks like there's another process listening on #{@address}:#{@port}, please chose a different port."
|
97
101
|
end
|
98
102
|
end
|
99
103
|
|
@@ -120,7 +124,7 @@ class DNSD
|
|
120
124
|
expression = $2
|
121
125
|
|
122
126
|
if address == 'local'
|
123
|
-
address = Context.get.
|
127
|
+
address = Context.get.iface.ip.to_s
|
124
128
|
end
|
125
129
|
|
126
130
|
raise BetterCap::Error, "Invalid IPv4 address '#{address}' on line #{lineno + 1} of '#{filename}'." \
|
@@ -22,12 +22,16 @@ class HTTPD
|
|
22
22
|
def initialize( port = 8081, path = './' )
|
23
23
|
@port = port
|
24
24
|
@path = path
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
begin
|
26
|
+
@server = WEBrick::HTTPServer.new(
|
27
|
+
Port: @port,
|
28
|
+
DocumentRoot: @path,
|
29
|
+
Logger: WEBrick::Log.new("/dev/null"),
|
30
|
+
AccessLog: []
|
31
|
+
)
|
32
|
+
rescue Errno::EADDRINUSE
|
33
|
+
raise BetterCap::Error, "[HTTPD] It looks like there's another process listening on port #{@port}, please chose a different port."
|
34
|
+
end
|
31
35
|
end
|
32
36
|
|
33
37
|
# Start the server.
|
@@ -20,10 +20,13 @@ class Target
|
|
20
20
|
attr_accessor :ip
|
21
21
|
# The MAC address of the device network interface.
|
22
22
|
attr_accessor :mac
|
23
|
+
# Network object.
|
24
|
+
attr_accessor :network
|
23
25
|
# Vendor of the device network interface if available.
|
24
26
|
attr_accessor :vendor
|
25
|
-
# NetBIOS hostname of the device if available
|
26
|
-
|
27
|
+
# NetBIOS hostname of the device if available OR interface name in case of
|
28
|
+
# local address.
|
29
|
+
attr_accessor :name
|
27
30
|
# True if the IP attribute of this target needs to be updated.
|
28
31
|
attr_accessor :ip_refresh
|
29
32
|
|
@@ -43,7 +46,7 @@ class Target
|
|
43
46
|
# The +ip+ argument could also be a MAC address itself, in this case the
|
44
47
|
# ip address will be parsed from the computer ARP cache and updated
|
45
48
|
# accordingly.
|
46
|
-
def initialize( ip, mac=nil )
|
49
|
+
def initialize( ip, mac=nil, network=nil, name=nil )
|
47
50
|
if Network::Validator.is_ip?(ip)
|
48
51
|
@ip = ip
|
49
52
|
@ip_refresh = false
|
@@ -57,8 +60,9 @@ class Target
|
|
57
60
|
|
58
61
|
@mac = Target.normalized_mac(mac) unless mac.nil?
|
59
62
|
@vendor = Target.lookup_vendor(@mac) unless mac.nil?
|
60
|
-
@
|
61
|
-
@
|
63
|
+
@name = name
|
64
|
+
@network = network
|
65
|
+
@resolver = Thread.new { resolve! } unless Context.get.options.core.no_target_nbns or @ip.nil? or !@network.nil?
|
62
66
|
end
|
63
67
|
|
64
68
|
# Return the integer representation of the +ip+ attribute which can be
|
@@ -86,15 +90,15 @@ class Target
|
|
86
90
|
vendor = @vendor.nil?? " ( ??? )" : " ( #{@vendor} )"
|
87
91
|
|
88
92
|
s = sprintf( fmt, address, @mac )
|
89
|
-
s += " / #{@
|
93
|
+
s += " / #{@name}" unless @name.nil?
|
90
94
|
s += vendor
|
91
95
|
s
|
92
96
|
end
|
93
97
|
|
94
98
|
# Return a compact string representation of this object.
|
95
99
|
def to_s_compact
|
96
|
-
if @
|
97
|
-
"#{@
|
100
|
+
if @name
|
101
|
+
"#{@name}/#{@ip}"
|
98
102
|
else
|
99
103
|
@ip
|
100
104
|
end
|
@@ -131,8 +135,8 @@ private
|
|
131
135
|
sock.recvfrom(NBNS_BUFSIZE)
|
132
136
|
end
|
133
137
|
if resp
|
134
|
-
@
|
135
|
-
Logger.info "Found NetBIOS name '#{@
|
138
|
+
@name = parse_nbns_response resp
|
139
|
+
Logger.info "Found NetBIOS name '#{@name}' for address #{@ip}"
|
136
140
|
end
|
137
141
|
rescue Exception => e
|
138
142
|
Logger.debug e
|
@@ -22,9 +22,9 @@ class CoreOptions
|
|
22
22
|
attr_accessor :targets
|
23
23
|
# Comma separated list of ip addresses to ignore.
|
24
24
|
attr_accessor :ignore
|
25
|
-
# If
|
25
|
+
# If false will disable active network discovery, the program will just use
|
26
26
|
# the current ARP cache.
|
27
|
-
attr_accessor :
|
27
|
+
attr_accessor :discovery
|
28
28
|
# If true, targets NBNS hostname resolution won't be performed.
|
29
29
|
attr_accessor :no_target_nbns
|
30
30
|
# Log file name.
|
@@ -49,7 +49,7 @@ class CoreOptions
|
|
49
49
|
@silent = false
|
50
50
|
@debug = false
|
51
51
|
@ignore = nil
|
52
|
-
@
|
52
|
+
@discovery = true
|
53
53
|
@no_target_nbns = false
|
54
54
|
@packet_throttle = 0.0
|
55
55
|
@check_updates = false
|
@@ -78,7 +78,7 @@ class CoreOptions
|
|
78
78
|
end
|
79
79
|
|
80
80
|
opts.on( '--no-discovery', "Do not actively search for hosts, just use the current ARP cache, default to #{'false'.yellow}." ) do
|
81
|
-
@
|
81
|
+
@discovery = false
|
82
82
|
end
|
83
83
|
|
84
84
|
opts.on( '--no-target-nbns', 'Disable target NBNS hostname resolution.' ) do
|
@@ -129,7 +129,7 @@ class CoreOptions
|
|
129
129
|
|
130
130
|
# Return true if active host discovery is enabled, otherwise false.
|
131
131
|
def discovery?
|
132
|
-
( @targets.nil?
|
132
|
+
( @discovery and @targets.nil? )
|
133
133
|
end
|
134
134
|
|
135
135
|
# Split specified targets and parse them ( either as IP or MAC ), will raise a
|
@@ -93,28 +93,28 @@ class Options
|
|
93
93
|
|
94
94
|
# Create a list of BetterCap::Firewalls::Redirection objects which are needed
|
95
95
|
# given the specified command line arguments.
|
96
|
-
def get_redirections
|
96
|
+
def get_redirections iface
|
97
97
|
redirections = []
|
98
98
|
|
99
99
|
if @servers.dnsd or @proxies.sslstrip?
|
100
|
-
redirections << redir(
|
101
|
-
redirections << redir(
|
100
|
+
redirections << redir( iface.ip, 53, @servers.dnsd_port )
|
101
|
+
redirections << redir( iface.ip, 53, @servers.dnsd_port, 'UDP' )
|
102
102
|
end
|
103
103
|
|
104
104
|
if @proxies.proxy
|
105
105
|
@proxies.http_ports.each do |port|
|
106
|
-
redirections << redir(
|
106
|
+
redirections << redir( iface.ip, port, @proxies.proxy_port )
|
107
107
|
end
|
108
108
|
end
|
109
109
|
|
110
110
|
if @proxies.proxy_https
|
111
111
|
@proxies.https_ports.each do |port|
|
112
|
-
redirections << redir(
|
112
|
+
redirections << redir( iface.ip, port, @proxies.proxy_https_port )
|
113
113
|
end
|
114
114
|
end
|
115
115
|
|
116
116
|
if @proxies.tcp_proxy
|
117
|
-
redirections << redir_single( @proxies.tcp_proxy_upstream_address,
|
117
|
+
redirections << redir_single( @proxies.tcp_proxy_upstream_address, iface.ip, @proxies.tcp_proxy_upstream_port, @proxies.tcp_proxy_port )
|
118
118
|
end
|
119
119
|
|
120
120
|
if @proxies.custom_proxy
|
@@ -130,7 +130,7 @@ class Options
|
|
130
130
|
end
|
131
131
|
|
132
132
|
@proxies.custom_redirections.each do |r|
|
133
|
-
redirections << redir(
|
133
|
+
redirections << redir( iface.ip, r[:from], r[:to], r[:proto] )
|
134
134
|
end
|
135
135
|
|
136
136
|
redirections
|
@@ -145,15 +145,18 @@ class ProxyOptions
|
|
145
145
|
end
|
146
146
|
|
147
147
|
opts.on( '--no-sslstrip', 'Disable SSLStrip.' ) do
|
148
|
+
@proxy = true
|
148
149
|
@sslstrip = false
|
149
150
|
end
|
150
151
|
|
151
152
|
opts.on( '--proxy-module MODULE', "Ruby proxy module to load, either a custom file or one of the following: #{Proxy::HTTP::Module.available.map{|x| x.yellow}.join(', ')}." ) do |v|
|
152
153
|
Proxy::HTTP::Module.load(ctx, opts, v)
|
154
|
+
@proxy = true
|
153
155
|
end
|
154
156
|
|
155
157
|
opts.on( '--http-ports PORT1,PORT2', "Comma separated list of HTTP ports to redirect to the proxy, default to #{@http_ports.map{|x| x.to_s.yellow }.join(', ')}." ) do |v|
|
156
|
-
@http_ports =
|
158
|
+
@http_ports = ProxyOptions.parse_ports( v )
|
159
|
+
@proxy = true
|
157
160
|
end
|
158
161
|
|
159
162
|
opts.separator ""
|
@@ -176,7 +179,8 @@ class ProxyOptions
|
|
176
179
|
end
|
177
180
|
|
178
181
|
opts.on( '--https-ports PORT1,PORT2', "Comma separated list of HTTPS ports to redirect to the proxy, default to #{@https_ports.map{|x| x.to_s.yellow }.join(', ')}." ) do |v|
|
179
|
-
@https_ports =
|
182
|
+
@https_ports = ProxyOptions.parse_ports( v )
|
183
|
+
@proxy_https = true
|
180
184
|
end
|
181
185
|
|
182
186
|
opts.separator ""
|
@@ -213,7 +217,7 @@ class ProxyOptions
|
|
213
217
|
raise BetterCap::Error, "No TCP proxy upstream server port specified ( --tcp-proxy-upstream-port PORT )." if @tcp_proxy_upstream_port.nil?
|
214
218
|
end
|
215
219
|
|
216
|
-
if @sslstrip and ctx.options.servers.dnsd
|
220
|
+
if @proxy and @sslstrip and ctx.options.servers.dnsd
|
217
221
|
raise BetterCap::Error, "SSL Stripping and builtin DNS server are mutually exclusive features, " \
|
218
222
|
"either use the --no-sslstrip option or remove the --dns option."
|
219
223
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
=begin
|
3
|
+
|
4
|
+
BETTERCAP
|
5
|
+
|
6
|
+
Author : Simone 'evilsocket' Margaritelli
|
7
|
+
Email : evilsocket@gmail.com
|
8
|
+
Blog : http://www.evilsocket.net/
|
9
|
+
|
10
|
+
This project is released under the GPL 3 license.
|
11
|
+
|
12
|
+
=end
|
13
|
+
|
14
|
+
module BetterCap
|
15
|
+
# Simple base class for modules and plugins of various types.
|
16
|
+
class Pluggable
|
17
|
+
@@metadata = {
|
18
|
+
'Name' => '',
|
19
|
+
'Version' => '',
|
20
|
+
'Author' => "Simone 'evilsocket' Margaritelli",
|
21
|
+
'License' => 'GPL3',
|
22
|
+
'Description' => ''
|
23
|
+
}
|
24
|
+
|
25
|
+
# Define the metadata for this module.
|
26
|
+
def self.meta(meta = {})
|
27
|
+
meta.each do |key,value|
|
28
|
+
@@metadata[key] = value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Get the metadata of this module.
|
33
|
+
def self.metadata
|
34
|
+
@@metadata
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -13,6 +13,14 @@ This project is released under the GPL 3 license.
|
|
13
13
|
|
14
14
|
# This proxy module will take care of CSS code injection.
|
15
15
|
class InjectCSS < BetterCap::Proxy::HTTP::Module
|
16
|
+
meta(
|
17
|
+
'Name' => 'InjectCSS',
|
18
|
+
'Description' => 'This proxy module will take care of CSS code injection.',
|
19
|
+
'Version' => '1.0.0',
|
20
|
+
'Author' => "Simone 'evilsocket' Margaritelli",
|
21
|
+
'License' => 'GPL3'
|
22
|
+
)
|
23
|
+
|
16
24
|
# CSS data to be injected.
|
17
25
|
@@cssdata = nil
|
18
26
|
# CSS file URL to be injected.
|
@@ -13,6 +13,14 @@ This project is released under the GPL 3 license.
|
|
13
13
|
|
14
14
|
# This proxy module will take care of HTML code injection.
|
15
15
|
class InjectHTML < BetterCap::Proxy::HTTP::Module
|
16
|
+
meta(
|
17
|
+
'Name' => 'InjectHTML',
|
18
|
+
'Description' => 'This proxy module will take care of HTML code injection.',
|
19
|
+
'Version' => '1.0.0',
|
20
|
+
'Author' => "Simone 'evilsocket' Margaritelli",
|
21
|
+
'License' => 'GPL3'
|
22
|
+
)
|
23
|
+
|
16
24
|
# URL of the iframe if --html-iframe-url was specified.
|
17
25
|
@@iframe = nil
|
18
26
|
# HTML data to be injected.
|
@@ -13,6 +13,14 @@ This project is released under the GPL 3 license.
|
|
13
13
|
|
14
14
|
# This proxy module will take care of Javascript code injection.
|
15
15
|
class InjectJS < BetterCap::Proxy::HTTP::Module
|
16
|
+
meta(
|
17
|
+
'Name' => 'InjectJS',
|
18
|
+
'Description' => 'This proxy module will take care of Javascript code injection.',
|
19
|
+
'Version' => '1.0.0',
|
20
|
+
'Author' => "Simone 'evilsocket' Margaritelli",
|
21
|
+
'License' => 'GPL3'
|
22
|
+
)
|
23
|
+
|
16
24
|
# JS data to be injected.
|
17
25
|
@@jsdata = nil
|
18
26
|
# JS file URL to be injected.
|
@@ -69,9 +69,10 @@ class Proxy
|
|
69
69
|
end
|
70
70
|
|
71
71
|
@main_thread = Thread.new &method(:server_thread)
|
72
|
+
rescue Errno::EADDRINUSE
|
73
|
+
raise BetterCap::Error, "[#{@type} PROXY] It looks like there's another process listening on #{@address}:#{@port}, please chose a different port."
|
72
74
|
rescue Exception => e
|
73
75
|
Logger.error "Error starting #{@type} proxy: #{e.inspect}"
|
74
|
-
@socket.close unless @socket.nil?
|
75
76
|
end
|
76
77
|
end
|
77
78
|
|
@@ -106,7 +106,7 @@ class Strip
|
|
106
106
|
@stripped = []
|
107
107
|
@cookies = CookieMonitor.new
|
108
108
|
@favicon = Response.from_file( File.dirname(__FILE__) + '/lock.ico', 'image/x-icon' )
|
109
|
-
@resolver = BetterCap::Network::Servers::DNSD.new( nil, ctx.
|
109
|
+
@resolver = BetterCap::Network::Servers::DNSD.new( nil, ctx.iface.ip, ctx.options.servers.dnsd_port )
|
110
110
|
|
111
111
|
@resolver.start
|
112
112
|
end
|
@@ -203,6 +203,13 @@ class Streamer
|
|
203
203
|
end
|
204
204
|
end
|
205
205
|
|
206
|
+
# Handle a PUT request, +req+ is the request object and +res+ the response.
|
207
|
+
def do_PUT(req, res)
|
208
|
+
perform_proxy_request(req, res) do |http, path, header|
|
209
|
+
http.put(path, req.body || "", header)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
206
213
|
end
|
207
214
|
end
|
208
215
|
end
|
@@ -31,7 +31,7 @@ class StreamLogger
|
|
31
31
|
def self.addr2s( addr, alt = nil )
|
32
32
|
ctx = Context.get
|
33
33
|
# check for the local address
|
34
|
-
return 'local' if addr == ctx.
|
34
|
+
return 'local' if addr == ctx.iface.ip
|
35
35
|
# is it a known target?
|
36
36
|
target = ctx.find_target addr, nil
|
37
37
|
return target.to_s_compact unless target.nil?
|
@@ -26,7 +26,7 @@ module TCP
|
|
26
26
|
# event.data = 'bbb'
|
27
27
|
# end
|
28
28
|
# end
|
29
|
-
class Module
|
29
|
+
class Module < BetterCap::Pluggable
|
30
30
|
# This callback is called when the target is sending data to the upstream server.
|
31
31
|
# +event+ is an instance of the BetterCap::Proxy::TCP::Event class.
|
32
32
|
def on_data( event ); end
|
@@ -52,7 +52,7 @@ class Proxy
|
|
52
52
|
# address is discovered and put in the ARP cache before we even start the proxy,
|
53
53
|
# otherwise internal connections won't be spoofed and the proxy will be useless
|
54
54
|
# until some random event will fill the ARP cache for the server.
|
55
|
-
if @ctx.
|
55
|
+
if @ctx.iface.network.include?( @upstream_address )
|
56
56
|
Logger.debug "[#{'TCP PROXY'.green}] Sending probe to upstream server address ..."
|
57
57
|
BetterCap::Network.get_hw_address( @ctx, @upstream_address )
|
58
58
|
# wait for the system to acknowledge the ARP cache changes.
|
@@ -88,7 +88,7 @@ class Sniffer
|
|
88
88
|
return true unless pkt.is_ip?
|
89
89
|
# skip if local packet and --local|-L was not specified.
|
90
90
|
unless @@ctx.options.sniff.local
|
91
|
-
return ( pkt.ip_saddr == @@ctx.
|
91
|
+
return ( pkt.ip_saddr == @@ctx.iface.ip or pkt.ip_daddr == @@ctx.iface.ip )
|
92
92
|
end
|
93
93
|
rescue; end
|
94
94
|
false
|
@@ -104,13 +104,13 @@ class Arp < Base
|
|
104
104
|
end
|
105
105
|
else
|
106
106
|
# tell the target we're the gateway
|
107
|
-
send_spoofed_packet( @ctx.gateway.ip, @ctx.
|
107
|
+
send_spoofed_packet( @ctx.gateway.ip, @ctx.iface.mac, target.ip, target.mac )
|
108
108
|
# tell the gateway we're the target
|
109
|
-
send_spoofed_packet( target.ip, @ctx.
|
109
|
+
send_spoofed_packet( target.ip, @ctx.iface.mac, @ctx.gateway.ip, @ctx.gateway.mac ) unless @ctx.options.spoof.half_duplex
|
110
110
|
# tell the target we're everybody else in the network :D
|
111
111
|
@ctx.targets.each do |e|
|
112
112
|
if e.spoofable? and e.ip != target.ip and e.ip != @ctx.gateway.ip
|
113
|
-
send_spoofed_packet( e.ip, @ctx.
|
113
|
+
send_spoofed_packet( e.ip, @ctx.iface.mac, target.ip, target.mac )
|
114
114
|
end
|
115
115
|
end
|
116
116
|
end
|
@@ -131,7 +131,7 @@ class Arp < Base
|
|
131
131
|
# we're only interested in 'who-has' packets
|
132
132
|
pkt.arp_opcode == 1 and \
|
133
133
|
pkt.arp_dst_mac.to_s == '00:00:00:00:00:00' and \
|
134
|
-
pkt.arp_src_ip.to_s != @ctx.
|
134
|
+
pkt.arp_src_ip.to_s != @ctx.iface.ip
|
135
135
|
end
|
136
136
|
|
137
137
|
# Will watch for incoming ARP requests and spoof the source address.
|
@@ -141,7 +141,7 @@ class Arp < Base
|
|
141
141
|
sniff_packets('arp') { |pkt|
|
142
142
|
if is_arp_query?(pkt)
|
143
143
|
Logger.info "[#{'ARP'.green}] #{pkt.arp_src_ip.to_s} is asking who #{pkt.arp_dst_ip.to_s} is."
|
144
|
-
send_spoofed_packet pkt.arp_dst_ip.to_s, @ctx.
|
144
|
+
send_spoofed_packet pkt.arp_dst_ip.to_s, @ctx.iface.mac, pkt.arp_src_ip.to_s, pkt.arp_src_mac.to_s
|
145
145
|
end
|
146
146
|
}
|
147
147
|
end
|
@@ -79,7 +79,6 @@ class Icmp < Base
|
|
79
79
|
def initialize
|
80
80
|
@ctx = Context.get
|
81
81
|
@forwarding = @ctx.firewall.forwarding_enabled?
|
82
|
-
@local = @ctx.ifconfig[:ip_saddr]
|
83
82
|
@spoof_thread = nil
|
84
83
|
@watch_thread = nil
|
85
84
|
@running = false
|
@@ -97,7 +96,7 @@ class Icmp < Base
|
|
97
96
|
Logger.debug "Sending ICMP Redirect to #{target.to_s_compact} redirecting #{address} to us ..."
|
98
97
|
|
99
98
|
pkt = ICMPRedirectPacket.new
|
100
|
-
pkt.update!( @ctx.gateway, target, @
|
99
|
+
pkt.update!( @ctx.gateway, target, @ctx.iface.ip, address )
|
101
100
|
@ctx.packets.push(pkt)
|
102
101
|
rescue Exception => e
|
103
102
|
Logger.debug "#{self.class.name} : #{e.message}"
|
@@ -147,7 +146,7 @@ class Icmp < Base
|
|
147
146
|
|
148
147
|
# Return true if the +pkt+ packet comes from one of our targets.
|
149
148
|
def is_interesting_packet?(pkt)
|
150
|
-
return false if pkt.ip_saddr == @
|
149
|
+
return false if pkt.ip_saddr == @ctx.iface.ip
|
151
150
|
@ctx.targets.each do |target|
|
152
151
|
if target.ip and ( target.ip == pkt.ip_saddr or target.ip == pkt.ip_daddr )
|
153
152
|
return true
|
data/lib/bettercap/version.rb
CHANGED
@@ -12,7 +12,7 @@ This project is released under the GPL 3 license.
|
|
12
12
|
=end
|
13
13
|
module BetterCap
|
14
14
|
# Current version of bettercap.
|
15
|
-
VERSION = '1.5.
|
15
|
+
VERSION = '1.5.2'
|
16
16
|
# Program banner.
|
17
17
|
BANNER = File.read( File.dirname(__FILE__) + '/banner' ).gsub( '#VERSION#', "v#{VERSION}")
|
18
18
|
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.5.
|
4
|
+
version: 1.5.2
|
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-03-
|
11
|
+
date: 2016-03-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|
@@ -178,6 +178,7 @@ files:
|
|
178
178
|
- lib/bettercap/loader.rb
|
179
179
|
- lib/bettercap/logger.rb
|
180
180
|
- lib/bettercap/memory.rb
|
181
|
+
- lib/bettercap/monkey/celluloid/actor.rb
|
181
182
|
- lib/bettercap/monkey/celluloid/io/udp_socket.rb
|
182
183
|
- lib/bettercap/monkey/em-proxy/proxy.rb
|
183
184
|
- lib/bettercap/monkey/openssl/server.rb
|
@@ -204,6 +205,7 @@ files:
|
|
204
205
|
- lib/bettercap/options/server_options.rb
|
205
206
|
- lib/bettercap/options/sniff_options.rb
|
206
207
|
- lib/bettercap/options/spoof_options.rb
|
208
|
+
- lib/bettercap/pluggable.rb
|
207
209
|
- lib/bettercap/proxy/http/module.rb
|
208
210
|
- lib/bettercap/proxy/http/modules/injectcss.rb
|
209
211
|
- lib/bettercap/proxy/http/modules/injecthtml.rb
|