bettercap 1.1.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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +225 -0
  3. data/README.md +96 -0
  4. data/bettercap.gemspec +28 -0
  5. data/bin/bettercap +184 -0
  6. data/example_proxy_module.rb +21 -0
  7. data/lib/bettercap/base/ifirewall.rb +28 -0
  8. data/lib/bettercap/base/ispoofer.rb +24 -0
  9. data/lib/bettercap/context.rb +124 -0
  10. data/lib/bettercap/discovery/arp.rb +37 -0
  11. data/lib/bettercap/discovery/icmp.rb +37 -0
  12. data/lib/bettercap/discovery/syn.rb +88 -0
  13. data/lib/bettercap/discovery/udp.rb +74 -0
  14. data/lib/bettercap/error.rb +16 -0
  15. data/lib/bettercap/factories/firewall_factory.rb +32 -0
  16. data/lib/bettercap/factories/parser_factory.rb +53 -0
  17. data/lib/bettercap/factories/spoofer_factory.rb +36 -0
  18. data/lib/bettercap/firewalls/linux.rb +55 -0
  19. data/lib/bettercap/firewalls/osx.rb +70 -0
  20. data/lib/bettercap/hw-prefixes +19651 -0
  21. data/lib/bettercap/logger.rb +53 -0
  22. data/lib/bettercap/monkey/packetfu/utils.rb +96 -0
  23. data/lib/bettercap/network.rb +131 -0
  24. data/lib/bettercap/proxy/module.rb +39 -0
  25. data/lib/bettercap/proxy/proxy.rb +262 -0
  26. data/lib/bettercap/proxy/request.rb +77 -0
  27. data/lib/bettercap/proxy/response.rb +76 -0
  28. data/lib/bettercap/shell.rb +31 -0
  29. data/lib/bettercap/sniffer/parsers/base.rb +31 -0
  30. data/lib/bettercap/sniffer/parsers/ftp.rb +19 -0
  31. data/lib/bettercap/sniffer/parsers/httpauth.rb +45 -0
  32. data/lib/bettercap/sniffer/parsers/https.rb +36 -0
  33. data/lib/bettercap/sniffer/parsers/irc.rb +19 -0
  34. data/lib/bettercap/sniffer/parsers/mail.rb +19 -0
  35. data/lib/bettercap/sniffer/parsers/ntlmss.rb +38 -0
  36. data/lib/bettercap/sniffer/parsers/post.rb +24 -0
  37. data/lib/bettercap/sniffer/parsers/url.rb +28 -0
  38. data/lib/bettercap/sniffer/sniffer.rb +39 -0
  39. data/lib/bettercap/spoofers/arp.rb +130 -0
  40. data/lib/bettercap/spoofers/none.rb +23 -0
  41. data/lib/bettercap/target.rb +52 -0
  42. data/lib/bettercap/version.rb +14 -0
  43. metadata +129 -0
