Class: Msf::Modules::Loader::Base Abstract

Inherits:
Object
  • Object
show all
Defined in:
lib/msf/core/modules/loader/base.rb

Overview

This class is abstract.

Subclass and override #each_module_reference_name, #loadable?, #module_path, and #read_module_content.

Responsible for loading modules for Msf::ModuleManager.

Direct Known Subclasses

Archive, Directory

Constant Summary collapse

DIRECTORY_BY_TYPE =

Not all types are pluralized when a directory name, so here's the mapping that currently exists

{
  Msf::MODULE_AUX => 'auxiliary',
  Msf::MODULE_ENCODER => 'encoders',
  Msf::MODULE_EXPLOIT => 'exploits',
  Msf::MODULE_NOP => 'nops',
  Msf::MODULE_PAYLOAD => 'payloads',
  Msf::MODULE_POST => 'post'
}
NAMESPACE_MODULE_LINE =

This must calculate the first line of the NAMESPACE_MODULE_CONTENT string so that errors are reported correctly

__LINE__ + 4
NAMESPACE_MODULE_CONTENT =

By calling module_eval from inside the module definition, the lexical scope is captured and available to the code in module_content.

<<-EOS
  # ensure the namespace module can respond to checks during loading
  extend Msf::Modules::Namespace

  class << self
    # The loader that originally loaded this module
    #
    # @return [Msf::Modules::Loader::Base] the loader that loaded this namespace module and can reload it.
    attr_accessor :loader

    # @return [String] The path under which the module of the given type was found.
    attr_accessor :parent_path
  end

  # Calls module_eval on the module_content, but the lexical scope of the namespace_module is passed through
  # module_eval, so that module_content can act like it was written inline in the namespace_module.
  #
  # @param [String] module_content The content of the {Msf::Module}.
  # @param [String] module_path The path to the module, so that error messages in evaluating the module_content can
  #   be reported correctly.
  def self.module_eval_with_lexical_scope(module_content, module_path)
    # By calling module_eval from inside the module definition, the lexical scope is captured and available to the
    # code in module_content.
    module_eval(module_content, module_path)
  end
EOS
MODULE_EXTENSION =

The extension for metasploit modules.

'.rb'
MODULE_SEPARATOR =

String used to separate module names in a qualified module name.

'::'
NAMESPACE_MODULE_NAMES =

The base namespace name under which namespace modules are created.

['Msf', 'Modules']
UNIT_TEST_REGEX =

Regex that can distinguish regular ruby source from unit test source.

/rb\.(ut|ts)\.rb$/

Instance Method Summary collapse

Constructor Details

#initialize(module_manager) ⇒ Base

Returns a new instance of Base

Parameters:

  • module_manager (Msf::ModuleManager)

    The module manager that caches the loaded modules.


69
70
71
# File 'lib/msf/core/modules/loader/base.rb', line 69

def initialize(module_manager)
  @module_manager = module_manager
end

Instance Method Details

#load_module(parent_path, type, module_reference_name, options = {}) ⇒ false, true

Loads a module from the supplied path and module_reference_name.

Parameters:

  • parent_path (String)

    The path under which the module exists. This is not necessarily the same path as passed to #load_modules: it may just be derived from that path.

  • type (String)

    The type of module.

  • module_reference_name (String)

    The canonical name for referring to the module.

  • options (Hash) (defaults to: {})

    Options used to force loading and track statistics

Options Hash (options):

  • :count_by_type (Hash{String => Integer})

    Maps the module type to the number of module loaded

  • :force (Boolean) — default: false

    whether to force loading of the module even if the module has not changed.

  • :recalculate_by_type (Hash{String => Boolean})

    Maps type to whether its Msf::ModuleManager::ModuleSets#module_set needs to be recalculated.

  • :reload (Boolean) — default: false

    whether this is a reload.

