bettercap 1.5.1 → 1.5.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|