Recon modules and the recon event subsystem have been temporarily removed.

The 'auxiliary' system is designed to replace it and recon features will
slowly be moved back into the framework


git-svn-id: file:///home/svn/incoming/trunk@3438 4d416f70-5f16-0410-b530-b9f4589650da
unstable
HD Moore 2006-01-24 03:59:44 +00:00
parent 55ba865f30
commit bdfd2c5152
39 changed files with 258 additions and 1928 deletions

View File

@ -31,7 +31,6 @@ class Flatfile < PersistentStorage
begin
store_general(framework)
store_recon(framework)
ensure
self.fd.close
end
@ -53,47 +52,6 @@ protected
"Generated: #{Time.now}\n\n")
end
#
# This method stores the recon information that has been collected by this
# framework instance.
#
def store_recon(framework)
fd.print(
"Reconnaissance Information\n" +
"==========================\n\n")
framework.reconmgr.each_host { |address, host|
store_recon_host(framework, host)
}
end
#
# This method stores information about a specific host and its services.
#
def store_recon_host(framework, host)
fd.print(
"Host: #{host.address}\n")
store_recon_host_services(framework, host)
fd.print("\n")
end
#
# This method stores information about the services running on a host, if
# any.
#
def store_recon_host_services(framework, host)
host.services.entities.each { |name, proto|
if (proto.kind_of?(Msf::Recon::Entity::Group) == true)
proto.entities.each { |name, serv|
fd.print(
"\tService: #{serv.port} (#{serv.proto})\n")
}
end
}
end
Msf::PersistentStorage.add_storage_class('flatfile', self)
end

View File

@ -27,8 +27,8 @@ class ReadableText
return dump_basic_module(mod, indent)
when MODULE_EXPLOIT
return dump_exploit_module(mod, indent)
when MODULE_RECON
return dump_recon_module(mod, indent)
when MODULE_AUX
return dump_auxiliary_module(mod, indent)
else
return dump_generic_module(mod, indent)
end
@ -150,9 +150,9 @@ class ReadableText
end
#
# Dumps information about a recon module.
# Dumps information about an auxiliary module.
#
def self.dump_recon_module(mod, indent = '')
def self.dump_auxiliary_module(mod, indent = '')
output = "\n"
output += " Name: #{mod.name}\n"
output += " Version: #{mod.version}\n"

View File

@ -8,7 +8,7 @@ require 'msf/base/simple/encoder'
require 'msf/base/simple/exploit'
require 'msf/base/simple/nop'
require 'msf/base/simple/payload'
require 'msf/base/simple/recon'
require 'msf/base/simple/auxiliary'
# Simplified framework interface
require 'msf/base/simple/framework'

View File

@ -0,0 +1,38 @@
module Msf
module Simple
###
#
# A simplified recon wrapper.
#
###
module Auxiliary
include Module
#
# Wraps the execution process in a simple wrapper.
#
def self.run_simple(mod, opts = {})
# Initialize user interaction
mod.init_ui(
opts['LocalInput'],
opts['LocalOutput'])
mod.run()
# Reset the user interface
mod.reset_ui
end
#
# Calls the class method.
#
def run_simple(opts = {})
Msf::Simple::Auxiliary.run_simple(self, opts)
end
end
end
end

View File

@ -44,7 +44,7 @@ module Framework
MODULE_EXPLOIT => Msf::Simple::Exploit,
MODULE_NOP => Msf::Simple::Nop,
MODULE_PAYLOAD => Msf::Simple::Payload,
MODULE_RECON => Msf::Simple::Recon,
MODULE_AUX => Msf::Simple::Auxiliary,
}
#

View File

@ -1,42 +0,0 @@
module Msf
module Simple
###
#
# A simplified recon wrapper.
#
###
module Recon
include Module
#
# Wraps the discovery process in a simple wrapper.
#
def self.discover_simple(recon, opts = {})
# Initialize user interaction
recon.init_ui(
opts['LocalInput'],
opts['LocalOutput'])
# Start the discovery process
recon.start_discovery
# Wait for the discovery to complete
recon.wait_for_completion
# Reset the user interface
recon.reset_ui
end
#
# Calls the class method.
#
def discover_simple(opts = {})
Msf::Simple::Recon.discover_simple(self, opts)
end
end
end
end

View File

@ -46,10 +46,10 @@ class Statistics
end
#
# Returns the number of recon modules in the framework.
# Returns the number of auxiliary modules in the framework.
#
def num_recon
self.framework.recon.length
def num_auxiliary
self.framework.auxiliary.length
end
#

View File

@ -5,7 +5,7 @@
#
# The core library provides all of the means by which to interact
# with the framework insofar as maniuplating encoders, nops,
# payloads, exploits, recon, and sessions.
# payloads, exploits, auxiliary, and sessions.
#
###
@ -45,7 +45,7 @@ require 'msf/core/encoder'
require 'msf/core/exploit'
require 'msf/core/nop'
require 'msf/core/payload'
require 'msf/core/recon'
require 'msf/core/auxiliary'
# Drivers
require 'msf/core/exploit_driver'

63
lib/msf/core/auxiliary.rb Normal file
View File

@ -0,0 +1,63 @@
require 'msf/core/module'
module Msf
###
#
# The auxiliary class acts as a base class for all modules that perform
# reconnaisance, retrieve data, brute force logins, or any other action
# that doesn't fit our concept of an 'exploit' (involving payloads and
# targets and whatnot).
#
###
class Auxiliary < Msf::Module
#
# Returns MODULE_AUX to indicate that this is an auxiliary module.
#
def self.type
MODULE_AUX
end
#
# Returns MODULE_AUX to indicate that this is an auxiliary module.
#
def type
MODULE_AUX
end
#
# Creates an instance of the exploit module. Mad skillz.
#
def initialize(info = {})
# Call the parent constructor after making any necessary modifications
# to the information hash.
super(info)
self.actions = Rex::Transformer.transform(
info['Actions'], Array,
[ AuxiliaryAction ], 'AuxiliaryAction'
)
self.default_action = info['DefaultAction']
end
def run
print_status("Running the default Auxiliary handler")
end
#
# Allow access to the hash table of actions and the string containing
# the default action
#
attr_reader :actions, :default_action
protected
attr_writer :actions, :default_action
end
end

View File

