Skip to content

Commit

Permalink
Land #2922, multithreaded check command
Browse files Browse the repository at this point in the history
  • Loading branch information
wvu committed Feb 4, 2014
2 parents cccf2e4 + 4d008ca commit a58698c
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 34 deletions.
12 changes: 11 additions & 1 deletion lib/msf/core/auxiliary/scanner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ def initialize(info = {})
end


def check
nmod = replicant
begin
nmod.check_host(datastore['RHOST'])
rescue NoMethodError
Exploit::CheckCode::Unsupported
end
end


#
# The command handler when launched from the console
#
Expand Down Expand Up @@ -79,7 +89,7 @@ def run

@tl = []

while (true)
loop do
# Spawn threads for each host
while (@tl.length < threads_max)
ip = ar.next_ip
Expand Down
4 changes: 2 additions & 2 deletions lib/msf/core/exploit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,9 +104,9 @@ module CheckCode
Vulnerable = [ 'vulnerable', "The target is vulnerable." ]

#
# The exploit does not support the check method.
# The module does not support the check method.
#
Unsupported = [ 'unsupported', "This exploit does not support check." ]
Unsupported = [ 'unsupported', "This module does not support check." ]
end

#
Expand Down
154 changes: 124 additions & 30 deletions lib/msf/ui/console/module_command_dispatcher.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,39 +36,138 @@ def mod=(m)
self.driver.active_module = m
end

def check_progress
return 0 unless @range_done and @range_count
pct = (@range_done / @range_count.to_f) * 100
end

def check_show_progress
pct = check_progress
if(pct >= (@range_percent + @show_percent))
@range_percent = @range_percent + @show_percent
tdlen = @range_count.to_s.length
print_status("Checked #{"%.#{tdlen}d" % @range_done} of #{@range_count} hosts (#{"%.3d" % pct.to_i}% complete)")
end
end

def check_multiple(hosts)
# This part of the code is mostly from scanner.rb
@show_progress = framework.datastore['ShowProgress'] || mod.datastore['ShowProgress'] || false
@show_percent = ( framework.datastore['ShowProgressPercent'] || mod.datastore['ShowProgressPercent'] ).to_i

@range_count = hosts.length || 0
@range_done = 0
@range_percent = 0

# Set the default thread to 1. The same behavior as before.
threads_max = (framework.datastore['THREADS'] || mod.datastore['THREADS'] || 1).to_i
@tl = []


if Rex::Compat.is_windows
if threads_max > 16
print_warning("Thread count has been adjusted to 16")
threads_max = 16
end
end

if Rex::Compat.is_cygwin
if threads_max > 200
print_warning("Thread count has been adjusted to 200")
threads_max = 200
end
end

loop do
while (@tl.length < threads_max)
ip = hosts.next_ip
break unless ip

@tl << framework.threads.spawn("CheckHost-#{ip}", false, ip.dup) { |tip|
# Make sure this is thread-safe when assigning an IP to the RHOST
# datastore option
instance = mod.replicant
instance.datastore['RHOST'] = tip.dup
framework.events.on_module_created(instance)
check_simple(instance)
}
end

break if @tl.length == 0

tla = @tl.length

# This exception handling is necessary, the first thread with errors can kill the
# whole check_multiple and leave the rest of the threads running in background and
# only accessible with the threads command (Thread.list)
begin
@tl.first.join
rescue ::Exception => exception
if exception.kind_of?(::Interrupt)
raise exception
else
elog("#{exception} #{exception.class}:\n#{exception.backtrace.join("\n")}")
end
end

@tl.delete_if { |t| not t.alive? }
tlb = @tl.length

@range_done += (tla - tlb)
check_show_progress if @show_progress
end
end

#
# Checks to see if a target is vulnerable.
#
def cmd_check(*args)
defanged?

ip_range_arg = args.shift || ''
ip_range_arg = args.shift || framework.datastore['RHOSTS'] || mod.datastore['RHOSTS'] || ''
hosts = Rex::Socket::RangeWalker.new(ip_range_arg)

if hosts.ranges.blank?
# Check a single rhost
check_simple
else
# Check a range
last_rhost_opt = mod.rhost
begin
hosts.each do |ip|
mod.datastore['RHOST'] = ip
check_simple
begin
if hosts.ranges.blank?
# Check a single rhost
check_simple
else
# Check multiple hosts
last_rhost_opt = mod.rhost
last_rhosts_opt = mod.datastore['RHOSTS']
mod.datastore['RHOSTS'] = ip_range_arg
begin
check_multiple(hosts)
ensure
# Restore the original rhost if set
mod.datastore['RHOST'] = last_rhost_opt
mod.datastore['RHOSTS'] = last_rhosts_opt
mod.cleanup
end
ensure
# Restore the original rhost if set
mod.datastore['RHOST'] = last_rhost_opt
end
rescue ::Interrupt
# When the user sends interrupt trying to quit the task, some threads will still be active.
# This means even though the console tells the user the task has aborted (or at least they
# assume so), the checks are still running. Because of this, as soon as we detect interrupt,
# we force the threads to die.
if @tl
@tl.each { |t| t.kill }
end
print_status("Caught interrupt from the console...")
return
end
end

def check_simple
rhost = mod.rhost
rport = mod.rport
def check_simple(instance=nil)
unless instance
instance = mod
end

rhost = instance.rhost
rport = instance.rport

begin
code = mod.check_simple(
code = instance.check_simple(
'LocalInput' => driver.input,
'LocalOutput' => driver.output)
if (code and code.kind_of?(Array) and code.length > 1)
Expand All @@ -80,19 +179,14 @@ def check_simple
else
print_error("#{rhost}:#{rport} - Check failed: The state could not be determined.")
end
rescue ::Interrupt
raise $!
rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error
# Connection issues while running check should be handled by the module
rescue ::RuntimeError
# Some modules raise RuntimeError but we don't necessarily care about those when we run check()
rescue Msf::OptionValidateError => e
print_error("Check failed: #{e.message}")
rescue ::Exception => e
if(e.class.to_s != 'Msf::OptionValidateError')
print_error("Exploit check failed: #{e.class} #{e}")
print_error("Call stack:")
e.backtrace.each do |line|
break if line =~ /lib.msf.base.simple/
print_error(" #{line}")
end
else
print_error("#{rhost}:#{rport} - Exploit check failed: #{e.class} #{e}")
end
print_error("#{rhost}:#{rport} - Check failed: #{e.class} #{e}")
end
end

Expand Down
3 changes: 3 additions & 0 deletions modules/auxiliary/scanner/smb/ms08_067_check.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,16 @@
##

require "msf/core"
require 'msf/core/module/deprecated'

class Metasploit4 < Msf::Auxiliary

include Msf::Exploit::Remote::DCERPC
include Msf::Exploit::Remote::SMB
include Msf::Auxiliary::Scanner
include Msf::Auxiliary::Report
include Msf::Module::Deprecated
deprecated Date.new(2014, 2, 26), "exploit/windows/smb/ms08_067_netapi"

def initialize(info = {})
super(update_info(info,
Expand Down
2 changes: 1 addition & 1 deletion modules/exploits/windows/smb/ms08_067_netapi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1091,7 +1091,7 @@ def check
return Msf::Exploit::CheckCode::Safe
end

print_status("Verifying vulnerable status... (path: 0x%08x)" % path.length)
vprint_status("Verifying vulnerable status... (path: 0x%08x)" % path.length)

stub =
NDR.uwstring(server) +
Expand Down

0 comments on commit a58698c

Please sign in to comment.