bettercap 1.1.7 → 1.1.8

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.
@@ -19,46 +19,56 @@ require 'bettercap/context'
19
19
  class ArpAgent < BaseAgent
20
20
 
21
21
  def self.parse( ctx )
22
- arp = Shell.arp
23
22
  targets = []
24
-
25
- Logger.debug "ARP:\n#{arp}"
26
-
27
- arp.split("\n").each do |line|
28
- m = /[^\s]+\s+\(([0-9\.]+)\)\s+at\s+([a-f0-9:]+).+#{ctx.ifconfig[:iface]}.*/i.match(line)
29
- if !m.nil?
30
- if m[1] != ctx.gateway and m[1] != ctx.ifconfig[:ip_saddr] and m[2] != 'ff:ff:ff:ff:ff:ff'
31
- if !ctx.options[:ignore].nil? and ctx.options[:ignore].include?( m[1] )
32
- Logger.debug "Ignoring #{m[1]} ..."
23
+ self.parse_cache do |ip,mac|
24
+ if ip != ctx.gateway and ip != ctx.ifconfig[:ip_saddr]
25
+ if ctx.options.ignore_ip?(ip)
26
+ Logger.debug "Ignoring #{ip} ..."
27
+ else
28
+ # reuse Target object if it's already a known address
29
+ known = ctx.find_target ip, mac
30
+ if known.nil?
31
+ targets << Target.new( ip, mac )
33
32
  else
34
- target = Target.new( m[1], m[2] )
35
- targets << target
36
- Logger.debug "FOUND #{target}"
33
+ targets << known
37
34
  end
38
35
  end
39
36
  end
40
37
  end
41
-
42
38
  targets
43
39
  end
44
40
 
45
- def self.find_address( ip )
41
+ def self.find_address( address )
42
+ self.parse_cache do |ip,mac|
43
+ if ip == address
44
+ return mac
45
+ end
46
+ end
47
+ nil
48
+ end
49
+
50
+ private
51
+
52
+ def self.parse_cache
46
53
  arp = Shell.arp
47
- mac = nil
54
+
55
+ Logger.debug "ARP CACHE:\n#{arp}"
48
56
 
49
57
  arp.split("\n").each do |line|
50
- m = /[^\s]+\s+\(([0-9\.]+)\)\s+at\s+([a-f0-9:]+).+#{Context.get.ifconfig[:iface]}.*/i.match(line)
58
+ m = self.parse_cache_line(line)
51
59
  if !m.nil?
52
- if m[1] == ip and m[2] != 'ff:ff:ff:ff:ff:ff'
53
- mac = m[2]
60
+ ip = m[1]
61
+ hw = m[2]
62
+ if hw != 'ff:ff:ff:ff:ff:ff'
63
+ yield( ip, hw )
54
64
  end
55
65
  end
56
66
  end
57
-
58
- mac
59
67
  end
60
68
 
61
- private
69
+ def self.parse_cache_line( line )
70
+ /[^\s]+\s+\(([0-9\.]+)\)\s+at\s+([a-f0-9:]+).+#{Context.get.ifconfig[:iface]}.*/i.match(line)
71
+ end
62
72
 
63
73
  def send_probe( ip )
64
74
  pkt = PacketFu::ARPPacket.new
@@ -37,7 +37,7 @@ class Network
37
37
  out = nstat.split(/\n/).select {|n| n =~ /UG/ }
38
38
  gw = nil
39
39
  out.each do |line|
40
- if line.include?( Context.get.options[:iface] )
40
+ if line.include?( Context.get.options.iface )
41
41
  tmp = line.split[1]
42
42
  if is_ip?(tmp)
43
43
  gw = tmp
@@ -46,7 +46,7 @@ class Network
46
46
  end
47
47
  end
48
48
 
49
- raise BetterCap::Error, "Could not detect the gateway address for interface #{Context.get.options[:iface]}, "\
49
+ raise BetterCap::Error, "Could not detect the gateway address for interface #{Context.get.options.iface}, "\
50
50
  'make sure you\'ve specified the correct network interface to use and to have the '\
