bettercap 1.1.10 → 1.2.0
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/TODO.md +8 -7
- data/bin/bettercap +8 -2
- data/lib/bettercap.rb +5 -4
- data/lib/bettercap/context.rb +54 -8
- data/lib/bettercap/discovery/agents/arp.rb +17 -4
- data/lib/bettercap/discovery/agents/base.rb +16 -52
- data/lib/bettercap/discovery/agents/icmp.rb +25 -17
- data/lib/bettercap/discovery/agents/udp.rb +9 -20
- data/lib/bettercap/discovery/{discovery.rb → thread.rb} +10 -9
- data/lib/bettercap/error.rb +1 -2
- data/lib/bettercap/factories/{firewall_factory.rb → firewall.rb} +11 -7
- data/lib/bettercap/factories/{parser_factory.rb → parser.rb} +13 -3
- data/lib/bettercap/factories/{spoofer_factory.rb → spoofer.rb} +10 -3
- data/lib/bettercap/firewalls/base.rb +76 -0
- data/lib/bettercap/firewalls/linux.rb +26 -16
- data/lib/bettercap/firewalls/osx.rb +22 -13
- data/lib/bettercap/firewalls/redirection.rb +15 -1
- data/lib/bettercap/httpd/server.rb +5 -0
- data/lib/bettercap/logger.rb +29 -8
- data/lib/bettercap/network.rb +105 -105
- data/lib/bettercap/options.rb +99 -41
- data/lib/bettercap/packet_queue.rb +92 -0
- data/lib/bettercap/proxy/certstore.rb +49 -43
- data/lib/bettercap/proxy/module.rb +4 -2
- data/lib/bettercap/proxy/proxy.rb +7 -2
- data/lib/bettercap/proxy/request.rb +28 -16
- data/lib/bettercap/proxy/response.rb +23 -2
- data/lib/bettercap/proxy/stream_logger.rb +6 -0
- data/lib/bettercap/proxy/streamer.rb +13 -5
- data/lib/bettercap/proxy/thread_pool.rb +6 -14
- data/lib/bettercap/shell.rb +5 -3
- data/lib/bettercap/sniffer/parsers/base.rb +7 -1
- data/lib/bettercap/sniffer/parsers/custom.rb +6 -1
- data/lib/bettercap/sniffer/parsers/ftp.rb +8 -5
- data/lib/bettercap/sniffer/parsers/httpauth.rb +4 -1
- data/lib/bettercap/sniffer/parsers/https.rb +4 -1
- data/lib/bettercap/sniffer/parsers/irc.rb +8 -5
- data/lib/bettercap/sniffer/parsers/mail.rb +8 -5
- data/lib/bettercap/sniffer/parsers/ntlmss.rb +21 -18
- data/lib/bettercap/sniffer/parsers/post.rb +4 -1
- data/lib/bettercap/sniffer/parsers/url.rb +4 -1
- data/lib/bettercap/sniffer/sniffer.rb +7 -3
- data/lib/bettercap/spoofers/arp.rb +69 -94
- data/lib/bettercap/spoofers/base.rb +132 -0
- data/lib/bettercap/spoofers/icmp.rb +200 -0
- data/lib/bettercap/spoofers/none.rb +8 -2
- data/lib/bettercap/target.rb +117 -90
- data/lib/bettercap/update_checker.rb +6 -0
- data/lib/bettercap/version.rb +3 -1
- metadata +24 -8
- data/lib/bettercap/base/ifirewall.rb +0 -46
- data/lib/bettercap/base/ispoofer.rb +0 -32
@@ -0,0 +1,92 @@
|
|
1
|
+
=begin
|
2
|
+
BETTERCAP
|
3
|
+
Author : Simone 'evilsocket' Margaritelli
|
4
|
+
Email : evilsocket@gmail.com
|
5
|
+
Blog : http://www.evilsocket.net/
|
6
|
+
This project is released under the GPL 3 license.
|
7
|
+
=end
|
8
|
+
require 'bettercap/error'
|
9
|
+
|
10
|
+
module BetterCap
|
11
|
+
# This class is responsible for sending various network packets.
|
12
|
+
class PacketQueue
|
13
|
+
# Initialize the PacketQueue, it will spawn +nworkers+ thread and
|
14
|
+
# will send packets to the +iface+ network interface.
|
15
|
+
def initialize( iface, nworkers = 4 )
|
16
|
+
@iface = iface
|
17
|
+
@nworkers = nworkers
|
18
|
+
@running = true
|
19
|
+
@injector = PacketFu::Inject.new(:iface => iface)
|
20
|
+
@queue = Queue.new
|
21
|
+
@workers = (0...nworkers).map {
|
22
|
+
::Thread.new {
|
23
|
+
Logger.debug "PacketQueue worker started."
|
24
|
+
|
25
|
+
while @running
|
26
|
+
begin
|
27
|
+
packet = @queue.pop
|
28
|
+
# nil packet pushed to signal stopping
|
29
|
+
if packet.nil?
|
30
|
+
Logger.debug "Got nil packet, PacketQueue stopping ..."
|
31
|
+
break
|
32
|
+
|
33
|
+
# [ ip, port, data ] pushed by Discovery::Agents::Udp
|
34
|
+
elsif packet.is_a?(Array)
|
35
|
+
ip, port, data = packet
|
36
|
+
Logger.debug "Sending UDP data packet to #{ip}:#{port} ..."
|
37
|
+
|
38
|
+
# TODO: Maybe just create one globally?
|
39
|
+
sd = UDPSocket.new
|
40
|
+
sd.send( data, 0, ip, port )
|
41
|
+
sd = nil
|
42
|
+
|
43
|
+
# PacketFu packet
|
44
|
+
else
|
45
|
+
Logger.debug "Sending #{packet.class.name} packet ..."
|
46
|
+
|
47
|
+
# Use a global PacketFu::Inject object.
|
48
|
+
@injector.array = [packet.headers[0].to_s]
|
49
|
+
@injector.inject
|
50
|
+
end
|
51
|
+
rescue Exception => e
|
52
|
+
Logger.debug "#{self.class.name} ( #{packet.class.name} ) : #{e.message}"
|
53
|
+
|
54
|
+
# If we've got an error message such as:
|
55
|
+
# (cannot open BPF device) /dev/bpf0: Too many open files
|
56
|
+
# We want to retry to probe this ip in a while.
|
57
|
+
if e.message.include? 'Too many open files'
|
58
|
+
Logger.debug "Repushing #{self.class.name} to the packet queue ..."
|
59
|
+
push(packet)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
Logger.debug "PacketQueue worker stopped."
|
65
|
+
}
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
# Push a packet to the queue.
|
70
|
+
def push(packet)
|
71
|
+
@queue.push(packet)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Wait for the packet queue to be empty.
|
75
|
+
def wait_empty( timeout )
|
76
|
+
begin
|
77
|
+
Timeout::timeout(timeout) {
|
78
|
+
while !@queue.empty?
|
79
|
+
sleep 0.5
|
80
|
+
end
|
81
|
+
}
|
82
|
+
rescue; end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Notify the queue to stop and wait for every worker to finish.
|
86
|
+
def stop
|
87
|
+
@running = false
|
88
|
+
@nworkers.times { push(nil) }
|
89
|
+
@workers.map(&:join)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -14,55 +14,61 @@ require 'openssl'
|
|
14
14
|
|
15
15
|
module BetterCap
|
16
16
|
module Proxy
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
# Class responsible of handling digital certificate loading or on the fly
|
18
|
+
# creation.
|
19
|
+
class CertStore
|
20
|
+
@@selfsigned = {}
|
21
|
+
@@frompems = {}
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
# Load a certificate from the +filename+ file and return an
|
24
|
+
# OpenSSL::X509::Certificate instance for it.
|
25
|
+
def self.from_file( filename )
|
26
|
+
unless @@frompems.has_key? filename
|
27
|
+
Logger.info "Loading self signed HTTPS certificate from '#{filename}' ..."
|
24
28
|
|
25
|
-
|
29
|
+
pem = File.read filename
|
26
30
|
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
@@frompems[filename]
|
31
|
+
@@frompems[filename] = { :cert => OpenSSL::X509::Certificate.new(pem), :key => OpenSSL::PKey::RSA.new(pem) }
|
31
32
|
end
|
32
33
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
34
|
+
@@frompems[filename]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Create a self signed digital certificate using the specified +subject+ string.
|
38
|
+
# Will return a OpenSSL::X509::Certificate instance.
|
39
|
+
def self.get_selfsigned( subject = '/C=US/ST=California/L=Mountain View/O=Google Inc/CN=www.google.com' )
|
40
|
+
unless @@selfsigned.has_key? subject
|
41
|
+
Logger.info "Generating self signed HTTPS certificate for subject '#{subject}' ..."
|
42
|
+
|
43
|
+
key = OpenSSL::PKey::RSA.new(2048)
|
44
|
+
public_key = key.public_key
|
45
|
+
|
46
|
+
cert = OpenSSL::X509::Certificate.new
|
47
|
+
cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject)
|
48
|
+
cert.not_before = Time.now
|
49
|
+
cert.not_after = Time.now + 365 * 24 * 60 * 60
|
50
|
+
cert.public_key = public_key
|
51
|
+
cert.serial = 0x0
|
52
|
+
cert.version = 2
|
53
|
+
|
54
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
55
|
+
ef.subject_certificate = cert
|
56
|
+
ef.issuer_certificate = cert
|
57
|
+
cert.extensions = [
|
58
|
+
ef.create_extension("basicConstraints","CA:TRUE", true),
|
59
|
+
ef.create_extension("subjectKeyIdentifier", "hash"),
|
60
|
+
ef.create_extension("keyUsage", "cRLSign,keyCertSign", true),
|
61
|
+
]
|
62
|
+
cert.add_extension ef.create_extension("authorityKeyIdentifier",
|
63
|
+
"keyid:always,issuer:always")
|
64
|
+
|
65
|
+
cert.sign key, OpenSSL::Digest::SHA256.new
|
66
|
+
|
67
|
+
@@selfsigned[subject] = { :cert => cert, :key => key }
|
65
68
|
end
|
69
|
+
|
70
|
+
@@selfsigned[subject]
|
66
71
|
end
|
67
72
|
end
|
68
73
|
end
|
74
|
+
end
|
@@ -13,18 +13,20 @@ require 'bettercap/logger'
|
|
13
13
|
|
14
14
|
module BetterCap
|
15
15
|
module Proxy
|
16
|
+
# Base class for transparent proxy modules.
|
16
17
|
class Module
|
17
18
|
@@modules = []
|
18
|
-
|
19
|
+
# Return a list of registered modules.
|
19
20
|
def self.modules
|
20
21
|
@@modules
|
21
22
|
end
|
22
23
|
|
23
|
-
#
|
24
|
+
# Return true if the module is enabled, otherwise false.
|
24
25
|
def enabled?
|
25
26
|
true
|
26
27
|
end
|
27
28
|
|
29
|
+
# Register available proxy modules into the system.
|
28
30
|
def self.register_modules
|
29
31
|
Object.constants.each do |klass|
|
30
32
|
const = Kernel.const_get(klass)
|
@@ -18,8 +18,11 @@ require 'bettercap/network'
|
|
18
18
|
|
19
19
|
module BetterCap
|
20
20
|
module Proxy
|
21
|
-
|
21
|
+
# Transparent proxy class.
|
22
22
|
class Proxy
|
23
|
+
# Initialize the transparent proxy, making it listen on +address+:+port+ and
|
24
|
+
# use the specified +processor+ routine for each request.
|
25
|
+
# If +is_https+ is true a HTTPS proxy will be created, otherwise a HTTP one.
|
23
26
|
def initialize( address, port, is_https, processor )
|
24
27
|
@socket = nil
|
25
28
|
@address = address
|
@@ -49,6 +52,7 @@ class Proxy
|
|
49
52
|
end
|
50
53
|
end
|
51
54
|
|
55
|
+
# Start this proxy instance.
|
52
56
|
def start
|
53
57
|
begin
|
54
58
|
@server = @socket = TCPServer.new( @address, @port )
|
@@ -70,6 +74,7 @@ class Proxy
|
|
70
74
|
end
|
71
75
|
end
|
72
76
|
|
77
|
+
# Stop this proxy instance.
|
73
78
|
def stop
|
74
79
|
begin
|
75
80
|
Logger.info "Stopping #{@type} proxy ..."
|
@@ -77,7 +82,7 @@ class Proxy
|
|
77
82
|
if @socket and @running
|
78
83
|
@running = false
|
79
84
|
@socket.close
|
80
|
-
@pool.shutdown
|
85
|
+
@pool.shutdown false
|
81
86
|
end
|
82
87
|
rescue
|
83
88
|
end
|
@@ -12,9 +12,22 @@ This project is released under the GPL 3 license.
|
|
12
12
|
|
13
13
|
module BetterCap
|
14
14
|
module Proxy
|
15
|
+
# HTTP request parser.
|
15
16
|
class Request
|
16
|
-
|
17
|
-
|
17
|
+
# Patched request lines.
|
18
|
+
attr_reader :lines
|
19
|
+
# HTTP verb.
|
20
|
+
attr_reader :verb
|
21
|
+
# Request URL.
|
22
|
+
attr_reader :url
|
23
|
+
# Hostname.
|
24
|
+
attr_reader :host
|
25
|
+
# Request port.
|
26
|
+
attr_reader :port
|
27
|
+
# Content length.
|
28
|
+
attr_reader :content_length
|
29
|
+
|
30
|
+
# Initialize this object setting #port to +default_port+.
|
18
31
|
def initialize( default_port = 80 )
|
19
32
|
@lines = []
|
20
33
|
@verb = nil
|
@@ -24,6 +37,8 @@ class Request
|
|
24
37
|
@content_length = 0
|
25
38
|
end
|
26
39
|
|
40
|
+
# Read lines from the +sock+ socket and parse them.
|
41
|
+
# Will raise an exception if the #hostname can not be parsed.
|
27
42
|
def read(sock)
|
28
43
|
# read the first line
|
29
44
|
self << sock.readline
|
@@ -40,6 +55,7 @@ class Request
|
|
40
55
|
raise "Couldn't extract host from the request." unless @host
|
41
56
|
end
|
42
57
|
|
58
|
+
# Parse a single request line, patch it if needed and append it to #lines.
|
43
59
|
def <<(line)
|
44
60
|
line = line.chomp
|
45
61
|
|
@@ -57,7 +73,6 @@ class Request
|
|
57
73
|
end
|
58
74
|
|
59
75
|
line = "#{@verb} #{@url} HTTP/1.1"
|
60
|
-
|
61
76
|
# get the host header value
|
62
77
|
elsif line =~ /^Host:\s*(.*)$/
|
63
78
|
@host = $1
|
@@ -65,34 +80,31 @@ class Request
|
|
65
80
|
@host = $1
|
66
81
|
@port = $2.to_i
|
67
82
|
end
|
68
|
-
|
69
83
|
# parse content length, this will speed up data streaming
|
70
84
|
elsif line =~ /^Content-Length:\s+(\d+)\s*$/i
|
71
85
|
@content_length = $1.to_i
|
72
|
-
|
73
86
|
# we don't want to have hundreds of threads running
|
74
87
|
elsif line =~ /^Connection: keep-alive/i
|
75
88
|
line = 'Connection: close'
|
76
|
-
|
77
89
|
elsif line =~ /^Proxy-Connection: (.+)/i
|
78
90
|
line = "Connection: #{$1}"
|
79
|
-
|
80
91
|
# disable gzip, chunked, etc encodings
|
81
92
|
elsif line =~ /^Accept-Encoding:.*/i
|
82
93
|
line = 'Accept-Encoding: identity'
|
83
|
-
|
84
94
|
end
|
85
95
|
|
86
|
-
|
87
|
-
|
96
|
+
@lines << line
|
97
|
+
end
|
88
98
|
|
89
|
-
|
90
|
-
|
91
|
-
|
99
|
+
# Return true if this is a POST request, otherwise false.
|
100
|
+
def post?
|
101
|
+
@verb == 'POST'
|
102
|
+
end
|
92
103
|
|
93
|
-
|
94
|
-
|
95
|
-
|
104
|
+
# Return a string representation of the HTTP request.
|
105
|
+
def to_s
|
106
|
+
@lines.join("\n") + "\n"
|
96
107
|
end
|
97
108
|
end
|
98
109
|
end
|
110
|
+
end
|
@@ -12,11 +12,26 @@ This project is released under the GPL 3 license.
|
|
12
12
|
|
13
13
|
module BetterCap
|
14
14
|
module Proxy
|
15
|
-
|
15
|
+
# HTTP response parser.
|
16
16
|
class Response
|
17
|
-
|
17
|
+
# Response content type.
|
18
|
+
attr_reader :content_type
|
19
|
+
# Response charset, default to UTF-8.
|
20
|
+
attr_reader :charset
|
21
|
+
# Response content length.
|
22
|
+
attr_reader :content_length
|
23
|
+
# True if this is a chunked encoded response, otherwise false.
|
24
|
+
attr_reader :chunked
|
25
|
+
# A list of response headers.
|
26
|
+
attr_reader :headers
|
27
|
+
# Response status code.
|
28
|
+
attr_reader :code
|
29
|
+
# True if the parser finished to parse the headers, otherwise false.
|
30
|
+
attr_reader :headers_done
|
31
|
+
# Response body.
|
18
32
|
attr_accessor :body
|
19
33
|
|
34
|
+
# Initialize this response object state.
|
20
35
|
def initialize
|
21
36
|
@content_type = nil
|
22
37
|
@charset = 'UTF-8'
|
@@ -28,6 +43,8 @@ class Response
|
|
28
43
|
@chunked = false
|
29
44
|
end
|
30
45
|
|
46
|
+
# Read lines from the +sock+ socket until all headers are correctly parsed
|
47
|
+
# and return a BetterCap::Proxy::Response instance.
|
31
48
|
def self.from_socket(sock)
|
32
49
|
response = Response.new
|
33
50
|
|
@@ -43,6 +60,7 @@ class Response
|
|
43
60
|
response
|
44
61
|
end
|
45
62
|
|
63
|
+
# Parse a single response +line+.
|
46
64
|
def <<(line)
|
47
65
|
# we already parsed the heders, collect response body
|
48
66
|
if @headers_done
|
@@ -80,10 +98,13 @@ class Response
|
|
80
98
|
end
|
81
99
|
end
|
82
100
|
|
101
|
+
# Return true if the response content type is textual, otherwise false.
|
83
102
|
def textual?
|
84
103
|
@content_type and ( @content_type =~ /^text\/.+/ or @content_type =~ /^application\/.+/ )
|
85
104
|
end
|
86
105
|
|
106
|
+
# Return a string representation of this response object, patching the
|
107
|
+
# Content-Length header if the #body was modified.
|
87
108
|
def to_s
|
88
109
|
if textual?
|
89
110
|
@headers.map! do |header|
|
@@ -12,6 +12,7 @@ This project is released under the GPL 3 license.
|
|
12
12
|
require 'bettercap/logger'
|
13
13
|
|
14
14
|
module BetterCap
|
15
|
+
# Raw or http streams pretty logging.
|
15
16
|
class StreamLogger
|
16
17
|
@@MAX_REQ_SIZE = 50
|
17
18
|
|
@@ -22,6 +23,8 @@ class StreamLogger
|
|
22
23
|
'5' => :red
|
23
24
|
}
|
24
25
|
|
26
|
+
# Search for the +addr+ IP address inside the list of collected targets and return
|
27
|
+
# its compact string representation ( @see BetterCap::Target#to_s_compact ).
|
25
28
|
def self.addr2s( addr )
|
26
29
|
ctx = Context.get
|
27
30
|
|
@@ -33,6 +36,7 @@ class StreamLogger
|
|
33
36
|
addr
|
34
37
|
end
|
35
38
|
|
39
|
+
# Log a raw packet ( +pkt+ ) data +payload+ using the specified +label+.
|
36
40
|
def self.log_raw( pkt, label, payload )
|
37
41
|
nl = if label.include?"\n" then "\n" else " " end
|
38
42
|
label = label.strip
|
@@ -40,6 +44,8 @@ class StreamLogger
|
|
40
44
|
"[#{label.green}]#{nl}#{payload.strip.yellow}" )
|
41
45
|
end
|
42
46
|
|
47
|
+
# Log a HTTP ( HTTPS if +is_https+ is true ) stream performed by the +client+
|
48
|
+
# with the +request+ and +response+ most important informations.
|
43
49
|
def self.log_http( is_https, client, request, response )
|
44
50
|
request_s = "#{is_https ? 'https' : 'http'}://#{request.host}#{request.url}"
|
45
51
|
response_s = "( #{response.content_type} )"
|
@@ -13,16 +13,25 @@ require 'bettercap/logger'
|
|
13
13
|
|
14
14
|
module BetterCap
|
15
15
|
module Proxy
|
16
|
+
# Handle data streaming between clients and servers for the BetterCap::Proxy::Proxy.
|
16
17
|
class Streamer
|
18
|
+
# Default buffer size for data streaming.
|
19
|
+
BUFSIZE = 1024 * 16
|
20
|
+
|
21
|
+
# Initialize the class with the given +processor+ routine.
|
17
22
|
def initialize( processor )
|
18
23
|
@processor = processor
|
19
24
|
end
|
20
25
|
|
26
|
+
# Redirect the +client+ to a funny video.
|
21
27
|
def rickroll( client )
|
22
28
|
client.write "HTTP/1.1 302 Found\n"
|
23
29
|
client.write "Location: https://www.youtube.com/watch?v=dQw4w9WgXcQ\n\n"
|
24
30
|
end
|
25
31
|
|
32
|
+
# Perform HTML streaming for the given +request+, applying the #processor
|
33
|
+
# to the +response+.
|
34
|
+
# +from+ and +to+ are the two TCP endpoints.
|
26
35
|
def html( request, response, from, to )
|
27
36
|
buff = ''
|
28
37
|
|
@@ -79,12 +88,13 @@ class Streamer
|
|
79
88
|
to.write response.to_s
|
80
89
|
end
|
81
90
|
|
82
|
-
|
91
|
+
# Perform binary streaming using the +opts+ dictionary.
|
92
|
+
# If response|request object is available inside +opts+ and a content length
|
93
|
+
# as well use it to speed up data streaming with precise data size
|
94
|
+
# +from+ and +to+ are the two TCP endpoints.
|
83
95
|
def binary( from, to, opts = {} )
|
84
96
|
total_size = 0
|
85
97
|
|
86
|
-
# if response|request object is available and a content length as well
|
87
|
-
# use it to speed up data streaming with precise data size
|
88
98
|
if not opts[:response].nil?
|
89
99
|
to.write opts[:response].to_s
|
90
100
|
|
@@ -127,8 +137,6 @@ class Streamer
|
|
127
137
|
|
128
138
|
private
|
129
139
|
|
130
|
-
BUFSIZE = 1024 * 16
|
131
|
-
|
132
140
|
def consume_stream io, size
|
133
141
|
read_timeout = 60.0
|
134
142
|
dest = ''
|