bettercap 1.6.1 → 1.6.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +33 -34
  3. data/bin/bettercap +1 -1
  4. data/lib/bettercap/context.rb +1 -1
  5. data/lib/bettercap/discovery/agents/mdns.rb +61 -0
  6. data/lib/bettercap/discovery/agents/upnp.rb +60 -0
  7. data/lib/bettercap/discovery/agents/wsd.rb +75 -0
  8. data/lib/bettercap/firewalls/linux.rb +0 -4
  9. data/lib/bettercap/logger.rb +63 -34
  10. data/lib/bettercap/network/network.rb +1 -1
  11. data/lib/bettercap/options/core_options.rb +1 -1
  12. data/lib/bettercap/proxy/http/modules/redirect.rb +1 -1
  13. data/lib/bettercap/proxy/http/proxy.rb +1 -9
  14. data/lib/bettercap/proxy/http/sslstrip/strip.rb +5 -5
  15. data/lib/bettercap/sniffer/parsers/asterisk.rb +37 -0
  16. data/lib/bettercap/sniffer/parsers/bfd.rb +159 -0
  17. data/lib/bettercap/sniffer/parsers/dhcp.rb +23 -23
  18. data/lib/bettercap/sniffer/parsers/dict.rb +13 -11
  19. data/lib/bettercap/sniffer/parsers/hsrp.rb +262 -0
  20. data/lib/bettercap/sniffer/parsers/https.rb +17 -19
  21. data/lib/bettercap/sniffer/parsers/mpd.rb +12 -10
  22. data/lib/bettercap/sniffer/parsers/nntp.rb +5 -1
  23. data/lib/bettercap/sniffer/parsers/post.rb +8 -9
  24. data/lib/bettercap/sniffer/parsers/radius.rb +410 -0
  25. data/lib/bettercap/sniffer/parsers/redis.rb +15 -13
  26. data/lib/bettercap/sniffer/parsers/rlogin.rb +20 -19
  27. data/lib/bettercap/sniffer/parsers/snmp.rb +16 -17
  28. data/lib/bettercap/sniffer/parsers/snpp.rb +13 -11
  29. data/lib/bettercap/sniffer/parsers/teamtalk.rb +41 -0
  30. data/lib/bettercap/sniffer/parsers/teamviewer.rb +8 -8
  31. data/lib/bettercap/sniffer/parsers/url.rb +6 -6
  32. data/lib/bettercap/sniffer/parsers/whatsapp.rb +6 -7
  33. data/lib/bettercap/sniffer/parsers/wol.rb +68 -0
  34. data/lib/bettercap/spoofers/arp.rb +3 -3
  35. data/lib/bettercap/spoofers/hsrp.rb +351 -0
  36. data/lib/bettercap/spoofers/mac.rb +126 -0
  37. data/lib/bettercap/version.rb +1 -1
  38. 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/docs/".bold
122
+ puts "\nFor examples & docs please visit " + "https://bettercap.org/".bold
123
123
  exit
124
124
  end
125
125
 
@@ -5,7 +5,7 @@ BETTERCAP
5
5
 
6
6
  Author : Simone 'evilsocket' Margaritelli
7
7
  Email : evilsocket@gmail.com
8
- Blog : http://www.evilsocket.net/
8
+ Blog : https://www.evilsocket.net/
9
9
 
10
10
  This project is released under the GPL 3 license.
11
11
 
@@ -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? IPSocket.getaddress(request.host)
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.info "[#{'SSLSTRIP'.green} #{request.client}] Sending expired cookies for '#{request.host}'."
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.info "[#{'SSLSTRIP'.green} #{request.client}] Found stripped HTTPS link '#{url}', proxying via SSL ( #{request.to_url} )."
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.info "[#{'SSLSTRIP'.green} #{request.client}] Sending spoofed favicon '#{request.to_url }'."
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.info "[#{'SSLSTRIP'.green} #{request.client}] Found redirect to HTTPS '#{original}' -> '#{stripped}'."
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.info "[#{'SSLSTRIP'.green} #{request.client}] Found redirect to HTTPS ( with cookies ) '#{original}' -> '#{stripped}'."
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
- begin
20
- if pkt.udp_dst == 67 or pkt.udp_dst == 68
21
- packet = Network::Protos::DHCP::Packet.parse( pkt.payload )
22
- unless packet.nil?
23
- auth = packet.authentication
24
- cid = auth.nil?? nil : packet.client_identifier
25
- msg = "[#{packet.type.yellow}] #{'Transaction-ID'.green}=#{sprintf( "0x%X", packet.xid ).yellow}"
26
-
27
- unless cid.nil?
28
- msg += " #{'Client-ID'.green}='#{cid.yellow}'"
29
- end
30
-
31
- unless auth.nil?
32
- msg += "\n#{'AUTHENTICATION'.green}:\n\n"
33
- auth.each do |k,v|
34
- msg += " #{k.blue} : #{v.yellow}\n"
35
- end
36
- msg += "\n"
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
- rescue; end
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
- begin
22
- if pkt.tcp_dst == 2628
23
- lines = pkt.to_s.split(/\r?\n/)
24
- lines.each do |line|
25
- if line =~ /AUTH\s+(.+)\s+(.+)$/
26
- user = $1
27
- pass = $2
28
- StreamLogger.log_raw( pkt, @name, "username=#{user} password=#{pass}" )
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