51
51
  'correct network configuration, this could also happen if bettercap '\
52
52
  'is launched from a virtual environment.' unless !gw.nil? and is_ip?(gw)
@@ -66,7 +66,7 @@ class Network
66
66
  end
67
67
 
68
68
  def get_alive_targets( ctx, timeout = 5 )
69
- if ctx.options[:arpcache] == false
69
+ if ctx.options.should_discover_hosts?
70
70
  icmp = IcmpAgent.new timeout
71
71
  udp = UdpAgent.new ctx.ifconfig, ctx.gateway, ctx.ifconfig[:ip_saddr]
72
72
  arp = ArpAgent.new ctx.ifconfig, ctx.gateway, ctx.ifconfig[:ip_saddr]
@@ -0,0 +1,189 @@
1
+ =begin
2
+
3
+ BETTERCAP
4
+
5
+ Author : Simone 'evilsocket' Margaritelli
6
+ Email : evilsocket@gmail.com
7
+ Blog : http://www.evilsocket.net/
8
+
9
+ This project is released under the GPL 3 license.
10
+
11
+ =end
12
+
13
+ class Options
14
+ attr_accessor :gateway,
15
+ :iface,
16
+ :spoofer,
17
+ :half_duplex,
18
+ :target,
19
+ :logfile,
20
+ :debug,
21
+ :arpcache,
22
+ :ignore,
23
+ :sniffer,
24
+ :sniffer_pcap,
25
+ :sniffer_filter,
26
+ :sniffer_src,
27
+ :parsers,
28
+ :local,
29
+ :proxy,
30
+ :proxy_https,
31
+ :proxy_port,
32
+ :proxy_https_port,
33
+ :proxy_pem_file,
34
+ :proxy_module,
35
+ :custom_proxy,
36
+ :custom_proxy_port,
37
+ :custom_https_proxy,
38
+ :custom_https_proxy_port,
39
+ :httpd,
40
+ :httpd_port,
41
+ :httpd_path,
42
+ :check_updates
43
+
44
+ def initialize( iface )
45
+ @gateway = nil
46
+ @iface = iface
47
+ @spoofer = 'ARP'
48
+ @half_duplex = false
49
+ @target = nil
50
+ @logfile = nil
51
+ @debug = false
52
+ @arpcache = false
53
+
54
+ @ignore = nil
55
+
56
+ @sniffer = false
57
+ @sniffer_pcap = nil
58
+ @sniffer_filter = nil
59
+ @sniffer_src = nil
60
+ @parsers = ['*']
61
+ @local = false
62
+
63
+ @proxy = false
64
+ @proxy_https = false
65
+ @proxy_port = 8080
66
+ @proxy_https_port = 8083
67
+ @proxy_pem_file = nil
68
+ @proxy_module = nil
69
+
70
+ @custom_proxy = nil
71
+ @custom_proxy_port = 8080
72
+
73
+ @custom_https_proxy = nil
74
+ @custom_https_proxy_port = 8083
75
+
76
+ @httpd = false
77
+ @httpd_port = 8081
78
+ @httpd_path = './'
79
+
80
+ @check_updates = false
81
+ end
82
+
83
+ def should_discover_hosts?
84
+ !@arpcache
85
+ end
86
+
87
+ def has_proxy_module?
88
+ !@proxy_module.nil?
89
+ end
90
+
91
+ def has_spoofer?
92
+ @spoofer == 'NONE' or @spoofer == 'none'
93
+ end
94
+
95
+ def has_http_sniffer_enabled?
96
+ @sniffer and ( @parsers.include?'*' or @parsers.include?'URL' )
97
+ end
98
+
99
+ def ignore_ip?(ip)
100
+ !@ignore.nil? and @ignore.include?(ip)
101
+ end
102
+
103
+ def ignore=(value)
104
+ @ignore = value.split(",")
105
+ valid = @ignore.select { |target| Network.is_ip?(target) }
106
+
107
+ raise BetterCap::Error, "Invalid ignore addresses specified." if valid.empty?
108
+
109
+ invalid = @ignore - valid
110
+ invalid.each do |target|
111
+ Logger.warn "Not a valid address: #{target}"
112
+ end
113
+
114
+ @ignore = valid
115
+
116
+ Logger.warn "Ignoring #{valid.join(", ")} ."
117
+ end
118
+
119
+ def custom_proxy=(value)
120
+ @custom_proxy = value
121
+ raise BetterCap::Error, 'Invalid custom HTTP upstream proxy address specified.' unless Network.is_ip? @custom_proxy
122
+ end
123
+
124
+ def custom_https_proxy=(value)
125
+ @custom_https_proxy = value
126
+ raise BetterCap::Error, 'Invalid custom HTTPS upstream proxy address specified.' unless Network.is_ip? @custom_https_proxy
127
+ end
128
+
129
+ def to_targets
130
+ targets = @target.split(",")
131
+ valid_targets = targets.select { |target| Network.is_ip?(target) }
132
+
133
+ raise BetterCap::Error, "Invalid target" if valid_targets.empty?
134
+
135
+ invalid_targets = targets - valid_targets
136
+ invalid_targets.each do |target|
137
+ Logger.warn "Invalid target #{target}"
138
+ end
139
+
140
+ valid_targets.map { |target| Target.new(target) }
141
+ end
142
+
143
+ def to_spoofers
144
+ spoofers = []
145
+ spoofer_modules_names = @spoofer.split(",")
146
+ spoofer_modules_names.each do |module_name|
147
+ spoofers << SpooferFactory.get_by_name( module_name )
148
+ end
149
+ spoofers
150
+ end
151
+
152
+ def to_redirections ifconfig
153
+ redirections = []
154
+
155
+ if @proxy
156
+ redirections << Redirection.new( @iface,
157
+ 'TCP',
158
+ 80,
159
+ ifconfig[:ip_saddr],
160
+ @proxy_port )
161
+ end
162
+
163
+ if @proxy_https
164
+ redirections << Redirection.new( @iface,
165
+ 'TCP',
166
+ 443,
167
+ ifconfig[:ip_saddr],
168
+ @proxy_https_port )
169
+ end
170
+
171
+ if @custom_proxy
172
+ redirections << Redirection.new( @iface,
173
+ 'TCP',
174
+ 80,
175
+ @custom_proxy,
176
+ @custom_proxy_port )
177
+ end
178
+
179
+ if @custom_https_proxy
180
+ redirections << Redirection.new( @iface,
181
+ 'TCP',
182
+ 443,
183
+ @custom_https_proxy,
184
+ @custom_https_proxy_port )
185
+ end
186
+
187
+ redirections
188
+ end
189
+ end
@@ -32,7 +32,7 @@ class Proxy
32
32
  @running = false