Returns:

  • (false)

    if :force is false and parent_path has not changed.

  • (false)

    if exception encountered while parsing module content

  • (false)

    if the module is incompatible with the Core or API version.

  • (false)

    if the module does not implement a Metasploit(d+) class.

  • (false)

    if the module's is_usable method returns false.

  • (true)

    if all those condition pass and the module is successfully loaded.

See Also:


117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/msf/core/modules/loader/base.rb', line 117

def load_module(parent_path, type, module_reference_name, options={})
  options.assert_valid_keys(:count_by_type, :force, :recalculate_by_type, :reload)
  force = options[:force] || false
  reload = options[:reload] || false

  module_path = self.module_path(parent_path, type, module_reference_name)
  file_changed = module_manager.file_changed?(module_path)

  unless force or file_changed
    dlog("Cached module from #{module_path} has not changed.", 'core', LEV_2)

    return false
  end

  reload ||= force || file_changed

  metasploit_class = nil

  module_content = read_module_content(parent_path, type, module_reference_name)

  if module_content.empty?
    # read_module_content is responsible for calling {#load_error}, so just return here.
    return false
  end

  try_eval_module = lambda { |namespace_module|
    # set the parent_path so that the module can be reloaded with #load_module
    namespace_module.parent_path = parent_path

    begin
      namespace_module.module_eval_with_lexical_scope(module_content, module_path)
    # handle interrupts as pass-throughs unlike other Exceptions so users can bail with Ctrl+C
    rescue ::Interrupt
      raise
    rescue ::Exception => error
      # Hide eval errors when the module version is not compatible
      begin
        namespace_module.version_compatible!(module_path, module_reference_name)
      rescue Msf::Modules::VersionCompatibilityError => version_compatibility_error
        load_error(module_path, version_compatibility_error)
      else
        load_error(module_path, error)
      end

      return false
    end

    begin
      namespace_module.version_compatible!(module_path, module_reference_name)
    rescue Msf::Modules::VersionCompatibilityError => version_compatibility_error
      load_error(module_path, version_compatibility_error)

      return false
    end

    begin
      metasploit_class = namespace_module.metasploit_class!(module_path, module_reference_name)
    rescue Msf::Modules::MetasploitClassCompatibilityError => error
      load_error(module_path, error)

      return false
    end

    unless usable?(metasploit_class)
      ilog(
          "Skipping module (#{module_reference_name} from #{module_path}) because is_usable returned false.",
          'core',
          LEV_1
      )

      return false
    end

    if reload
      ilog("Reloading #{type} module #{module_reference_name}. Ambiguous module warnings are safe to ignore", 'core', LEV_2)
    else
      ilog("Loaded #{type} module #{module_reference_name} under #{parent_path}", 'core', LEV_2)
    end

    module_manager.module_load_error_by_path.delete(module_path)

    true
  }

  loaded = namespace_module_transaction(type + "/" + module_reference_name, :reload => reload, &try_eval_module)
  unless loaded
    return false
  end

  # Do some processing on the loaded module to get it into the right associations
  module_manager.on_module_load(
      metasploit_class,
      type,
      module_reference_name,
      {
          # files[0] is stored in the {Msf::Module#file_path} and is used to reload the module, so it needs to be a
          # full path
          'files' => [
              module_path
          ],
          'paths' => [
              module_reference_name
          ],
          'type' => type
      }
  )

  # Set this module type as needing recalculation
  recalculate_by_type = options[:recalculate_by_type]

  if recalculate_by_type
    recalculate_by_type[type] = true
  end

  # The number of loaded modules this round
  count_by_type = options[:count_by_type]

  if count_by_type
    count_by_type[type] ||= 0
    count_by_type[type] += 1
  end

  return true
end

#load_modules(path, options = {}) ⇒ Hash{String => Integer}

Note:

Only paths where #loadable? returns true should be passed to this method.

Loads all of the modules from the supplied path.

Parameters:

  • path (String)

    Path under which there are modules

  • options (Hash) (defaults to: {})