@ -14,7 +14,7 @@ MODULE_ANY = '_any_'
MODULE_ENCODER = 'encoder'
MODULE_EXPLOIT = 'exploit'
MODULE_NOP = 'nop'
MODULE_RECON = 'recon'
MODULE_AUX = 'aux'
MODULE_PAYLOAD = 'payload'
MODULE_TYPES =
[
@ -22,7 +22,7 @@ MODULE_TYPES =
MODULE_PAYLOAD,
MODULE_EXPLOIT,
MODULE_NOP,
MODULE_RECON
MODULE_AUX
]
#

View File

@ -23,7 +23,7 @@ end
#
# This class manages subscriber registration and is the entry point
# for dispatching various events that occur for modules, such as
# recon discovery and exploit success or failure. The framework
# exploit results and auxiliary module data. The framework
# and external modules can register themselves as subscribers to
# various events such that they can perform custom actions when
# a specific event or events occur.
@ -35,7 +35,6 @@ class EventDispatcher
self.general_event_subscribers = []
self.exploit_event_subscribers = []
self.session_event_subscribers = []
self.recon_event_subscribers = []
self.subscribers_rwlock = Rex::ReadWriteLock.new
end
@ -60,23 +59,6 @@ class EventDispatcher
remove_event_subscriber(general_event_subscribers, subscriber)
end
#
# This method adds a recon event subscriber. Recon event subscribers
# receive notifications when events occur that pertain to recon modules.
# The subscriber provided must implement the ReconEvents module methods in
# some form.
#
def add_recon_subscriber(subscriber)
add_event_subscriber(recon_event_subscribers, subscriber)
end
#
# Removes a recon event subscriber.
#
def remove_recon_subscriber(subscriber)
remove_event_subscriber(recon_event_subscribers, subscriber)
end
#
# This method adds an exploit event subscriber. Exploit event subscribers
# receive notifications when events occur that pertain to exploits, such as
@ -145,46 +127,6 @@ class EventDispatcher
}
end
##
#
# Recon events
#
##
#
# This routine is called whenever a host's state changes, such as when it's
# added, updated, or removed. This event is dispatched by the Recon
# Manager once it makes a determination on the accurate state of a host as
# provided by one or more host recon modules.
#
def on_host_changed(context, host, change_type)
subscribers_rwlock.synchronize_read {
recon_event_subscribers.each { |subscriber|
cls = (subscriber.kind_of?(Class)) ? subscriber : subscriber.class
next if (cls.include?(Msf::ReconEvent::HostSubscriber) == false)
subscriber.on_host_changed(context, host, change_type)
}
}
end
#
# This routine is called whenever a service's state changes, such as when
# it's found, updated, or removed. This event is dispatched by the Recon
# Manager.
#
def on_service_changed(context, host, service, change_type)
subscribers_rwlock.synchronize_read {
recon_event_subscribers.each { |subscriber|
cls = (subscriber.kind_of?(Class)) ? subscriber : subscriber.class
next if (cls.include?(Msf::ReconEvent::ServiceSubscriber) == false)
subscriber.on_service_changed(context, host, service, change_type)
}
}
end
##
#
@ -269,7 +211,6 @@ protected
attr_accessor :general_event_subscribers # :nodoc:
attr_accessor :exploit_event_subscribers # :nodoc:
attr_accessor :session_event_subscribers # :nodoc:
attr_accessor :recon_event_subscribers # :nodoc:
attr_accessor :subscribers_rwlock # :nodoc:
end

View File

@ -34,7 +34,6 @@ class Framework
require 'msf/core/module_manager'
require 'msf/core/session_manager'
require 'msf/core/recon_manager'
#
# Creates an instance of the framework context.
@ -43,7 +42,6 @@ class Framework
self.events = EventDispatcher.new
self.modules = ModuleManager.new(self)
self.sessions = SessionManager.new(self)
self.reconmgr = ReconManager.new(self)
self.datastore = DataStore.new
self.jobs = Rex::JobContainer.new
self.plugins = PluginManager.new(self)
@ -78,10 +76,10 @@ class Framework
end
#
# Returns the module set for recon modules
# Returns the module set for auxiliary modules
#
def recon
return modules.recon
def auxiliary
return modules.auxiliary
end
#
@ -111,11 +109,11 @@ class Framework
#
attr_reader :datastore
#
# The framework instance's recon manager. The recon manager is responsible
# for collecting and catalogging all recon information that comes in from
# recon modules.
# The framework instance's aux manager. The aux manager is responsible
# for collecting and catalogging all aux information that comes in from
# aux modules.
#
attr_reader :reconmgr
attr_reader :auxmgr
#
# Background job management specific to things spawned from this instance
# of the framework.
@ -134,7 +132,7 @@ protected
attr_writer :modules # :nodoc:
attr_writer :sessions # :nodoc:
attr_writer :datastore # :nodoc:
attr_writer :reconmgr # :nodoc:
attr_writer :auxmgr # :nodoc:
attr_writer :jobs # :nodoc:
attr_writer :plugins # :nodoc:

View File

@ -91,6 +91,7 @@ class Module
require 'msf/core/module/platform_list'
require 'msf/core/module/reference'
require 'msf/core/module/target'
require 'msf/core/module/auxiliary_action'
#
# Creates an instance of an abstract module using the supplied information
@ -387,10 +388,10 @@ class Module
end
#
# Returns true if this module is a recon module.
# Returns true if this module is an auxiliary module.
#
def recon?
return (type == MODULE_RECON)
def auxiliary?
return (type == MODULE_AUX)
end
#

View File

@ -0,0 +1,54 @@
require 'msf/core'
###
#
# A target for an exploit.
#
###
class Msf::Module::AuxiliaryAction
#
# Serialize from an array to a Target instance.
#
def self.from_a(ary)
return nil if (ary.length < 2)
self.new(ary.shift, ary.shift)
end
#
# Transforms the supplied source into an array of AuxiliaryActions.
#
def self.transform(src)
Rex::Transformer.transform(src, Array, [ self, String ], 'AuxiliaryAction')
end
def initialize(name, opts={})
self.name = name
self.opts = opts
end
#
# Index the options directly.
#
def [](key)
opts[key]
end
#
# The name of the action ('info')
#
attr_reader :name
#
# Action specific parameters
#
attr_reader :opts
protected
attr_writer :name, :opts # :nodoc:
end

View File