33
33
  @streamer = Streamer.new processor
34
34
  @local_ips = []
35
-
35
+
36
36
  begin
37
37
  @local_ips = Socket.ip_address_list.collect { |x| x.ip_address }
38
38
  rescue
@@ -93,7 +93,8 @@ class Proxy
93
93
  begin
94
94
  @pool << @server.accept
95
95
  rescue Exception => e
96
- Logger.warn "Error while accepting #{@type} connection: #{e.inspect}"
96
+ Logger.warn("Error while accepting #{@type} connection: #{e.inspect}") \
97
+ unless !@running
97
98
  end
98
99
  end
99
100
 
@@ -106,7 +107,7 @@ class Proxy
106
107
 
107
108
  def create_upstream_connection( request )
108
109
  sock = TCPSocket.new( request.host, request.port )
109
-
110
+
110
111
  if @is_https
111
112
  ctx = OpenSSL::SSL::SSLContext.new
112
113
  # do we need this? :P ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER)
@@ -196,13 +197,13 @@ class Proxy
196
197
 
197
198
  rescue Exception => e
198
199
  if request.host
199
- Logger.debug "Error while serving #{request.host}#{request.url}: #{e.inspect}"
200
- Logger.debug e.backtrace
200
+ Logger.warn "Error while serving #{request.host}#{request.url}: #{e.inspect}"
201
+ Logger.debug e.backtrace.join("\n")
201
202
  end
