bettercap 1.6.1 → 1.6.2
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/README.md +33 -34
- data/bin/bettercap +1 -1
- data/lib/bettercap/context.rb +1 -1
- data/lib/bettercap/discovery/agents/mdns.rb +61 -0
- data/lib/bettercap/discovery/agents/upnp.rb +60 -0
- data/lib/bettercap/discovery/agents/wsd.rb +75 -0
- data/lib/bettercap/firewalls/linux.rb +0 -4
- data/lib/bettercap/logger.rb +63 -34
- data/lib/bettercap/network/network.rb +1 -1
- data/lib/bettercap/options/core_options.rb +1 -1
- data/lib/bettercap/proxy/http/modules/redirect.rb +1 -1
- data/lib/bettercap/proxy/http/proxy.rb +1 -9
- data/lib/bettercap/proxy/http/sslstrip/strip.rb +5 -5
- data/lib/bettercap/sniffer/parsers/asterisk.rb +37 -0
- data/lib/bettercap/sniffer/parsers/bfd.rb +159 -0
- data/lib/bettercap/sniffer/parsers/dhcp.rb +23 -23
- data/lib/bettercap/sniffer/parsers/dict.rb +13 -11
- data/lib/bettercap/sniffer/parsers/hsrp.rb +262 -0
- data/lib/bettercap/sniffer/parsers/https.rb +17 -19
- data/lib/bettercap/sniffer/parsers/mpd.rb +12 -10
- data/lib/bettercap/sniffer/parsers/nntp.rb +5 -1
- data/lib/bettercap/sniffer/parsers/post.rb +8 -9
- data/lib/bettercap/sniffer/parsers/radius.rb +410 -0
- data/lib/bettercap/sniffer/parsers/redis.rb +15 -13
- data/lib/bettercap/sniffer/parsers/rlogin.rb +20 -19
- data/lib/bettercap/sniffer/parsers/snmp.rb +16 -17
- data/lib/bettercap/sniffer/parsers/snpp.rb +13 -11
- data/lib/bettercap/sniffer/parsers/teamtalk.rb +41 -0
- data/lib/bettercap/sniffer/parsers/teamviewer.rb +8 -8
- data/lib/bettercap/sniffer/parsers/url.rb +6 -6
- data/lib/bettercap/sniffer/parsers/whatsapp.rb +6 -7
- data/lib/bettercap/sniffer/parsers/wol.rb +68 -0
- data/lib/bettercap/spoofers/arp.rb +3 -3
- data/lib/bettercap/spoofers/hsrp.rb +351 -0
- data/lib/bettercap/spoofers/mac.rb +126 -0
- data/lib/bettercap/version.rb +1 -1
- metadata +13 -2
@@ -142,7 +142,7 @@ class << self
|
|
142
142
|
if ctx.options.core.use_ipv6
|
143
143
|
BetterCap::Loader.load("BetterCap::Discovery::Agents::Ndp").new(ctx, address)
|
144
144
|
else
|
145
|
-
[ 'Icmp', 'Udp', 'Arp' ].each do |name|
|
145
|
+
[ 'Icmp', 'Udp', 'Arp', 'Mdns', 'Upnp', 'Wsd' ].each do |name|
|
146
146
|
BetterCap::Loader.load("BetterCap::Discovery::Agents::#{name}").new(ctx, address)
|
147
147
|
end
|
148
148
|
end
|
@@ -119,7 +119,7 @@ class CoreOptions
|
|
119
119
|
|
120
120
|
opts.on( '-h', '--help', 'Display the available options.') do
|
121
121
|
puts opts
|
122
|
-
puts "\nFor examples & docs please visit " + "https://bettercap.org/
|
122
|
+
puts "\nFor examples & docs please visit " + "https://bettercap.org/".bold
|
123
123
|
exit
|
124
124
|
end
|
125
125
|
|
@@ -136,7 +136,7 @@ class Proxy
|
|
136
136
|
# ip addresses.
|
137
137
|
def is_self_request?(request)
|
138
138
|
begin
|
139
|
-
return @local_ips.include?
|
139
|
+
return @local_ips.include? request.host
|
140
140
|
rescue; end
|
141
141
|
false
|
142
142
|
end
|
@@ -146,12 +146,7 @@ class Proxy
|
|
146
146
|
request = Request.new @upstream_port
|
147
147
|
|
148
148
|
begin
|
149
|
-
Logger.debug 'Reading request ...'
|
150
|
-
|
151
149
|
request.read(client)
|
152
|
-
|
153
|
-
Logger.debug 'Request parsed.'
|
154
|
-
|
155
150
|
# stripped request
|
156
151
|
if @streamer.was_stripped?( request, client )
|
157
152
|
@streamer.handle( request, client )
|
@@ -163,11 +158,8 @@ class Proxy
|
|
163
158
|
@streamer.handle( request, client )
|
164
159
|
end
|
165
160
|
|
166
|
-
Logger.debug "#{@type} client served."
|
167
|
-
|
168
161
|
rescue SocketError => se
|
169
162
|
Logger.debug "Socket error while serving client: #{se.message}"
|
170
|
-
# Logger.exception se
|
171
163
|
rescue Errno::EPIPE => ep
|
172
164
|
Logger.debug "Connection closed while serving client."
|
173
165
|
rescue EOFError => eof
|
@@ -195,7 +195,7 @@ class Strip
|
|
195
195
|
response = nil
|
196
196
|
# check for cookies.
|
197
197
|
unless @cookies.is_clean?(request)
|
198
|
-
Logger.
|
198
|
+
Logger.debug "[#{'SSLSTRIP'.green} #{request.client}] Sending expired cookies for '#{request.host}'."
|
199
199
|
expired = @cookies.get_expired_headers!(request)
|
200
200
|
response = Response.redirect( request.to_url(nil), expired )
|
201
201
|
end
|
@@ -222,14 +222,14 @@ class Strip
|
|
222
222
|
end
|
223
223
|
request.port = 443
|
224
224
|
|
225
|
-
Logger.
|
225
|
+
Logger.debug "[#{'SSLSTRIP'.green} #{request.client}] Found stripped HTTPS link '#{url}', proxying via SSL ( #{request.to_url} )."
|
226
226
|
end
|
227
227
|
end
|
228
228
|
|
229
229
|
# If +request+ is the favicon of a stripped host, send our spoofed lock icon.
|
230
230
|
def spoof_favicon!(request)
|
231
231
|
if was_stripped?(request) and is_favicon?(request)
|
232
|
-
Logger.
|
232
|
+
Logger.debug "[#{'SSLSTRIP'.green} #{request.client}] Sending spoofed favicon '#{request.to_url }'."
|
233
233
|
return @favicon
|
234
234
|
end
|
235
235
|
nil
|
@@ -254,14 +254,14 @@ class Strip
|
|
254
254
|
|
255
255
|
# no cookies set, just a normal http -> https redirect
|
256
256
|
if response['Set-Cookie'].empty?
|
257
|
-
Logger.
|
257
|
+
Logger.debug "[#{'SSLSTRIP'.green} #{request.client}] Found redirect to HTTPS '#{original}' -> '#{stripped}'."
|
258
258
|
# The request will be retried on port 443 if MAX_REDIRECTS is not reached.
|
259
259
|
request.port = 443
|
260
260
|
# retry the request if possible
|
261
261
|
return true
|
262
262
|
# cookies set, this is probably a redirect after a login.
|
263
263
|
else
|
264
|
-
Logger.
|
264
|
+
Logger.debug "[#{'SSLSTRIP'.green} #{request.client}] Found redirect to HTTPS ( with cookies ) '#{original}' -> '#{stripped}'."
|
265
265
|
# we know this session, do not kill it!
|
266
266
|
@cookies.add!( request )
|
267
267
|
# remove the 'secure' flag from every cookie.
|
@@ -0,0 +1,37 @@
|
|
1
|
+
=begin
|
2
|
+
|
3
|
+
BETTERCAP
|
4
|
+
|
5
|
+
Author : Simone 'evilsocket' Margaritelli
|
6
|
+
Email : evilsocket@gmail.com
|
7
|
+
Blog : https://www.evilsocket.net/
|
8
|
+
|
9
|
+
Asterisk Call Manager authentication parser:
|
10
|
+
Author : Brendan Coles
|
11
|
+
Email : bcoles[at]gmail.com
|
12
|
+
|
13
|
+
This project is released under the GPL 3 license.
|
14
|
+
|
15
|
+
=end
|
16
|
+
|
17
|
+
module BetterCap
|
18
|
+
module Parsers
|
19
|
+
# Asterisk Call Manager authentication parser.
|
20
|
+
class Asterisk < Base
|
21
|
+
def initialize
|
22
|
+
@name = 'Asterisk'
|
23
|
+
end
|
24
|
+
def on_packet( pkt )
|
25
|
+
return unless pkt.tcp_dst == 5038
|
26
|
+
return unless pkt.to_s =~ /action:\s+login\r?\n/i
|
27
|
+
|
28
|
+
if pkt.to_s =~ /username:\s+(.+?)\r?\n/i && pkt.to_s =~ /secret:\s+(.+?)\r?\n/i
|
29
|
+
user = pkt.to_s.scan(/username:\s+(.+?)\r?\n/i).flatten.first
|
30
|
+
pass = pkt.to_s.scan(/secret:\s+(.+?)\r?\n/i).flatten.first
|
31
|
+
StreamLogger.log_raw( pkt, @name, "username=#{user} password=#{pass}" )
|
32
|
+
end
|
33
|
+
rescue
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
=begin
|
3
|
+
|
4
|
+
BETTERCAP
|
5
|
+
|
6
|
+
Author : Simone 'evilsocket' Margaritelli
|
7
|
+
Email : evilsocket@gmail.com
|
8
|
+
Blog : https://www.evilsocket.net/
|
9
|
+
|
10
|
+
Bidirectional Forwarding Detection (BFD) packet and authentication parser:
|
11
|
+
Author : Brendan Coles
|
12
|
+
Email : bcoles[at]gmail.com
|
13
|
+
|
14
|
+
This project is released under the GPL 3 license.
|
15
|
+
|
16
|
+
=end
|
17
|
+
|
18
|
+
module BetterCap
|
19
|
+
module Parsers
|
20
|
+
#
|
21
|
+
# Bidirectional Forwarding Detection (BFD) packet and authentication parser.
|
22
|
+
#
|
23
|
+
# References:
|
24
|
+
# - https://tools.ietf.org/html/rfc5880#section-4
|
25
|
+
# - https://en.wikipedia.org/wiki/Bidirectional_Forwarding_Detection
|
26
|
+
#
|
27
|
+
class Bfd < Base
|
28
|
+
def initialize
|
29
|
+
@name = 'BFD'
|
30
|
+
end
|
31
|
+
|
32
|
+
def on_packet( pkt )
|
33
|
+
return unless (pkt.udp_dst == 3784 && pkt.payload.length > 30)
|
34
|
+
|
35
|
+
# It appears PacketFu drops the first two bits from packet.payload
|
36
|
+
# (because they're 00?), so let's insert them for consistency.
|
37
|
+
data = '00' + pkt.payload.to_s.unpack('H*').first.to_i(16).to_s(2)
|
38
|
+
|
39
|
+
=begin
|
40
|
+
|
41
|
+
Packet format from RFC5880, section 4:
|
42
|
+
|
43
|
+
The Mandatory Section of a BFD Control packet has the following
|
44
|
+
format:
|
45
|
+
|
46
|
+
0 1 2 3
|
47
|
+
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
48
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
49
|
+
|Vers | Diag |Sta|P|F|C|A|D|M| Detect Mult | Length |
|
50
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
51
|
+
| My Discriminator |
|
52
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
53
|
+
| Your Discriminator |
|
54
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
55
|
+
| Desired Min TX Interval |
|
56
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
57
|
+
| Required Min RX Interval |
|
58
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
59
|
+
| Required Min Echo RX Interval |
|
60
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
61
|
+
|
62
|
+
An optional Authentication Section MAY be present:
|
63
|
+
|
64
|
+
0 1 2 3
|
65
|
+
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
66
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
67
|
+
| Auth Type | Auth Len | Authentication Data... |
|
68
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
69
|
+
|
70
|
+
=end
|
71
|
+
|
72
|
+
# Mandatory Section of a BFD Control packet
|
73
|
+
# -----------------------------------------
|
74
|
+
# data[0..2] # Protocol version
|
75
|
+
|
76
|
+
# This parser supports only BFD version 1
|
77
|
+
return unless data[0..2] == '001'
|
78
|
+
|
79
|
+
# data[3..7] # Diagnostic Code
|
80
|
+
# data[8..9] # State
|
81
|
+
# data[10..15] # Message Flags
|
82
|
+
# - data[10] # - Poll (P)
|
83
|
+
# - data[11] # - Final (F)
|
84
|
+
# - data[12] # - Control Plane Independent (C)
|
85
|
+
# - data[13] # - Authentication Present (A)
|
86
|
+
# - data[14] # - Demand (D)
|
87
|
+
# - data[15] # - Multipoint (M)
|
88
|
+
|
89
|
+
# We're only interested in packets with authentication present
|
90
|
+
return unless data[13] == '1'
|
91
|
+
|
92
|
+
# data[16..23] # Detection time multiplier
|
93
|
+
# data[24..31] # Length of the BFD Control packet, in bytes.
|
94
|
+
# data[32..63] # My Discriminator
|
95
|
+
# data[64..95] # Your Discriminator
|
96
|
+
# data[96..127] # Desired Min TX Interval
|
97
|
+
# data[128..159] # Required Min RX Interval
|
98
|
+
# data[160..191] # Required Min Echo RX Interval
|
99
|
+
|
100
|
+
|
101
|
+
=begin
|
102
|
+
|
103
|
+
Simple Password Authentication Section Format from RFC5880, section 4.2:
|
104
|
+
|
105
|
+
0 1 2 3
|
106
|
+
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
107
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
108
|
+
| Auth Type | Auth Len | Auth Key ID | Password... |
|
109
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
110
|
+
| ... |
|
111
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
112
|
+
|
113
|
+
=end
|
114
|
+
|
115
|
+
# Simple Authentication Section
|
116
|
+
# -----------------------------
|
117
|
+
# data[192..199] # Auth Type
|
118
|
+
# 0 - Reserved
|
119
|
+
# 1 - Simple Password
|
120
|
+
# 2 - Keyed MD5
|
121
|
+
# 3 - Meticulous Keyed MD5
|
122
|
+
# 4 - Keyed SHA1
|
123
|
+
# 5 - Meticulous Keyed SHA1
|
124
|
+
# 6-255 - Reserved for future use
|
125
|
+
auth_type = data[192..199].to_i(2)
|
126
|
+
|
127
|
+
# We're only interested in simple authentication
|
128
|
+
return unless auth_type == 1
|
129
|
+
|
130
|
+
log = []
|
131
|
+
log << "#{'AuthType'.blue}=Simple"
|
132
|
+
|
133
|
+
# data[200..207] # Auth Length - the length (in bytes) of the authentication section, including:
|
134
|
+
# Auth Type (1 byte)
|
135
|
+
# Auth Length (1 byte)
|
136
|
+
# Auth Key ID (1 byte)
|
137
|
+
auth_len = data[200..207].to_i(2) - 3 # minus 3 to account for auth headers
|
138
|
+
|
139
|
+
# data[208..215] # Auth Key ID
|
140
|
+
auth_key = data[208..215].to_i(2)
|
141
|
+
log << "#{'AuthKeyID'.blue}=#{auth_key}"
|
142
|
+
|
143
|
+
# data[216..??] # Password:
|
144
|
+
# 216 to (216 + auth_len)
|
145
|
+
if auth_len <= 0
|
146
|
+
log << "#{'Password'.blue}=null"
|
147
|
+
else
|
148
|
+
password = data[216...( 216 + (auth_len * 8) )]
|
149
|
+
password_hex = password.to_i(2).to_s(16)
|
150
|
+
password_ascii = [password.to_i(2).to_s(16)].pack('H*')
|
151
|
+
log << "#{'Password'.blue}=#{password_hex} ('#{password_ascii.yellow}')"
|
152
|
+
end
|
153
|
+
|
154
|
+
StreamLogger.log_raw pkt, @name, log.join(' ')
|
155
|
+
rescue
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -16,30 +16,30 @@ module Parsers
|
|
16
16
|
# DHCP packets and authentication parser.
|
17
17
|
class DHCP < Base
|
18
18
|
def on_packet( pkt )
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
StreamLogger.log_raw( pkt, 'DHCP', msg )
|
40
|
-
end
|
19
|
+
return unless (pkt.udp_dst == 67 || pkt.udp_dst == 68)
|
20
|
+
|
21
|
+
packet = Network::Protos::DHCP::Packet.parse( pkt.payload )
|
22
|
+
|
23
|
+
return if packet.nil?
|
24
|
+
|
25
|
+
auth = packet.authentication
|
26
|
+
cid = auth.nil?? nil : packet.client_identifier
|
27
|
+
msg = "[#{packet.type.yellow}] #{'Transaction-ID'.green}=#{sprintf( "0x%X", packet.xid ).yellow}"
|
28
|
+
|
29
|
+
unless cid.nil?
|
30
|
+
msg += " #{'Client-ID'.green}='#{cid.yellow}'"
|
31
|
+
end
|
32
|
+
|
33
|
+
unless auth.nil?
|
34
|
+
msg += "\n#{'AUTHENTICATION'.green}:\n\n"
|
35
|
+
auth.each do |k,v|
|
36
|
+
msg += " #{k.blue} : #{v.yellow}\n"
|
41
37
|
end
|
42
|
-
|
38
|
+
msg += "\n"
|
39
|
+
end
|
40
|
+
|
41
|
+
StreamLogger.log_raw( pkt, 'DHCP', msg )
|
42
|
+
rescue
|
43
43
|
end
|
44
44
|
end
|
45
45
|
end
|
@@ -6,6 +6,10 @@ Author : Simone 'evilsocket' Margaritelli
|
|
6
6
|
Email : evilsocket@gmail.com
|
7
7
|
Blog : https://www.evilsocket.net/
|
8
8
|
|
9
|
+
DICT authentication parser:
|
10
|
+
Author : Brendan Coles
|
11
|
+
Email : bcoles[at]gmail.com
|
12
|
+
|
9
13
|
This project is released under the GPL 3 license.
|
10
14
|
|
11
15
|
=end
|
@@ -18,19 +22,17 @@ class Dict < Base
|
|
18
22
|
@name = 'DICT'
|
19
23
|
end
|
20
24
|
def on_packet( pkt )
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
end
|
30
|
-
end
|
25
|
+
return unless pkt.tcp_dst == 2628
|
26
|
+
|
27
|
+
lines = pkt.to_s.split(/\r?\n/)
|
28
|
+
lines.each do |line|
|
29
|
+
if line =~ /AUTH\s+(.+)\s+(.+)$/
|
30
|
+
user = $1
|
31
|
+
pass = $2
|
32
|
+
StreamLogger.log_raw( pkt, @name, "username=#{user} password=#{pass}" )
|
31
33
|
end
|
32
|
-
rescue
|
33
34
|
end
|
35
|
+
rescue
|
34
36
|
end
|
35
37
|
end
|
36
38
|
end
|
@@ -0,0 +1,262 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
=begin
|
3
|
+
|
4
|
+
BETTERCAP
|
5
|
+
|
6
|
+
Author : Simone 'evilsocket' Margaritelli
|
7
|
+
Email : evilsocket@gmail.com
|
8
|
+
Blog : https://www.evilsocket.net/
|
9
|
+
|
10
|
+
HSRP packet and authentication parser:
|
11
|
+
Author : Brendan Coles
|
12
|
+
Email : bcoles[at]gmail.com
|
13
|
+
|
14
|
+
This project is released under the GPL 3 license.
|
15
|
+
|
16
|
+
=end
|
17
|
+
|
18
|
+
module BetterCap
|
19
|
+
module Parsers
|
20
|
+
#
|
21
|
+
# HSRP packet and authentication parser.
|
22
|
+
#
|
23
|
+
# Supports HSRP version 0
|
24
|
+
# Supports text authentication
|
25
|
+
# Supports IPv4
|
26
|
+
#
|
27
|
+
# Does not support IPv6
|
28
|
+
# Does not support MD5 authentication
|
29
|
+
# Does not support HSRP version 2
|
30
|
+
#
|
31
|
+
# References:
|
32
|
+
# - https://en.wikipedia.org/wiki/Hot_Standby_Router_Protocol
|
33
|
+
# - https://tools.ietf.org/html/rfc2281#section-5
|
34
|
+
# - https://www.cisco.com/c/en/us/support/docs/ip/hot-standby-router-protocol-hsrp/9234-hsrpguidetoc.html
|
35
|
+
# - https://www.cisco.com/c/en/us/td/docs/ios-xml/ios/ipapp_fhrp/configuration/xe-3s/fhp-xe-3s-book/fhp-hsrp-md5.html
|
36
|
+
#
|
37
|
+
class Hsrp < Base
|
38
|
+
def initialize
|
39
|
+
@name = 'HSRP'
|
40
|
+
end
|
41
|
+
|
42
|
+
def on_packet( pkt )
|
43
|
+
if pkt.respond_to? 'hsrp_version'
|
44
|
+
parse_hsrp_packetfu pkt
|
45
|
+
elsif is_hsrp? pkt
|
46
|
+
parse_hsrp_raw pkt
|
47
|
+
end
|
48
|
+
rescue
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def parse_hsrp_raw(pkt)
|
54
|
+
log = []
|
55
|
+
data = pkt.payload.to_s.unpack('H*').first
|
56
|
+
|
57
|
+
=begin
|
58
|
+
|
59
|
+
Packet format from RFC2281, section 5:
|
60
|
+
|
61
|
+
1 2 3
|
62
|
+
|
63
|
+
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
64
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
65
|
+
| Version | Op Code | State | Hellotime |
|
66
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
67
|
+
| Holdtime | Priority | Group | Reserved |
|
68
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
69
|
+
| Authentication Data |
|
70
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
71
|
+
| Authentication Data |
|
72
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
73
|
+
| Virtual IP Address |
|
74
|
+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
75
|
+
|
76
|
+
=end
|
77
|
+
|
78
|
+
# data[0...2] # HSRP version
|
79
|
+
# HSRP versions 0 and 2 both advertise as version 0
|
80
|
+
version = data[0...2].to_i(16)
|
81
|
+
|
82
|
+
# This parser supports only HSRP version 0
|
83
|
+
return unless version == 0
|
84
|
+
|
85
|
+
# data[2...4] # Op Code:
|
86
|
+
# 0 - Hello
|
87
|
+
# Hello messages are sent to indicate that a router is running and
|
88
|
+
# is capable of becoming the active or standby router.
|
89
|
+
# 1 - Coup
|
90
|
+
# Coup messages are sent when a router wishes to become the active router.
|
91
|
+
# 2 - Resign
|
92
|
+
# Resign messages are sent when a router no longer wishes to be the
|
93
|
+
# active router.
|
94
|
+
# 3 - Advertise
|
95
|
+
# Passive HSRP Router Advertisements
|
96
|
+
# This OpCode is not described in RFC2281.
|
97
|
+
op_code = data[2...4].to_i(16)
|
98
|
+
case op_code
|
99
|
+
when 0
|
100
|
+
log << 'hello'.yellow
|
101
|
+
when 1
|
102
|
+
log << 'coup'.yellow
|
103
|
+
when 2
|
104
|
+
log << 'resign'.yellow
|
105
|
+
when 3
|
106
|
+
log << 'advertise'.yellow
|
107
|
+
else
|
108
|
+
log << 'unknown'.yellow
|
109
|
+
end
|
110
|
+
|
111
|
+
log << "#{'OpCode'.blue}=#{op_code}"
|
112
|
+
|
113
|
+
# data[4...6] # State:
|
114
|
+
# 0 - Initial
|
115
|
+
# 1 - Learn
|
116
|
+
# 2 - Listen
|
117
|
+
# 4 - Speak
|
118
|
+
# 8 - Standby
|
119
|
+
# 16 - Active
|
120
|
+
state = data[4...6].to_i(16)
|
121
|
+
case state
|
122
|
+
when 0
|
123
|
+
state_desc = 'Initial'
|
124
|
+
when 1
|
125
|
+
state_desc = 'Learn'
|
126
|
+
when 2
|
127
|
+
state_desc = 'Listen'
|
128
|
+
when 4
|
129
|
+
state_desc = 'Speak'
|
130
|
+
when 8
|
131
|
+
state_desc = 'StandBy'
|
132
|
+
when 16
|
133
|
+
state_desc = 'Active'
|
134
|
+
else
|
135
|
+
state_desc = 'Unknown'
|
136
|
+
end
|
137
|
+
|
138
|
+
log << "#{'State'.blue}=#{state_desc.yellow} (#{state})"
|
139
|
+
|
140
|
+
# data[6...8] # HelloTime - Time (in seconds) between Hello messages
|
141
|
+
hello_time = data[6...8].to_i(16)
|
142
|
+
log << "#{'HelloTime'.blue}=#{hello_time}"
|
143
|
+
|
144
|
+
# data[8...10] # HoldTime - Time (in seconds) for which the Hello message is valid
|
145
|
+
hold_time = data[8...10].to_i(16)
|
146
|
+
log << "#{'HoldTime'.blue}=#{hold_time}"
|
147
|
+
|
148
|
+
# data[10...12] # Priority
|
149
|
+
priority = data[10...12].to_i(16)
|
150
|
+
log << "#{'Priority'.blue}=#{priority.to_s.yellow}"
|
151
|
+
|
152
|
+
# data[12...14] # StandBy Group
|
153
|
+
group = data[12...14].to_i(16)
|
154
|
+
log << "#{'Group'.blue}=#{group.to_s.yellow}"
|
155
|
+
|
156
|
+
# data[14...16] # Reserved (0x00)
|
157
|
+
|
158
|
+
# data[16...32] # Authentication data
|
159
|
+
auth = data[16...32]
|
160
|
+
log << "#{'Authentication'.blue}=#{auth}"
|
161
|
+
|
162
|
+
# strip trailing null bytes from the authentication data
|
163
|
+
# and check if the remaining data is ASCII
|
164
|
+
auth_ascii = [auth.gsub(/(00)*\z/, '')].pack('H*')
|
165
|
+
if auth_ascii == ''
|
166
|
+
# null authentication may indicate HSRPv2 MD5 data occurs later in the packet
|
167
|
+
# HSRPv2 is not supported by this parser
|
168
|
+
if pkt.payload.length > 50
|
169
|
+
log << '(null, md5?)'
|
170
|
+
else
|
171
|
+
log << '(null)'
|
172
|
+
end
|
173
|
+
elsif auth_ascii =~ /\A[a-zA-Z0-9_\-+\.,"' ]*\z/
|
174
|
+
log << "('#{auth_ascii.yellow}')"
|
175
|
+
end
|
176
|
+
|
177
|
+
# data[32...40] # Virtual IP Address
|
178
|
+
virtual_ip = data[32...40].scan(/\w{2}/).map{ |c| c.to_i(16).to_s(10) }.join('.')
|
179
|
+
log << "#{'VirtualIP'.blue}='#{virtual_ip.yellow}'"
|
180
|
+
|
181
|
+
StreamLogger.log_raw pkt, @name, log.join(' ')
|
182
|
+
rescue
|
183
|
+
end
|
184
|
+
|
185
|
+
def parse_hsrp_packetfu(pkt)
|
186
|
+
log = []
|
187
|
+
|
188
|
+
op_code = pkt.hsrp_opcode
|
189
|
+
case op_code
|
190
|
+
when 0
|
191
|
+
log << 'hello'.yellow
|
192
|
+
when 1
|
193
|
+
log << 'coup'.yellow
|
194
|
+
when 2
|
195
|
+
log << 'resign'.yellow
|
196
|
+
when 3
|
197
|
+
log << 'advertise'.yellow
|
198
|
+
else
|
199
|
+
log << 'unknown'.yellow
|
200
|
+
end
|
201
|
+
|
202
|
+
log << "#{'OpCode'.blue}=#{op_code}"
|
203
|
+
|
204
|
+
state = pkt.hsrp_state
|
205
|
+
case state
|
206
|
+
when 0
|
207
|
+
state_desc = 'Initial'
|
208
|
+
when 1
|
209
|
+
state_desc = 'Learn'
|
210
|
+
when 2
|
211
|
+
state_desc = 'Listen'
|
212
|
+
when 4
|
213
|
+
state_desc = 'Speak'
|
214
|
+
when 8
|
215
|
+
state_desc = 'StandBy'
|
216
|
+
when 16
|
217
|
+
state_desc = 'Active'
|
218
|
+
else
|
219
|
+
state_desc = 'Unknown'
|
220
|
+
end
|
221
|
+
|
222
|
+
log << "#{'State'.blue}=#{state_desc.yellow} (#{state})"
|
223
|
+
log << "#{'HelloTime'.blue}=#{pkt.hsrp_hellotime}"
|
224
|
+
log << "#{'HoldTime'.blue}=#{pkt.hsrp_holdtime}"
|
225
|
+
log << "#{'Priority'.blue}=#{pkt.hsrp_priority.to_s.yellow}"
|
226
|
+
log << "#{'Group'.blue}=#{pkt.hsrp_group.to_s.yellow}"
|
227
|
+
|
228
|
+
auth = pkt.hsrp_password.unpack('H*').first
|
229
|
+
log << "#{'Authentication'.blue}=#{auth}"
|
230
|
+
|
231
|
+
# strip trailing null bytes from the authentication data
|
232
|
+
# and check if the remaining data is ASCII
|
233
|
+
auth_ascii = [auth.gsub(/(00)*\z/, '')].pack('H*')
|
234
|
+
if auth_ascii == ''
|
235
|
+
# null authentication may indicate HSRPv2 MD5 data occurs later in the packet
|
236
|
+
# HSRPv2 is not supported by this parser
|
237
|
+
if pkt.payload.length > 0
|
238
|
+
log << '(null, md5?)'
|
239
|
+
else
|
240
|
+
log << '(null)'
|
241
|
+
end
|
242
|
+
elsif auth_ascii =~ /\A[a-zA-Z0-9_\-+\.,"' ]*\z/
|
243
|
+
log << "('#{auth_ascii.yellow}')"
|
244
|
+
end
|
245
|
+
|
246
|
+
virtual_ip = pkt.hsrp_vip.to_s.unpack('H*').first.to_s.scan(/\w{2}/).map{ |c| c.to_i(16).to_s(10) }.join('.')
|
247
|
+
log << "#{'VirtualIP'.blue}='#{virtual_ip.yellow}'"
|
248
|
+
|
249
|
+
StreamLogger.log_raw pkt, @name, log.join(' ')
|
250
|
+
rescue
|
251
|
+
end
|
252
|
+
|
253
|
+
def is_hsrp?(pkt)
|
254
|
+
return ( pkt.eth_daddr == '01:00:5e:00:00:02' && \
|
255
|
+
pkt.ip_daddr == '224.0.0.2' && \
|
256
|
+
pkt.respond_to?('udp_src') && pkt.respond_to?('udp_dst') && \
|
257
|
+
( pkt.udp_src == 1985 || pkt.udp_dst == 1985 ) && \
|
258
|
+
pkt.payload.length >= 20 )
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|