bettercap 1.1.0

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