bettercap 1.1.5 → 1.1.6

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.
@@ -29,8 +29,7 @@ module Proxy
29
29
  @@frompems[filename]
30
30
  end
31
31
 
32
- # TODO: Put a better default subject here!
33
- def self.get_selfsigned( subject = '/C=BE/O=Test/OU=Test/CN=Test' )
32
+ def self.get_selfsigned( subject = '/C=US/ST=California/L=Mountain View/O=Google Inc/CN=www.google.com' )
34
33
  if !@@selfsigned.has_key? subject
35
34
  Logger.info "Generating self signed HTTPS certificate for subject '#{subject}' ..."
36
35
 
@@ -27,11 +27,12 @@ class Proxy
27
27
  @type = is_https ? 'HTTPS' : 'HTTP'
28
28
  @sslserver = nil
29
29
  @sslcontext = nil
30
+ @server = nil
30
31
  @main_thread = nil
31
32
  @running = false
32
- @processor = processor
33
+ @streamer = Streamer.new processor
33
34
  @local_ips = []
34
-
35
+
35
36
  begin
36
37
  @local_ips = Socket.ip_address_list.collect { |x| x.ip_address }
37
38
  rescue
@@ -39,11 +40,17 @@ class Proxy
39
40
 
40
41
  @local_ips = Network.get_local_ips
41
42
  end
43
+
44
+ BasicSocket.do_not_reverse_lookup = true
45
+
46
+ @pool = ThreadPool.new( 4, 16 ) do |client|
47
+ client_worker client
48
+ end
42
49
  end
43
50
 
44
51
  def start
45
52
  begin
46
- @socket = TCPServer.new( @address, @port )
53
+ @server = @socket = TCPServer.new( @address, @port )
47
54
 
48
55
  if @is_https
49
56
  cert = Context.get.certificate
@@ -52,7 +59,7 @@ class Proxy
52
59
  @sslcontext.cert = cert[:cert]
53
60
  @sslcontext.key = cert[:key]
54
61
 
55
- @sslserver = OpenSSL::SSL::SSLServer.new( @socket, @sslcontext )
62
+ @server = @sslserver = OpenSSL::SSL::SSLServer.new( @socket, @sslcontext )
56
63
  end
57
64
 
58
65
  @main_thread = Thread.new &method(:server_thread)
@@ -69,6 +76,7 @@ class Proxy
69
76
  if @socket and @running
70
77
  @running = false
71
78
  @socket.close
79
+ @pool.shutdown
72
80
  end
73
81
  rescue
74
82
  end
@@ -76,155 +84,40 @@ class Proxy
76
84
 
77
85
  private
78
86
 
79
- def async_accept
80
- if @is_https
81
- begin
82
- Thread.new @sslserver.accept, &method(:client_thread)
83
- rescue Exception => e
84
- Logger.warn "Error while accepting #{@type} connection: #{e.inspect}"
85
- end
86
- else
87
- Thread.new @socket.accept, &method(:client_thread)
88
- end
89
- end
90
-
91
87
  def server_thread
92
88
  Logger.info "#{@type} Proxy started on #{@address}:#{@port} ...\n"
93
89
 
94
90
  @running = true
95
91
 
