|
1 | 1 | ##
|
2 |
| -# This module requires Metasploit: http//metasploit.com/download |
| 2 | +# This module requires Metasploit: http://metasploit.com/download |
3 | 3 | # Current source: https://github.com/rapid7/metasploit-framework
|
4 | 4 | ##
|
5 | 5 |
|
6 | 6 | require 'msf/core'
|
7 | 7 |
|
8 |
| - |
9 | 8 | class Metasploit3 < Msf::Auxiliary
|
10 |
| - |
11 | 9 | include Msf::Auxiliary::Report
|
12 | 10 | include Msf::Exploit::Capture
|
13 | 11 |
|
14 | 12 | def initialize
|
15 | 13 | super(
|
16 |
| - 'Name' => 'CDP Discovery and Spoofing', |
17 |
| - 'Description' => 'This module captures and sends Cisco Discovery Protocol packets for discovery', |
18 |
| - 'Author' => 'fozavci', |
19 |
| - 'License' => MSF_LICENSE, |
20 |
| - 'Actions' => |
21 |
| - [ |
22 |
| - [ 'Sniffer' ], |
23 |
| - [ 'Spoof' ] |
24 |
| - ], |
25 |
| - 'PassiveActions' => |
26 |
| - [ |
27 |
| - 'Sniffer' |
28 |
| - ], |
29 |
| - 'DefaultAction' => 'Sniffer' |
| 14 | + 'Name' => 'CDP Discovery and Spoofing', |
| 15 | + 'Description' => 'This module captures and sends Cisco Discovery Protocol (CDP) packets for discovery', |
| 16 | + 'Author' => 'Fatih Ozavci <viproy.com/fozavci>', |
| 17 | + 'License' => MSF_LICENSE, |
| 18 | + 'Actions' => [ |
| 19 | + ['Sniff', { 'Description' => 'Sniffs CDP packets' }], |
| 20 | + ['Spoof', { 'Description' => 'Sends spoofed CDP packets' }] |
| 21 | + ], |
| 22 | + 'PassiveActions' => %w(Sniff), |
| 23 | + 'DefaultAction' => 'Sniff' |
30 | 24 | )
|
31 | 25 | register_options(
|
32 |
| - [ |
33 |
| - OptString.new('SMAC', [ false, "MAC Address for MAC Spoofing"]), |
34 |
| - OptString.new('VTPDOMAIN', [ false, "VTP Domain"]), |
35 |
| - OptString.new('DEVICE ID', [ false, "Device ID (e.g. SIP00070EEA3156,SEP00070EEA3156)", "SEP00070EEA3156"]), |
36 |
| - OptString.new('PORT', [ false, "The Switch Port", "1"]), |
37 |
| - OptString.new('CAPABILITIES', [ false, "Capabilities of the device (e.g. Router, Host, Switch)", "Router"]), |
38 |
| - OptString.new('PLATFORM', [ false, "Platform of the device", "Cisco IP Phone 7975"]), |
39 |
| - OptString.new('SOFTWARE', [ false, "Software of the device", "SCCP75.9-3-1SR2-1S"]), |
40 |
| - OptBool.new('DUPLEX', [false, 'Duplex', true]), |
41 |
| - ], self.class) |
| 26 | + [ |
| 27 | + OptString.new('SMAC', [false, "MAC Address for MAC Spoofing"]), |
| 28 | + OptString.new('VTPDOMAIN', [false, "VTP Domain"]), |
| 29 | + OptString.new('DEVICE_ID', [true, "Device ID (e.g. SIP00070EEA3156)", "SEP00070EEA3156"]), |
| 30 | + OptString.new('PORT', [true, "The CDP 'sent through interface' value", "Port 1"]), |
| 31 | + # XXX: this is not currently implemented |
| 32 | + # OptString.new('CAPABILITIES', [false, "Capabilities of the device (e.g. Router, Host, Switch)", "Router"]), |
| 33 | + OptString.new('PLATFORM', [true, "Platform of the device", "Cisco IP Phone 7975"]), |
| 34 | + OptString.new('SOFTWARE', [true, "Software of the device", "SCCP75.9-3-1SR2-1S"]), |
| 35 | + OptBool.new('FULL_DUPLEX', [true, 'True iff full-duplex, false otherwise', true]) |
| 36 | + ], self.class) |
42 | 37 | deregister_options('RHOST')
|
43 | 38 | end
|
44 | 39 |
|
45 |
| - def run |
46 |
| - check_pcaprub_loaded # Check first |
47 |
| - lbl=["CDP Version\t","Device Id\t","IP Address\t", "Switch Port\t", "Capabilities" , "Software\t","Platform\t", nil,"Cluster Management", "VTP Domain Management" ,"Native VLAN\t",nil, nil, nil, nil, "VoIP VLAN Query"] |
48 |
| - print_status("Sniffing traffic.....") |
49 |
| - |
| 40 | + def setup |
| 41 | + check_pcaprub_loaded |
| 42 | + unless smac |
| 43 | + fail ArgumentError, "Unable to get SMAC from #{interface} -- Set INTERFACE or SMAC" |
| 44 | + end |
50 | 45 | open_pcap
|
| 46 | + close_pcap |
| 47 | + end |
51 | 48 |
|
52 |
| - if(action.name == 'Spoof') |
53 |
| - send_spoof |
| 49 | + def interface |
| 50 | + @interface ||= datastore['INTERFACE'] || Pcap.lookupdev |
| 51 | + end |
| 52 | + |
| 53 | + def smac |
| 54 | + @smac ||= datastore['SMAC'] || get_mac(interface) |
| 55 | + end |
| 56 | + |
| 57 | + def run |
| 58 | + begin |
| 59 | + open_pcap |
| 60 | + |
| 61 | + case action.name |
| 62 | + when 'Spoof' |
| 63 | + do_spoof |
| 64 | + when 'Sniff' |
| 65 | + do_sniff |
| 66 | + else |
| 67 | + # this should never happen |
| 68 | + fail ArgumentError, "Invalid action #{action.name}" |
| 69 | + end |
| 70 | + ensure |
| 71 | + close_pcap |
54 | 72 | end
|
| 73 | + end |
55 | 74 |
|
| 75 | + def do_sniff |
| 76 | + print_status("Sniffing traffic on #{interface}") |
| 77 | + lbl = ["CDP Version\t", "Device Id\t", "IP Address\t", "Switch Port\t", "Capabilities", "Software\t", "Platform\t", nil, "Cluster Management", "VTP Domain Management" , "Native VLAN\t", nil, nil, nil, nil, "VoIP VLAN Query"] |
56 | 78 | each_packet do |pkt|
|
57 | 79 | p = PacketFu::Packet.parse(pkt)
|
58 |
| - if p.proto != ["Eth", "LLDP"] and p.payload =~ /\x01\x00\f\xCC\xCC\xCC/ |
59 |
| - pay=p.payload |
60 |
| - pos=30 |
61 |
| - cdp=pay[22].getbyte(0) |
62 |
| - report = "CDP Version\t\t: #{cdp}\n" |
63 |
| - if cdp == 2 |
64 |
| - while 1 |
65 |
| - type = pay[pos-4,2].getbyte(1) |
66 |
| - break if pay[pos-2,2].nil? |
67 |
| - l=pay[pos-2,2].unpack('H*')[0].to_i(16) |
| 80 | + next unless p.proto != ["Eth", "LLDP"] && p.payload =~ /\x01\x00\x0C\xCC\xCC\xCC/ |
| 81 | + pay = p.payload |
| 82 | + pos = 30 |
| 83 | + cdp = pay[22].getbyte(0) |
| 84 | + report = "CDP Version\t\t: #{cdp}\n" |
| 85 | + if cdp == 2 |
| 86 | + while true |
| 87 | + type = pay[pos - 4, 2].getbyte(1) |
| 88 | + break if pay[pos - 2, 2].nil? |
| 89 | + l = pay[pos - 2, 2].unpack('H*')[0].to_i(16) |
68 | 90 | case type
|
69 |
| - when 1 |
70 |
| - d=pay[pos,l] |
71 |
| - d.chop! if d[-1] == "\n" |
72 |
| - report << " #{lbl[type]} \t: #{d}\n" |
73 |
| - when 2 |
74 |
| - if pay[pos,4].unpack('H*')[0].to_i(16) == 1 |
75 |
| - addr=pay[pos+9,4] |
76 |
| - ip=[] |
77 |
| - 4.times {|i| ip << "#{addr.getbyte(i)}"} |
78 |
| - report << " #{lbl[type]}\t: #{ip.join(".")}\n" |
| 91 | + when 1 |
| 92 | + d = pay[pos, l] |
| 93 | + d.chop! if d[-1] == "\n" |
| 94 | + report << " #{lbl[type]} \t: #{d}\n" |
| 95 | + when 2 |
| 96 | + if pay[pos, 4].unpack('H*')[0].to_i(16) == 1 |
| 97 | + addr = pay[pos + 9, 4] |
| 98 | + ip = [] |
| 99 | + 4.times { |i| ip << "#{addr.getbyte(i)}" } |
| 100 | + report << " #{lbl[type]}\t: #{ip.join(".")}\n" |
| 101 | + end |
| 102 | + when 3 |
| 103 | + report << " #{lbl[type]}\t: #{pay[pos,l]}\n" |
| 104 | + when 4 |
| 105 | + c = pay[pos + 3, 1].getbyte(0) |
| 106 | + c = c.to_s(2) |
| 107 | + caps = ["Repeater\t\t", "IGMP Capable\t\t", "Host\t\t\t", "Switch\t\t", "Source Route Bridge\t", "Transparent Bridge\t", "Router\t\t"] |
| 108 | + report << " #{lbl[type]}\t: \n" |
| 109 | + c.length.times do |
| 110 | + if c[-1].to_i == 1 |
| 111 | + report << "\t\t\t #{caps[-1]} : Yes\n" |
| 112 | + else |
| 113 | + report << "\t\t\t #{caps[-1]} : No\n" |
79 | 114 | end
|
80 |
| - when 3 |
81 |
| - report << " #{lbl[type]}\t: #{pay[pos,l]}\n" |
82 |
| - when 4 |
83 |
| - c=pay[pos+3,1].getbyte(0) |
84 |
| - c=c.to_s(2) |
85 |
| - cap={} |
86 |
| - caps=["Repeater\t\t","IGMP Capable\t\t","Host\t\t\t","Switch\t\t","Source Route Bridge\t","Transparent Bridge\t","Router\t\t"] |
87 |
| - report << " #{lbl[type]}\t: \n" |
88 |
| - c.length.times {|i| |
89 |
| - if c[-1].to_i == 1 |
90 |
| - report << "\t\t\t #{caps[-1]} : Yes\n" |
91 |
| - else |
92 |
| - report << "\t\t\t #{caps[-1]} : No\n" |
93 |
| - end |
94 |
| - c.chop! |
95 |
| - caps.delete_at(-1) |
96 |
| - } |
97 |
| - caps.each {report << "\t\t\t #{caps[-1]} : No\n"} if caps.length > 0 |
98 |
| - when 5 |
99 |
| - report << " #{lbl[type]}\t: #{pay[pos,l].split("\n").join("\n\t\t\t ")}\n" |
100 |
| - when 8 |
101 |
| - #report << " #{lbl[type]}\t:\n" |
102 |
| - #report << " IP: #{pay[pos+14,4]}\n" |
103 |
| - when 10 |
104 |
| - report << " #{lbl[type]}\t: #{pay[pos,2].unpack('H*')[0].to_i(16)}\n" |
105 |
| - when 15 |
106 |
| - report << " #{lbl[type]}\t: #{pay[pos+1,2].unpack('H*')[0].to_i(16)}\n" |
107 |
| - else |
108 |
| - report << " #{lbl[type]}\t: #{pay[pos,l]}\n" if lbl[type] != nil |
| 115 | + c.chop! |
| 116 | + caps.delete_at(-1) |
| 117 | + end |
| 118 | + unless caps.empty? |
| 119 | + caps.each do |missing_cap| |
| 120 | + report << "\t\t\t #{missing_cap}: No\n" |
| 121 | + end |
| 122 | + end |
| 123 | + when 5 |
| 124 | + report << " #{lbl[type]}\t: #{pay[pos, l].split("\n").join("\n\t\t\t ")}\n" |
| 125 | + when 8 |
| 126 | + # TODO? |
| 127 | + # report << " #{lbl[type]}\t:\n" |
| 128 | + # report << " IP: #{pay[pos+14,4]}\n" |
| 129 | + when 10 |
| 130 | + report << " #{lbl[type]}\t: #{pay[pos, 2].unpack('H*')[0].to_i(16)}\n" |
| 131 | + when 15 |
| 132 | + report << " #{lbl[type]}\t: #{pay[pos + 1, 2].unpack('H*')[0].to_i(16)}\n" |
| 133 | + else |
| 134 | + report << " #{lbl[type]}\t: #{pay[pos, l]}\n" if lbl[type] |
109 | 135 | end
|
110 | 136 | if pos > pay.length
|
111 | 137 | break
|
112 | 138 | else
|
113 |
| - pos = pos+l |
| 139 | + pos += l |
114 | 140 | end
|
115 |
| - end |
116 |
| - else |
117 |
| - report << " TTL\t\t\t: #{pay[23].unpack('H*')[0].to_i(16)}" |
118 | 141 | end
|
119 |
| - print_good("#{report}") |
| 142 | + else |
| 143 | + report << " TTL\t\t\t: #{pay[23].unpack('H*')[0].to_i(16)}" |
120 | 144 | end
|
| 145 | + print_good("#{report}") |
121 | 146 | end
|
122 |
| - close_pcap |
123 | 147 | print_status("Finished sniffing")
|
124 | 148 | end
|
125 |
| - def send_spoof() |
126 |
| - p=prep_cdp #Preparation of the CDP content |
127 |
| - dst_mac="\x01\x00\f\xCC\xCC\xCC" #CDP multicast |
128 |
| - |
129 |
| - #Source Mac Address Preparation |
130 |
| - @interface = datastore['INTERFACE'] || Pcap.lookupdev |
131 |
| - smac = datastore['SMAC'] || get_mac(@interface) |
132 |
| - raise RuntimeError ,'SMAC should be defined' unless smac |
133 |
| - src_mac=mac_to_bytes(smac) |
134 |
| - |
135 |
| - #Injecting packet to the network |
136 |
| - l=PacketFu::Inject.new(:iface=>@interface) |
137 |
| - cdplength=["%04X" % (p.length+8).to_s].pack('H*') |
138 |
| - l.array_to_wire(:array=>["#{dst_mac}#{src_mac}#{cdplength}"+llc+p]) |
139 |
| - end |
140 |
| - def llc |
141 |
| - llc="\xAA\xAA\x03\x00\x00\f \x00" |
142 |
| - return llc |
| 149 | + |
| 150 | + def do_spoof |
| 151 | + print_status("Sending CDP message on #{interface}") |
| 152 | + p = prep_cdp # Preparation of the CDP content |
| 153 | + dst_mac = "\x01\x00\x0C\xCC\xCC\xCC" # CDP multicast |
| 154 | + |
| 155 | + # Source Mac Address Preparation |
| 156 | + src_mac = mac_to_bytes(smac) |
| 157 | + |
| 158 | + # Injecting packet to the network |
| 159 | + l = PacketFu::Inject.new(iface: interface) |
| 160 | + cdp_length = ["%04X" % (p.length + 8).to_s].pack('H*') |
| 161 | + dot3 = dst_mac + src_mac + cdp_length |
| 162 | + llc = "\xAA\xAA\x03\x00\x00\x0c\x20\x00" |
| 163 | + l.array_to_wire(array: [dot3 + llc + p]) |
143 | 164 | end
|
144 |
| - def mac_to_bytes(smac) |
145 |
| - return [smac.gsub(":","")].pack('H*') |
| 165 | + |
| 166 | + def mac_to_bytes(mac) |
| 167 | + [mac.gsub(':', '')].pack('H*') |
146 | 168 | end
|
147 |
| - def prep_cdp |
148 |
| - #options from the user |
149 |
| - device=datastore['DEVICE ID'] || "SEP00070EEA3156" |
150 |
| - port=datastore['PORT'] || "1" |
151 |
| - capabilities=datastore['CAPABILITIES'] || "Host" |
152 |
| - platform=datastore['PLATFORM'] || "Cisco IP Phone 7975" |
153 |
| - software=datastore['SOFTWARE'] || "SCCP75.9-3-1SR2-1S" |
154 |
| - vtpdomain=datastore['VTPDOMAIN'] if datastore['VTPDOMAIN'] |
155 |
| - if datastore['DUPLEX'] |
156 |
| - dup=1 |
157 |
| - else |
158 |
| - dup=0 |
159 |
| - end |
160 | 169 |
|
161 |
| - #CAPABILITIES |
162 |
| - #define CDP_CAP_LEVEL1 0x40 |
163 |
| - #define CDP_CAP_FORWARD_IGMP 0x20 |
164 |
| - #define CDP_CAP_NETWORK_LAYER 0x10 |
165 |
| - #define CDP_CAP_LEVEL2_SWITCH 0x08 |
166 |
| - #define CDP_CAP_LEVEL2_SRB 0x04 |
167 |
| - #define CDP_CAP_LEVEL2_TRBR 0x02 |
168 |
| - #define CDP_CAP_LEVEL3_ROUTER 0x01 |
169 |
| - |
170 |
| - #Package Preperation |
171 |
| - p = "\x00\x01#{l(device)}#{device}" # Device ID |
172 |
| - p << "\x00\x03#{l("Port #{port}")}Port #{port}" # Port ID |
173 |
| - p << "\x00\x04\x00\b\x00\x00\x00A" # Capabilities |
174 |
| - p << "\x00\x05#{l(software)}#{software}" # Software Version |
175 |
| - p << "\x00\x06#{l(platform)}#{platform}" # Platform |
176 |
| - p << "\x00\x09#{l(vtpdomain)}#{vtpdomain}" if vtpdomain # VTP Domain Management |
177 |
| - p << "\x00\x10\x00\x06\x18\x9C" # Power Consumption 6300 mW |
178 |
| - p << "\x00\v\x00\x05#{dup}" # Duplex |
179 |
| - p << "\x00\x0F\x00\b \x02\x00\x01" # VLAN Query |
180 |
| - |
181 |
| - #Header Preperation |
182 |
| - version = "\x02" # CDP version |
183 |
| - ttl = "\xB4" # TTL (180 seconds) |
184 |
| - checksum = cdpchecksum(version+ttl+"\x00\x00"+p) # CDP Checksum |
185 |
| - |
186 |
| - p=version+ttl+checksum+p # CDP Payload |
187 |
| - |
188 |
| - return p |
| 170 | + def prep_cdp |
| 171 | + # device ID |
| 172 | + p = tlv(1, datastore['DEVICE_ID']) |
| 173 | + # port ID |
| 174 | + p << tlv(3, datastore['PORT']) |
| 175 | + # TODO: implement this correctly |
| 176 | + # capabilities = datastore['CAPABILITIES'] || "Host" |
| 177 | + # CAPABILITIES |
| 178 | + # define CDP_CAP_LEVEL1 0x40 |
| 179 | + # define CDP_CAP_FORWARD_IGMP 0x20 |
| 180 | + # define CDP_CAP_NETWORK_LAYER 0x10 |
| 181 | + # define CDP_CAP_LEVEL2_SWITCH 0x08 |
| 182 | + # define CDP_CAP_LEVEL2_SRB 0x04 |
| 183 | + # define CDP_CAP_LEVEL2_TRBR 0x02 |
| 184 | + # define CDP_CAP_LEVEL3_ROUTER 0x01 |
| 185 | + p << tlv(4, "\x00\x00\x00\x41") |
| 186 | + # software version |
| 187 | + p << tlv(5, datastore['SOFTWARE']) |
| 188 | + # platform |
| 189 | + p << tlv(6, datastore['PLATFORM']) |
| 190 | + # VTP management domain |
| 191 | + p << tlv(9, datastore['VTPDOMAIN']) if datastore['VTPDOMAIN'] |
| 192 | + # random 1000-7000 power consumption in mW |
| 193 | + p << tlv(0x10, [1000 + rand(6000)].pack('n')) |
| 194 | + # duplex |
| 195 | + p << tlv(0x0b, datastore['FULL_DUPLEX'] ? "\x01" : "\x00") |
| 196 | + # VLAn query. TOD: figure out this field, use tlv, make configurable |
| 197 | + p << "\x00\x0F\x00\b \x02\x00\x01" |
| 198 | + |
| 199 | + # VDP version |
| 200 | + version = "\x02" |
| 201 | + # TTL (180s) |
| 202 | + ttl = "\xB4" |
| 203 | + checksum = cdpchecksum(version + ttl + "\x00\x00" + p) |
| 204 | + version + ttl + checksum + p |
189 | 205 | end
|
190 | 206 |
|
191 |
| - def l(s,n=2) |
192 |
| - l=s.length+4 |
193 |
| - l="%0#{n*2}X" % l |
194 |
| - b=[l].pack('H*') |
195 |
| - return b |
| 207 | + def tlv(t, v) |
| 208 | + [ t, v.length + 4 ].pack("nn") + v |
196 | 209 | end
|
197 | 210 |
|
198 | 211 | def cdpchecksum(p)
|
199 | 212 | num_shorts = p.length / 2
|
200 | 213 | cs = 0
|
201 | 214 | c = p.length
|
202 | 215 |
|
203 |
| - p.unpack("S#{num_shorts}").each { |x| |
| 216 | + p.unpack("S#{num_shorts}").each do |x| |
204 | 217 | cs += x
|
205 | 218 | c -= 2
|
206 |
| - } |
207 |
| - |
208 |
| - if (c == 1) |
209 |
| - cs += p[p.length - 1].getbyte(0) << 8 |
210 | 219 | end
|
211 | 220 |
|
| 221 | + cs += p[p.length - 1].getbyte(0) << 8 if c == 1 |
212 | 222 | cs = (cs >> 16) + (cs & 0xffff)
|
213 | 223 | cs = ~((cs >> 16) + cs) & 0xffff
|
214 | 224 | cs = ([cs].pack("S*")).unpack("n*")[0]
|
215 | 225 |
|
216 |
| - cs = [ "%02X" % cs ].pack('H*') |
217 |
| - return cs |
| 226 | + [ "%02X" % cs ].pack('H*') |
218 | 227 | end
|
219 | 228 | end
|
0 commit comments