Skip to content

Commit 96ba6da

Browse files
author
HD Moore
committed
Add the UDP scanner template, lands rapid7#4113.
There is some additional work to do regarding CHOST/CPORT, but this is not tied to the udp template changes.
2 parents 6653d5e + 8f197d4 commit 96ba6da

File tree

4 files changed

+159
-13
lines changed

4 files changed

+159
-13
lines changed

.rubocop.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
# inherit_from: .rubocop_todo.yml
1010

11-
Style/ClassLength:
11+
Metrics/ClassLength:
1212
Description: 'Most Metasploit modules are quite large. This is ok.'
1313
Enabled: true
1414
Exclude:
@@ -25,14 +25,14 @@ Style/Encoding:
2525
Description: 'We prefer binary to UTF-8.'
2626
EnforcedStyle: 'when_needed'
2727

28-
Style/LineLength:
28+
Metrics/LineLength:
2929
Description: >-
3030
Metasploit modules often pattern match against very
3131
long strings when identifying targets.
3232
Enabled: true
3333
Max: 180
3434

35-
Style/MethodLength:
35+
Metrics/MethodLength:
3636
Enabled: true
3737
Description: >-
3838
While the style guide suggests 10 lines, exploit definitions
@@ -44,6 +44,11 @@ Style/MethodLength:
4444
Style/Encoding:
4545
Enabled: false
4646

47+
# %q() is super useful for long strings split over multiple lines and
48+
# is very common in module constructors for things like descriptions
49+
Style/UnneededPercentQ:
50+
Enabled: false
51+
4752
Style/NumericLiterals:
4853
Enabled: false
4954
Description: 'This often hurts readability for exploit-ish code.'

lib/msf/core/auxiliary/scanner.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -241,10 +241,10 @@ def scanner_progress
241241

