bettercap 1.1.5 → 1.1.6

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