Options Hash (options):

  • force (Boolean) — default: false

    Whether to force loading of the module even if the module has not changed.

  • whitelist (Array)

    An array of regex patterns to search for specific modules

Returns:

  • (Hash{String => Integer})

    Maps module type to number of modules loaded


254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# File 'lib/msf/core/modules/loader/base.rb', line 254

def load_modules(path, options={})
  options.assert_valid_keys(:force, :whitelist)

  force = options[:force]
  count_by_type = {}
  recalculate_by_type = {}

  # This is used to avoid loading the same thing twice
  loaded_items = []

  each_module_reference_name(path, options) do |parent_path, type, module_reference_name|
    # In msfcli mode, if a module is already loaded, avoid loading it again
    next if loaded_items.include?(module_reference_name) and options[:whitelist]

    # Keep track of loaded modules in msfcli mode
    loaded_items << module_reference_name if options[:whitelist]
    load_module(
        parent_path,
        type,
        module_reference_name,
        :recalculate_by_type => recalculate_by_type,
        :count_by_type => count_by_type,
        :force => force
    )
  end

  recalculate_by_type.each do |type, recalculate|
    if recalculate
      module_set = module_manager.module_set(type)
      module_set.recalculate
    end
  end

  count_by_type
end

#loadable?(path) ⇒ Boolean

This method is abstract.

Override and determine from properties of the path or the file to which the path points whether it is loadable using #load_modules for the subclass.

Returns whether the path can be loaded this module loader.

Parameters:

  • path (String)

    Path under which there are modules

Returns:

  • (Boolean)

Raises:

  • (::NotImplementedError)

81
82
83
# File 'lib/msf/core/modules/loader/base.rb', line 81

def loadable?(path)
  raise ::NotImplementedError
end

#reload_module(original_metasploit_class_or_instance) ⇒ Class, Msf::Module

Reloads the specified module.

Parameters:

  • original_metasploit_class_or_instance (Class, Msf::Module)

    either an instance of a module or a module class. If an instance is given, then the datastore will be copied to the new instance returned by this method.

Returns:

  • (Class, Msf::Module)

    original_metasploit_class_or_instance if an instance of the reloaded module cannot be created.

  • (Msf::Module)

    new instance of original_metasploit_class with datastore copied from original_metasploit_instance.


298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/msf/core/modules/loader/base.rb', line 298

def reload_module(original_metasploit_class_or_instance)
  if original_metasploit_class_or_instance.is_a? Msf::Module
    original_metasploit_instance = original_metasploit_class_or_instance
    original_metasploit_class = original_metasploit_class_or_instance.class
  else
    original_metasploit_instance = nil
    original_metasploit_class = original_metasploit_class_or_instance
  end

  namespace_module = original_metasploit_class.parent
  parent_path = namespace_module.parent_path

  type = original_metasploit_class_or_instance.type
  module_reference_name = original_metasploit_class_or_instance.refname

  dlog("Reloading module #{module_reference_name}...", 'core')

  if load_module(parent_path, type, module_reference_name, :force => true, :reload => true)
    # Create a new instance of the module
    reloaded_module_instance = module_manager.create(module_reference_name)

    if reloaded_module_instance
      if original_metasploit_instance
        # copy over datastore
        reloaded_module_instance.datastore.update(original_metasploit_instance.datastore)
      end
    else
      elog("Failed to create instance of #{original_metasploit_class_or_instance.refname} after reload.", 'core')

      # Return the old module instance to avoid an strace trace
      return original_metasploit_class_or_instance
    end
  else
    elog("Failed to reload #{module_reference_name}")

    return nil
  end

  # Let the specific module sets have an opportunity to handle the fact
  # that this module was reloaded.
  module_set = module_manager.module_set(type)
  module_set.on_module_reload(reloaded_module_instance)

  # Rebuild the cache for just this module
  module_manager.refresh_cache_from_module_files(reloaded_module_instance)

  reloaded_module_instance
end