bettercap 1.1.10 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 = ''
|