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.
@@ -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
- # add schema if needed
57
- if has_schema
58
- has_slash = ( url =~ /^.+:\/\/.+\/.*$/ )
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 unless r.nil?
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
@@ -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
- if pkt.respond_to?(:tcp_dst) and pkt.tcp_dst == 443
23
- Thread.new do
24
- hostname = BetterCap::Network.ip2name( pkt.ip_daddr )
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
- end
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