bettercap 1.3.6 → 1.3.7
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 +4 -4
- data/lib/bettercap.rb +24 -60
- data/lib/bettercap/discovery/agents/base.rb +12 -6
- data/lib/bettercap/network/protos/base.rb +71 -9
- data/lib/bettercap/network/protos/dhcp.rb +0 -4
- data/lib/bettercap/network/protos/mysql.rb +40 -0
- data/lib/bettercap/network/protos/ntlm.rb +97 -0
- data/lib/bettercap/network/services +2182 -0
- data/lib/bettercap/proxy/response.rb +18 -1
- data/lib/bettercap/proxy/sslstrip/strip.rb +1 -18
- data/lib/bettercap/proxy/stream_logger.rb +36 -3
- data/lib/bettercap/proxy/streamer.rb +1 -2
- data/lib/bettercap/sniffer/parsers/dhcp.rb +2 -5
- data/lib/bettercap/sniffer/parsers/httpauth.rb +2 -2
- data/lib/bettercap/sniffer/parsers/mysql.rb +27 -0
- data/lib/bettercap/sniffer/parsers/ntlmss.rb +13 -19
- data/lib/bettercap/sniffer/parsers/pgsql.rb +36 -0
- data/lib/bettercap/sniffer/sniffer.rb +18 -8
- data/lib/bettercap/version.rb +1 -1
- metadata +7 -2
@@ -48,6 +48,23 @@ class Response
|
|
48
48
|
r
|
49
49
|
end
|
50
50
|
|
51
|
+
# Return a 302 response object redirecting to +url+, setting optional +cookies+.
|
52
|
+
def self.redirect( url, cookies = [] )
|
53
|
+
r = Response.new
|
54
|
+
|
55
|
+
r << "HTTP/1.1 302 Moved"
|
56
|
+
r << "Location: #{url}"
|
57
|
+
|
58
|
+
cookies.each do |cookie|
|
59
|
+
r << "Set-Cookie: #{cookie}"
|
60
|
+
end
|
61
|
+
|
62
|
+
r << "Connection: close"
|
63
|
+
r << "\n\n"
|
64
|
+
|
65
|
+
r
|
66
|
+
end
|
67
|
+
|
51
68
|
# Initialize this response object state.
|
52
69
|
def initialize
|
53
70
|
@content_type = nil
|
@@ -158,7 +175,7 @@ class Response
|
|
158
175
|
end
|
159
176
|
end
|
160
177
|
|
161
|
-
@headers.join("\n") + "\n" + @body
|
178
|
+
@headers.join("\n") + "\n" + ( @body || "\n" )
|
162
179
|
end
|
163
180
|
end
|
164
181
|
|
@@ -78,8 +78,7 @@ class Strip
|
|
78
78
|
unless @cookies.is_clean?(request)
|
79
79
|
Logger.info "[#{'SSLSTRIP'.green} #{request.client}] Sending expired cookies for '#{request.host}'."
|
80
80
|
expired = @cookies.get_expired_headers!(request)
|
81
|
-
|
82
|
-
response = build_expired_cookies( expired, request )
|
81
|
+
response = Response.redirect( "http://#{request.host}#{request.url}", expired )
|
83
82
|
end
|
84
83
|
response
|
85
84
|
end
|
@@ -156,22 +155,6 @@ class Strip
|
|
156
155
|
end
|
157
156
|
end
|
158
157
|
end
|
159
|
-
|
160
|
-
# Return a 302 Redirect BetterCap::Proxy::Response object for the
|
161
|
-
# +request+, using +expired+ cookie headers to kill a client session.
|
162
|
-
def build_expired_cookies( expired, request )
|
163
|
-
resp = Response.new
|
164
|
-
|
165
|
-
resp << "HTTP/1.1 302 Moved"
|
166
|
-
resp << "Connection: close"
|
167
|
-
resp << "Location: http://#{request.host}#{request.url}"
|
168
|
-
|
169
|
-
expired.each do |cookie|
|
170
|
-
resp << "Set-Cookie: #{cookie}"
|
171
|
-
end
|
172
|
-
|
173
|
-
resp
|
174
|
-
end
|
175
158
|
end
|
176
159
|
|
177
160
|
end
|
@@ -24,6 +24,8 @@ class StreamLogger
|
|
24
24
|
'5' => :red
|
25
25
|
}
|
26
26
|
|
27
|
+
@@services = nil
|
28
|
+
|
27
29
|
# Search for the +addr+ IP address inside the list of collected targets and return
|
28
30
|
# its compact string representation ( @see BetterCap::Target#to_s_compact ).
|
29
31
|
def self.addr2s( addr, alt = nil )
|
@@ -36,17 +38,48 @@ class StreamLogger
|
|
36
38
|
|
37
39
|
if addr == '0.0.0.0' and !alt.nil?
|
38
40
|
return alt
|
41
|
+
elsif addr == '255.255.255.255'
|
42
|
+
return '*'
|
39
43
|
end
|
40
44
|
|
41
45
|
addr
|
42
46
|
end
|
43
47
|
|
48
|
+
# Given +proto+ and +port+ return the network service name if possible.
|
49
|
+
def self.service( proto, port )
|
50
|
+
if @@services.nil?
|
51
|
+
Logger.info 'Preloading network services ...'
|
52
|
+
|
53
|
+
@@services = { :tcp => {}, :udp => {} }
|
54
|
+
filename = File.dirname(__FILE__) + '/../network/services'
|
55
|
+
File.open( filename ).each do |line|
|
56
|
+
if line =~ /([^\s]+)\s+(\d+)\/([a-z]+).*/i
|
57
|
+
@@services[$3.to_sym][$2.to_i] = $1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if @@services.has_key?(proto) and @@services[proto].has_key?(port)
|
63
|
+
@@services[proto][port]
|
64
|
+
else
|
65
|
+
port
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
44
69
|
# Log a raw packet ( +pkt+ ) data +payload+ using the specified +label+.
|
45
70
|
def self.log_raw( pkt, label, payload )
|
46
|
-
nl =
|
71
|
+
nl = label.include?("\n") ? "\n" : " "
|
47
72
|
label = label.strip
|
48
|
-
|
49
|
-
|
73
|
+
from = self.addr2s( pkt.ip_saddr, pkt.eth2s(:src) )
|
74
|
+
to = self.addr2s( pkt.ip_daddr, pkt.eth2s(:dst) )
|
75
|
+
|
76
|
+
if pkt.respond_to?('tcp_dst')
|
77
|
+
to += ':' + self.service( :tcp, pkt.tcp_dst ).to_s
|
78
|
+
elsif pkt.respond_to?('udp_dst')
|
79
|
+
to += ':' + self.service( :udp, pkt.udp_dst ).to_s
|
80
|
+
end
|
81
|
+
|
82
|
+
Logger.raw( "[#{from} > #{to}] [#{label.green}]#{nl}#{payload.strip}" )
|
50
83
|
end
|
51
84
|
|
52
85
|
# Log a HTTP ( HTTPS if +is_https+ is true ) stream performed by the +client+
|
@@ -29,8 +29,7 @@ class Streamer
|
|
29
29
|
|
30
30
|
Logger.warn "#{client_ip}:#{client_port} is connecting to us directly."
|
31
31
|
|
32
|
-
client.write "
|
33
|
-
client.write "Location: https://www.youtube.com/watch?v=dQw4w9WgXcQ\n\n"
|
32
|
+
client.write Response.redirect( "https://www.youtube.com/watch?v=dQw4w9WgXcQ" ).to_s
|
34
33
|
end
|
35
34
|
|
36
35
|
# Handle the HTTP +request+ from +client+.
|
@@ -21,7 +21,7 @@ class DHCP < Base
|
|
21
21
|
unless packet.nil?
|
22
22
|
auth = packet.authentication
|
23
23
|
cid = auth.nil?? nil : packet.client_identifier
|
24
|
-
msg = "[#{packet.type.yellow}] #{'Transaction-ID'.green}=#{packet.
|
24
|
+
msg = "[#{packet.type.yellow}] #{'Transaction-ID'.green}=#{sprintf( "0x%X", packet.xid ).yellow}"
|
25
25
|
|
26
26
|
unless cid.nil?
|
27
27
|
msg += " #{'Client-ID'.green}='#{cid.yellow}'"
|
@@ -38,10 +38,7 @@ class DHCP < Base
|
|
38
38
|
StreamLogger.log_raw( pkt, 'DHCP', msg )
|
39
39
|
end
|
40
40
|
end
|
41
|
-
rescue
|
42
|
-
Logger.debug e.message
|
43
|
-
Logger.debug e.backtrace.join("\n")
|
44
|
-
end
|
41
|
+
rescue; end
|
45
42
|
end
|
46
43
|
end
|
47
44
|
end
|
@@ -35,8 +35,8 @@ class Httpauth < Base
|
|
35
35
|
|
36
36
|
StreamLogger.log_raw( pkt, 'HTTP BASIC AUTH', "http://#{hostname}#{path} - username=#{user} password=#{pass}".yellow )
|
37
37
|
|
38
|
-
elsif line =~ /Authorization:\s*
|
39
|
-
StreamLogger.log_raw( pkt,
|
38
|
+
elsif line =~ /Authorization:\s*([^\s]+)\s+(.+)/i
|
39
|
+
StreamLogger.log_raw( pkt, "HTTP #{$1} AUTH", "http://#{hostname}#{path}\n#{$1.blue}: #{$2.yellow}" )
|
40
40
|
end
|
41
41
|
end
|
42
42
|
end
|
@@ -0,0 +1,27 @@
|
|
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
|
+
|
14
|
+
module BetterCap
|
15
|
+
module Parsers
|
16
|
+
# MySQL authentication parser.
|
17
|
+
class MySQL < Base
|
18
|
+
def on_packet( pkt )
|
19
|
+
packet = Network::Protos::MySQL::Packet.parse( pkt.payload )
|
20
|
+
unless packet.nil? or !packet.is_auth?
|
21
|
+
StreamLogger.log_raw( pkt, 'MYSQL', "#{'username'.blue}='#{packet.username.yellow}' "\
|
22
|
+
"#{'password'.blue}='#{packet.password.map { |x| sprintf("%02X", x )}.join.yellow}'" )
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -13,26 +13,20 @@ This project is released under the GPL 3 license.
|
|
13
13
|
|
14
14
|
module BetterCap
|
15
15
|
module Parsers
|
16
|
-
# NTLMSS
|
17
|
-
class
|
18
|
-
# Convert binary +data+ into human readable hexadecimal representation.
|
19
|
-
def bin2hex( data )
|
20
|
-
hex = ''
|
21
|
-
data.each_byte do |byte|
|
22
|
-
if /[[:print:]]/ === byte.chr
|
23
|
-
hex += byte.chr
|
24
|
-
else
|
25
|
-
hex += "\\x" + byte.to_s(16)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
hex
|
29
|
-
end
|
30
|
-
|
16
|
+
# NTLMSS authentication parser.
|
17
|
+
class NTLMSS < Base
|
31
18
|
def on_packet( pkt )
|
32
|
-
|
33
|
-
if
|
34
|
-
|
35
|
-
|
19
|
+
packet = Network::Protos::NTLM::Packet.parse( pkt.payload )
|
20
|
+
if !packet.nil? and packet.is_auth?
|
21
|
+
msg = "NTLMSSP Authentication:\n"
|
22
|
+
msg += " #{'LM Response'.blue} : #{packet.lm_response.map { |x| sprintf("%02X", x )}.join.yellow}\n"
|
23
|
+
msg += " #{'NTLM Response'.blue} : #{packet.ntlm_response.map { |x| sprintf("%02X", x )}.join.yellow}\n"
|
24
|
+
msg += " #{'Domain Name'.blue} : #{packet.domain_name.yellow}\n"
|
25
|
+
msg += " #{'User Name'.blue} : #{packet.user_name.yellow}\n"
|
26
|
+
msg += " #{'Host Name'.blue} : #{packet.host_name.yellow}\n"
|
27
|
+
msg += " #{'Session Key'.blue} : #{packet.session_key_resp.map { |x| sprintf("%02X", x )}.join.yellow}"
|
28
|
+
|
29
|
+
StreamLogger.log_raw( pkt, 'NTLM', msg )
|
36
30
|
end
|
37
31
|
end
|
38
32
|
end
|
@@ -0,0 +1,36 @@
|
|
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
|
+
|
14
|
+
module BetterCap
|
15
|
+
module Parsers
|
16
|
+
# PgSQL authentication parser.
|
17
|
+
class PgSQL < Base
|
18
|
+
STARTUP_EXPR = /....\x00\x03\x00\x00user\x00([^\x00]+)\x00database\x00([^\x00]+)/
|
19
|
+
MD5_AUTH_REQ_EXPR = /\x52....\x00\x00\x00\x05(....)/
|
20
|
+
MD5_PASSWORD_EXPR = /\x70....md5(.+)/
|
21
|
+
|
22
|
+
def on_packet( pkt )
|
23
|
+
if pkt.payload =~ STARTUP_EXPR
|
24
|
+
StreamLogger.log_raw( pkt, 'PGSQL', "STARTUP #{'username'.blue}='#{$1.yellow}' #{'database'.blue}='#{$2.yellow}'" )
|
25
|
+
|
26
|
+
elsif pkt.payload =~ MD5_AUTH_REQ_EXPR
|
27
|
+
salt = $1.reverse.unpack('L')[0]
|
28
|
+
StreamLogger.log_raw( pkt, 'PGSQL', "MD5 AUTH REQUEST #{'salt'.blue}=#{sprintf("0x%X", salt).yellow}" )
|
29
|
+
|
30
|
+
elsif pkt.payload =~ MD5_PASSWORD_EXPR
|
31
|
+
StreamLogger.log_raw( pkt, 'PGSQL', "PASSWORD #{'md5'.blue}='#{$1.yellow}'" )
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -34,18 +34,25 @@ class Sniffer
|
|
34
34
|
|
35
35
|
setup( ctx )
|
36
36
|
|
37
|
-
self.stream.each do |
|
37
|
+
self.stream.each do |raw_packet|
|
38
38
|
break unless @@ctx.running
|
39
39
|
begin
|
40
|
-
parsed = Packet.parse
|
40
|
+
parsed = PacketFu::Packet.parse(raw_packet)
|
41
41
|
rescue Exception => e
|
42
42
|
parsed = nil
|
43
|
-
Logger.debug e.message
|
43
|
+
#Logger.debug e.message
|
44
|
+
#Logger.debug e.backtrace.join("\n")
|
44
45
|
end
|
45
46
|
|
46
47
|
if not parsed.nil? and parsed.is_ip? and !skip_packet?(parsed)
|
47
|
-
|
48
|
+
Logger.debug "Parsing packet ..."
|
49
|
+
append_packet raw_packet
|
48
50
|
parse_packet parsed
|
51
|
+
else
|
52
|
+
Logger.debug "[SNIFFER] Skipping packet:" \
|
53
|
+
" parsed=#{parsed.nil?? 'false' : 'true'}" \
|
54
|
+
" is_ip?=#{parsed.nil?? 'false' : parsed.is_ip?}" \
|
55
|
+
" skip_packet?=#{parsed.nil?? 'true' : skip_packet?(parsed)}"
|
49
56
|
end
|
50
57
|
end
|
51
58
|
end
|
@@ -66,9 +73,12 @@ class Sniffer
|
|
66
73
|
|
67
74
|
# Return true if the +pkt+ packet instance must be skipped.
|
68
75
|
def self.skip_packet?( pkt )
|
69
|
-
|
70
|
-
|
71
|
-
|
76
|
+
begin
|
77
|
+
!@@ctx.options.local and
|
78
|
+
( pkt.ip_saddr == @@ctx.ifconfig[:ip_saddr] or
|
79
|
+
pkt.ip_daddr == @@ctx.ifconfig[:ip_saddr] )
|
80
|
+
rescue; end
|
81
|
+
false
|
72
82
|
end
|
73
83
|
|
74
84
|
# Apply each parser on the given +parsed+ packet.
|
@@ -77,7 +87,7 @@ class Sniffer
|
|
77
87
|
begin
|
78
88
|
parser.on_packet parsed
|
79
89
|
rescue Exception => e
|
80
|
-
Logger.
|
90
|
+
Logger.debug e.message
|
81
91
|
end
|
82
92
|
end
|
83
93
|
end
|
data/lib/bettercap/version.rb
CHANGED
@@ -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.
|
15
|
+
VERSION = '1.3.7'
|
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.
|
4
|
+
version: 1.3.7
|
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-
|
11
|
+
date: 2016-02-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|
@@ -141,8 +141,11 @@ files:
|
|
141
141
|
- lib/bettercap/network/packet_queue.rb
|
142
142
|
- lib/bettercap/network/protos/base.rb
|
143
143
|
- lib/bettercap/network/protos/dhcp.rb
|
144
|
+
- lib/bettercap/network/protos/mysql.rb
|
145
|
+
- lib/bettercap/network/protos/ntlm.rb
|
144
146
|
- lib/bettercap/network/servers/dnsd.rb
|
145
147
|
- lib/bettercap/network/servers/httpd.rb
|
148
|
+
- lib/bettercap/network/services
|
146
149
|
- lib/bettercap/network/target.rb
|
147
150
|
- lib/bettercap/options.rb
|
148
151
|
- lib/bettercap/proxy/certstore.rb
|
@@ -172,8 +175,10 @@ files:
|
|
172
175
|
- lib/bettercap/sniffer/parsers/irc.rb
|
173
176
|
- lib/bettercap/sniffer/parsers/mail.rb
|
174
177
|
- lib/bettercap/sniffer/parsers/mpd.rb
|
178
|
+
- lib/bettercap/sniffer/parsers/mysql.rb
|
175
179
|
- lib/bettercap/sniffer/parsers/nntp.rb
|
176
180
|
- lib/bettercap/sniffer/parsers/ntlmss.rb
|
181
|
+
- lib/bettercap/sniffer/parsers/pgsql.rb
|
177
182
|
- lib/bettercap/sniffer/parsers/post.rb
|
178
183
|
- lib/bettercap/sniffer/parsers/redis.rb
|
179
184
|
- lib/bettercap/sniffer/parsers/rlogin.rb
|