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