202
- ensure
203
- client.close
204
- server.close unless server.nil?
205
203
  end
204
+
205
+ client.close
206
+ server.close unless server.nil?
206
207
  end
207
208
  end
208
209
 
@@ -56,7 +56,7 @@ class Request
56
56
  @url = "#{uri.path}" + ( uri.query ? "?#{uri.query}" : '' )
57
57
  end
58
58
 
59
- line = "#{@verb} #{url} HTTP/1.0"
59
+ line = "#{@verb} #{@url} HTTP/1.1"
60
60
 
61
61
  # get the host header value
62
62
  elsif line =~ /^Host:\s*(.*)$/
@@ -13,7 +13,7 @@ This project is released under the GPL 3 license.
13
13
  module Proxy
14
14
 
15
15
  class Response
16
- attr_reader :content_type, :charset, :content_length, :headers, :code, :headers_done
16
+ attr_reader :content_type, :charset, :content_length, :chunked, :headers, :code, :headers_done
17
17
  attr_accessor :body
18
18
 
19
19
  def initialize
@@ -24,6 +24,7 @@ class Response
24
24
  @code = nil
25
25
  @headers = []
26
26
  @headers_done = false
27
+ @chunked = false
27
28
  end
28
29
 
29
30
  def self.from_socket(sock)
@@ -46,6 +47,8 @@ class Response
46
47
  if @headers_done
47
48
  @body << line.force_encoding( @charset )
48
49
  else
50
+ Logger.debug " RESPONSE LINE: '#{line.chomp}'"
51
+
49
52
  # parse the response status
50
53
  if @code.nil? and line =~ /^HTTP\/[\d\.]+\s+(.+)/
51
54
  @code = $1.chomp
@@ -61,13 +64,18 @@ class Response
61
64
  elsif line =~ /^Content-Length:\s+(\d+)\s*$/i
62
65
  @content_length = $1.to_i
63
66
 
67
+ # check if we have a chunked encoding
68
+ elsif line =~ /^Transfer-Encoding:\s*chunked.*$/i
69
+ @chunked = true
70
+ line = nil
71
+
64
72
  # last line, we're done with the headers
65
73
  elsif line.chomp.empty?
66
74
  @headers_done = true
67
75
 
68
76
  end
69
77
 
70
- @headers << line.chomp
78
+ @headers << line.chomp unless line.nil?
71
79
  end
72
80
  end
73
81
 
@@ -14,14 +14,20 @@ require 'bettercap/logger'
14
14
  module Proxy
15
15
  class StreamLogger
16
16
  @@MAX_REQ_SIZE = 50
17
-
17
+
18
18
  @@CODE_COLORS = {
19
19
  '2' => :green,
20
20
  '3' => :light_black,
21
21
  '4' => :yellow,
22
22
  '5' => :red
23
23
  }
24
-
24
+
25
+ def self.addr2s( addr )
26
+ target = Context.get.find_target addr, nil
27
+ return target.to_s_compact unless target.nil?
28
+ addr
29
+ end
30
+
25
31
  def self.log( is_https, client, request, response )
26
32
  request_s = "#{is_https ? 'https' : 'http'}://#{request.host}#{request.url}"
27
33
  response_s = "( #{response.content_type} )"
@@ -29,13 +35,12 @@ class StreamLogger
29
35
  code = response.code[0]
30
36
 
31
37
  if @@CODE_COLORS.has_key? code
32
- response_s += " [#{response.code}]".send( @@CODE_COLORS[ code ] )
38
+ response_s += " [#{response.code}]".send( @@CODE_COLORS[ code ] )
33
39
  else
34
- response_s += " [#{response.code}]"
40
+ response_s += " [#{response.code}]"
35
41
  end
36
42
 