96
- begin
97
- while @running do
98
- async_accept
99
- end
100
- rescue Exception => e
101
- if @running
102
- Logger.error "Error while accepting #{@type} connection: #{e.inspect}"
103
- end
104
- ensure
105
- @socket.close unless @socket.nil?
106
- end
107
- end
108
-
109
- def binary_streaming( from, to, opts = {} )
110
-
111
- total_size = 0
112
-
113
- # if response|request object is available and a content length as well
114
- # use it to speed up data streaming with precise data size
115
- if not opts[:response].nil?
116
- to.write opts[:response].to_s
117
-
118
- total_size = opts[:response].content_length unless opts[:response].content_length.nil?
119
- elsif not opts[:request].nil?
120
-
121
- total_size = opts[:request].content_length unless opts[:request].content_length.nil?
122
- end
123
-
124
- buff = ''
125
- read = 0
126
-
127
-
128
- if total_size
129
- chunk_size = [ 1024, total_size ].min
130
- else
131
- chunk_size = 1024
132
- end
133
-
134
- if chunk_size > 0
135
- loop do
136
- from.read chunk_size, buff
137
-
138
- # nothing more to read?
139
- break unless buff.size > 0
140
-
141
- to.write buff
142
-
143
- read += buff.size
144
-
145
- # collect into the proper object
146
- if not opts[:request].nil? and opts[:request].post?
147
- opts[:request] << buff
148
- end
149
-
150
- # we've done reading?
151
- break unless read != total_size
152
- end
153
- end
154
- end
155
-
156
- def html_streaming( request, response, from, to )
157
- buff = ''
158
-
159
- if response.content_length.nil?
160
- loop do
161
- from.read 1024, buff
162
-
163
- break unless buff.size > 0
164
-
165
- response << buff
92
+ while @running do
93
+ begin
94
+ @pool << @server.accept
95
+ rescue Exception => e
96
+ Logger.warn "Error while accepting #{@type} connection: #{e.inspect}"
166
97
  end
167
- else
168
- from.read response.content_length, buff
169
- response << buff
170
- end
171
-
172
- @processor.call( request, response )
173
-
174
- # Response::to_s will patch the headers if needed
175
- to.write response.to_s
176
- end
177
-
178
- def log_stream( client, request, response )
179
- client_s = "[#{client}]"
180
- verb_s = request.verb
181
- request_s = "#{@is_https ? 'https' : 'http'}://#{request.host}#{request.url}"
182
- response_s = "( #{response.content_type} )"
183
- request_s = request_s.slice(0..50) + '...' unless request_s.length <= 50
184
-
185
- verb_s = verb_s.light_blue
186
-
187
- if response.code[0] == '2'
188
- response_s += " [#{response.code}]".green
189
- elsif response.code[0] == '3'
190
- response_s += " [#{response.code}]".light_black
191
- elsif response.code[0] == '4'
192
- response_s += " [#{response.code}]".yellow
193
- elsif response.code[0] == '5'
194
- response_s += " [#{response.code}]".red
195
- else
196
- response_s += " [#{response.code}]"
197
98
  end
198
99
 
199
- Logger.write "#{client_s} #{verb_s} #{request_s} #{response_s}"
100
+ @socket.close unless @socket.nil?
200
101
  end
201
102
 
202
103
  def is_self_request?(request)
203
104
  @local_ips.include? IPSocket.getaddress(request.host)
204
105
  end
205
106
 
206
- def rickroll_lamer(client)
207
- client.write "HTTP/1.1 302 Found\n"
208
- client.write "Location: https://www.youtube.com/watch?v=dQw4w9WgXcQ\n\n"
209
- end
210
-
211
107
  def create_upstream_connection( request )
108
+ sock = TCPSocket.new( request.host, request.port )
109
+
212
110
  if @is_https
213
- sock = TCPSocket.new( request.host, request.port )
214
-
215
111
  ctx = OpenSSL::SSL::SSLContext.new
216
-
217
112
  # do we need this? :P ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER)
218
113
 
219
- socket = OpenSSL::SSL::SSLSocket.new(sock, ctx).tap do |socket|
220
- socket.sync_close = true
221
- socket.connect
114
+ sock = OpenSSL::SSL::SSLSocket.new(sock, ctx).tap do |socket|
115
+ sock.sync_close = true
116
+ sock.connect
222
117
  end
223
-
224
- socket
225
- else
226
- TCPSocket.new( request.host, request.port )
227
118
  end
119
+
120
+ sock
228
121
  end
229
122
 
230
123
  def get_client_details( client )
@@ -237,7 +130,7 @@ class Proxy
237
130
  [ client_ip, client_port ]
238
131
  end
239
132
 
240
- def client_thread( client )
133
+ def client_worker( client )
241
134
  client_ip, client_port = get_client_details client
242
135
 
243
136
  Logger.debug "New #{@type} connection from #{client_ip}:#{client_port}"
@@ -255,7 +148,7 @@ class Proxy
255
148
 
256
149
  Logger.warn "#{client_ip} is connecting to us directly."
