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