37
- Logger.write "[#{client}] #{request.verb.light_blue} #{request_s} #{response_s}"
43
+ Logger.write "[#{self.addr2s(client)}] #{request.verb.light_blue} #{request_s} #{response_s}"
38
44
  end
39
45
  end
40
46
  end
41
-
@@ -14,7 +14,7 @@ require 'bettercap/logger'
14
14
  module Proxy
15
15
  class Streamer
16
16
  def initialize( processor )
17
- @processor = processor
17
+ @processor = processor
18
18
  end
19
19
 
20
20
  def rickroll( client )
@@ -25,12 +25,39 @@ class Streamer
25
25
  def html( request, response, from, to )
26
26
  buff = ''
27
27
 
28
- if response.content_length.nil?
28
+ if response.chunked
29
+ Logger.debug "Reading response body using chunked encoding ..."
30
+
31
+ begin
32
+ len = nil
33
+ total = 0
34
+
35
+ while true
36
+ line = from.readline
37
+ hexlen = line.slice(/[0-9a-fA-F]+/) or raise "Wrong chunk size line: #{line}"
38
+ len = hexlen.hex
39
+ break if len == 0
40
+ begin
41
+ Logger.debug "Reading chunk of size #{len} ..."
42
+ tmp = read( from, len )
43
+ Logger.debug "Read #{tmp.bytesize}/#{len} chunk."
44
+ response << tmp
45
+ ensure
46
+ total += len
47
+ from.read 2
48
+ end
49
+ end
50
+
51
+ rescue Exception => e
52
+ Logger.debug e
53
+ end
54
+
55
+ elsif response.content_length.nil?
29
56
  Logger.debug "Reading response body using 1024 bytes chunks ..."
30
57
 
31
58
  loop do
32
59
  buff = read( from, 1024 )
33
-
60
+
34
61
  break unless buff.size > 0
35
62
 
36
63
  response << buff
@@ -41,12 +68,12 @@ class Streamer
41
68
  buff = read( from, response.content_length )
42
69
 
43
70
  Logger.debug "Read #{buff.size} / #{response.content_length} bytes."
44
-
71
+
45
72
  response << buff
46
73
  end
47
74
 
48
75
  @processor.call( request, response )
49
-
76
+
50
77
  # Response::to_s will patch the headers if needed
51
78
  to.write response.to_s
52
79
  end
@@ -97,23 +124,48 @@ class Streamer
97
124
  end
98
125
  end
99
126
 
100
- private
127
+ private
101
128
 
102
- def read( sd, size )
103
- buffer = ''
129
+ BUFSIZE = 1024 * 16
104
130
 
105
- while size > 0
106
- tmp = sd.read(size)
107
- unless tmp.nil?
108
- buffer << tmp
109
- size -= tmp.bytesize
131
+ def consume_stream io, size
132
+ read_timeout = 60.0
133
+ dest = ''
134
+ begin
135
+ dest << io.read_nonblock(size)
136
+ rescue IO::WaitReadable
137
+ if IO.select([io], nil, nil, read_timeout)
138
+ retry
139
+ else
140
+ raise Net::ReadTimeout
141
+ end
142
+ rescue IO::WaitWritable
143
+ # OpenSSL::Buffering#read_nonblock may fail with IO::WaitWritable.
144
+ # http://www.openssl.org/support/faq.html#PROG10
145
+ if IO.select(nil, [io], nil, read_timeout)
146
+ retry
147
+ else
148
+ raise Net::ReadTimeout
110
149
  end
111
150
  end
151
+ dest
152
+ end
112
153
 
154
+ def read( sd, size )
155
+ buffer = ''
156
+ begin
157
+ while size > 0
158
+ tmp = consume_stream sd, [ BUFSIZE, size ].min
159
+ unless tmp.nil? or tmp.bytesize == 0
160
+ buffer << tmp
161
+ size -= tmp.bytesize
162
+ end
163
+ end
164
+ rescue EOFError
165
+ ;
166
+ end
113
167
  buffer
114
168
  end
115
169
 
116
170
  end
117
171
  end
118
-
119
-