257
150
 
258
- rickroll_lamer client
151
+ @streamer.rickroll client
259
152
 
260
153
  elsif request.verb == 'CONNECT'
261
154
 
@@ -267,13 +160,17 @@ class Proxy
267
160
 
268
161
  server = create_upstream_connection request
269
162
 
270
- server.write request.to_s
163
+ sreq = request.to_s
164
+
165
+ Logger.debug "Sending request:\n#{sreq}"
166
+
167
+ server.write sreq
271
168
 
272
169
  # this is probably a POST request, collect incoming data
273
170
  if request.content_length > 0
274
171
  Logger.debug "Getting #{request.content_length} bytes from client"
275
172
 
276
- binary_streaming client, server, request: request
173
+ @streamer.binary client, server, request: request
277
174
  end
278
175
 
279
176
  Logger.debug 'Reading response ...'
@@ -281,17 +178,17 @@ class Proxy
281
178
  response = Response.from_socket server
282
179
 
283
180
  if response.textual?
284
- log_stream client_ip, request, response
181
+ StreamLogger.log( @is_https, client_ip, request, response )
285
182
 
286
183
  Logger.debug 'Detected textual response'
287
184
 
288
- html_streaming request, response, server, client
185
+ @streamer.html request, response, server, client
289
186
  else
290
187
  Logger.debug "[#{client_ip}] -> #{request.host}#{request.url} [#{response.code}]"
291
188
 
292
189
  Logger.debug 'Binary streaming'
293
190
 
294
- binary_streaming server, client, response: response
191
+ @streamer.binary server, client, response: response
295
192
  end
296
193
 
297
194
  Logger.debug "#{@type} client served."
@@ -70,15 +70,18 @@ class Request
70
70
  elsif line =~ /^Content-Length:\s+(\d+)\s*$/i
71
71
  @content_length = $1.to_i
72
72
 
73
- # we don't want to have hundreds of threads running
74
- elsif line =~ /Connection: keep-alive/i
75
- line = 'Connection: close'
73
+ # we don't want to have hundreds of threads running
74
+ elsif line =~ /^Connection: keep-alive/i
75
+ line = 'Connection: close'
76
76
 
77
- # disable gzip, chunked, etc encodings
78
- elsif line =~ /^Accept-Encoding:.*/i
79
- line = 'Accept-Encoding: identity'
77
+ elsif line =~ /^Proxy-Connection: (.+)/i
78
+ line = "Connection: #{$1}"
80
79
 
81
- end
80
+ # disable gzip, chunked, etc encodings
81
+ elsif line =~ /^Accept-Encoding:.*/i
82
+ line = 'Accept-Encoding: identity'
83
+
84
+ end
82
85
 
83
86
  @lines << line
84
87
  end
@@ -44,7 +44,7 @@ class Response
44
44
  def <<(line)
45
45
  # we already parsed the heders, collect response body
46
46
  if @headers_done
47
- @body += line.force_encoding( @charset )
47
+ @body << line.force_encoding( @charset )
48
48
  else
49
49
  # parse the response status
50
50
  if @code.nil? and line =~ /^HTTP\/[\d\.]+\s+(.+)/
@@ -67,11 +67,11 @@ class Response
67
67
 
68
68
  end
69
69
 
70
- @headers << line.chomp
70
+ @headers << line.chomp
71
71
  end
72
72
  end
73
73
 
74
- def textual? # textual?
74
+ def textual?
75
75
  @content_type and ( @content_type =~ /^text\/.+/ or @content_type =~ /^application\/.+/ )
76
76
  end
77
77
 
@@ -81,7 +81,9 @@ class Response
81
81
  # update content length in case the body was
82
82
  # modified
83
83
  if header =~ /Content-Length:\s*(\d+)/i
84
- "Content-Length: #{@body.size}"
84
+ Logger.debug "Updating response content length from #{$1} to #{@body.bytesize}"
85
+
86
+ "Content-Length: #{@body.bytesize}"
85
87
  else
86
88
  header
87
89
  end
