bettercap 1.1.7 → 1.1.8
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/.gitignore +3 -1
- data/README.md +18 -14
- data/TODO.md +11 -4
- data/bettercap.gemspec +1 -0
- data/bin/bettercap +75 -112
- data/lib/bettercap.rb +1 -0
- data/lib/bettercap/context.rb +48 -114
- data/lib/bettercap/discovery/arp.rb +32 -22
- data/lib/bettercap/network.rb +3 -3
- data/lib/bettercap/options.rb +189 -0
- data/lib/bettercap/proxy/proxy.rb +9 -8
- data/lib/bettercap/proxy/request.rb +1 -1
- data/lib/bettercap/proxy/response.rb +10 -2
- data/lib/bettercap/proxy/stream_logger.rb +11 -6
- data/lib/bettercap/proxy/streamer.rb +67 -15
- data/lib/bettercap/proxy/thread_pool.rb +0 -64
- data/lib/bettercap/sniffer/parsers/base.rb +7 -1
- data/lib/bettercap/sniffer/parsers/httpauth.rb +4 -5
- data/lib/bettercap/sniffer/parsers/https.rb +1 -1
- data/lib/bettercap/sniffer/parsers/ntlmss.rb +1 -1
- data/lib/bettercap/sniffer/parsers/post.rb +1 -1
- data/lib/bettercap/sniffer/parsers/url.rb +1 -1
- data/lib/bettercap/sniffer/sniffer.rb +10 -10
- data/lib/bettercap/spoofers/arp.rb +12 -17
- data/lib/bettercap/target.rb +47 -5
- data/lib/bettercap/version.rb +1 -1
- metadata +17 -2
@@ -42,9 +42,6 @@ class ThreadPool
|
|
42
42
|
|
43
43
|
@workers = []
|
44
44
|
|
45
|
-
@auto_trim = nil
|
46
|
-
@reaper = nil
|
47
|
-
|
48
45
|
@mutex.synchronize do
|
49
46
|
@min.times { spawn_thread }
|
50
47
|
end
|
@@ -184,64 +181,6 @@ class ThreadPool
|
|
184
181
|
end
|
185
182
|
end
|
186
183
|
|
187
|
-
class AutoTrim
|
188
|
-
def initialize(pool, timeout)
|
189
|
-
@pool = pool
|
190
|
-
@timeout = timeout
|
191
|
-
@running = false
|
192
|
-
end
|
193
|
-
|
194
|
-
def start!
|
195
|
-
@running = true
|
196
|
-
|
197
|
-
@thread = Thread.new do
|
198
|
-
while @running
|
199
|
-
@pool.trim
|
200
|
-
sleep @timeout
|
201
|
-
end
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
def stop
|
206
|
-
@running = false
|
207
|
-
@thread.wakeup
|
208
|
-
end
|
209
|
-
end
|
210
|
-
|
211
|
-
def auto_trim!(timeout=5)
|
212
|
-
@auto_trim = AutoTrim.new(self, timeout)
|
213
|
-
@auto_trim.start!
|
214
|
-
end
|
215
|
-
|
216
|
-
class Reaper
|
217
|
-
def initialize(pool, timeout)
|
218
|
-
@pool = pool
|
219
|
-
@timeout = timeout
|
220
|
-
@running = false
|
221
|
-
end
|
222
|
-
|
223
|
-
def start!
|
224
|
-
@running = true
|
225
|
-
|
226
|
-
@thread = Thread.new do
|
227
|
-
while @running
|
228
|
-
@pool.reap
|
229
|
-
sleep @timeout
|
230
|
-
end
|
231
|
-
end
|
232
|
-
end
|
233
|
-
|
234
|
-
def stop
|
235
|
-
@running = false
|
236
|
-
@thread.wakeup
|
237
|
-
end
|
238
|
-
end
|
239
|
-
|
240
|
-
def auto_reap!(timeout=5)
|
241
|
-
@reaper = Reaper.new(self, timeout)
|
242
|
-
@reaper.start!
|
243
|
-
end
|
244
|
-
|
245
184
|
# Tell all threads in the pool to exit and wait for them to finish.
|
246
185
|
#
|
247
186
|
def shutdown
|
@@ -249,9 +188,6 @@ class ThreadPool
|
|
249
188
|
@shutdown = true
|
250
189
|
@not_empty.broadcast
|
251
190
|
@not_full.broadcast
|
252
|
-
|
253
|
-
@auto_trim.stop if @auto_trim
|
254
|
-
@reaper.stop if @reaper
|
255
191
|
# dup workers so that we join them all safely
|
256
192
|
@workers.dup
|
257
193
|
end
|
@@ -18,11 +18,17 @@ class BaseParser
|
|
18
18
|
@name = 'BASE'
|
19
19
|
end
|
20
20
|
|
21
|
+
def addr2s( addr )
|
22
|
+
target = Context.get.find_target addr, nil
|
23
|
+
return target.to_s_compact unless target.nil?
|
24
|
+
addr
|
25
|
+
end
|
26
|
+
|
21
27
|
def on_packet( pkt )
|
22
28
|
s = pkt.to_s
|
23
29
|
@filters.each do |filter|
|
24
30
|
if s =~ filter
|
25
|
-
Logger.write "[#{pkt.ip_saddr}:#{pkt.tcp_src} > #{pkt.ip_daddr}:#{pkt.tcp_dst} #{pkt.proto.last}] " +
|
31
|
+
Logger.write "[#{addr2s(pkt.ip_saddr)}:#{pkt.tcp_src} > #{addr2s(pkt.ip_daddr)}:#{pkt.tcp_dst} #{pkt.proto.last}] " +
|
26
32
|
"[#{@name}] ".green +
|
27
33
|
pkt.payload.strip.yellow
|
28
34
|
end
|
@@ -22,7 +22,7 @@ class HttpauthParser < BaseParser
|
|
22
22
|
lines.each do |line|
|
23
23
|
if line =~ /[A-Z]+\s+(\/[^\s]+)\s+HTTP\/\d\.\d/
|
24
24
|
path = $1
|
25
|
-
|
25
|
+
|
26
26
|
elsif line =~ /Host:\s*([^\s]+)/i
|
27
27
|
hostname = $1
|
28
28
|
|
@@ -31,15 +31,14 @@ class HttpauthParser < BaseParser
|
|
31
31
|
decoded = Base64.decode64(encoded)
|
32
32
|
user, pass = decoded.split(':')
|
33
33
|
|
34
|
-
Logger.write "[#{pkt.ip_saddr}:#{pkt.tcp_src} > #{pkt.ip_daddr}:#{pkt.tcp_dst} #{pkt.proto.last}] " +
|
34
|
+
Logger.write "[#{addr2s(pkt.ip_saddr)}:#{pkt.tcp_src} > #{addr2s(pkt.ip_daddr)}:#{pkt.tcp_dst} #{pkt.proto.last}] " +
|
35
35
|
'[HTTP BASIC AUTH]'.green + " http://#{hostname}#{path} - username=#{user} password=#{pass}".yellow
|
36
|
-
|
36
|
+
|
37
37
|
elsif line =~ /Authorization:\s*Digest\s+(.+)/i
|
38
|
-
Logger.write "[#{pkt.ip_saddr}:#{pkt.tcp_src} > #{pkt.ip_daddr}:#{pkt.tcp_dst} #{pkt.proto.last}] " +
|
38
|
+
Logger.write "[#{addr2s(pkt.ip_saddr)}:#{pkt.tcp_src} > #{addr2s(pkt.ip_daddr)}:#{pkt.tcp_dst} #{pkt.proto.last}] " +
|
39
39
|
'[HTTP DIGEST AUTH]'.green + " http://#{hostname}#{path}\n#{$1}".yellow
|
40
40
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
end
|
44
44
|
end
|
45
|
-
|
@@ -28,7 +28,7 @@ class HttpsParser < BaseParser
|
|
28
28
|
end
|
29
29
|
|
30
30
|
if @@prev.nil? or @@prev != hostname
|
31
|
-
Logger.write "[#{pkt.ip_saddr}:#{pkt.tcp_src} > #{pkt.ip_daddr}:#{pkt.tcp_dst} #{pkt.proto.last}] " +
|
31
|
+
Logger.write "[#{addr2s(pkt.ip_saddr)}:#{pkt.tcp_src} > #{addr2s(pkt.ip_daddr)}:#{pkt.tcp_dst} #{pkt.proto.last}] " +
|
32
32
|
'[HTTPS] '.green +
|
33
33
|
"https://#{hostname}/".yellow
|
34
34
|
|
@@ -30,7 +30,7 @@ class NtlmssParser < BaseParser
|
|
30
30
|
s = pkt.to_s
|
31
31
|
if s =~ /NTLMSSP\x00\x03\x00\x00\x00.+/
|
32
32
|
# TODO: Parse NTLMSSP packet.
|
33
|
-
Logger.write "[#{pkt.ip_saddr} > #{pkt.ip_daddr} #{pkt.proto.last}] " +
|
33
|
+
Logger.write "[#{addr2s(pkt.ip_saddr)} > #{addr2s(pkt.ip_daddr)} #{pkt.proto.last}] " +
|
34
34
|
'[NTLMSS] '.green +
|
35
35
|
bin2hex( pkt.payload ).yellow
|
36
36
|
end
|
@@ -16,7 +16,7 @@ class PostParser < BaseParser
|
|
16
16
|
def on_packet( pkt )
|
17
17
|
s = pkt.to_s
|
18
18
|
if s =~ /POST\s+[^\s]+\s+HTTP.+/
|
19
|
-
Logger.write "[#{pkt.ip_saddr}:#{pkt.tcp_src} > #{pkt.ip_daddr}:#{pkt.tcp_dst} #{pkt.proto.last}] " +
|
19
|
+
Logger.write "[#{addr2s(pkt.ip_saddr)}:#{pkt.tcp_src} > #{addr2s(pkt.ip_daddr)}:#{pkt.tcp_dst} #{pkt.proto.last}] " +
|
20
20
|
"[POST]\n".green +
|
21
21
|
pkt.payload.strip.yellow
|
22
22
|
end
|
@@ -19,7 +19,7 @@ class UrlParser < BaseParser
|
|
19
19
|
host = $2
|
20
20
|
url = $1
|
21
21
|
if not url =~ /.+\.(png|jpg|jpeg|bmp|gif|img|ttf|woff|css|js).*/i
|
22
|
-
Logger.write "[#{pkt.ip_saddr}:#{pkt.tcp_src} > #{pkt.ip_daddr}:#{pkt.tcp_dst} #{pkt.proto.last}] " +
|
22
|
+
Logger.write "[#{addr2s(pkt.ip_saddr)}:#{pkt.tcp_src} > #{addr2s(pkt.ip_daddr)}:#{pkt.tcp_dst} #{pkt.proto.last}] " +
|
23
23
|
'[GET] '.green +
|
24
24
|
"http://#{host}#{url}".yellow
|
25
25
|
end
|
@@ -45,17 +45,17 @@ class Sniffer
|
|
45
45
|
private
|
46
46
|
|
47
47
|
def self.stream
|
48
|
-
if @@ctx.options
|
48
|
+
if @@ctx.options.sniffer_src.nil?
|
49
49
|
@@cap.stream
|
50
50
|
else
|
51
|
-
Logger.info "Reading packets from #{@@ctx.options
|
51
|
+
Logger.info "Reading packets from #{@@ctx.options.sniffer_src} ..."
|
52
52
|
|
53
|
-
PcapFile.file_to_array @@ctx.options
|
53
|
+
PcapFile.file_to_array @@ctx.options.sniffer_src
|
54
54
|
end
|
55
55
|
end
|
56
56
|
|
57
57
|
def self.skip_packet?( pkt )
|
58
|
-
!@@ctx.options
|
58
|
+
!@@ctx.options.local and
|
59
59
|
( pkt.ip_saddr == @@ctx.ifconfig[:ip_saddr] or
|
60
60
|
pkt.ip_daddr == @@ctx.ifconfig[:ip_saddr] )
|
61
61
|
end
|
@@ -73,7 +73,7 @@ class Sniffer
|
|
73
73
|
def self.append_packet( p )
|
74
74
|
begin
|
75
75
|
@@pcap.array_to_file(
|
76
|
-
filename: @@ctx.options
|
76
|
+
filename: @@ctx.options.sniffer_pcap,
|
77
77
|
array: [p],
|
78
78
|
append: true ) unless @@pcap.nil?
|
79
79
|
rescue Exception => e
|
@@ -84,16 +84,16 @@ class Sniffer
|
|
84
84
|
def self.setup( ctx )
|
85
85
|
@@ctx = ctx
|
86
86
|
|
87
|
-
if !@@ctx.options
|
87
|
+
if !@@ctx.options.sniffer_pcap.nil?
|
88
88
|
@@pcap = PcapFile.new
|
89
|
-
Logger.warn "Saving packets to #{@@ctx.options
|
89
|
+
Logger.warn "Saving packets to #{@@ctx.options.sniffer_pcap} ."
|
90
90
|
end
|
91
91
|
|
92
|
-
@@parsers = ParserFactory.load_by_names @@ctx.options
|
92
|
+
@@parsers = ParserFactory.load_by_names @@ctx.options.parsers
|
93
93
|
|
94
94
|
@@cap = Capture.new(
|
95
|
-
iface: @@ctx.options
|
96
|
-
filter: @@ctx.options
|
95
|
+
iface: @@ctx.options.iface,
|
96
|
+
filter: @@ctx.options.sniffer_filter,
|
97
97
|
start: true
|
98
98
|
)
|
99
99
|
end
|
@@ -51,35 +51,31 @@ class ArpSpoofer < ISpoofer
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def start
|
54
|
-
|
54
|
+
Logger.info "Starting ARP spoofer ( #{@ctx.options.half_duplex ? 'Half' : 'Full'} Duplex ) ..."
|
55
55
|
|
56
|
-
|
57
|
-
|
58
|
-
if @forwarding == false
|
59
|
-
Logger.debug 'Enabling packet forwarding.'
|
56
|
+
stop() unless !@running
|
57
|
+
@running = true
|
60
58
|
|
61
|
-
|
62
|
-
end
|
59
|
+
@ctx.firewall.enable_forwarding(true) unless @forwarding
|
63
60
|
|
64
|
-
@running = true
|
65
61
|
@sniff_thread = Thread.new do
|
66
62
|
Logger.info 'ARP watcher started ...'
|
67
63
|
begin
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
64
|
+
@capture = PacketFu::Capture.new(
|
65
|
+
iface: @ctx.options.iface,
|
66
|
+
filter: 'arp',
|
67
|
+
start: true
|
68
|
+
)
|
73
69
|
rescue Exception => e
|
74
70
|
Logger.error e.message
|
75
71
|
end
|
72
|
+
|
76
73
|
@capture.stream.each do |p|
|
77
74
|
begin
|
78
75
|
pkt = PacketFu::Packet.parse p
|
79
76
|
# we're only interested in 'who-has' packets
|
80
77
|
if pkt.arp_opcode == 1 and pkt.arp_dst_mac.to_s == '00:00:00:00:00:00'
|
81
78
|
is_from_us = ( pkt.arp_src_ip.to_s == @ctx.ifconfig[:ip_saddr] )
|
82
|
-
|
83
79
|
if !is_from_us
|
84
80
|
Logger.info "[ARP] #{pkt.arp_src_ip.to_s} is asking who #{pkt.arp_dst_ip.to_s} is."
|
85
81
|
|
@@ -122,13 +118,12 @@ class ArpSpoofer < ISpoofer
|
|
122
118
|
next
|
123
119
|
else
|
124
120
|
Logger.info " Target MAC : #{hw}"
|
125
|
-
|
126
121
|
target.mac = hw
|
127
122
|
end
|
128
123
|
end
|
129
124
|
|
130
125
|
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
|
126
|
+
send_spoofed_packet( target.ip, @ctx.ifconfig[:eth_saddr], @ctx.gateway, @gw_hw ) unless @ctx.options.half_duplex
|
132
127
|
end
|
133
128
|
|
134
129
|
prev_size = @ctx.targets.size
|
@@ -157,7 +152,7 @@ class ArpSpoofer < ISpoofer
|
|
157
152
|
@ctx.targets.each do |target|
|
158
153
|
if !target.mac.nil?
|
159
154
|
send_spoofed_packet( @ctx.gateway, @gw_hw, target.ip, target.mac )
|
160
|
-
send_spoofed_packet( target.ip, target.mac, @ctx.gateway, @gw_hw ) unless @ctx.options
|
155
|
+
send_spoofed_packet( target.ip, target.mac, @ctx.gateway, @gw_hw ) unless @ctx.options.half_duplex
|
161
156
|
end
|
162
157
|
end
|
163
158
|
sleep 1
|
data/lib/bettercap/target.rb
CHANGED
@@ -10,17 +10,24 @@ This project is released under the GPL 3 license.
|
|
10
10
|
|
11
11
|
=end
|
12
12
|
require 'bettercap/logger'
|
13
|
+
require 'socket'
|
13
14
|
|
14
15
|
class Target
|
15
16
|
attr_accessor :ip, :mac, :vendor, :hostname
|
16
17
|
|
18
|
+
NBNS_TIMEOUT = 30
|
19
|
+
NBNS_PORT = 137
|
20
|
+
NBNS_BUFSIZE = 65536
|
21
|
+
NBNS_REQUEST = "\x82\x28\x0\x0\x0\x1\x0\x0\x0\x0\x0\x0\x20\x43\x4B\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x41\x0\x0\x21\x0\x1"
|
22
|
+
|
17
23
|
@@prefixes = nil
|
18
24
|
|
19
25
|
def initialize( ip, mac=nil )
|
20
|
-
@ip
|
21
|
-
@mac
|
22
|
-
@vendor
|
23
|
-
@hostname = nil
|
26
|
+
@ip = ip
|
27
|
+
@mac = mac
|
28
|
+
@vendor = Target.lookup_vendor(mac) if not mac.nil?
|
29
|
+
@hostname = nil
|
30
|
+
@resolver = Thread.new { resolve! }
|
24
31
|
end
|
25
32
|
|
26
33
|
def mac=(value)
|
@@ -29,11 +36,46 @@ class Target
|
|
29
36
|
end
|
30
37
|
|
31
38
|
def to_s
|
32
|
-
|
39
|
+
s = @ip
|
40
|
+
s += " ( #{@hostname} )" unless @hostname.nil?
|
41
|
+
s += " : #{@mac}"
|
42
|
+
s += " ( #{@vendor} )" unless @vendor.nil?
|
43
|
+
s
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s_compact
|
47
|
+
if @hostname
|
48
|
+
"#{@hostname}/#{@ip}"
|
49
|
+
else
|
50
|
+
@ip
|
51
|
+
end
|
33
52
|
end
|
34
53
|
|
35
54
|
private
|
36
55
|
|
56
|
+
def resolve!
|
57
|
+
resp, sock = nil, nil
|
58
|
+
begin
|
59
|
+
sock = UDPSocket.open
|
60
|
+
sock.send( NBNS_REQUEST, 0, @ip, NBNS_PORT )
|
61
|
+
resp = if select([sock], nil, nil, NBNS_TIMEOUT)
|
62
|
+
sock.recvfrom(NBNS_BUFSIZE)
|
63
|
+
end
|
64
|
+
if resp
|
65
|
+
@hostname = parse_nbns_response resp
|
66
|
+
Logger.info "Found NetBIOS name '#{@hostname}' for address #{@ip}"
|
67
|
+
end
|
68
|
+
rescue Exception => e
|
69
|
+
Logger.debug e
|
70
|
+
ensure
|
71
|
+
sock.close if sock
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def parse_nbns_response resp
|
76
|
+
resp[0][57,15].to_s.strip
|
77
|
+
end
|
78
|
+
|
37
79
|
def self.lookup_vendor( mac )
|
38
80
|
if @@prefixes == nil
|
39
81
|
Logger.debug 'Preloading hardware vendor prefixes ...'
|
data/lib/bettercap/version.rb
CHANGED
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.1.
|
4
|
+
version: 1.1.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Simone Margaritelli
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-12-
|
11
|
+
date: 2015-12-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ~>
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: 0.12.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: network_interface
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.0.1
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 0.0.1
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: minitest
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -104,6 +118,7 @@ files:
|
|
104
118
|
- lib/bettercap/logger.rb
|
105
119
|
- lib/bettercap/monkey/packetfu/utils.rb
|
106
120
|
- lib/bettercap/network.rb
|
121
|
+
- lib/bettercap/options.rb
|
107
122
|
- lib/bettercap/proxy/certstore.rb
|
108
123
|
- lib/bettercap/proxy/module.rb
|
109
124
|
- lib/bettercap/proxy/proxy.rb
|