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.
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