bettercap 1.6.0 → 1.6.1
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.rb +2 -0
- data/lib/bettercap/context.rb +33 -13
- data/lib/bettercap/discovery/agents/ndp.rb +43 -0
- data/lib/bettercap/discovery/thread.rb +1 -1
- data/lib/bettercap/firewalls/base.rb +2 -2
- data/lib/bettercap/firewalls/bsd.rb +13 -2
- data/lib/bettercap/firewalls/linux.rb +38 -7
- data/lib/bettercap/monkey/packetfu/utils.rb +219 -1
- data/lib/bettercap/network/ndp_reader.rb +35 -0
- data/lib/bettercap/network/network.rb +32 -4
- data/lib/bettercap/network/target.rb +1 -1
- data/lib/bettercap/network/validator.rb +33 -0
- data/lib/bettercap/options/core_options.rb +26 -3
- data/lib/bettercap/options/options.rb +5 -0
- data/lib/bettercap/options/proxy_options.rb +73 -1
- data/lib/bettercap/options/spoof_options.rb +10 -2
- data/lib/bettercap/proxy/http/proxy.rb +6 -1
- data/lib/bettercap/proxy/http/response.rb +8 -2
- data/lib/bettercap/proxy/http/ssl/bettercap-ca.pem +52 -48
- data/lib/bettercap/proxy/http/sslstrip/strip.rb +3 -9
- data/lib/bettercap/proxy/http/streamer.rb +1 -1
- data/lib/bettercap/proxy/udp/module.rb +85 -0
- data/lib/bettercap/proxy/udp/pool.rb +86 -0
- data/lib/bettercap/proxy/udp/proxy.rb +92 -0
- data/lib/bettercap/shell.rb +8 -2
- data/lib/bettercap/sniffer/parsers/https.rb +13 -4
- data/lib/bettercap/spoofers/ndp.rb +144 -0
- data/lib/bettercap/version.rb +1 -1
- metadata +15 -3
@@ -53,17 +53,11 @@ class StrippedObject
|
|
53
53
|
# Return a normalized version of +url+.
|
54
54
|
def self.normalize( url, schema = 'https' )
|
55
55
|
has_schema = url.include?('://')
|
56
|
-
|
57
|
-
if
|
58
|
-
|
59
|
-
else
|
60
|
-
has_slash = ( url =~ /^.+\/.*$/ )
|
56
|
+
|
57
|
+
# add schema if none
|
58
|
+
unless has_schema
|
61
59
|
url = "#{schema}://#{url}"
|
62
60
|
end
|
63
|
-
# add slash if needed
|
64
|
-
unless has_slash
|
65
|
-
url = "#{url}/"
|
66
|
-
end
|
67
61
|
url
|
68
62
|
end
|
69
63
|
|
@@ -114,7 +114,7 @@ class Streamer
|
|
114
114
|
r = mod.on_pre_request request
|
115
115
|
# the handler returned a response, do not execute
|
116
116
|
# the request
|
117
|
-
response = r
|
117
|
+
response = r if r.instance_of?(BetterCap::Proxy::HTTP::Response)
|
118
118
|
else
|
119
119
|
mod.on_request request, response
|
120
120
|
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
=begin
|
3
|
+
|
4
|
+
BETTERCAP
|
5
|
+
|
6
|
+
Author : Simone 'evilsocket' Margaritelli
|
7
|
+
Email : evilsocket@gmail.com
|
8
|
+
Blog : https://www.evilsocket.net/
|
9
|
+
|
10
|
+
This project is released under the GPL 3 license.
|
11
|
+
|
12
|
+
=end
|
13
|
+
|
14
|
+
module BetterCap
|
15
|
+
module Proxy
|
16
|
+
module UDP
|
17
|
+
|
18
|
+
# Base class for transparent UDP proxy modules, example:
|
19
|
+
#
|
20
|
+
# class SampleModule < BetterCap::Proxy::UDP::Module
|
21
|
+
# def on_data( event )
|
22
|
+
# event.data = 'aaa'
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# def on_response( event )
|
26
|
+
# event.data = 'aaa'
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
class Module < BetterCap::Pluggable
|
30
|
+
def on_options( opts ); end
|
31
|
+
def check_opts; end
|
32
|
+
# This callback is called when the target is sending data to the upstream server.
|
33
|
+
# +event+ is an instance of the BetterCap::Proxy::UDP::Event class.
|
34
|
+
def on_data( event ); end
|
35
|
+
# This callback is called when the upstream server is sending data to the target.
|
36
|
+
# +event+ is an instance of the BetterCap::Proxy::UDP::Event class.
|
37
|
+
def on_response( event ); end
|
38
|
+
|
39
|
+
# Loaded modules.
|
40
|
+
@@loaded = {}
|
41
|
+
|
42
|
+
class << self
|
43
|
+
# Register custom options for each available module.
|
44
|
+
def register_options(opts)
|
45
|
+
@@loaded.each do |name,mod|
|
46
|
+
if mod.respond_to?(:on_options)
|
47
|
+
mod.on_options(opts)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Called when a class inherits this base class.
|
53
|
+
def inherited(subclass)
|
54
|
+
name = subclass.name.upcase
|
55
|
+
@@loaded[name] = subclass
|
56
|
+
end
|
57
|
+
|
58
|
+
# Load +file+ as a proxy module.
|
59
|
+
def load( file, opts )
|
60
|
+
begin
|
61
|
+
require file
|
62
|
+
rescue LoadError => e
|
63
|
+
raise BetterCap::Error, "Invalid UDP proxy module specified: #{e.message}"
|
64
|
+
end
|
65
|
+
|
66
|
+
@@loaded.each do |name,mod|
|
67
|
+
@@loaded[name] = mod.new
|
68
|
+
end
|
69
|
+
|
70
|
+
self.register_options(opts)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Execute method +even_name+ for each loaded module instance using +event+
|
74
|
+
# as its argument.
|
75
|
+
def dispatch( event_name, event )
|
76
|
+
@@loaded.each do |name,mod|
|
77
|
+
mod.send( event_name, event )
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
=begin
|
3
|
+
|
4
|
+
BETTERCAP
|
5
|
+
|
6
|
+
Author : Simone 'evilsocket' Margaritelli
|
7
|
+
Email : evilsocket@gmail.com
|
8
|
+
Blog : https://www.evilsocket.net/
|
9
|
+
|
10
|
+
This project is released under the GPL 3 license.
|
11
|
+
|
12
|
+
=end
|
13
|
+
|
14
|
+
module BetterCap
|
15
|
+
module Proxy
|
16
|
+
module UDP
|
17
|
+
|
18
|
+
class Client < EM::Connection
|
19
|
+
attr_accessor :client_ip, :client_port
|
20
|
+
|
21
|
+
def initialize(client_ip, client_port, server)
|
22
|
+
@client_ip, @client_port = client_ip, client_port
|
23
|
+
@server = server
|
24
|
+
end
|
25
|
+
|
26
|
+
# upstream -> ip
|
27
|
+
def receive_data(data)
|
28
|
+
port, ip = Socket.unpack_sockaddr_in(get_peername)
|
29
|
+
event = Event.new( ip, port, data )
|
30
|
+
|
31
|
+
Logger.info "[#{'UDP PROXY'.green}] #{'upstream'.yellow}:#{@server.relay_port} #{'->'.green} #{ip} ( #{event.data.bytesize} bytes )"
|
32
|
+
|
33
|
+
Module.dispatch( 'on_response', event )
|
34
|
+
|
35
|
+
@server.send_datagram( event.data, client_ip, client_port )
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Server < EM::Connection
|
40
|
+
attr_accessor :relay_ip, :relay_port
|
41
|
+
|
42
|
+
def initialize(address, relay_ip, relay_port)
|
43
|
+
@relay_ip, @relay_port = relay_ip, relay_port
|
44
|
+
@pool = Pool.new(self, address)
|
45
|
+
end
|
46
|
+
|
47
|
+
# ip -> upstream
|
48
|
+
def receive_data(data)
|
49
|
+
port, ip = Socket.unpack_sockaddr_in(get_peername)
|
50
|
+
client = @pool.client(ip, port)
|
51
|
+
event = Event.new( ip, port, data )
|
52
|
+
|
53
|
+
Logger.info "[#{'UDP PROXY'.green}] #{ip} #{'->'.green} #{'upstream'.yellow}:#{@relay_port} ( #{event.data.bytesize} bytes )"
|
54
|
+
|
55
|
+
Module.dispatch( 'on_data', event )
|
56
|
+
|
57
|
+
client.send_datagram( event.data, @relay_ip, @relay_port )
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Pool
|
62
|
+
def initialize(server, address)
|
63
|
+
@address = address
|
64
|
+
@clients = {}
|
65
|
+
@server = server
|
66
|
+
end
|
67
|
+
|
68
|
+
def client(ip, port)
|
69
|
+
@clients[key(ip, port)] || @clients[key(ip,port)] = create_client(ip, port)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def key(ip, port)
|
75
|
+
"#{ip}:#{port}"
|
76
|
+
end
|
77
|
+
|
78
|
+
def create_client(ip, port)
|
79
|
+
Logger.debug "#{'UDP'.green} Creating new client for #{ip}:#{port}"
|
80
|
+
client = EM::open_datagram_socket @address, 0, Client, ip, port, @server
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
=begin
|
3
|
+
|
4
|
+
BETTERCAP
|
5
|
+
|
6
|
+
Author : Simone 'evilsocket' Margaritelli
|
7
|
+
Email : evilsocket@gmail.com
|
8
|
+
Blog : https://www.evilsocket.net/
|
9
|
+
|
10
|
+
This project is released under the GPL 3 license.
|
11
|
+
|
12
|
+
=end
|
13
|
+
|
14
|
+
module BetterCap
|
15
|
+
module Proxy
|
16
|
+
module UDP
|
17
|
+
|
18
|
+
|
19
|
+
# Class used to encapsulate ( and keep references ) of a single UDP event-.
|
20
|
+
# https://stackoverflow.com/questions/161510/pass-parameter-by-reference-in-ruby
|
21
|
+
class Event
|
22
|
+
# The source IP address of this event.
|
23
|
+
attr_accessor :ip
|
24
|
+
# Source port.
|
25
|
+
attr_accessor :port
|
26
|
+
# Reference to the buffer being transmitted.
|
27
|
+
attr_accessor :data
|
28
|
+
|
29
|
+
def initialize( ip, port, data = nil )
|
30
|
+
@ip = ip
|
31
|
+
@port = port
|
32
|
+
@data = data
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Transparent UDP proxy class.
|
37
|
+
class Proxy
|
38
|
+
# Initialize the UDP proxy with given arguments.
|
39
|
+
def initialize( address, port, up_address, up_port )
|
40
|
+
@address = address
|
41
|
+
@port = port
|
42
|
+
@upstream_address = up_address
|
43
|
+
@upstream_port = up_port
|
44
|
+
@ctx = BetterCap::Context.get
|
45
|
+
@worker = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
# Start the proxy.
|
49
|
+
def start
|
50
|
+
@worker = Thread.new &method(:worker)
|
51
|
+
# If the upstream server is in this network, we need to make sure that its MAC
|
52
|
+
# address is discovered and put in the ARP cache before we even start the proxy,
|
53
|
+
# otherwise internal connections won't be spoofed and the proxy will be useless
|
54
|
+
# until some random event will fill the ARP cache for the server.
|
55
|
+
if @ctx.iface.network.include?( @upstream_address )
|
56
|
+
Logger.debug "[#{'UDP PROXY'.green}] Sending probe to upstream server address ..."
|
57
|
+
BetterCap::Network.get_hw_address( @ctx, @upstream_address )
|
58
|
+
# wait for the system to acknowledge the ARP cache changes.
|
59
|
+
sleep( 1 )
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Stop the proxy.
|
64
|
+
def stop
|
65
|
+
Logger.info "Stopping UDP proxy ..."
|
66
|
+
EventMachine.stop
|
67
|
+
@worker.join
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def worker
|
73
|
+
begin
|
74
|
+
up_addr = @upstream_address
|
75
|
+
up_port = @upstream_port
|
76
|
+
up_svc = BetterCap::StreamLogger.service( :udp, @upstream_port )
|
77
|
+
|
78
|
+
EM::run do
|
79
|
+
Logger.info "[#{'UDP PROXY'.green}] Starting on #{@address}:#{@port} ( -> #{up_addr}:#{up_port} ) ..."
|
80
|
+
EM::open_datagram_socket @address, @port, Server, @address, up_addr, up_port
|
81
|
+
end
|
82
|
+
|
83
|
+
rescue Exception => e
|
84
|
+
Logger.error e.message
|
85
|
+
Logger.exception e
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
data/lib/bettercap/shell.rb
CHANGED
@@ -70,13 +70,19 @@ module Shell
|
|
70
70
|
|
71
71
|
# Get the +iface+ network interface configuration ( using iproute2 ).
|
72
72
|
def ip(iface = '')
|
73
|
-
self.execute( "LANG=en && ip addr show #{iface}" )
|
73
|
+
self.execute( "LANG=en && LANGUAGE=en_EN.UTF-8 && ip addr show #{iface}" )
|
74
74
|
end
|
75
75
|
|
76
76
|
# Get the ARP table cached on this computer.
|
77
77
|
def arp
|
78
|
-
self.execute( 'LANG=en && arp -a -n' )
|
78
|
+
self.execute( 'LANG=en && LANGUAGE=en_EN.UTF-8 && arp -a -n' )
|
79
79
|
end
|
80
|
+
|
81
|
+
# Get the NDP table cached on this computer.
|
82
|
+
def ndp
|
83
|
+
self.execute( 'LANG=en && LANGUAGE=en_EN.UTF-8 && ip -6 neighbor show')
|
84
|
+
end
|
85
|
+
|
80
86
|
end
|
81
87
|
end
|
82
88
|
end
|
@@ -16,17 +16,26 @@ module Parsers
|
|
16
16
|
# HTTPS connections parser.
|
17
17
|
class Https < Base
|
18
18
|
@@prev = nil
|
19
|
+
@@lock = Mutex.new
|
19
20
|
|
20
21
|
def on_packet( pkt )
|
21
22
|
begin
|
22
|
-
|
23
|
-
|
24
|
-
|
23
|
+
# poor man's TLS Client Hello with SNI extension parser :P
|
24
|
+
if pkt.respond_to?(:tcp_dst) and \
|
25
|
+
pkt.payload[0] == "\x16" and \
|
26
|
+
pkt.payload[1] == "\x03" and \
|
27
|
+
pkt.payload =~ /\x00\x00.{4}\x00.{2}([a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6})\x00/
|
28
|
+
hostname = $1
|
29
|
+
if pkt.tcp_dst != 443
|
30
|
+
hostname += ":#{pkt.tcp_dst}"
|
31
|
+
end
|
32
|
+
|
33
|
+
@@lock.synchronize {
|
25
34
|
if @@prev.nil? or @@prev != hostname
|
26
35
|
StreamLogger.log_raw( pkt, 'HTTPS', "https://#{hostname}/" )
|
27
36
|
@@prev = hostname
|
28
37
|
end
|
29
|
-
|
38
|
+
}
|
30
39
|
end
|
31
40
|
rescue; end
|
32
41
|
end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
module BetterCap
|
4
|
+
module Spoofers
|
5
|
+
# This class is responsible of performing NDP spoofing on the network.
|
6
|
+
class Ndp < Base
|
7
|
+
# Initialize the BetterCap::Spoofers::NDP object.
|
8
|
+
def initialize
|
9
|
+
@ctx = Context.get
|
10
|
+
@forwarding = @ctx.firewall.ipv6_forwarding_enabled?
|
11
|
+
@spoof_thread = nil
|
12
|
+
@sniff_thread = nil
|
13
|
+
@capture = nil
|
14
|
+
@running = false
|
15
|
+
|
16
|
+
update_gateway!
|
17
|
+
end
|
18
|
+
|
19
|
+
# Send a spoofed NDP reply to the target identified by the +daddr+ IP address
|
20
|
+
# and +dmac+ MAC address, spoofing the +saddr+ IP address and +smac+ MAC
|
21
|
+
# address as the source device.
|
22
|
+
def send_spoofed_packet( saddr, smac, daddr, dmac )
|
23
|
+
pkt = PacketFu::NDPPacket.new
|
24
|
+
pkt.eth_saddr = smac
|
25
|
+
pkt.eth_daddr = dmac
|
26
|
+
pkt.eth_proto = 0x86dd
|
27
|
+
|
28
|
+
pkt.ipv6_saddr = saddr
|
29
|
+
pkt.ipv6_daddr = daddr
|
30
|
+
pkt.ipv6_recalc
|
31
|
+
|
32
|
+
if @ctx.gateway.ip == daddr
|
33
|
+
pkt.ndp_set_flags = "001"
|
34
|
+
else
|
35
|
+
pkt.ndp_set_flags = "111"
|
36
|
+
end
|
37
|
+
|
38
|
+
pkt.ndp_type = 136
|
39
|
+
pkt.ndp_taddr = saddr
|
40
|
+
pkt.ndp_opt_type = 2
|
41
|
+
pkt.ndp_opt_len = 1
|
42
|
+
pkt.ndp_lladdr = smac
|
43
|
+
|
44
|
+
pkt.ndp_recalc
|
45
|
+
|
46
|
+
@ctx.packets.push(pkt)
|
47
|
+
end
|
48
|
+
|
49
|
+
# Start the NDP spoofing.
|
50
|
+
def start
|
51
|
+
Logger.debug "Starting NDP spoofer ( #{@ctx.options.spoof.half_duplex ? 'Half' : 'Full'} Duplex ) ..."
|
52
|
+
|
53
|
+
stop() if @running
|
54
|
+
@running = true
|
55
|
+
|
56
|
+
if @ctx.options.spoof.kill
|
57
|
+
Logger.warn "Disabling packet forwarding."
|
58
|
+
@ctx.firewall.enable_ipv6_forwarding(false) if @forwarding
|
59
|
+
else
|
60
|
+
@ctx.firewall.enable_ipv6_forwarding(true) unless @forwarding
|
61
|
+
end
|
62
|
+
|
63
|
+
@sniff_thread = Thread.new { ndp_watcher }
|
64
|
+
@spoof_thread = Thread.new { ndp_spoofer }
|
65
|
+
end
|
66
|
+
|
67
|
+
# Stop the NDP spoofing, reset firewall state and restore targets IPv6 table.
|
68
|
+
def stop
|
69
|
+
raise 'NDP spoofer is not running' unless @running
|
70
|
+
|
71
|
+
Logger.debug 'Stopping NDP spoofer ...'
|
72
|
+
|
73
|
+
@running = false
|
74
|
+
begin
|
75
|
+
@spoof_thread.exit
|
76
|
+
rescue
|
77
|
+
end
|
78
|
+
|
79
|
+
Logger.debug "Restoring IPv6 table of #{@ctx.targets.size} targets ..."
|
80
|
+
|
81
|
+
@ctx.targets.each do |target|
|
82
|
+
if target.spoofable?
|
83
|
+
5.times do
|
84
|
+
spoof(target, true)
|
85
|
+
sleep 0.3
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
Logger.debug "Resetting packet forwarding to #{@forwarding} ..."
|
91
|
+
|
92
|
+
@ctx.firewall.enable_forwarding( @forwarding )
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
|
97
|
+
# Send an NDP spoofing packet to +target+, if +restore+ is true it will
|
98
|
+
# restore its IPv6 cache instead.
|
99
|
+
def spoof( target, restore = false )
|
100
|
+
if restore
|
101
|
+
send_spoofed_packet( @ctx.gateway.ip, @ctx.gateway.mac, target.ip, target.mac )
|
102
|
+
send_spoofed_packet( target.ip, target.mac, @ctx.gateway.ip, @ctx,gateway.mac ) unless @ctx.options.spoof.half_duplex
|
103
|
+
else
|
104
|
+
# tell the target we're the gateway
|
105
|
+
send_spoofed_packet( @ctx.gateway.ip, @ctx.iface.mac, target.ip, target.mac )
|
106
|
+
# tell the gateway we're the target
|
107
|
+
send_spoofed_packet( target.ip, @ctx.iface.mac, @ctx.gateway.ip, @ctx.gateway.mac ) unless @ctx.options.spoof.half_duplex
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Main spoofer loop.
|
112
|
+
def ndp_spoofer
|
113
|
+
spoof_loop(1) { |target|
|
114
|
+
if target.spoofable?
|
115
|
+
spoof(target)
|
116
|
+
end
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
# Return true if the +pkt+ packet is an NDP 'who-has' query coming
|
121
|
+
# from some network endpoint.
|
122
|
+
def is_ndp_query?(pkt)
|
123
|
+
begin
|
124
|
+
# we're only interested in 'who-has' packets
|
125
|
+
return ( pkt.ndp_type == 135 and \
|
126
|
+
pkt.ipv6_saddr.to_s != @ctx.iface.ip )
|
127
|
+
rescue; end
|
128
|
+
false
|
129
|
+
end
|
130
|
+
|
131
|
+
# Will watch for incoming Neighbor Solicitation messages and spoof the source address.
|
132
|
+
def ndp_watcher
|
133
|
+
Logger.debug 'NDP watcher started ...'
|
134
|
+
|
135
|
+
sniff_packets('icmp6') { |pkt|
|
136
|
+
if is_ndp_query?(pkt)
|
137
|
+
Logger.info "[#{'NDP'.green}] #{pkt.ipv6_saddr.to_s} is asking who #{pkt.ipv6_daddr.to_s} is."
|
138
|
+
send_spoofed_packet pkt.ipv6_daddr.to_s, @ctx.iface.mac, pkt.ipv6_saddr.to_s, pkt.eth_saddr
|
139
|
+
end
|
140
|
+
}
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|