@ -288,10 +288,10 @@ class ModuleManager < ModuleSet
end
#
# Returns the set of loaded recon module classes.
# Returns the set of loaded auxiliary module classes.
#
def recon
return module_sets[MODULE_RECON]
def auxiliary
return module_sets[MODULE_AUX]
end
##
@ -639,7 +639,7 @@ protected
# This method automatically subscribes a module to whatever event providers
# it wishes to monitor. This can be used to allow modules to automatically
# execute or perform other tasks when certain events occur. For instance,
# when a new host is detected, other recon modules may wish to run such
# when a new host is detected, other aux modules may wish to run such
# that they can collect more information about the host that was detected.
#
def auto_subscribe_module(mod)
@ -653,18 +653,6 @@ protected
# if it subscribes to any particular interfaces.
inst = nil
#
# Recon event subscriber check
#
[
Msf::ReconEvent::HostSubscriber,
Msf::ReconEvent::ServiceSubscriber,
].each { |iface|
if (mod.include?(iface) == true)
framework.events.add_recon_subscriber((inst) ? inst : (inst = mod.new))
end
}
#
# Exploit event subscriber check
#

View File

@ -1,208 +0,0 @@
require 'msf/core/module'
module Msf
###
#
# This interface is called by recon modules to notify the framework when
# network elements, services, or other types of things recon modules
# might discovery.
#
###
module ReconEvent
###
#
# The types of changes an entity can undergo.
#
###
module EntityChangeType
Add = 1
Update = 2
Remove = 3
end
###
#
# This module provides methods for handling host entity notifications.
#
###
module HostSubscriber
#
# This routine is called when a change is made to a host, such as it being
# added, modified, or removed.
#
def on_host_changed(context, host, change_type)
case change_type
when EntityChangeType::Add
on_new_host(context, host)
when EntityChangeType::Update
on_updated_host(context, host)
when EntityChangeType::Remove
on_dead_host(context, host)
end
end
#
# This routine is called whenever a new host is found.
#
def on_new_host(context, host)
end
#
# This routine is called whenever a change is made to an existing
# host.
#
def on_updated_host(context, host)
end
#
# Called when a host is considered to be dead after having
# previously been valid.
#
def on_dead_host(context, host)
end
#
# This routine is called whenever a host attribute is found.
#
def on_new_host_attribute(context, host, attribute)
end
end
###
#
# This module provides methods for handling notifications that deal with
# service entities.
#
###
module ServiceSubscriber
#
# This routine is called when a change is made to a service, such as it being
# added, modified, or removed.
#
def on_service_changed(context, host, service, change_type)
case change_type
when EntityChangeType::Add
on_new_service(context, host, service)
when EntityChangeType::Update
on_updated_service(context, host, service)
when EntityChangeType::Remove
on_dead_service(context, host, service)
end
end
#
# This routine is called whenever a new service is found.
#
def on_new_service(context, host, service)
end
#
# This routine is called whenever a change is made to an existing
# service.
#
def on_updated_service(context, host, service)
end
#
# Called when a service is considered to be dead after having
# previously been valid.
#
def on_dead_service(context, host, service)
end
#
# This routine is called whenever a service attribute is found.
#
def on_new_service_attribute(context, host, service, attribute)
end
end
#
# The ReconEvents base mixin includes all methods from the Host and Service
# subscriber interfaces.
#
include HostSubscriber
include ServiceSubscriber
end
###
#
# The recon class acts as a base class for all recon modules. It provides a
# common interface for detecting the presence of hosts, services, and the
# attributes of everything in between. The type of information that can be
# discovered is designed to be generic.
#
###
class Recon < Msf::Module
#
# The various basic sub-types of recon modules.
#
module Type
#
# Indicates that this is an unknown recon module. This recon module
# does something other than discover and analyze.
#
Unknown = "unknown"
#
# Indicates that the recon module discovers things. Discoverer recon
# modules are responsible for collecting information about the presence
# of entities and the attributes of those entities. For instance,
# a discoverer module finds hosts and the services running on those
# hosts and could also determine more granular information about
# the host and service by determining some of their attributes, such
# as a host's platform.
#
Discoverer = "discoverer"
#
# Indicates that the recon module analyzes things. Analyzer recon
# modules take information collected by discoverer recon modules and
# determine or derived more detailed information about an entity or a
# group of entities. For instance, an analyzer module may determine
# that five distinct hosts detected by a discoverer module may actually
# be on the same machine but just virtual hosted. Also, analyzer
# modules might try to do more advanced stuff like crack passwords
# collected by recon modules and other such fun things.
#
Analyzer = "analyzer"
end
require 'msf/core/recon/discoverer'
require 'msf/core/recon/entity'
require 'msf/core/recon/event_context'
#
# Returns MODULE_RECON to indicate that this is a recon module.
#
def self.type
MODULE_RECON
end
#
# Returns MODULE_RECON to indicate that this is a recon module.
#
def type
MODULE_RECON
end
#
# This method returns the general type of recon module.
#
def recon_type
Type::Unknown
end
end
end

View File