@@ -0,0 +1,41 @@
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
+ require 'bettercap/logger'
13
+
14
+ module Proxy
15
+ class StreamLogger
16
+ @@MAX_REQ_SIZE = 50
17
+
18
+ @@CODE_COLORS = {
19
+ '2' => :green,
20
+ '3' => :light_black,
21
+ '4' => :yellow,
22
+ '5' => :red
23
+ }
24
+
25
+ def self.log( is_https, client, request, response )
26
+ request_s = "#{is_https ? 'https' : 'http'}://#{request.host}#{request.url}"
27
+ response_s = "( #{response.content_type} )"
28
+ request_s = request_s.slice(0..@@MAX_REQ_SIZE) + '...' unless request_s.length <= @@MAX_REQ_SIZE
29
+ code = response.code[0]
30
+
31
+ if @@CODE_COLORS.has_key? code
32
+ response_s += " [#{response.code}]".send( @@CODE_COLORS[ code ] )
33
+ else
34
+ response_s += " [#{response.code}]"
35
+ end
36
+
37
+ Logger.write "[#{client}] #{request.verb.light_blue} #{request_s} #{response_s}"
38
+ end
39
+ end
40
+ end
41
+
@@ -0,0 +1,119 @@
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
+ require 'bettercap/logger'
13
+
14
+ module Proxy
15
+ class Streamer
16
+ def initialize( processor )
17
+ @processor = processor
18
+ end
19
+
20
+ def rickroll( client )
21
+ client.write "HTTP/1.1 302 Found\n"
22
+ client.write "Location: https://www.youtube.com/watch?v=dQw4w9WgXcQ\n\n"
23
+ end
24
+
25
+ def html( request, response, from, to )
26
+ buff = ''
27
+
28
+ if response.content_length.nil?
29
+ Logger.debug "Reading response body using 1024 bytes chunks ..."
30
+
31
+ loop do
32
+ buff = read( from, 1024 )
33
+
34
+ break unless buff.size > 0
35
+
36
+ response << buff
37
+ end
38
+ else
39
+ Logger.debug "Reading response body using #{response.content_length} bytes buffer ..."
40
+
41
+ buff = read( from, response.content_length )
42
+
43
+ Logger.debug "Read #{buff.size} / #{response.content_length} bytes."
44
+
45
+ response << buff
46
+ end
47
+
48
+ @processor.call( request, response )
49
+
50
+ # Response::to_s will patch the headers if needed
51
+ to.write response.to_s
52
+ end
53
+
54
+
55
+ def binary( from, to, opts = {} )
56
+ total_size = 0
57
+
58
+ # if response|request object is available and a content length as well
59
+ # use it to speed up data streaming with precise data size
60
+ if not opts[:response].nil?
61
+ to.write opts[:response].to_s
62
+
63
+ total_size = opts[:response].content_length unless opts[:response].content_length.nil?
64
+ elsif not opts[:request].nil?
65
+
66
+ total_size = opts[:request].content_length unless opts[:request].content_length.nil?
67
+ end
68
+
69
+ buff = ''
70
+ read = 0
71
+
72
+ if total_size
73
+ chunk_size = [ 1024, total_size ].min
74
+ else
75
+ chunk_size = 1024
76
+ end
77
+
78
+ if chunk_size > 0
79
+ loop do
80
+ buff = read( from, chunk_size )
81
+
82
+ # nothing more to read?
83
+ break unless buff.size > 0
84
+
85
+ to.write buff
86
+
87
+ read += buff.size
88
+
89
+ # collect into the proper object
90
+ if not opts[:request].nil? and opts[:request].post?
91
+ opts[:request] << buff
92
+ end
93
+
94
+ # we've done reading?
95
+ break unless read != total_size
96
+ end
97
+ end
98
+ end
99
+
100
+ private
101
+
102
+ def read( sd, size )
103
+ buffer = ''
104
+
105
+ while size > 0
106
+ tmp = sd.read(size)
107
+ unless tmp.nil?
108
+ buffer << tmp
109
+ size -= tmp.bytesize
110
+ end
111
+ end
112
+
113
+ buffer
114
+ end
115
+
116
+ end
117
+ end
118
+
119
+