242242
def scanner_show_progress
243243
pct = scanner_progress
244-
if(pct >= (@range_percent + @show_percent))
244+
if pct >= (@range_percent + @show_percent)
245245
@range_percent = @range_percent + @show_percent
246246
tdlen = @range_count.to_s.length
247-
print_status("Scanned #{"%.#{tdlen}d" % @range_done} of #{@range_count} hosts (#{"%.3d" % pct.to_i}% complete)")
247+
print_status(sprintf("Scanned %#{tdlen}d of %d hosts (%d%% complete)", @range_done, @range_count, pct))
248248
end
249249
end
250250

lib/msf/core/auxiliary/udp_scanner.rb

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,33 +8,36 @@ module Msf
88
#
99
###
1010
module Auxiliary::UDPScanner
11-
1211
include Auxiliary::Scanner
1312

13+
# A hash of results of a given batch run, keyed by host
14+
attr_accessor :results
15+
1416
#
1517
# Initializes an instance of an auxiliary module that scans UDP
1618
#
17-
1819
def initialize(info = {})
1920
super
2021

2122
register_options(
2223
[
23-
Opt::CHOST,
24+
Opt::RPORT,
2425
OptInt.new('BATCHSIZE', [true, 'The number of hosts to probe in each set', 256]),
26+
OptInt.new('THREADS', [true, "The number of concurrent threads", 10])
2527
], self.class)
2628

2729
register_advanced_options(
2830
[
31+
Opt::CHOST,
32+
Opt::CPORT,
2933
OptInt.new('ScannerRecvInterval', [true, 'The maximum numbers of sends before entering the processing loop', 30]),
3034
OptInt.new('ScannerMaxResends', [true, 'The maximum times to resend a packet when out of buffers', 10]),
3135
OptInt.new('ScannerRecvQueueLimit', [true, 'The maximum queue size before breaking out of the processing loop', 100]),
32-
OptInt.new('ScannerRecvWindow', [true, 'The number of seconds to wait post-scan to catch leftover replies', 15]),
36+
OptInt.new('ScannerRecvWindow', [true, 'The number of seconds to wait post-scan to catch leftover replies', 15])
3337

3438
], self.class)
3539
end
3640

37-
3841
# Define our batch size
3942
def run_batch_size
4043
datastore['BATCHSIZE'].to_i
@@ -44,6 +47,7 @@ def run_batch_size
4447
def run_batch(batch)
4548
@udp_sock = Rex::Socket::Udp.create({
4649
'LocalHost' => datastore['CHOST'] || nil,
50+
'LocalPort' => datastore['CPORT'] || 0,
4751
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
4852
})
4953
add_socket(@udp_sock)
@@ -155,12 +159,25 @@ def scanner_recv(timeout=0.1)
155159
queue.length
156160
end
157161

162+
def cport
163+
datastore['CPORT']
164+
end
165+
166+
def rport
167+
datastore['RPORT']
168+
end
169+
158170
#
159-
# The including module override these methods
171+
# The including module may override some of these methods
160172
#
161173

162-
# Called for each IP in the batch
174+
# Builds and returns the probe to be sent
175+
def build_probe
176+
end
177+
178+
# Called for each IP in the batch. This will send all necessary probes.
163179
def scan_host(ip)
180+
scanner_send(build_probe, ip, rport)
164181
end
165182

166183
# Called for each response packet
@@ -169,11 +186,12 @@ def scanner_process(data, shost, sport)
169186

170187
# Called before the scan block
171188
def scanner_prescan(batch)
189+
vprint_status("Sending probes to #{batch[0]}->#{batch[-1]} (#{batch.length} hosts)")
190+
@results = {}
172191
end
173192

174193
# Called after the scan block
175194
def scanner_postscan(batch)
176195
end
177-
178196
end
179197
end
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
##
2+
# This module requires Metasploit: http://metasploit.com/download
3+
# Current source: https://github.com/rapid7/metasploit-framework
4+
##
5+
6+
require 'msf/core'
7+
8+
class Metasploit3 < Msf::Auxiliary
9+
include Msf::Auxiliary::Report
10+
include Msf::Auxiliary::UDPScanner
11+
12+
def initialize(info = {})
13+
super(
14+
update_info(
15+
info,
16+
# TODO: fill in all of this
17+
'Name' => 'UDP Scanner Example',
18+
'Description' => %q(
19+
This module is an example of how to send probes to UDP services
20+
en-masse, analyze any responses, and then report on any discovered
21+
hosts, services, vulnerabilities or otherwise noteworthy things.
22+
Simply address any of the TODOs.
23+
),
24+
'Author' => 'Joe Contributor <joe_contributor[at]example.com>',
25+
'References' =>
26+
[
27+
['URL', 'https://example.com/~jcontributor']
28+
],
29+
'DisclosureDate' => 'Mar 15 2014',
30+
'License' => MSF_LICENSE
31+
)
32+
)
33+
34+
register_options(
35+
[
36+
# TODO: change to the port you need to scan
37+
Opt::RPORT(12345)
38+
], self.class)
39+
40+
# TODO: add any advanced, special options here, otherwise remove
41+
register_advanced_options(
42+
[
43+
OptBool.new('SPECIAL', [true, 'Try this special thing', false])
44+
], self.class)
45+
end
46+
47+
def setup
48+
super
49+
# TODO: do any sort of preliminary sanity checking, like perhaps validating some options
50+
# in the datastore, etc.
51+
end
52+
53+
# TODO: construct the appropriate probe here.
54+
def build_probe
55+
@probe ||= 'abracadabra!'
56+
end
57+
58+
# TODO: this is called before the scan block for each batch of hosts. Do any
59+
# per-batch setup here, otherwise remove it.
60+
def scanner_prescan(batch)
61+
super
62+
end
63+
64+
# TODO: this is called for each IP in the batch. This will send all of the
65+
# necessary probes. If something different must be done for each IP, do it
66+
# here, otherwise remove it.
67+
def scan_host(ip)
68+
super
69+
end
70+
71+
# Called for each response packet
72+
def scanner_process(response, src_host, _src_port)
73+
# TODO: inspect each response, perhaps confirming that it is a valid
74+
# response for the service/protocol in question and/or analyzing it more
75+
# closely. In this case, we simply check to see that it is of reasonable
76+
# size and storing a result for this host iff so. Note that src_port may
77+
# not actually be the same as the original RPORT for some services if they
78+
# respond back from different ports
79+
return unless response.size >= 42
80+
@results[src_host] ||= []
81+
82+
# TODO: store something about this response, perhaps the response itself,
83+
# some metadata obtained by analyzing it, the proof that it is vulnerable
84+
# to something, etc. In this example, we simply look for any response
85+
# with a sequence of 5 useful ASCII characters and, iff found, we store
86+
# that sequence
87+
/(?<relevant>[\x20-\x7E]{5})/ =~ response && @results[src_host] << relevant
88+
end
89+
90+
# Called after the scan block
91+
def scanner_postscan(_batch)
92+
@results.each_pair do |host, relevant_responses|
93+
peer = "#{host}:#{rport}"
94+
95+
# report on the host
96+
report_host(host: host)
97+
98+
# report on the service, since it responded
99+
report_service(
100+
host: host,
101+
proto: 'udp',
102+
port: rport,
103+
name: 'example',
104+
# show at most 4 relevant responses
105+
info: relevant_responses[0, 4].join(',')
106+
)
107+
108+
if relevant_responses.empty?
109+
vprint_status("#{peer} Not vulnerable to something")
110+
else
111+
print_good("#{peer} Vulnerable to something!")
112+
report_vuln(
113+
host: host,
114+
port: rport,
115+
proto: 'udp',
116+
name: 'something!',
117+
info: "Got #{relevant_responses.size} response(s)",
118+
refs: references
119+
)
120+
end
121+
end
122+
end
123+
end

0 commit comments

Comments
 (0)