@ -1,108 +0,0 @@
module Msf
class Recon
module Attribute
###
#
# This mixin provides methods for setting and getting the values
# of various attributes. As attributes are defined, aliases methods are added
# to the container to make it easier to access the attributes for reading.
# This is done to encourage the direct referencing of attributes for reading
# by an accessor method rather than through using get_attribute which will
# simply return nil if no match was found. By doing it this way, recon
# modules can try to access the attributes of a host, service, or other entity
# that may not yet be defined and thus lead to an exception being thrown.
# This will indicate that not enough information has been gathered yet for the
# recon module to proceed.
#
###
module Container
#
# Initializes the attribute hash.
#
def initialize_attributes
self._attr_hash = Hash.new
end
#
# Wraps get_attribute.
#
def [](key)
get_attribute(key)
end
#
# Wraps set_attribute.
#
def []=(key, val)
set_attribute(key, val)
end
#
# Sets the value of an attribute with the supplied name.
#
def set_attribute(name, val)
# If we've yet to define this method on the container, do so now.
#
# TODO: evalulate the performance of doing it this way.
if (respond_to?(name) == false)
begin
instance_eval("
def #{name}
_attr_hash['#{name}']
end")
rescue SyntaxError
end
end
_attr_hash[name] = val
end
#
# Unsets an attribute entirely.
#
def unset_attribute
_attr_hash.delete(name)
end
#
# Returns the value associated with the supplied attribute name.
#
def get_attribute(name)
_attr_hash[name]
end
#
# Returns a list of all attributes that have had a value set.
#
def attributes
_attr_hash.keys
end
#
# Returns the attribute hash in case direct interaction is necessary.
#
def attribute_hash
_attr_hash
end
#
# Serializes from a hash.
#
def from_hash(hsh)
hsh.each_pair { |k,v|
set_attribute(k, v)
}
end
protected
attr_accessor :_attr_hash # :nodoc:
end
end
end
end

View File

@ -1,44 +0,0 @@
module Msf
class Recon
module Attribute
require 'msf/core/recon/attribute/container'
###
#
# This class acts as a symbolic attribute group and is simply used as a
# means of containing attributes without being an actual attribute or
# an entity itself.
#
###
class Group
include Container
#
# This routine defines an attribute by name by creating accessors that
# reference the internal hash.
#
def self.def_attr(*args)
args.each { |name|
class_eval("
def #{name}
attribute_hash['#{name}']
end
def #{name}=(val)
attribute_hash['#{name}'] = val
end")
}
end
#
# Initializes the attribute group which is simply a container of
# attributes.
#
def initialize
initialize_attributes
end
end
end
end
end

View File

@ -1,26 +0,0 @@
#!/usr/bin/env ruby
$:.unshift(File.join(File.dirname(__FILE__), '..', '..', '..', '..'))
require 'test/unit'
require 'msf/core'
require 'msf/core/recon/attribute/group'
class Msf::Recon::Attribute::Group::UnitTest < Test::Unit::TestCase
Klass = Msf::Recon::Attribute::Group
class Tester < Klass
def_attr :testing
end
def test_target
e = Tester.new
assert_not_nil(e)
e.testing = 1
assert_equal(1, e.testing)
assert_equal(1, e.get_attribute('testing'))
end
end

View File

@ -1,286 +0,0 @@
require 'thread'
require 'rex/sync/event'
module Msf
class Recon
###
#
# This class acts as a base class for all recon modules that attempt to
# discover new entities and attributes of entities. For instance, recon
# modules that attempt to discover the presence of a host, service, user, or
# other forms of entities are considered sub-classes of discoverer recon
# modules. On top of that, recon modules that attempt to discover the
# attributes of any of the previously described entities are considered
# discoverer recon modules as well.
#
###
class Discoverer < Msf::Recon
##
#
# The types of discoverer recon modules that are known about by default.
#
##
module Type
#
# Unknown discoverer module type.
#
Unknown = 'unknown'
#
# Host discoverer.
#
Host = 'host'
#
# Host attribute discoverer.
#
HostAttribute = 'host attribute'
end
##
#
# The set of flags that discoverer modules can use to instruct the
# framework (or themselves) on how to operate.
#
#
module Flags
#
# This flag is used to indicate that a discoverer supports multithreaded
# discovery.
#
Multithreaded = 0x01
end
require 'msf/core/recon/discoverer/host'
require 'msf/core/recon/discoverer/service'
#
# Initializes the base of a recon discoverer module and adds any advanced
# options that may be useful, like the number of threads the framework
# should use for scanning.
#
def initialize(info = {})
super
# Attribute initialization
self.discovery_threads = Array.new
self.discovery_thread_mutex = Mutex.new
self.discovery_thread_event = Rex::Sync::Event.new(false, false)
# If the derived class supports multithreaded scanning, then allow the
# user to set an option to control it
if ((discoverer_flags & Flags::Multithreaded) != 0)
register_advanced_options(
[
OptInt.new('ScanningThreads', [ 0, 'Number of threads to scan with', 1 ])
], Msf::Recon::Discoverer)
end
end
##
#
# Getters/Setters
#
##
#
# This method indicates that this recon module is a discoverer.
#
def recon_type
Msf::Recon::Type::Discoverer
end
#
# This method returns the type of discoverer recon module.
#
def discoverer_type
Type::Unknown
end
#
# This method returns the default discoverer flags that are used to drive
# the host discovery process.
#
def discoverer_flags
Flags::Multithreaded
end
##
#
# Core discoverer interface
#
##
#
# Initiates the discovery process in an implementation independent fashion.
#
def start_discovery
# If the module is already discovering, then we don't need to start
# again
if (is_discovering)
raise RuntimeError, "#{self.refname} is already discovering.", caller
end
# Validate that all the options are okay
options.validate(datastore)
# Reset the discovery event
discovery_thread_event.reset
# Get the default number of threads to spawn
num_threads = default_scanning_threads;
# If more scanning threads were supplied through an advanced option,
# then let us use those
if (datastore['ScanningThreads'] and
datastore['ScanningThreads'].to_i > 0)
num_threads = datastore['ScanningThreads'].to_i
end
# Call a method that allows the derived class to pre-initialization
# before actually starting
discovery_startup
# Spawn as many worker threads as were requested
num_threads.times { |x|
dlog("#{self.refname}: Spawning worker thread #{x+1}/#{num_threads}...", "core", LEV_3)
spawn_discovery_thread
}
end
#
# Terminates the discovery process in an implementation independent
# fashion.
#
def stop_discovery
remaining = 0
# Instruct all remaining discovery threads to stop.
discovery_thread_mutex.synchronize {
remaining = discovery_threads.length
discovery_threads.each { |thr|
thr.stop
}
}
# Flush out the list of discovery threads
discovery_threads.clear
# Call the discovery complete method before the final wakeup.
discovery_complete((remaining > 0) ? true : false)
# Wake up any other threads that might be waiting for this operation to
# complete
discovery_thread_event.set
end
#
# Waits for the recon discoverer to complete its operation.
#
def wait_for_completion(timeout = nil)
discovery_thread_event.wait(timeout)
end
#
# This method returns whether or not the recon module is discovering.
#
def is_discovering
(discovery_threads.length > 0) ? true : false
end
##
#
# Defaults
#
##
#
# This method returns the default number of scanning threads for this
# discoverer.
#
def default_scanning_threads
1
end
protected
##
#
# Overridable protected methods
#
##
#
# This method is called when discovery is about to begin as the result of a
# call being made to start_discovery.
#
def discovery_startup
end
#
# This method is called when discovery is finished, whether it was aborted
# or finished as normal. If it was aborted, the first parameter will
# indicate that fact by passing true.
#
def discovery_complete(aborted)
end
#
# The entry point for all discovery threads. This method should be
# overridden by derived classes.
#
def discovery_thread
end
##
#
# Internal routines
#
##
#
# This method spawns a worker thread and adds it to the list of active
# workers.
#
def spawn_discovery_thread
# Synchronize the addition of the thread to the list of threads
discovery_thread_mutex.synchronize {
self.discovery_threads << Thread.new {
begin
# Perform the arbitrary discovery task.
discovery_thread
# Synchronize the removal of ourself from the list of threads.
discovery_thread_mutex.synchronize {
discovery_threads.delete(Thread.current)
# If we detect that there are no more threads running, set the
# event to indicate that we've reached completion.
if (discovery_threads.length == 0)
# Call the discovery complete method
discovery_complete(false)
discovery_thread_event.set
end
}
rescue
elog("Exception in discovery thread: #{$!}\n#{$@.join("\n")}")
end
}
}
end
attr_accessor :discovery_threads # :nodoc:
attr_accessor :discovery_thread_mutex # :nodoc:
attr_accessor :discovery_thread_event # :nodoc:
end
end
end

View File

@ -1,231 +0,0 @@
require 'thread'
require 'rex/socket/subnet_walker'
module Msf
class Recon
class Discoverer
###
#
# This class provides a base class for all recon modules that attempt to
# discover the presence of a host.
#
###
class Host < Msf::Recon::Discoverer
#
# Initializes an instance of a host discoverer recon module and adds
# options that are common to all host discoverers, like subnet and netmask.
#
def initialize(info = {})
super
# Initialize the mutex that we'll use to synchronize the subnet walker
# instance that's created during discovery
self.swalker_mutex = Mutex.new
# TODO - support random scan
#register_advanced_options(
# [
# OptBool.new('RandomScan', [ 0, 'Scan the subnet in a random order', 'false' ])
# ], Msf::Recon::Discoverer::Host)
# Register the options that this particular discovery module uses
register_options(
[
OptAddress.new('SUBNET', [ 1, 'The subnet to scan' ]),
OptAddress.new('NETMASK', [ 1, 'The netmask of the subnet' ])
], Msf::Recon::Discoverer::Host)
end
#
# This method returns that this is a host discoverer recon module.
#
def discoverer_type
Type::Host
end
#
# This method is called when a host should be probed to determine whether
# or not it is alive. If the host is found to be alive, HostState::Alive
# should be returned. Otherwise, if a host was found to be dead, then
# HostState::Dead should be returned. If its state could not be
# determined, HostState::Unknown should be returned.
#
# This method can also return a hash that contains information that will be
# passed as part of the event context to the reporting subsystem of the
# recon manager. This EventContext instance will, in turn, be passed to
# any subscribers of recon-related events. For instance, if a port scanner
# connects to a port on a given host, it can pass the connection around to
# other recon modules to give them a chance to work with it. The following
# keys are special in a hash returned from probe_host:
#
# state - Equivalent to one of the three HostState values.
# connection - The connection associated with the host (TCP, UDP, etc).
#
def probe_host(address)
HostState::Unknown
end
#
# This method acts the same as the probe_host method except it takes as an
# argument an array of IP addresses to probe and expects an array of
# address statuses to be returned to that corresponds to the array of
# addresses passed in as arguments. This method is only called if
# hosts_per_block is not one.
#
# The array elements can also take the form of a hash as described in the
# probe_host method description.
#
def probe_host_block(addresses)
addresses.map { HostState::Unknown }
end
#
# Allows a derived class to cleanup anything, like a socket, that may have
# been used during the probe operation. The state parameter is equivalent
# to the return value from probe_host (or probe_host_block for each entry
# in the array).
#
def probe_host_cleanup(address, state)
end
protected
#
# This method initializes the subnet walker for this instance and gets the
# ball rolling.
#
def discovery_startup
# Create the subnet walker instance
self.swalker = Rex::Socket::SubnetWalker.new(
datastore['SUBNET'], datastore['NETMASK'])
end
#
# This is the entry point for any number of threads that may be spawned to
# discover hosts for this module.
#
def discovery_thread
# If we're processing one IP address per block, then do a singular probe
# by calling probe_host for each address.
if (hosts_per_block == 1)
while (ip = next_ip)
report_host_state(ip, probe_host(ip))
end
# Otherwise, get up to the number of hosts per block defined and call
# probe_host_block.
else
begin
addresses = []
# Fill up the array of addresses as high as we can go
while (ip = next_ip)
addresses << ip
end
# If we have no addresses to process, then break out of the loop
break if (addresses.length == 0)
# Probe the host block and get the statuses
statuses = probe_host_block(addresses)
# If no statuses were returned, something odd happened, break out.
if (statuses.length == 0)
wlog("#{self.refname}: probe of #{addresses.length} addresses returned no status")
break
end
# Report the status associated with each address
addresses.each_with_index { |address, idx|
report_host_state(address, statuses[idx])
}
end while (true)
end
end
#
# Returns the next IP address to the caller in a synchronized fashion. If
# no IPs are left to be enumerated, nil is returned.
#
def next_ip
swalker_mutex.synchronize {
swalker.next_ip
}
end
#
# This method reports host state information to the recon manager, possibly
# including an event context.
#
def report_host_state(ip, istate)
# Create a nil context
context = nil
state = istate
# If a hash was returned, we should create an event context to
# pass to the notification.
if (istate.kind_of?(Hash))
context = Msf::Recon::EventContext.new
# Serialize the context from the hash
context.from_hash(state)
# Grab the real state from the hash
state = istate['state']
end
# Report the host's state to the recon manager.
framework.reconmgr.report_host_state(
self, ip, state, context)
# Perform cleanup as necessary (only if istate was a Hash)
if (context)
probe_host_cleanup(ip, istate)
end
end
##
#
# Defaults that can be overridden by derived classes
#
##
#
# The default number of hosts to process per-block. If this number is one,
# the probe_host method will be called. Otherwise, the probe_block method
# will be called which takes an array of IP addresses to probe.
#
def hosts_per_block
1
end
attr_accessor :swalker # :nodoc:
attr_accessor :swalker_mutex # :nodoc:
end
###
#
# HostAttribute
# -------------
#
# This class provides a base class for all recon modules that attempt to
# discover specific attributes about a host that was detected through a Host
# discoverer recon module.
#
###
class HostAttribute < Msf::Recon::Discoverer
#
# Returns Type::HostAttribute.
#
def discoverer_type
Type::HostAttribute
end
end
end
end
end

View File

@ -1,98 +0,0 @@
module Msf
class Recon
class Discoverer
###
#
# This class provides a base class for all recon modules that attempt to
# discover the presence of a service on a host.
#
###
class Service < Msf::Recon::Discoverer
#
# Initializes a service discoverer recon module that is responsible for
# locating running services or information about services that are running
# on hosts.
#
def initialize(info = {})
super
# Register the options that this particular discovery module uses
register_options(
[
Opt::RHOST
], Msf::Recon::Discoverer::Service)
end
#
# This method returns that this is a service discoverer recon module.
#
def discoverer_type
Type::Service
end
#
# By default, service discoverer recon modules do no support
# multi-threading.
#
def discoverer_flags
0
end
#
# Probes a host entity to see what services it has open. Extended modules
# should report service state changes directly via the report_service_state
# instance method.
#
def probe_host(host)
end
protected
#
# Wraps the probing of a host.
#
def discovery_thread
if ((host = framework.reconmgr.get_host(datastore['RHOST'])) == nil)
host = Msf::Recon::Entity::Host.new(datastore['RHOST'])
end
probe_host(host)
end
#
# This method reports the state of a service to the recon manager so that
# it can be tracked appropriately.
#
def report_service_state(host, proto, port, istate)
state = istate
context = nil
# If the state passed in as an argument is a hash, then create an event
# context that we'll pass along to the recon manager in case other
# subscribers might be able to make use of it.
if (istate.kind_of?(Hash))
context = Msf::Recon::EventContext.new
context.from_hash(istate)
state = istate['state']
end
# Log that we detected an up service
if (state == ServiceState::Up)
dlog("Found port #{port} (#{proto}) open on #{host.address}.", "core",
LEV_2)
end
# Pass the normalized service state notifications to the recon manager.
framework.reconmgr.report_service_state(self, host, proto, port,
state, context)
end
end
end
end
end

View File

@ -1,55 +0,0 @@
module Msf
class Recon
###
#
# This class represents an abstract entity that can be discovered during the
# recon process, such as a host, a service, a user, or some other formal
# and distinct thing. All entities can have zero or may attributes and
# may contain zero or more entities of various types. This is pretty
# abstract, like woah.
#
###
class Entity
require 'msf/core/recon/attribute/group'
require 'msf/core/recon/entity/group'
#
# Entities are all offspring of the framework
#
include Framework::Offspring
#
# All entities can contain attributes
#
include Attribute::Container
#
# All entities can contain zero or more entities
#
include Entity::Container
require 'msf/core/recon/entity/host'
require 'msf/core/recon/entity/service'
require 'msf/core/recon/entity/user'
#
# Initializes the entity's attributes and sub-entities.
#
def initialize
initialize_attributes
initialize_entities
end
#
# Returns the entity's type.
#
def entity_type
'unknown'
end
attr_accessor :needs_register # :nodoc:
end
end
end

View File

@ -1,26 +0,0 @@
#!/usr/bin/env ruby
$:.unshift(File.join(File.dirname(__FILE__), '..', '..', '..'))
require 'test/unit'
require 'msf/core'
require 'msf/core/recon/entity'
class Msf::Recon::Entity::UnitTest < Test::Unit::TestCase
Klass = Msf::Recon::Entity
def test_target
e = Klass.new
assert_not_nil(e)
e.set_attribute('foo', 4)
assert_not_nil(e.foo)
assert_equal(4, e.foo)
assert_equal(4, e.get_attribute('foo'))
e.set_attribute('foo', 5)
assert_equal(5, e.foo)
assert_equal(5, e.get_attribute('foo'))
end
end

View File

@ -1,90 +0,0 @@
module Msf
class Recon
class Entity
class Group
end
###
#
# This mixin is included when something wishes to be capable of containing
# entities of an arbitrary type.
#
###
module Container
#
# Initializes the array of entities.
#
def initialize_entities
self._entity_hash = Hash.new
end
#
# This routine adds a sub-container of entities to this entity container.
#
def add_entity_subcontainer(name, container = Group.new)
if (self._entity_hash[name] == nil)
add_entity(name, container)
end
self._entity_hash[name]
end
#
# Adds an entity to the container.
#
def <<(entity)
add_entity(entity)
end
#
# Adds an entity to the container.
#
def add_entity(name, entity)
self._entity_hash[name] = entity
if (respond_to?(name) == false)
instance_eval("
def #{name}
_entity_hash[#{name}]
end
")
end
entity
end
#
# Returns the entity associated with the supplied name.
#
def get_entity(name)
_entity_hash[name]
end
#
# Removes an entity from the hash of entities.
#
def delete_entity(entity)
self._entity_hash.delete(entity)
end
#
# Returns the hash of entities to the caller.
#
def entities
_entity_hash
end
protected
#
# The protected entity hash itself.
#
attr_accessor :_entity_hash
end
end
end
end

View File

@ -1,72 +0,0 @@
module Msf
class Recon
class Entity
require 'msf/core/recon/entity/container'
###
#
# This class acts as a symbolic entity group and is simply used to group
# entities together without itself being an entity. This is analagous to the
# Attribute::Group class.
#
###
class Group
include Container
#
# Initializes an entity group which is simply an entity container.
#
def initialize
initialize_entities
end
end
###
#
# This class extends the Group base class to provide some default protocol
# group sub-containers for entities that are specific to a given service
# protocol.
#
###
class ServiceGroup < Group
#
# Initializes a group of services and breaks them down into their
# sub-protocols which can be accessed through the 'tcp' and 'udp'
# attributes.
#
def initialize
super
# Add protocol-specific subcontainer groups
self.tcp = Group.new
self.udp = Group.new
end
#
# This attribute is a sub-group that contains all TCP services.
#
attr_reader :tcp
#
# This attribute is a sub-group that contains all UDP services.
#
attr_reader :udp
protected
attr_writer :tcp, :udp # :nodoc:
end
#
# Aliased class names for now.
#
HostGroup = Group
UserGroup = Group
end
end
end

View File

@ -1,88 +0,0 @@
module Msf
class Recon
class Entity
require 'msf/core/recon/entity/group'
###
#
# This class represents a logical host entity. Hosts symbolize a machine that
# may have zero or more services running on it. Information about the host,
# such as platform, architecture, and other attributes may be gathered and
# populated over time after its initialize discovery through a host discoverer
# recon module.
#
###
class Host < Entity
###
#
# This class defines some of the standard system attributes that a host
# would have.
#
###
class SystemAttributeGroup < Attribute::Group
#
# The platform that the host is running.
#
def_attr :platform
#
# The architecture that the host is running.
#
def_attr :arch
#
# The time on the host machine.
#
def_attr :time
end
#
# Initializes a host entity with the supplied address after being found
# during recon.
#
def initialize(address)
super()
# Holds the address of the host that this entity instance is associated
# with.
self.address = address
# Add an attribute group that will contain system information for this
# host.
self.sys = SystemAttributeGroup.new
# Create a service group instance for this host.
self.services = ServiceGroup.new
end
#
# This method returns a pretty string representing this host.
#
def pretty
"#{address}"
end
#
# The address that the host instance is associated with.
#
attr_reader :address
#
# The system attributes for this host.
#
attr_reader :sys
#
# The services known to be running on this host.
#
attr_reader :services
protected
attr_writer :address, :sys, :services # :nodoc:
end
end
end
end

View File

@ -1,27 +0,0 @@
#!/usr/bin/env ruby
$:.unshift(File.join(File.dirname(__FILE__), '..', '..', '..', '..'))
require 'test/unit'
require 'msf/core'
require 'msf/core/recon/entity'
class Msf::Recon::Entity::Host::UnitTest < Test::Unit::TestCase
Klass = Msf::Recon::Entity::Host
def test_target
e = Klass.new
assert_not_nil(e)
assert_not_nil(e.sys)
assert_not_nil(e.services)
e.sys.platform = 4
assert_equal(4, e.sys.platform)
e.sys.arch = 4
assert_equal(4, e.sys.arch)
e.sys.time = 4
assert_equal(4, e.sys.time)
end
end

View File

@ -1,53 +0,0 @@
module Msf
class Recon
class Entity
###
#
# This class represents a logical service entity. Services symbolize remote
# functionality provided by a host by means of some network-based protocol,
# such as TCP over IP. Information about a service, such as its protocol,
# port, banner, and other information is conveyed through attributes of the
# service entity.
#
###
class Service < Entity
#
# Initializes a service entity that has been found to be running on a host.
#
def initialize(proto, port = nil)
super()
#
# Initialize the local attributes
#
self.proto = proto
self.port = port
end
#
# This method returns a pretty string representation of the service.
#
def pretty
"#{port} (#{proto})"
end
#
# The protocol this service is using, such as 'tcp'.
#
attr_reader :proto
#
# The port this service is listening on, if applicable.
#
attr_reader :port
protected
attr_writer :proto, :port # :nodoc:
end
end
end
end

View File

@ -1,28 +0,0 @@
module Msf
class Recon
require 'msf/core/recon/attribute/group'
###
#
# This mixin provides an abstract interface to the ``context'' parameter that
# is passed around to the recon event handlers. Instances of this are meant
# to contain data that is specific to the event being reported and is meant to
# be conveyed in a form that allows other recon modules to only pay attention
# to the things that it cares about. For instance, one recon module may wish
# to allow other recon modules to re-use the TCP connection it established to
# a host for future probing. This connection instance would be conveyed
# through the event context in a known attribute.
#
###
class EventContext < Attribute::Group
#
# This named attribute can be used to pass a connection handle between
# recon modules when certain events occur.
#
def_attr :connection
end
end
end

View File

@ -1,21 +0,0 @@
#!/usr/bin/env ruby
$:.unshift(File.join(File.dirname(__FILE__), '..', '..', '..'))
require 'test/unit'
require 'msf/core'
require 'msf/core/recon/event_context'
class Msf::Recon::EventContext::UnitTest < Test::Unit::TestCase
Klass = Msf::Recon::EventContext
def test_target
e = Klass.new
assert_not_nil(e)
e.connection = 4
assert_equal(4, e.connection)
assert_equal(4, e.get_attribute('connection'))
end
end

View File

@ -1,219 +0,0 @@
require 'thread'
require 'msf/core/recon'
module Msf
###
#
# The states that a host can be in.
#
###
module HostState
#
# The host is alive.
#
Alive = "alive"
#
# The host is dead.
#
Dead = "down"
#
# The host state is unknown.
#
Unknown = "unknown"
end
###
#
# The states that a service can be in.
#
###
module ServiceState
#
# The service is alive.
#
Up = "up"
#
# The service is dead.
#
Dead = "down"
#
# The service state is unknown.
#
Unknown = "unknown"
end
###
#
# This class manages the tracking of entities and the dispatching of events
# pertaining to recon information collection. When hosts are discovered, the
# recon module tracks them and dispatches the appropriate events to the
# framework event manager so that subscribers can be notified.
#
###
class ReconManager
include Framework::Offspring
###
#
# This mixin is used to extend Host entity instances such that the recon
# manager can track internal state information.
#
###
module ExtendedHostState
#
# Tracks the number of manager state unknowns returned.
#
attr_accessor :_mgr_state_unknown
end
def initialize(framework)
self.framework = framework;
self.host_hash = Hash.new
self.host_hash_mutex = Mutex.new
end
##
#
# Host reporting
#
##
#
# Reports a host as being in a given state by address.
#
def report_host_state(mod, address, state, context = nil)
# TODO: use the current thread's Comm as part of the hash key to support
# conflicting addresses in different networks (established through
# different comms).
hash_key = host_hash_key(address)
# If a host already exists with this information, then check to see what
# status we received.
if (host = host_hash[hash_key])
if (state == HostState::Unknown)
host._mgr_state_unknown += 1
elsif (state == HostState::Dead)
dead_host(host, context)
else
host._mgr_state_unknown = 0
end
# Otherwise, if we have no host yet, get one and start handling it.
elsif (state == HostState::Alive)
host = Recon::Entity::Host.new(address)
new_host(hash_key, host, context)
end
# TODO: evalulate _mgr_state_unknown to determine if the host should be
# dead
end
#
# This method reports a host's service state.
#
def report_service_state(mod, host, proto, port, state, context = nil)
# If the supplied host object has not yet been registered, do so now.
# This occurs when a service module happens to discover a host that was
# not originally thought to have existed
if (host.needs_register != false and
state == ServiceState::Up)
new_host(host_hash_key(host.address), host, context)
end
# Define the service entity name
ename = "port_#{port}"
# Get the proto subcontainer for this entity, or add it if it isn't
# already defined.
p = host.services.add_entity_subcontainer(proto.downcase)
# Now that we have the protocol subcontainer, get the service instance
# associated witht he port (if one has been defined).
if (service = p.get_entity(ename))
# TODO: update
elsif (state == ServiceState::Up)
service = Recon::Entity::Service.new(proto, port)
p.add_entity("port_#{ename}", service)
# TODO: Notify
end
end
#
# This method returns the host object associated with the supplied address.
# If one does not exist, it is created.
#
def get_host(address)
host_hash[host_hash_key(address)]
end
#
# This method iterates the host hash calling the supplied block with the
# address and host instances of each entry in the host hash.
#
def each_host(&block)
host_hash.each_pair { |k, host|
block.call(host.address, host)
}
end
protected
#
# This method returns the hash key to use with the supplied address.
#
# TODO: make this use comm info
#
def host_hash_key(address)
address
end
#
# Called when a new host is detected.
#
def new_host(hash_key, host, context)
# Extend the host and initialize it properly
host.extend(ExtendedHostState)
host._mgr_state_unknown = 0
# Add the host tot he host hash
host_hash_mutex.synchronize {
self.host_hash[hash_key] = host
}
ilog("recon: New host discoverered: #{host.pretty}", "core",
LEV_1)
# Now that the host has registered, we can't flag it up.
host.needs_register = false
# Notify any host event subscribes of our new found fate.
framework.events.on_host_changed(
context, host, ReconEvent::EntityChangeType::Add)
end
#
# Processes cleanup necessary when a dead host is encountered.
#
def dead_host(host, context)
host_hash_mutex.synchronize {
self.host_hash.delete(host)
}
# Notify any host event subscribers that the host has died.
framework.events.on_host_changed(
context, host, ReconEvent::EntityChangeType::Remove)
end
attr_accessor :host_hash
attr_accessor :host_hash_mutex
end
end

View File

@ -0,0 +1,57 @@
module Msf
module Ui
module Console
module CommandDispatcher
###
#
# Recon module command dispatcher.
#
###
class Auxiliary
include Msf::Ui::Console::ModuleCommandDispatcher
#
# Returns the hash of commands specific to auxiliary modules.
#
def commands
{
"run" => "Initiates the auxiliary module",
}.merge( (mod ? mod.auxiliary_commands : {}) )
end
#
# Allow modules to define their own commands :-)
#
def method_missing(meth, *args)
if (mod and mod.respond_to?(meth.to_s))
return mod.send(meth.to_s, *args)
end
return
end
#
#
# Returns the command dispatcher name.
#
def name
"Auxiliary"
end
# Executes the standard 'run' command
#
def cmd_run(*args)
begin
mod.run_simple(
'LocalInput' => driver.input,
'LocalOutput' => driver.output)
rescue
log_error("Auxiliary failed: #{$!}")
return false
end
end
end
end end end end

View File

@ -2,7 +2,7 @@ require 'msf/ui/console/command_dispatcher/encoder'
require 'msf/ui/console/command_dispatcher/exploit'
require 'msf/ui/console/command_dispatcher/nop'
require 'msf/ui/console/command_dispatcher/payload'
require 'msf/ui/console/command_dispatcher/recon'
require 'msf/ui/console/command_dispatcher/auxiliary'
module Msf
module Ui
@ -107,7 +107,7 @@ class Core
banner += "#{framework.stats.num_encoders} encoders - "
banner += "#{framework.stats.num_nops} nops\n"
banner += " =[ "
banner += "#{framework.stats.num_recon} recon\n"
banner += "#{framework.stats.num_auxiliary} aux\n"
banner += "\n"
# Display the banner
@ -756,7 +756,7 @@ class Core
show_nops
show_exploits
show_payloads
show_recon
show_auxiliary
show_plugins
when 'encoders'
show_encoders
@ -766,8 +766,8 @@ class Core
show_exploits
when 'payloads'
show_payloads
when 'recon'
show_recon
when 'aux'
show_auxiliary
when 'options'
if (mod)
show_options(mod)
@ -802,7 +802,7 @@ class Core
# Tab completion for the show command
#
def cmd_show_tabs(str, words)
res = %w{all encoders nops exploits payloads recon plugins}
res = %w{all encoders nops exploits payloads aux plugins}
if (active_module)
res.concat(%w{ options advanced evasion targets })
end
@ -943,8 +943,8 @@ class Core
dispatcher = Nop
when MODULE_PAYLOAD
dispatcher = Payload
when MODULE_RECON
dispatcher = Recon
when MODULE_AUX
dispatcher = Auxiliary
else
print_error("Unsupported module type: #{mod.type}")
return false
@ -1153,8 +1153,8 @@ protected
end
end
def show_recon # :nodoc:
show_module_set("Recon", framework.recon)
def show_auxiliary # :nodoc:
show_module_set("Auxiliary", framework.auxiliary)
end
def show_options(mod) # :nodoc:

View File

@ -1,47 +0,0 @@
module Msf
module Ui
module Console
module CommandDispatcher
###
#
# Recon module command dispatcher.
#
###
class Recon
include Msf::Ui::Console::ModuleCommandDispatcher
#
# Returns the hash of commands specific to recon modules.
#
def commands
{
"discover" => "Initiates the recon discovery process for this module",
}
end
#
#
# Returns the command dispatcher name.
#
def name
"Recon"
end
# Starts discovering like a good recon module should.
#
def cmd_discover(*args)
begin
mod.discover_simple(
'LocalInput' => driver.input,
'LocalOutput' => driver.output)
rescue
log_error("Recon failed: #{$!}")
return false
end
end
end
end end end end

View File

@ -378,7 +378,7 @@ module Text
#
def self.gzip(str, level = 9)
raise RuntimeError, "Gzip support is not present." if (!zlib_present?)
raise RuntimeError, "Invalid gzip compression level" if (level < 1 or level > 9)
raise RuntimeError, "Invalid gzip compression level" if (level < 1 or level > 9)
s = ""
gz = Zlib::GzipWriter.new(StringIO.new(s), level)

View File

@ -189,10 +189,11 @@ module DispatcherShell
entries = dispatcher_stack.length
dispatcher_stack.each { |dispatcher|
begin
if (dispatcher.respond_to?('cmd_' + method))
run_command(dispatcher, method, arguments)
next if not dispatcher.respond_to?('commands')
begin
if (dispatcher.commands.has_key?(method))
run_command(dispatcher, method, arguments)
found = true
end
rescue
@ -225,7 +226,7 @@ module DispatcherShell
# Runs the supplied command on the given dispatcher.
#
def run_command(dispatcher, method, arguments)
eval("dispatcher.#{'cmd_' + method}(*arguments)")
dispatcher.send('cmd_' + method, *arguments)
end
#