@@ -0,0 +1,53 @@
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
+ module Logger
13
+ class << self
14
+ attr_accessor :logfile, :debug_enabled
15
+
16
+ @@semaphore = Mutex.new
17
+
18
+ def error(message)
19
+ write(formatted_message(message, 'E').red)
20
+ end
21
+
22
+ def info(message)
23
+ write(formatted_message(message, 'I'))
24
+ end
25
+
26
+ def warn(message)
27
+ write(formatted_message(message, 'W').yellow)
28
+ end
29
+
30
+ def debug(message)
31
+ if @debug_enabled
32
+ write(formatted_message(message, 'D').light_black)
33
+ end
34
+ end
35
+
36
+ def write(message)
37
+ # make sure that logging is thread safe
38
+ @@semaphore.synchronize {
39
+ puts message
40
+ if !@logfile.nil?
41
+ f = File.open( @logfile, 'a+t' )
42
+ f.puts( message.gsub( /\e\[(\d+)(;\d+)*m/, '') + "\n")
43
+ f.close
44
+ end
45
+ }
46
+ end
47
+
48
+ private
49
+ def formatted_message(message, message_type)
50
+ "[#{message_type}] #{message}"
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,96 @@
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
+
13
+ # PacketFu::Utils.ifconfig is broken under OS X, it does
14
+ # not correctly parse the netmask field due to a wrong
15
+ # regular expression.
16
+ #
17
+ # ORIGINAL: https://github.com/packetfu/packetfu/blob/master/lib/packetfu/utils.rb#L204
18
+ require 'bettercap/logger'
19
+
20
+ module PacketFu
21
+ class Utils
22
+ def self.ifconfig(iface='eth0')
23
+ ret = {}
24
+ iface = iface.to_s.scan(/[0-9A-Za-z]/).join
25
+
26
+ Logger.debug "ifconfig #{iface}"
27
+
28
+ ifconfig_data = Shell.ifconfig(iface)
29
+
30
+ case RUBY_PLATFORM
31
+ when /linux/i
32
+ Logger.debug "Linux ifconfig #{iface}:\n#{ifconfig_data}"
33
+
34
+ if ifconfig_data =~ /#{iface}/i
35
+ ifconfig_data = ifconfig_data.split(/[\s]*\n[\s]*/)
36
+ else
37
+ raise ArgumentError, "Cannot ifconfig #{iface}"
38
+ end
39
+ real_iface = ifconfig_data.first
40
+ ret[:iface] = real_iface.split.first.downcase.gsub(':','')
41
+ if real_iface =~ /[\s]HWaddr[\s]+([0-9a-fA-F:]{17})/i
42
+ ret[:eth_saddr] = $1.downcase
43
+ ret[:eth_src] = EthHeader.mac2str(ret[:eth_saddr])
44
+ end
45
+ ifconfig_data.each do |s|
46
+ case s
47
+ when /inet [a-z]+:[\s]*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(.*[a-z]+:([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+))?/i
48
+ ret[:ip_saddr] = $1
49
+ ret[:ip_src] = [IPAddr.new($1).to_i].pack("N")
50
+ ret[:ip4_obj] = IPAddr.new($1)
51
+ ret[:ip4_obj] = ret[:ip4_obj].mask($3) if $3
52
+ when /inet[\s]+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(.*Mask[\s]+([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+))?/i
53
+ ret[:ip_saddr] = $1
54
+ ret[:ip_src] = [IPAddr.new($1).to_i].pack("N")
55
+ ret[:ip4_obj] = IPAddr.new($1)
56
+ ret[:ip4_obj] = ret[:ip4_obj].mask($3) if $3
57
+ when /inet6 [a-z]+:[\s]*([0-9a-fA-F:\x2f]+)/
58
+ ret[:ip6_saddr] = $1
59
+ ret[:ip6_obj] = IPAddr.new($1)
60
+ end
61
+ end # linux
62
+ when /darwin/i
63
+ Logger.debug "OSX ifconfig #{iface}:\n#{ifconfig_data}"
64
+
65
+ if ifconfig_data =~ /#{iface}/i
66
+ ifconfig_data = ifconfig_data.split(/[\s]*\n[\s]*/)
67
+ else
68
+ raise ArgumentError, "Cannot ifconfig #{iface}"
69
+ end
70
+ real_iface = ifconfig_data.first
71
+ ret[:iface] = real_iface.split(':')[0]
72
+ ifconfig_data.each do |s|
73
+ case s
74
+ when /ether[\s]([0-9a-fA-F:]{17})/i
75
+ ret[:eth_saddr] = $1
76
+ ret[:eth_src] = EthHeader.mac2str(ret[:eth_saddr])
77
+ when /inet[\s]*([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)(.*Mask[\s]+(0x[a-f0-9]+))?/i
78
+ imask = 0
79
+ if $3
80
+ imask = $3.to_i(16).to_s(2).count("1")
81
+ end
82
+
83
+ ret[:ip_saddr] = $1
84
+ ret[:ip_src] = [IPAddr.new($1).to_i].pack("N")
85
+ ret[:ip4_obj] = IPAddr.new($1)
86
+ ret[:ip4_obj] = ret[:ip4_obj].mask(imask) if imask
87
+ when /inet6[\s]*([0-9a-fA-F:\x2f]+)/
88
+ ret[:ip6_saddr] = $1
89
+ ret[:ip6_obj] = IPAddr.new($1)
90
+ end
91
+ end # darwin
92
+ end # RUBY_PLATFORM
93
+ ret
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,131 @@
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 'thread'
13
+
14
+ require 'bettercap/error'
15
+ require 'bettercap/logger'
16
+ require 'bettercap/shell'
17
+ require 'bettercap/target'
18
+ require 'bettercap/factories/firewall_factory'
19
+ require 'bettercap/discovery/icmp'
20
+ require 'bettercap/discovery/udp'
21
+ require 'bettercap/discovery/syn'
22
+ require 'bettercap/discovery/arp'
23
+
24
+ class Network
25
+
26
+ def Network.is_ip?(ip)
27
+ if /\A(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\Z/ =~ ip
28
+ return $~.captures.all? {|i| i.to_i < 256}
29
+ end
30
+ false
31
+ end
32
+
33
+ def Network.get_gateway
34
+ nstat = Shell.execute('netstat -nr')
35
+
36
+ Logger.debug "NETSTAT:\n#{nstat}"
37
+
38
+ out = nstat.split(/\n/).select {|n| n =~ /UG/ }
39
+ gw = out.first.split[1]
40
+
41
+ raise BetterCap::Error, 'Could not detect gateway address' unless is_ip?(gw)
42
+ gw
43
+ end
44
+
45
+ def Network.get_local_ips
46
+ ips = []
47
+
48
+ Shell.ifconfig.split("\n").each do |line|
49
+ if line =~ /inet [adr:]*([\d\.]+)/
50
+ ips << $1
51
+ end
52
+ end
53
+
54
+ ips
55
+ end
56
+
57
+ def Network.get_alive_targets( ctx, timeout = 5 )
58
+ if ctx.options[:arpcache] == false
59
+ icmp = IcmpAgent.new timeout
60
+ udp = UdpAgent.new ctx.ifconfig, ctx.gateway, ctx.iface[:ip_saddr]
61
+ syn = SynAgent.new ctx.ifconfig, ctx.gateway, ctx.iface[:ip_saddr]
62
+
63
+ syn.wait
64
+ icmp.wait
65
+ udp.wait
66
+ else
67
+ Logger.debug 'Using current ARP cache.'
68
+ end
69
+
70
+ ArpAgent.parse ctx
71
+ end
72
+
73
+ =begin
74
+ FIXME:
75
+
76
+ Apparently on Mac OSX the gem pcaprub ( or libpcap itself ) has
77
+ a bug, so we can't use 'PacketFu::Utils::arp' since the funtion
78
+ it's using:
79
+
80
+ if cap.save > 0
81
+ ...
82
+ end
83
+
84
+ won't catch anything, instead we're using cap.stream.each.
85
+ =end
86
+ def Network.get_hw_address( iface, ip_address, attempts = 2 )
87
+ hw_address = nil
88
+
89
+ attempts.times do
90
+ arp_pkt = PacketFu::ARPPacket.new
91
+
92
+ arp_pkt.eth_saddr = arp_pkt.arp_saddr_mac = iface[:eth_saddr]
93
+ arp_pkt.eth_daddr = 'ff:ff:ff:ff:ff:ff'
94
+ arp_pkt.arp_daddr_mac = '00:00:00:00:00:00'
95
+ arp_pkt.arp_saddr_ip = iface[:ip_saddr]
96
+ arp_pkt.arp_daddr_ip = ip_address
97
+
98
+ cap_thread = Thread.new do
99
+ target_mac = nil
100
+ cap = PacketFu::Capture.new(
101
+ :iface => iface[:iface],
102
+ :start => true,
103
+ :filter => "arp src #{ip_address} and ether dst #{arp_pkt.eth_saddr}"
104
+ )
105
+ arp_pkt.to_w(iface[:iface])
106
+
107
+ timeout = 0
108
+
109
+ while target_mac.nil? && timeout <= 5
110
+ cap.stream.each do |p|
111
+ arp_response = PacketFu::Packet.parse(p)
112
+ target_mac = arp_response.arp_saddr_mac if arp_response.arp_saddr_ip == ip_address
113
+
114
+ break unless target_mac.nil?
115
+ end
116
+
117
+ timeout += 0.1
118
+
119
+ Logger.debug 'Retrying ...'
120
+ sleep 0.1
121
+ end
122
+ target_mac
123
+ end
124
+ hw_address = cap_thread.value
125
+
126
+ break unless hw_address.nil?
127
+ end
128
+
129
+ hw_address
130
+ end
131
+ end
@@ -0,0 +1,39 @@
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
+
14
+ module Proxy
15
+
16
+ class Module
17
+ @@modules = []
18
+
19
+ def self.modules
20
+ @@modules
21
+ end
22
+
23
+ # we're enabled by default, yo!
24
+ def is_enabled?
25
+ true
26
+ end
27
+
28
+ def self.register_modules
29
+ Object.constants.each do |klass|
30
+ const = Kernel.const_get(klass)
31
+ if const.respond_to?(:superclass) and const.superclass == self
32
+ Logger.debug "Registering module #{const}"
33
+ @@modules << const.new
34
+ end
35
+ end
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,262 @@
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
+
13
+ require 'socket'
14
+ require 'uri'
15
+
16
+ require 'bettercap/logger'
17
+ require 'bettercap/network'
18
+
19
+ module Proxy
20
+
21
+ class Proxy
22
+ def initialize address, port, &processor
23
+ @socket = nil
24
+ @address = address
25
+ @port = port
26
+ @main_thread = nil
27
+ @running = false
28
+ @processor = processor
29
+ @local_ips = []
30
+
31
+ begin
32
+ @local_ips = Socket.ip_address_list.collect { |x| x.ip_address }
33
+ rescue
34
+ Logget.warn 'Could not get local ips using Socket module, using Network.get_local_ips method.'
35
+
36
+ @local_ips = Network.get_local_ips
37
+ end
38
+ end
39
+
40
+ def start
41
+ begin
42
+ @socket = TCPServer.new( @address, @port )
43
+ @main_thread = Thread.new &method(:server_thread)
44
+ rescue Exception => e
45
+ Logger.error "Error starting proxy: #{e.inspect}"
46
+ @socket.close unless @socket.nil?
47
+ end
48
+ end
49
+
50
+ def stop
51
+ begin
52
+ Logger.info 'Stopping proxy ...'
53
+
54
+ if @socket and @running
55
+ @running = false
56
+ @socket.close
57
+ end
58
+ rescue
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def server_thread
65
+ Logger.info "Proxy started on #{@address}:#{@port} ...\n"
66
+
67
+ @running = true
68
+
69
+ begin
70
+ while @running do
71
+ Thread.new @socket.accept, &method(:client_thread)
72
+ end
73
+ rescue Exception => e
74
+ if @running
75
+ Logger.warn "Error while accepting connection: #{e.inspect}"
76
+ end
77
+ ensure
78
+ @socket.close unless @socket.nil?
79
+ end
80
+ end
81
+
82
+ def binary_streaming from, to, opts = {}
83
+
84
+ total_size = 0
85
+
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
+ if not opts[:response].nil?
89
+ to.write opts[:response].to_s
90
+
91
+ total_size = opts[:response].content_length unless opts[:response].content_length.nil?
92
+ elsif not opts[:request].nil?
93
+
94
+ total_size = opts[:request].content_length unless opts[:request].content_length.nil?
95
+ end
96
+
97
+ buff = ""
98
+ read = 0
99
+
100
+ if total_size
101
+ chunk_size = 1024
102
+ else
103
+ chunk_size = [ 1024, total_size ].min
104
+ end
105
+
106
+ if chunk_size > 0
107
+ loop do
108
+ from.read chunk_size, buff
109
+
110
+ # nothing more to read?
111
+ break unless buff.size > 0
112
+
113
+ to.write buff
114
+
115
+ read += buff.size
116
+
117
+ # collect into the proper object
118
+ if not opts[:request].nil? and opts[:request].is_post?
119
+ opts[:request] << buff
120
+ end
121
+
122
+ # we've done reading?
123
+ break unless read != total_size
124
+ end
125
+ end
126
+ end
127
+
128
+ def html_streaming request, response, from, to
129
+ buff = ""
130
+ loop do
131
+ from.read 1024, buff
132
+
133
+ break unless buff.size > 0
134
+
135
+ response << buff
136
+ end
137
+
138
+ @processor.call( request, response )
139
+
140
+ # Response::to_s will patch the headers if needed
141
+ to.write response.to_s
142
+ end
143
+
144
+ def log_stream client, request, response
145
+ client_s = "[#{client}]"
146
+ verb_s = request.verb
147
+ request_s = "http://#{request.host}#{request.url}"
148
+ response_s = "( #{response.content_type} )"
149
+ request_s = request_s.slice(0..50) + "..." unless request_s.length <= 50
150
+
151
+ verb_s = verb_s.light_blue
152
+
153
+ if response.code[0] == '2'
154
+ response_s += " [#{response.code}]".green
155
+ elsif response.code[0] == '3'
156
+ response_s += " [#{response.code}]".light_black
157
+ elsif response.code[0] == '4'
158
+ response_s += " [#{response.code}]".yellow
159
+ elsif response.code[0] == '5'
160
+ response_s += " [#{response.code}]".red
161
+ else
162
+ response_s += " [#{response.code}]"
163
+ end
164
+
165
+ Logger.write "#{client_s} #{verb_s} #{request_s} #{response_s}"
166
+ end
167
+
168
+ def is_self_request? request
169
+ @local_ips.include? IPSocket.getaddress(request.host)
170
+ end
171
+
172
+ def rickroll_lamer client
173
+ client.write "HTTP/1.1 302 Found\n"
174
+ client.write "Location: https://www.youtube.com/watch?v=dQw4w9WgXcQ\n\n"
175
+ end
176
+
177
+ def client_thread client
178
+ client_port, client_ip = Socket.unpack_sockaddr_in(client.getpeername)
179
+ Logger.debug "New connection from #{client_ip}:#{client_port}"
180
+
181
+ server = nil
182
+ request = Request.new
183
+
184
+ begin
185
+ # read the first line
186
+ request << client.readline
187
+
188
+ loop do
189
+ line = client.readline
190
+ request << line
191
+
192
+ if line.chomp == ""
193
+ break
194
+ end
195
+ end
196
+
197
+ raise "Couldn't extract host from the request." unless request.host
198
+
199
+ # someone is having fun with us =)
200
+ if is_self_request? request
201
+
202
+ Logger.warn "#{client_ip} is connecting to us directly."
203
+
204
+ rickroll_lamer client
205
+
206
+ else
207
+
208
+ server = TCPSocket.new( request.host, request.port )
209
+
210
+ server.write request.to_s
211
+
212
+ # this is probably a POST request, collect incoming data
213
+ if request.content_length > 0
214
+ Logger.debug "Getting #{request.content_length} bytes from client"
215
+
216
+ binary_streaming client, server, :request => request
217
+ end
218
+
219
+ Logger.debug 'Reading response ...'
220
+
221
+ response = Response.new
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
231
+
232
+ if response.is_textual?
233
+ log_stream client_ip, request, response
234
+
235
+ Logger.debug 'Detected textual response'
236
+
237
+ html_streaming request, response, server, client
238
+ else
239
+ Logger.debug "[#{client_ip}] -> #{request.host}#{request.url} [#{response.code}]"
240
+
241
+ Logger.debug 'Binary streaming'
242
+
243
+ binary_streaming server, client, :response => response
244
+ end
245
+
246
+ Logger.debug "#{client_ip}:#{client_port} served."
247
+ end
248
+
249
+ rescue Exception => e
250
+ if request.host
251
+ Logger.debug "Error while serving #{request.host}#{request.url}: #{e.inspect}"
252
+ Logger.debug e.backtrace
253
+ end
254
+ ensure
255
+ client.close
256
+ server.close unless server.nil?
257
+ end
258
+ end
259
+ end
260
+
261
+
262
+ end