CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/auxiliary/scanner/discovery/udp_sweep.rb
Views: 1904
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'openssl'
7
8
class MetasploitModule < Msf::Auxiliary
9
include Msf::Auxiliary::Report
10
include Msf::Auxiliary::UDPScanner
11
12
def initialize
13
super(
14
'Name' => 'UDP Service Sweeper',
15
'Description' => 'Detect interesting UDP services',
16
'Author' => 'hdm',
17
'License' => MSF_LICENSE
18
)
19
20
register_advanced_options(
21
[
22
OptBool.new('RANDOMIZE_PORTS', [false, 'Randomize the order the ports are probed', true])
23
])
24
25
# RPORT is required by UDPScanner but not used in this module since it
26
# works with multiple ports.
27
# TODO: update this module to simply use Scanner or update UDPScanner to support
28
# multiple ports.
29
deregister_options('RPORT')
30
31
# Initialize the probes array
32
@probes = []
33
34
# Add the UDP probe method names
35
@probes << 'probe_pkt_dns'
36
@probes << 'probe_pkt_netbios'
37
@probes << 'probe_pkt_portmap'
38
@probes << 'probe_pkt_mssql'
39
@probes << 'probe_pkt_ntp'
40
@probes << 'probe_pkt_snmp1'
41
@probes << 'probe_pkt_snmp2'
42
@probes << 'probe_pkt_sentinel'
43
@probes << 'probe_pkt_db2disco'
44
@probes << 'probe_pkt_citrix'
45
@probes << 'probe_pkt_pca_st'
46
@probes << 'probe_pkt_pca_nq'
47
@probes << 'probe_chargen'
48
end
49
50
def setup
51
super
52
53
if datastore['RANDOMIZE_PORTS']
54
@probes = @probes.sort_by { rand }
55
end
56
end
57
58
def scanner_prescan(batch)
59
print_status("Sending #{@probes.length} probes to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)")
60
@results = {}
61
end
62
63
def scan_host(ip)
64
@probes.each do |probe|
65
data, port = self.send(probe, ip)
66
scanner_send(data, ip, port)
67
end
68
end
69
70
def scanner_postscan(batch)
71
@results.each_key do |k|
72
next if not @results[k].respond_to?('keys')
73
data = @results[k]
74
75
conf = {
76
:host => data[:host],
77
:port => data[:port],
78
:proto => 'udp',
79
:name => data[:app],
80
:info => data[:info]
81
}
82
83
if data[:hname]
84
conf[:host_name] = data[:hname].downcase
85
end
86
87
if data[:mac]
88
conf[:mac] = data[:mac].downcase
89
end
90
91
report_service(conf)
92
print_good("Discovered #{data[:app]} on #{k} (#{data[:info]})")
93
end
94
end
95
96
97
def scanner_process(data, shost, sport)
98
99
hkey = "#{shost}:#{sport}"
100
app = 'unknown'
101
inf = ''
102
maddr = nil
103
hname = nil
104
105
# Work with protocols that return different data in different packets
106
# These are reported at the end of the scanning loop to build state
107
case sport
108
when 5632
109
110
@results[hkey] ||= {}
111
data = @results[hkey]
112
data[:app] = "pcAnywhere_stat"
113
data[:port] = sport
114
data[:host] = shost
115
116
case data
117
118
when /^NR(........................)(........)/
119
name = $1.dup
120
caps = $2.dup
121
name = name.gsub(/_+$/, '').gsub("\x00", '').strip
122
caps = caps.gsub(/_+$/, '').gsub("\x00", '').strip
123
data[:name] = name
124
data[:caps] = caps
125
126
when /^ST(.+)/
127
buff = $1.dup
128
stat = 'Unknown'
129
130
if buff[2,1].unpack("C")[0] == 67
131
stat = "Available"
132
end
133
134
if buff[2,1].unpack("C")[0] == 11
135
stat = "Busy"
136
end
137
138
data[:stat] = stat
139
end
140
141
if data[:name]
142
inf << "Name: #{data[:name]} "
143
end
144
145
if data[:stat]
146
inf << "- #{data[:stat]} "
147
end
148
149
if data[:caps]
150
inf << "( #{data[:caps]} ) "
151
end
152
data[:info] = inf
153
end
154
155
# Ignore duplicates
156
return if @results[hkey]
157
158
case sport
159
160
when 19
161
app = 'chargen'
162
ver = nil
163
return unless chargen_parse(data)
164
@results[hkey] = true
165
166
when 53
167
app = 'DNS'
168
ver = nil
169
170
if (not ver and data =~ /([6789]\.[\w\.\-_\:\(\)\[\]\/\=\+\|\{\}]+)/i)
171
ver = 'BIND ' + $1
172
end
173
174
ver = 'Microsoft DNS' if (not ver and data[2,4] == "\x81\x04\x00\x01")
175
ver = 'TinyDNS' if (not ver and data[2,4] == "\x81\x81\x00\x01")
176
177
ver = data.unpack('H*')[0] if not ver
178
inf = ver if ver
179
180
@results[hkey] = true
181
182
when 137
183
app = 'NetBIOS'
184
185
buff = data.dup
186
187
head = buff.slice!(0,12)
188
189
xid, flags, quests, answers, auths, adds = head.unpack('n6')
190
return if quests != 0
191
return if answers == 0
192
193
qname = buff.slice!(0,34)
194
rtype,rclass,rttl,rlen = buff.slice!(0,10).unpack('nnNn')
195
bits = buff.slice!(0,rlen)
196
197
names = []
198
199
case rtype
200
when 0x21
201
rcnt = bits.slice!(0,1).unpack("C")[0]
202
1.upto(rcnt) do
203
tname = bits.slice!(0,15).gsub(/\x00.*/, '').strip
204
ttype = bits.slice!(0,1).unpack("C")[0]
205
tflag = bits.slice!(0,2).unpack('n')[0]
206
names << [ tname, ttype, tflag ]
207
end
208
maddr = bits.slice!(0,6).unpack("C*").map{|c| "%.2x" % c }.join(":")
209
210
names.each do |name|
211
inf << name[0]
212
inf << ":<%.2x>" % name[1]
213
if (name[2] & 0x8000 == 0)
214
inf << ":U :"
215
else
216
inf << ":G :"
217
end
218
end
219
inf << maddr
220
221
if(names.length > 0)
222
hname = names[0][0]
223
end
224
end
225
226
@results[hkey] = true
227
228
when 111
229
app = 'Portmap'
230
buf = data
231
inf = ""
232
hed = buf.slice!(0,24)
233
svc = []
234
while(buf.length >= 20)
235
rec = buf.slice!(0,20).unpack("N5")
236
svc << "#{rec[1]} v#{rec[2]} #{rec[3] == 0x06 ? "TCP" : "UDP"}(#{rec[4]})"
237
report_service(
238
:host => shost,
239
:port => rec[4],
240
:proto => (rec[3] == 0x06 ? "tcp" : "udp"),
241
:name => "sunrpc",
242
:info => "#{rec[1]} v#{rec[2]}",
243
:state => "open"
244
)
245
end
246
inf = svc.join(", ")
247
248
@results[hkey] = true
249
250
when 123
251
app = 'NTP'
252
ver = nil
253
ver = data.unpack('H*')[0]
254
ver = 'NTP v3' if (ver =~ /^1c06|^1c05/)
255
ver = 'NTP v4' if (ver =~ /^240304/)
256
ver = 'NTP v4 (unsynchronized)' if (ver =~ /^e40/)
257
ver = 'Microsoft NTP' if (ver =~ /^dc00|^dc0f/)
258
inf = ver if ver
259
260
@results[hkey] = true
261
262
when 1434
263
app = 'MSSQL'
264
mssql_ping_parse(data).each_pair { |k,v|
265
inf += k+'='+v+' '
266
}
267
268
@results[hkey] = true
269
270
when 161
271
app = 'SNMP'
272
asn = OpenSSL::ASN1.decode(data) rescue nil
273
return if not asn
274
275
snmp_error = asn.value[0].value rescue nil
276
snmp_comm = asn.value[1].value rescue nil
277
snmp_data = asn.value[2].value[3].value[0] rescue nil
278
snmp_oid = snmp_data.value[0].value rescue nil
279
snmp_info = snmp_data.value[1].value rescue nil
280
281
return if not (snmp_error and snmp_comm and snmp_data and snmp_oid and snmp_info)
282
snmp_info = snmp_info.to_s.gsub(/\s+/, ' ')
283
284
inf = snmp_info
285
com = snmp_comm
286
287
@results[hkey] = true
288
289
when 5093
290
app = 'Sentinel'
291
@results[hkey] = true
292
293
when 523
294
app = 'ibm-db2'
295
inf = db2disco_parse(data)
296
@results[hkey] = true
297
298
when 1604
299
app = 'citrix-ica'
300
return unless citrix_parse(data)
301
@results[hkey] = true
302
303
end
304
305
report_service(
306
:host => shost,
307
:mac => (maddr and maddr != '00:00:00:00:00:00') ? maddr : nil,
308
:host_name => (hname) ? hname.downcase : nil,
309
:port => sport,
310
:proto => 'udp',
311
:name => app,
312
:info => inf,
313
:state => "open"
314
)
315
316
print_status("Discovered #{app} on #{shost}:#{sport} (#{inf})")
317
end
318
319
#
320
# Validate a chargen packet.
321
#
322
def chargen_parse(data)
323
data =~ /ABCDEFGHIJKLMNOPQRSTUVWXYZ|0123456789/i
324
end
325
326
#
327
# Parse a db2disco packet.
328
#
329
def db2disco_parse(data)
330
res = data.split("\x00")
331
"#{res[2]}_#{res[1]}"
332
end
333
334
#
335
# Validate this is truly Citrix ICA; returns true or false.
336
#
337
def citrix_parse(data)
338
server_response = "\x30\x00\x02\x31\x02\xfd\xa8\xe3\x02\x00\x06\x44" # Server hello response
339
data =~ /^#{server_response}/
340
end
341
342
#
343
# Parse a 'ping' response and format as a hash
344
#
345
def mssql_ping_parse(data)
346
res = {}
347
var = nil
348
idx = data.index('ServerName')
349
return res if not idx
350
351
data[idx, data.length-idx].split(';').each do |d|
352
if (not var)
353
var = d
354
else
355
if (var.length > 0)
356
res[var] = d
357
var = nil
358
end
359
end
360
end
361
362
return res
363
end
364
365
#
366
# The probe definitions
367
#
368
369
def probe_chargen(ip)
370
pkt = Rex::Text.rand_text_alpha_lower(1)
371
return [pkt, 19]
372
end
373
374
def probe_pkt_dns(ip)
375
data = [rand(0xffff)].pack('n') +
376
"\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00"+
377
"\x07"+ "VERSION"+
378
"\x04"+ "BIND"+
379
"\x00\x00\x10\x00\x03"
380
381
return [data, 53]
382
end
383
384
def probe_pkt_netbios(ip)
385
data =
386
[rand(0xffff)].pack('n')+
387
"\x00\x00\x00\x01\x00\x00\x00\x00"+
388
"\x00\x00\x20\x43\x4b\x41\x41\x41"+
389
"\x41\x41\x41\x41\x41\x41\x41\x41"+
390
"\x41\x41\x41\x41\x41\x41\x41\x41"+
391
"\x41\x41\x41\x41\x41\x41\x41\x41"+
392
"\x41\x41\x41\x00\x00\x21\x00\x01"
393
394
return [data, 137]
395
end
396
397
def probe_pkt_portmap(ip)
398
data =
399
[
400
rand(0xffffffff), # XID
401
0, # Type
402
2, # RPC Version
403
100000, # Program ID
404
2, # Program Version
405
4, # Procedure
406
0, 0, # Credentials
407
0, 0, # Verifier
408
].pack('N*')
409
410
return [data, 111]
411
end
412
413
def probe_pkt_mssql(ip)
414
return ["\x02", 1434]
415
end
416
417
def probe_pkt_ntp(ip)
418
data =
419
"\xe3\x00\x04\xfa\x00\x01\x00\x00\x00\x01\x00\x00\x00" +
420
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
421
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
422
"\x00\xc5\x4f\x23\x4b\x71\xb1\x52\xf3"
423
return [data, 123]
424
end
425
426
427
def probe_pkt_sentinel(ip)
428
return ["\x7a\x00\x00\x00\x00\x00", 5093]
429
end
430
431
def probe_pkt_snmp1(ip)
432
version = 1
433
data = OpenSSL::ASN1::Sequence([
434
OpenSSL::ASN1::Integer(version - 1),
435
OpenSSL::ASN1::OctetString("public"),
436
OpenSSL::ASN1::Set.new([
437
OpenSSL::ASN1::Integer(rand(0x80000000)),
438
OpenSSL::ASN1::Integer(0),
439
OpenSSL::ASN1::Integer(0),
440
OpenSSL::ASN1::Sequence([
441
OpenSSL::ASN1::Sequence([
442
OpenSSL::ASN1.ObjectId("1.3.6.1.2.1.1.1.0"),
443
OpenSSL::ASN1.Null(nil)
444
])
445
]),
446
], 0, :IMPLICIT)
447
]).to_der
448
[data, 161]
449
end
450
451
def probe_pkt_snmp2(ip)
452
version = 2
453
data = OpenSSL::ASN1::Sequence([
454
OpenSSL::ASN1::Integer(version - 1),
455
OpenSSL::ASN1::OctetString("public"),
456
OpenSSL::ASN1::Set.new([
457
OpenSSL::ASN1::Integer(rand(0x80000000)),
458
OpenSSL::ASN1::Integer(0),
459
OpenSSL::ASN1::Integer(0),
460
OpenSSL::ASN1::Sequence([
461
OpenSSL::ASN1::Sequence([
462
OpenSSL::ASN1.ObjectId("1.3.6.1.2.1.1.1.0"),
463
OpenSSL::ASN1.Null(nil)
464
])
465
]),
466
], 0, :IMPLICIT)
467
]).to_der
468
[data, 161]
469
end
470
471
def probe_pkt_db2disco(ip)
472
data = "DB2GETADDR\x00SQL05000\x00"
473
[data, 523]
474
end
475
476
def probe_pkt_citrix(ip) # Server hello packet from citrix_published_bruteforce
477
data =
478
"\x1e\x00\x01\x30\x02\xfd\xa8\xe3\x00\x00\x00\x00\x00" +
479
"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
480
"\x00\x00\x00\x00"
481
return [data, 1604]
482
end
483
484
def probe_pkt_pca_st(ip)
485
return ["ST", 5632]
486
end
487
488
def probe_pkt_pca_nq(ip)
489
return ["NQ", 5632]
490
end
491
end
492
493