Skip to content

Commit 4f0569c

Browse files
committed
support pivoting with UDP port scanners
Use bound UDP sockets for each UDP service/ip that we wish to scan, managing and closing them locally as they expire, rather than an unbound socket.
1 parent d5bc6a0 commit 4f0569c

File tree

1 file changed

+62
-26
lines changed

1 file changed

+62
-26
lines changed

lib/msf/core/auxiliary/udp_scanner.rb

Lines changed: 62 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,41 @@ def run_batch_size
4343
datastore['BATCHSIZE'].to_i
4444
end
4545

46+
def udp_sock(ip, port)
47+
@udp_socks_mutex.synchronize do
48+
key = "#{ip}:#{port}"
49+
unless @udp_socks.key?(key)
50+
@udp_socks[key] =
51+
Rex::Socket::Udp.create({
52+
'LocalHost' => datastore['CHOST'] || nil,
53+
'LocalPort' => datastore['CPORT'] || 0,
54+
'PeerHost' => ip,
55+
'PeerPort' => port,
56+
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
57+
})
58+
add_socket(@udp_socks[key])
59+
end
60+
return @udp_socks[key]
61+
end
62+
end
63+
64+
def cleanup_udp_socks
65+
@udp_socks_mutex.synchronize do
66+
@udp_socks.each do |key, sock|
67+
@udp_socks.delete(key)
68+
remove_socket(sock)
69+
sock.close
70+
end
71+
end
72+
end
73+
4674
# Start scanning a batch of IP addresses
4775
def run_batch(batch)
48-
@udp_sock = Rex::Socket::Udp.create({
49-
'LocalHost' => datastore['CHOST'] || nil,
50-
'LocalPort' => datastore['CPORT'] || 0,
51-
'Context' => { 'Msf' => framework, 'MsfExploit' => self }
52-
})
53-
add_socket(@udp_sock)
76+
@udp_socks = {}
77+
@udp_socks_mutex = Mutex.new
5478

5579
@udp_send_count = 0
80+
@interval_mutex = Mutex.new
5681

5782
# Provide a hook for pre-scanning setup
5883
scanner_prescan(batch)
@@ -95,9 +120,10 @@ def scanner_spoof_send(data, ip, port, srcip, num_packets=1)
95120
def scanner_send(data, ip, port)
96121

97122
resend_count = 0
123+
sock = nil
98124
begin
99-
100-
@udp_sock.sendto(data, ip, port, 0)
125+
sock = udp_sock(ip, port)
126+
sock.send(data, 0)
101127

102128
rescue ::Errno::ENOBUFS
103129
resend_count += 1
@@ -112,15 +138,16 @@ def scanner_send(data, ip, port)
112138

113139
retry
114140

115-
rescue ::Rex::ConnectionError
141+
rescue ::Rex::ConnectionError, ::Errno::ECONNREFUSED
116142
# This fires for host unreachable, net unreachable, and broadcast sends
117143
# We can safely ignore all of these for UDP sends
118144
end
119145

120-
@udp_send_count += 1
121-
122-
if @udp_send_count % datastore['ScannerRecvInterval'] == 0
123-
scanner_recv(0.1)
146+
@interval_mutex.synchronize do
147+
@udp_send_count += 1
148+
if @udp_send_count % datastore['ScannerRecvInterval'] == 0
149+
scanner_recv(0.1)
150+
end
124151
end
125152

126153
true
@@ -129,29 +156,38 @@ def scanner_send(data, ip, port)
129156
# Process incoming packets and dispatch to the module
130157
# Ensure a response flood doesn't trap us in a loop
131158
# Ignore packets outside of our project's scope
132-
def scanner_recv(timeout=0.1)
159+
def scanner_recv(timeout = 0.1)
133160
queue = []
134-
while (res = @udp_sock.recvfrom(65535, timeout))
161+
start = Time.now
162+
while Time.now - start < timeout do
163+
readable, _, _ = ::IO.select(@udp_socks.values, nil, nil, timeout)
164+
if readable
165+
for sock in readable
166+
res = sock.recvfrom(65535, timeout)
135167

136-
# Ignore invalid responses
137-
break if not res[1]
168+
# Ignore invalid responses
169+
break if not res[1]
138170

139-
# Ignore empty responses
140-
next if not (res[0] and res[0].length > 0)
171+
# Ignore empty responses
172+
next if not (res[0] and res[0].length > 0)
141173

142-
# Trim the IPv6-compat prefix off if needed
143-
shost = res[1].sub(/^::ffff:/, '')
174+
# Trim the IPv6-compat prefix off if needed
175+
shost = res[1].sub(/^::ffff:/, '')
144176

145-
# Ignore the response if we have a boundary
146-
next unless inside_workspace_boundary?(shost)
177+
# Ignore the response if we have a boundary
178+
next unless inside_workspace_boundary?(shost)
147179

148-
queue << [res[0], shost, res[2]]
180+
queue << [res[0], shost, res[2]]
149181

150-
if queue.length > datastore['ScannerRecvQueueLimit']
151-
break
182+
if queue.length > datastore['ScannerRecvQueueLimit']
183+
break
184+
end
185+
end
152186
end
153187
end
154188

189+
cleanup_udp_socks
190+
155191
queue.each do |q|
156192
scanner_process(*q)
157193
end

0 commit comments

Comments
 (0)