Browse Source

Refactor Msf::ModuleManager

[Fixes #36737359]

Refactor Msf::ModuleManager into concerns so its easier to understand and
duplicate code can be made DRY.  The refactoring also ensures that when
loading from directories, Fastlibs, or reloading, the wrapper module will
always be named so that activesupport/dependencies will function.
Luke Imhoff 7 years ago
parent
commit
555a9f2559

+ 2
- 0
.gitignore View File

@@ -1,9 +1,11 @@
1 1
 # Rubymine project directory
2 2
 .idea
3
+.yardoc
3 4
 # Mac OS X files
4 5
 .DS_Store
5 6
 data/meterpreter/ext_server_pivot.dll
6 7
 data/meterpreter/ext_server_pivot.x64.dll
8
+doc
7 9
 external/source/meterpreter/java/bin
8 10
 external/source/meterpreter/java/build
9 11
 external/source/meterpreter/java/extensions

+ 48
- 0
.rvmrc View File

@@ -0,0 +1,48 @@
1
+#!/usr/bin/env bash
2
+
3
+# This is an RVM Project .rvmrc file, used to automatically load the ruby
4
+# development environment upon cd'ing into the directory
5
+
6
+# First we specify our desired <ruby>[@<gemset>], the @gemset name is optional,
7
+# Only full ruby name is supported here, for short names use:
8
+#     echo "rvm use 1.9.3" > .rvmrc
9
+environment_id="ruby-1.9.3-p194@metasploit-framework"
10
+
11
+# Uncomment the following lines if you want to verify rvm version per project
12
+# rvmrc_rvm_version="1.15.7 (stable)" # 1.10.1 seams as a safe start
13
+# eval "$(echo ${rvm_version}.${rvmrc_rvm_version} | awk -F. '{print "[[ "$1*65536+$2*256+$3" -ge "$4*65536+$5*256+$6" ]]"}' )" || {
14
+#   echo "This .rvmrc file requires at least RVM ${rvmrc_rvm_version}, aborting loading."
15
+#   return 1
16
+# }
17
+
18
+# First we attempt to load the desired environment directly from the environment
19
+# file. This is very fast and efficient compared to running through the entire
20
+# CLI and selector. If you want feedback on which environment was used then
21
+# insert the word 'use' after --create as this triggers verbose mode.
22
+if [[ -d "${rvm_path:-$HOME/.rvm}/environments"
23
+  && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
24
+then
25
+  \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
26
+  [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]] &&
27
+    \. "${rvm_path:-$HOME/.rvm}/hooks/after_use" || true
28
+else
29
+  # If the environment file has not yet been created, use the RVM CLI to select.
30
+  rvm --create  "$environment_id" || {
31
+    echo "Failed to create RVM environment '${environment_id}'."
32
+    return 1
33
+  }
34
+fi
35
+
36
+# If you use bundler, this might be useful to you:
37
+# if [[ -s Gemfile ]] && {
38
+#   ! builtin command -v bundle >/dev/null ||
39
+#   builtin command -v bundle | GREP_OPTIONS= \grep $rvm_path/bin/bundle >/dev/null
40
+# }
41
+# then
42
+#   printf "%b" "The rubygem 'bundler' is not installed. Installing it now.\n"
43
+#   gem install bundler
44
+# fi
45
+# if [[ -s Gemfile ]] && builtin command -v bundle >/dev/null
46
+# then
47
+#   bundle install | GREP_OPTIONS= \grep -vE '^Using|Your bundle is complete'
48
+# fi

+ 9
- 5
Gemfile View File

@@ -1,6 +1,10 @@
1 1
 source 'http://rubygems.org'
2
-gem 'rails', '3.2.2'
3
-gem 'metasploit_data_models', '0.0.2', :git => "git://github.com/rapid7/metasploit_data_models.git"
4
-gem 'pg', '>=0.13'
5
-gem 'msgpack'
6
-gem 'nokogiri'
2
+
3
+group :development do
4
+  # running documention generation tasks
5
+  gem 'rake'
6
+  # Markdown formatting for yara
7
+  gem 'redcarpet'
8
+  # generating documention
9
+  gem 'yard'
10
+end

+ 14
- 0
Gemfile.lock View File

@@ -0,0 +1,14 @@
1
+GEM
2
+  remote: http://rubygems.org/
3
+  specs:
4
+    rake (0.9.2.2)
5
+    redcarpet (2.1.1)
6
+    yard (0.8.2.1)
7
+
8
+PLATFORMS
9
+  ruby
10
+
11
+DEPENDENCIES
12
+  rake
13
+  redcarpet
14
+  yard

+ 43
- 0
Rakefile View File

@@ -0,0 +1,43 @@
1
+require 'bundler/setup'
2
+
3
+require 'yard'
4
+
5
+namespace :yard do
6
+  yard_files = [
7
+      # Ruby source files first
8
+      'lib/**/*.rb',
9
+      # Anything after '-' is a normal documentation, not source
10
+      '-',
11
+      'COPYING',
12
+      'HACKING',
13
+      'THIRD-PARTY.md'
14
+  ]
15
+  yard_options = [
16
+      # don't generate documentation from the source of the gems in the gemcache.
17
+      '--exclude', 'lib/gemcache',
18
+      # include documentation for protected methods for developers extending the code.
19
+      '--protected'
20
+  ]
21
+
22
+  YARD::Rake::YardocTask.new(:doc) do |t|
23
+    t.files = yard_files
24
+    # --no-stats here as 'stats' task called after will print fuller stats
25
+    t.options = yard_options + ['--no-stats']
26
+
27
+    t.after = Proc.new {
28
+      Rake::Task['yard:stats'].execute
29
+    }
30
+  end
31
+
32
+  desc "Shows stats for YARD Documentation including listing undocumented modules, classes, constants, and methods"
33
+  task :stats => :environment do
34
+    stats = YARD::CLI::Stats.new
35
+    yard_arguments = yard_options + ['--compact', '--list-undoc'] + yard_files
36
+    stats.run *yard_arguments
37
+  end
38
+end
39
+
40
+# @todo Figure out how to just clone description from yard:doc
41
+desc "Generate YARD documentation"
42
+# allow calling namespace to as a task that goes to default task for namespace
43
+task :yard => ['yard:doc']

+ 1
- 0
lib/msf/core.rb View File

@@ -35,6 +35,7 @@ require 'msf/core/framework'
35 35
 require 'msf/core/db_manager'
36 36
 require 'msf/core/event_dispatcher'
37 37
 require 'msf/core/module_manager'
38
+require 'msf/core/module_set'
38 39
 require 'msf/core/plugin_manager'
39 40
 require 'msf/core/session'
40 41
 require 'msf/core/session_manager'

+ 0
- 8
lib/msf/core/module.rb View File

@@ -78,14 +78,6 @@ class Module
78 78
 		# The path from which the module was loaded.
79 79
 		#
80 80
 		attr_accessor :file_path
81
-
82
-		#
83
-		# Override the default Class#inspect which is useless for the way
84
-		# modules get loaded
85
-		#
86
-		def inspect
87
-			"#<Class for #{refname}>"
88
-		end
89 81
 	end
90 82
 
91 83
 	#

+ 150
- 1164
lib/msf/core/module_manager.rb
File diff suppressed because it is too large
View File


+ 61
- 0
lib/msf/core/module_manager/cache.rb View File

@@ -0,0 +1,61 @@
1
+# Concerns the module cache maintained by the {Msf::ModuleManager}.
2
+module Msf::ModuleManager::Cache
3
+  extend ActiveSupport::Concern
4
+
5
+  attr_accessor :cache # :nodoc:
6
+
7
+  #
8
+  # Return a listing of all cached modules
9
+  #
10
+  def cache_entries
11
+    module_detail_by_file = {}
12
+
13
+    if framework_migrated?
14
+      ::Mdm::ModuleDetail.find(:all).each do |module_detail|
15
+        module_type = module_detail.mtype
16
+        refname = module_detail.refname
17
+
18
+        module_detail_by_file[module_detail.file] = {
19
+            :mtype => module_type,
20
+            :refname => refname,
21
+            :file => module_detail.file,
22
+            :mtime => module_detail.mtime
23
+        }
24
+
25
+        module_set(module_type)[refname] ||= SymbolicModule
26
+      end
27
+    end
28
+
29
+    module_detail_by_file
30
+  end
31
+
32
+  #
33
+  # Rebuild the cache for the module set
34
+  #
35
+  def rebuild_cache(mod = nil)
36
+    unless framework_migrated?
37
+      if mod
38
+        framework.db.update_module_details(mod)
39
+      else
40
+        framework.db.update_all_module_details
41
+      end
42
+
43
+      refresh_cache
44
+    end
45
+  end
46
+
47
+  def framework_migrated?
48
+    if framework.db and framework.db.migrated
49
+      true
50
+    else
51
+      false
52
+    end
53
+  end
54
+
55
+  #
56
+  # Reset the module cache
57
+  #
58
+  def refresh_cache
59
+    self.cache = cache_entries
60
+  end
61
+end

+ 111
- 0
lib/msf/core/module_manager/loading.rb View File

@@ -0,0 +1,111 @@
1
+require 'msf/core/modules/loader/archive'
2
+require 'msf/core/modules/loader/directory'
3
+
4
+# Deals with loading modules for the {Msf::ModuleManager}
5
+module Msf::ModuleManager::Loading
6
+  extend ActiveSupport::Concern
7
+
8
+  #
9
+  # CONSTANTS
10
+  #
11
+
12
+  # Classes that can be used to load modules.
13
+  LOADER_CLASSES = [
14
+      Msf::Modules::Loader::Archive,
15
+      Msf::Modules::Loader::Directory
16
+  ]
17
+
18
+  #
19
+  # Returns the set of modules that failed to load.
20
+  #
21
+  def failed
22
+    return module_load_error_by_reference_name
23
+  end
24
+
25
+  def file_changed?(path)
26
+    changed = false
27
+
28
+    module_details = self.cache[path]
29
+
30
+    # if uncached then it counts as changed
31
+    # Payloads can't be cached due to stage/stager matching
32
+    if module_details.nil? or module_details[:mtype] == MODULE_PAYLOAD
33
+      changed = true
34
+    else
35
+      begin
36
+        current_modification_time = ::File.mtime(path).to_i
37
+      rescue ::Errno::ENOENT
38
+        # if the file does not exist now, that's a change
39
+        changed = true
40
+      else
41
+        cached_modification_time = module_details[:mtime].to_i
42
+
43
+        # if the file's modification time's different from the cache, then it's changed
44
+        if current_modification_time != cached_modification_time
45
+          changed = true
46
+        end
47
+      end
48
+    end
49
+
50
+    changed
51
+  end
52
+
53
+  attr_accessor :module_load_error_by_reference_name
54
+
55
+  # Called when a module is initially loaded such that it can be
56
+  # categorized accordingly.
57
+  #
58
+  def on_module_load(mod, type, name, modinfo)
59
+    # Payload modules require custom loading as the individual files
60
+    # may not directly contain a logical payload that a user would
61
+    # reference, such as would be the case with a payload stager or
62
+    # stage.  As such, when payload modules are loaded they are handed
63
+    # off to a special payload set.  The payload set, in turn, will
64
+    # automatically create all the permutations after all the payload
65
+    # modules have been loaded.
66
+
67
+    if (type != Msf::MODULE_PAYLOAD)
68
+      # Add the module class to the list of modules and add it to the
69
+      # type separated set of module classes
70
+      add_module(mod, name, modinfo)
71
+    end
72
+
73
+    module_set_by_type[type].add_module(mod, name, modinfo)
74
+  end
75
+
76
+  protected
77
+
78
+  # Return list of {LOADER_CLASSES} instances that load modules into this module manager
79
+  def loaders
80
+    unless instance_variable_defined? :@loaders
81
+      @loaders = LOADER_CLASSES.collect { |klass|
82
+        klass.new(self)
83
+      }
84
+    end
85
+
86
+    @loaders
87
+  end
88
+
89
+  # Load all of the modules from the supplied directory or archive
90
+  #
91
+  # @param [String] path Path to a directory or Fastlib archive
92
+  # @param [Hash] options
93
+  # @option options [Boolean] :force Whether the force loading the modules even if they are unchanged and already
94
+  #   loaded.
95
+  # @return [Hash{String => Integer}] Maps module type to number of modules loaded
96
+  def load_modules(path, options={})
97
+    options.assert_valid_keys(:force)
98
+
99
+    count_by_type = {}
100
+
101
+    loaders.each do |loader|
102
+      if loader.loadable?(path)
103
+        count_by_type = loader.load_modules(path, options)
104
+
105
+        break
106
+      end
107
+    end
108
+
109
+    count_by_type
110
+  end
111
+end

+ 67
- 0
lib/msf/core/module_manager/module_paths.rb View File

@@ -0,0 +1,67 @@
1
+# Deals with module paths in the {Msf::ModuleManager}
2
+module Msf::ModuleManager::ModulePaths
3
+  extend ActiveSupport::Concern
4
+
5
+  # Adds a path to be searched for new modules.
6
+  #
7
+  # @param [String] path
8
+  # @return (see Msf::Modules::Loader::Base#load_modules)
9
+  def add_module_path(path)
10
+    nested_paths = []
11
+
12
+    if path =~ /\.fastlib$/
13
+      unless ::File.exist?(path)
14
+        raise RuntimeError, "The path supplied does not exist", caller
15
+      end
16
+
17
+      nested_paths << ::File.expand_path(path)
18
+    else
19
+      path.sub!(/#{File::SEPARATOR}$/, '')
20
+
21
+      # Make the path completely canonical
22
+      path = Pathname.new(path).expand_path
23
+
24
+      # Make sure the path is a valid directory
25
+      unless path.directory?
26
+        raise RuntimeError, "The path supplied is not a valid directory.", caller
27
+      end
28
+
29
+      nested_paths << path
30
+
31
+      # Identify any fastlib archives inside of this path
32
+      fastlib_glob = path.join('**', '*.fastlib')
33
+      Dir.glob(fastlib_glob).each do |fp|
34
+        nested_paths << fp
35
+      end
36
+    end
37
+
38
+    # Update the module paths appropriately
39
+    self.module_paths = (module_paths + nested_paths).flatten.uniq
40
+
41
+    # Load all of the modules from the nested paths
42
+    count_by_type = {}
43
+    nested_paths.each { |path|
44
+      path_count_by_type = load_modules(path, :force => false)
45
+
46
+      # merge hashes
47
+      path_count_by_type.each do |type, path_count|
48
+        accumulated_count = count_by_type.fetch(type, 0)
49
+        count_by_type[type] = accumulated_count + path_count
50
+      end
51
+    }
52
+
53
+    return count_by_type
54
+  end
55
+
56
+  #
57
+  # Removes a path from which to search for modules.
58
+  #
59
+  def remove_module_path(path)
60
+    module_paths.delete(path)
61
+    module_paths.delete(::File.expand_path(path))
62
+  end
63
+
64
+  protected
65
+
66
+  attr_accessor :module_paths # :nodoc:
67
+end

+ 94
- 0
lib/msf/core/module_manager/module_sets.rb View File

@@ -0,0 +1,94 @@
1
+# Defines the MODULE_* constants
2
+require 'msf/core/constants'
3
+
4
+# Concerns the various type-specific module sets in a {Msf::ModuleManager}
5
+module Msf::ModuleManager::ModuleSets
6
+  extend ActiveSupport::Concern
7
+
8
+  #
9
+  # Returns the set of loaded auxiliary module classes.
10
+  #
11
+  def auxiliary
12
+    module_set(Msf::MODULE_AUX)
13
+  end
14
+
15
+  #
16
+  # Returns the set of loaded encoder module classes.
17
+  #
18
+  def encoders
19
+    module_set(Msf::MODULE_ENCODER)
20
+  end
21
+
22
+  #
23
+  # Returns the set of loaded exploit module classes.
24
+  #
25
+  def exploits
26
+    module_set(Msf::MODULE_EXPLOIT)
27
+  end
28
+
29
+  def init_module_set(type)
30
+    self.enablement_by_type[type] = true
31
+    case type
32
+      when Msf::MODULE_PAYLOAD
33
+        instance = Msf::PayloadSet.new(self)
34
+      else
35
+        instance = Msf::ModuleSet.new(type)
36
+    end
37
+
38
+    self.module_set_by_type[type] = instance
39
+
40
+    # Set the module set's framework reference
41
+    instance.framework = self.framework
42
+  end
43
+
44
+  #
45
+  # Provide a list of module names of a specific type
46
+  #
47
+  def module_names(set)
48
+    module_set_by_type[set] ? module_set_by_type[set].keys.dup : []
49
+  end
50
+
51
+  #
52
+  # Returns all of the modules of the specified type
53
+  #
54
+  def module_set(type)
55
+    module_set_by_type[type]
56
+  end
57
+
58
+  #
59
+  # Provide a list of the types of modules in the set
60
+  #
61
+  def module_types
62
+    module_set_by_type.keys.dup
63
+  end
64
+
65
+  #
66
+  # Returns the set of loaded nop module classes.
67
+  #
68
+  def nops
69
+    module_set(Msf::MODULE_NOP)
70
+  end
71
+
72
+  #
73
+  # Returns the set of loaded payload module classes.
74
+  #
75
+  def payloads
76
+    module_set(Msf::MODULE_PAYLOAD)
77
+  end
78
+
79
+  #
80
+  # Returns the set of loaded auxiliary module classes.
81
+  #
82
+  def post
83
+    module_set(Msf::MODULE_POST)
84
+  end
85
+
86
+  def type_enabled?(type)
87
+    enablement_by_type[type] || false
88
+  end
89
+
90
+  protected
91
+
92
+  attr_accessor :enablement_by_type # :nodoc:
93
+  attr_accessor :module_set_by_type # :nodoc:
94
+end

+ 49
- 0
lib/msf/core/module_manager/reloading.rb View File

@@ -0,0 +1,49 @@
1
+# Concerns reloading modules
2
+module Msf::ModuleManager::Reloading
3
+  extend ActiveSupport::Concern
4
+
5
+  # Reloads the module specified in mod.  This can either be an instance of a module or a module class.
6
+  #
7
+  # @param [Msf::Module, Class] mod either an instance of a module or a module class
8
+  def reload_module(mod)
9
+    refname = mod.refname
10
+
11
+    dlog("Reloading module #{refname}...", 'core')
12
+
13
+    # if it's can instance, then get its class
14
+    if mod.is_a? Msf::Module
15
+      metasploit_class = mod.class
16
+    else
17
+      metasploit_class = mod
18
+    end
19
+
20
+    namespace_module = metasploit_class.parent
21
+    loader = namespace_module.loader
22
+    loader.reload_module(mod)
23
+  end
24
+
25
+  #
26
+  # Reloads modules from all module paths
27
+  #
28
+  def reload_modules
29
+    self.module_history = {}
30
+    self.clear
31
+
32
+    self.enablement_by_type.each_key do |type|
33
+      module_set_by_type[type].clear
34
+      init_module_set(type)
35
+    end
36
+
37
+    # The number of loaded modules in the following categories:
38
+    # auxiliary/encoder/exploit/nop/payload/post
39
+    count = 0
40
+    module_paths.each do |path|
41
+      mods = load_modules(path, true)
42
+      mods.each_value {|c| count += c}
43
+    end
44
+
45
+    rebuild_cache
46
+
47
+    count
48
+  end
49
+end

+ 290
- 0
lib/msf/core/module_set.rb View File

@@ -0,0 +1,290 @@
1
+# -*- coding: binary -*-
2
+require 'msf/core'
3
+require 'fastlib'
4
+require 'pathname'
5
+
6
+module Msf
7
+
8
+  #
9
+  # Define used for a place-holder module that is used to indicate that the
10
+  # module has not yet been demand-loaded. Soon to go away.
11
+  #
12
+  SymbolicModule = "__SYMBOLIC__"
13
+
14
+  ###
15
+  #
16
+  # A module set contains zero or more named module classes of an arbitrary
17
+  # type.
18
+  #
19
+  ###
20
+  class ModuleSet < Hash
21
+    include Framework::Offspring
22
+
23
+    # Wrapper that detects if a symbolic module is in use.  If it is, it creates an instance to demand load the module
24
+    # and then returns the now-loaded class afterwords.
25
+    def [](name)
26
+      if (super == SymbolicModule)
27
+        create(name)
28
+      end
29
+
30
+      super
31
+    end
32
+
33
+    # Create an instance of the supplied module by its name
34
+    #
35
+    def create(name)
36
+      klass = fetch(name, nil)
37
+      instance = nil
38
+
39
+      # If there is no module associated with this class, then try to demand
40
+      # load it.
41
+      if klass.nil? or klass == SymbolicModule
42
+        # If we are the root module set, then we need to try each module
43
+        # type's demand loading until we find one that works for us.
44
+        if module_type.nil?
45
+          MODULE_TYPES.each { |type|
46
+            framework.modules.demand_load_module(type, name)
47
+          }
48
+        else
49
+          framework.modules.demand_load_module(module_type, name)
50
+        end
51
+
52
+        recalculate
53
+
54
+        klass = get_hash_val(name)
55
+      end
56
+
57
+      # If the klass is valid for this name, try to create it
58
+      if klass and klass != SymbolicModule
59
+        instance = klass.new
60
+      end
61
+
62
+      # Notify any general subscribers of the creation event
63
+      if instance
64
+        self.framework.events.on_module_created(instance)
65
+      end
66
+
67
+      return instance
68
+    end
69
+
70
+    #
71
+    # Overrides the builtin 'each' operator to avoid the following exception on Ruby 1.9.2+
72
+    #    "can't add a new key into hash during iteration"
73
+    #
74
+    def each(&block)
75
+      list = []
76
+      self.keys.sort.each do |sidx|
77
+        list << [sidx, self[sidx]]
78
+      end
79
+      list.each(&block)
80
+    end
81
+
82
+    #
83
+    # Enumerates each module class in the set.
84
+    #
85
+    def each_module(opts = {}, &block)
86
+      demand_load_modules
87
+
88
+      self.mod_sorted = self.sort
89
+
90
+      each_module_list(mod_sorted, opts, &block)
91
+    end
92
+
93
+    #
94
+    # Custom each_module filtering if an advanced set supports doing extended
95
+    # filtering.  Returns true if the entry should be filtered.
96
+    #
97
+    def each_module_filter(opts, name, entry)
98
+      return false
99
+    end
100
+
101
+    #
102
+    # Enumerates each module class in the set based on their relative ranking
103
+    # to one another.  Modules that are ranked higher are shown first.
104
+    #
105
+    def each_module_ranked(opts = {}, &block)
106
+      demand_load_modules
107
+
108
+      self.mod_ranked = rank_modules
109
+
110
+      each_module_list(mod_ranked, opts, &block)
111
+    end
112
+
113
+    #
114
+    # Forces all modules in this set to be loaded.
115
+    #
116
+    def force_load_set
117
+      each_module { |name, mod| }
118
+    end
119
+
120
+    #
121
+    # Initializes a module set that will contain modules of a specific type and
122
+    # expose the mechanism necessary to create instances of them.
123
+    #
124
+    def initialize(type = nil)
125
+      #
126
+      # Defaults
127
+      #
128
+      self.ambiguous_module_reference_name_set = Set.new
129
+      # Hashes that convey the supported architectures and platforms for a
130
+      # given module
131
+      self.mod_arch_hash     = {}
132
+      self.mod_platform_hash = {}
133
+      self.mod_sorted        = nil
134
+      self.mod_ranked        = nil
135
+      self.mod_extensions    = []
136
+
137
+      #
138
+      # Arguments
139
+      #
140
+      self.module_type = type
141
+    end
142
+
143
+    attr_reader   :module_type
144
+
145
+    #
146
+    # Gives the module set an opportunity to handle a module reload event
147
+    #
148
+    def on_module_reload(mod)
149
+    end
150
+
151
+    #
152
+    # Whether or not recalculations should be postponed.  This is used from the
153
+    # context of the each_module_list handler in order to prevent the demand
154
+    # loader from calling recalc for each module if it's possible that more
155
+    # than one module may be loaded.  This field is not initialized until used.
156
+    #
157
+    attr_accessor :postpone_recalc
158
+
159
+
160
+    #
161
+    # Dummy placeholder to relcalculate aliases and other fun things.
162
+    #
163
+    def recalculate
164
+    end
165
+
166
+    #
167
+    # Checks to see if the supplied module name is valid.
168
+    #
169
+    def valid?(name)
170
+      create(name)
171
+      (self[name]) ? true : false
172
+    end
173
+
174
+    protected
175
+
176
+    #
177
+    # Adds a module with a the supplied name.
178
+    #
179
+    def add_module(mod, name, modinfo = nil)
180
+      # Set the module's name so that it can be referenced when
181
+      # instances are created.
182
+      mod.framework = framework
183
+      mod.refname   = name
184
+      mod.file_path = ((modinfo and modinfo['files']) ? modinfo['files'][0] : nil)
185
+      mod.orig_cls  = mod
186
+
187
+      cached_module = self[name]
188
+
189
+      if (cached_module and cached_module != SymbolicModule)
190
+        ambiguous_module_reference_name_set.add(name)
191
+
192
+        wlog("The module #{mod.refname} is ambiguous with #{self[name].refname}.")
193
+      else
194
+        self[name] = mod
195
+      end
196
+
197
+      mod
198
+    end
199
+
200
+    #
201
+    # Load all modules that are marked as being symbolic.
202
+    #
203
+    def demand_load_modules
204
+      # Pre-scan the module list for any symbolic modules
205
+      self.each_pair { |name, mod|
206
+        if (mod == SymbolicModule)
207
+          self.postpone_recalc = true
208
+
209
+          mod = create(name)
210
+
211
+          next if (mod.nil?)
212
+        end
213
+      }
214
+
215
+      # If we found any symbolic modules, then recalculate.
216
+      if (self.postpone_recalc)
217
+        self.postpone_recalc = false
218
+
219
+        recalculate
220
+      end
221
+    end
222
+
223
+    #
224
+    # Enumerates the modules in the supplied array with possible limiting
225
+    # factors.
226
+    #
227
+    def each_module_list(ary, opts, &block)
228
+      ary.each { |entry|
229
+        name, mod = entry
230
+
231
+        # Skip any lingering symbolic modules.
232
+        next if (mod == SymbolicModule)
233
+
234
+        # Filter out incompatible architectures
235
+        if (opts['Arch'])
236
+          if (!mod_arch_hash[mod])
237
+            mod_arch_hash[mod] = mod.new.arch
238
+          end
239
+
240
+          next if ((mod_arch_hash[mod] & opts['Arch']).empty? == true)
241
+        end
242
+
243
+        # Filter out incompatible platforms
244
+        if (opts['Platform'])
245
+          if (!mod_platform_hash[mod])
246
+            mod_platform_hash[mod] = mod.new.platform
247
+          end
248
+
249
+          next if ((mod_platform_hash[mod] & opts['Platform']).empty? == true)
250
+        end
251
+
252
+        # Custom filtering
253
+        next if (each_module_filter(opts, name, entry) == true)
254
+
255
+        block.call(name, mod)
256
+      }
257
+    end
258
+
259
+    attr_accessor :ambiguous_module_reference_name_set
260
+    attr_accessor :mod_arch_hash
261
+    attr_accessor :mod_extensions
262
+    attr_accessor :mod_platform_hash
263
+    attr_accessor :mod_ranked
264
+    attr_accessor :mod_sorted
265
+    attr_writer   :module_type
266
+    attr_accessor :module_history
267
+
268
+    #
269
+    # Ranks modules based on their constant rank value, if they have one.
270
+    #
271
+    def rank_modules
272
+      self.mod_ranked = self.sort { |a, b|
273
+        a_name, a_mod = a
274
+        b_name, b_mod = b
275
+
276
+        # Dynamically loads the module if needed
277
+        a_mod = create(a_name) if a_mod == SymbolicModule
278
+        b_mod = create(b_name) if b_mod == SymbolicModule
279
+
280
+        # Extract the ranking between the two modules
281
+        a_rank = a_mod.const_defined?('Rank') ? a_mod.const_get('Rank') : NormalRanking
282
+        b_rank = b_mod.const_defined?('Rank') ? b_mod.const_get('Rank') : NormalRanking
283
+
284
+        # Compare their relevant rankings.  Since we want highest to lowest,
285
+        # we compare b_rank to a_rank in terms of higher/lower precedence
286
+        b_rank <=> a_rank
287
+      }
288
+    end
289
+  end
290
+end

+ 4
- 0
lib/msf/core/modules.rb View File

@@ -0,0 +1,4 @@
1
+# Namespace for loading Metasploit modules
2
+module Msf::Modules
3
+
4
+end

+ 6
- 0
lib/msf/core/modules/loader.rb View File

@@ -0,0 +1,6 @@
1
+require 'msf/core/modules'
2
+
3
+# Namespace for module loaders
4
+module Msf::Modules::Loader
5
+
6
+end

+ 77
- 0
lib/msf/core/modules/loader/archive.rb View File

@@ -0,0 +1,77 @@
1
+require 'msf/core/modules/loader/base'
2
+
3
+# Concerns loading modules form fastlib archives
4
+class Msf::Modules::Loader::Archive < Msf::Modules::Loader::Base
5
+  #
6
+  # CONSTANTS
7
+  #
8
+
9
+  # The extension for Fastlib archives.
10
+  ARCHIVE_EXTENSION = '.fastlib'
11
+
12
+  # Returns true if the path is a Fastlib archive.
13
+  #
14
+  # @param (see Msf::Modules::Loader::Base#loadable?)
15
+  # @return [true] if path has the {ARCHIVE_EXTENSION} extname.
16
+  # @return [false] otherwise
17
+  def loadable?(path)
18
+    if File.extname(path) == ARCHIVE_EXTENSION
19
+      true
20
+    else
21
+      false
22
+    end
23
+  end
24
+
25
+  protected
26
+
27
+  # Yields the module_reference_name for each module file in the Fastlib archive at path.
28
+  #
29
+  # @param path [String] The path to the Fastlib archive file.
30
+  # @yield (see Msf::Modules::Loader::Base#each_module_reference_name)
31
+  # @yieldparam (see Msf::Modules::Loader::Base#each_module_reference_name)
32
+  # @return (see Msf::Modules::Loader::Base#each_module_reference_name)
33
+  def each_module_reference_name(path)
34
+    entries = ::FastLib.list(path)
35
+
36
+    entries.each do |entry|
37
+      if entry.include?('.svn/')
38
+        next
39
+      end
40
+
41
+      type = entry.split('/', 2)[0]
42
+      type = type.singularize
43
+
44
+      unless module_manager.enablement_by_type[type]
45
+        next
46
+      end
47
+
48
+      if module_path?(entry)
49
+        # The module_reference_name doesn't have a file extension
50
+        module_reference_name = module_reference_name_from_path(entry)
51
+
52
+        yield path, type, module_reference_name
53
+      end
54
+    end
55
+  end
56
+
57
+  # Returns the path to the module inside the Fastlib archive.  The path to the archive is separated from the path to
58
+  # the file inside the archive by '::'.
59
+  #
60
+  # @param (see Msf::Modules::Loader::Base#module_path)
61
+  # @return [String] Path to module file inside the Fastlib archive.
62
+  def module_path(parent_path, type, module_reference_name)
63
+    file_path = typed_path(type, module_reference_name)
64
+    module_path = "#{parent_path}::#{file_path}"
65
+
66
+    module_path
67
+  end
68
+
69
+  # Loads the module content from the Fastlib archive.
70
+  #
71
+  # @return (see Msf::Modules::Loader::Base#read_module_content)
72
+  def read_module_content(path, type, module_reference_name)
73
+    file_path = typed_path(type, module_reference_name)
74
+
75
+    ::FastLib.load(path, file_path)
76
+  end
77
+end

+ 477
- 0
lib/msf/core/modules/loader/base.rb View File

@@ -0,0 +1,477 @@
1
+#
2
+# Project
3
+#
4
+require 'msf/core/modules/loader'
5
+require 'msf/core/modules/namespace'
6
+
7
+# Responsible for loading modules for {Msf::ModuleManagers}.
8
+#
9
+# @abstract Subclass and override {#base_path}, {#each_module_reference_name}, {#loadable?}, and
10
+#   {#read_module_content}.
11
+class Msf::Modules::Loader::Base
12
+  #
13
+  # CONSTANTS
14
+  #
15
+
16
+  # This must calculate the first line of the NAMESPACE_MODULE_CONTENT string so that errors are reported correctly
17
+  NAMESPACE_MODULE_LINE = __LINE__ + 4
18
+  # By calling module_eval from inside the module definition, the lexical scope is captured and available to the code in
19
+  # module_content.
20
+  NAMESPACE_MODULE_CONTENT = <<-EOS
21
+    # ensure the namespace module can respond to checks during loading
22
+    extend Msf::Modules::Namespace
23
+
24
+    class << self
25
+      # The loader that originally loaded this module
26
+      #
27
+      # @return [Msf::Modules::Loader::Base] the loader that loaded this namespace module and can reload it.
28
+      attr_accessor :loader
29
+
30
+      # @return [String] The path under which the module of the given type was found.
31
+      attr_accessor :parent_path
32
+    end
33
+
34
+    # Calls module_eval on the module_content, but the lexical scope of the namespace_module is passed through
35
+    # module_eval, so that module_content can act like it was written inline in the namespace_module.
36
+    #
37
+    # @param [String] module_content The content of the {Msf::Module}.
38
+    # @param [String] module_path The path to the module, so that error messages in evaluating the module_content can
39
+    #   be reported correctly.
40
+    def self.module_eval_with_lexical_scope(module_content, module_path)
41
+      # By calling module_eval from inside the module definition, the lexical scope is captured and available to the
42
+      # code in module_content.
43
+      module_eval(module_content)
44
+    end
45
+  EOS
46
+
47
+  # The extension for metasploit modules.
48
+  MODULE_EXTENSION = '.rb'
49
+  # String used to separate module names in a qualified module name.
50
+  MODULE_SEPARATOR = '::'
51
+  # The base namespace name under which {#namespace_module #namespace_modules} are created.
52
+  NAMESPACE_MODULE_NAMES = ['Msf', 'Modules']
53
+  # Regex that can distinguish regular ruby source from unit test source.
54
+  UNIT_TEST_REGEX = /rb\.(ut|ts)\.rb$/
55
+  # Not all types are pluralized when a directory name, so here's the mapping that currently exists
56
+  DIRECTORY_BY_TYPE = {
57
+    Msf::MODULE_AUX => 'auxiliary',
58
+    Msf::MODULE_ENCODER => 'encoders',
59
+    Msf::MODULE_EXPLOIT => 'exploits',
60
+    Msf::MODULE_NOP => 'nops',
61
+    Msf::MODULE_PAYLOAD => 'payloads',
62
+    Msf::MODULE_POST => 'post'
63
+  }
64
+
65
+  # @param [Msf::ModuleManager] module_manager The module manager that caches the loaded modules.
66
+  def initialize(module_manager)
67
+    @module_manager = module_manager
68
+  end
69
+
70
+  # Returns whether the path can be loaded this module loader.
71
+  #
72
+  # @param path (see #load_modules)
73
+  # @return [Boolean]
74
+  def loadable?(path)
75
+    raise ::NotImplementedError
76
+  end
77
+
78
+  # Loads all of the modules from the supplied path.
79
+  #
80
+  # @note Only paths where {#loadable?} returns true should be passed to this method.
81
+  #
82
+  # @param [String] path Path under which there are modules
83
+  # @param [Hash] options
84
+  # @option options [Boolean] force (false) whether to force loading of the module even if the module has not changed.
85
+  # @return [Hash{String => Integer}] Maps module type to number of modules loaded
86
+  def load_modules(path, options={})
87
+    options.assert_valid_keys(:force)
88
+
89
+    force = options[:force]
90
+    count_by_type = {}
91
+    recalculate_by_type = {}
92
+
93
+    each_module_reference_name(path) do |parent_path, type, module_reference_name|
94
+      load_module(
95
+          parent_path,
96
+          type,
97
+          module_reference_name,
98
+          :recalculate_by_type => recalculate_by_type,
99
+          :count_by_type => count_by_type,
100
+          :force => force
101
+      )
102
+    end
103
+
104
+    recalculate_by_type.each do |type, recalculate|
105
+      if recalculate
106
+        module_set = module_manager.module_set(type)
107
+        module_set.recalculate
108
+      end
109
+    end
110
+
111
+    count_by_type
112
+  end
113
+
114
+  # Reloads the specified module.
115
+  #
116
+  # @param [Class, Msf::Module] original_metasploit_class_or_instance either an instance of a module or a module class
117
+  def reload_module(original_metasploit_class_or_instance)
118
+    if original_metasploit_class_or_instance.is_a? Msf::Module
119
+      original_metasploit_instance = original_metasploit_class_or_instance
120
+      original_metasploit_class = original_metasploit_class_or_instance.class
121
+    else
122
+      original_metasploit_instance = nil
123
+      original_metasploit_class = original_metasploit_class_or_instance
124
+    end
125
+
126
+    namespace_module = original_metasploit_class.parent
127
+    parent_path = namespace_module.parent_path
128
+
129
+    type = original_metasploit_class_or_instance.type
130
+    module_reference_name = original_metasploit_class_or_instance.refname
131
+
132
+    dlog("Reloading module #{module_reference_name}...", 'core')
133
+
134
+    if load_module(parent_path, type, module_reference_name, :force => true)
135
+      # Create a new instance of the module
136
+      reloaded_module_instance = module_manager.create(module_reference_name)
137
+
138
+      if reloaded_module_instance and original_metasploit_instance
139
+        # copy over datastore
140
+        reloaded_module_instance.datastore.update(original_metasploit_instance.datastore)
141
+      else
142
+        elog("Failed to create instance of #{refname} after reload.", 'core')
143
+
144
+        # Return the old module instance to avoid a strace trace
145
+        return original_module
146
+      end
147
+    else
148
+      elog("Failed to reload #{module_reference_name}")
149
+
150
+      # Return the old module isntance to avoid a strace trace
151
+      return original_metasploit_class_or_instance
152
+    end
153
+
154
+    # Let the specific module sets have an opportunity to handle the fact
155
+    # that this module was reloaded.
156
+    module_set = module_manager.module_set(type)
157
+    module_set.on_module_reload(reloaded_module_instance)
158
+
159
+    # Rebuild the cache for just this module
160
+    module_manager.rebuild_cache(reloaded_module_instance)
161
+
162
+    reloaded_module_instance
163
+  end
164
+
165
+  protected
166
+
167
+  # Yields module reference names under path.
168
+  #
169
+  # @param path (see #load_modules)
170
+  # @yield [parent_path, type, module_reference_name] Gives the path and the module_reference_name of the module found
171
+  #   under the path.
172
+  # @yieldparam parent_path [String] the path under which the module of the given type was found.
173
+  # @yieldparam type [String] the type of the module.
174
+  # @yieldparam module_reference_name [String] The canonical name for referencing the module.
175
+  # @return [void]
176
+  def each_module_reference_name(path)
177
+    raise ::NotImplementedError
178
+  end
179
+
180
+  # Loads a module from the supplied path and module_reference_name.
181
+  #
182
+  # @param [String] parent_path The path under which the module exists.  This is not necessarily the same path as passed
183
+  #   to {#load_modules}: it may just be derived from that path.
184
+  # @param [String] type The type of module.
185
+  # @param [String] module_reference_name The canonical name for referring to the module.
186
+  # @param [Hash] options Options used to force loading and track statistics
187
+  # @option options [Hash{String => Integer}] :count_by_type Maps the module type to the number of module loaded
188
+  # @option options [Boolean] :force (false) whether to force loading of the module even if the module has not changed.
189
+  # @option options [Hash{String => Boolean}] :recalculate_by_type Maps type to whether its
190
+  #   {Msf::ModuleManager#module_set} needs to be recalculated.
191
+  def load_module(parent_path, type, module_reference_name, options={})
192
+    options.assert_valid_keys(:count_by_type, :force, :recalculate_by_type)
193
+    force = options[:force] || false
194
+
195
+    unless force or module_manager.file_changed?(parent_path)
196
+      dlog("Cached module from #{parent_path} has not changed.", 'core', LEV_2)
197
+
198
+      return false
199
+    end
200
+
201
+    namespace_module = self.namespace_module(module_reference_name)
202
+
203
+    # set the parent_path so that the module can be reloaded with #load_module
204
+    namespace_module.parent_path = parent_path
205
+
206
+    module_content = read_module_content(parent_path, type, module_reference_name)
207
+    module_path = module_path(parent_path, type, module_reference_name)
208
+
209
+    begin
210
+      namespace_module.module_eval_with_lexical_scope(module_content, module_path)
211
+    # handle interrupts as pass-throughs unlike other Exceptions
212
+    rescue ::Interrupt
213
+      raise
214
+    rescue ::Exception => error
215
+      # Hide eval errors when the module version is not compatible
216
+      begin
217
+        namespace_module.version_compatible!
218
+      rescue Msf::Modules::VersionCompatibilityError => version_compatibility_error
219
+        error_message = "Failed to load module (#{module_path}) due to error and #{version_compatibility_error}"
220
+      else
221
+        error_message = "#{error.class} #{error}:\n#{error.backtrace}"
222
+      end
223
+
224
+      elog(error_message)
225
+      module_manager.module_load_error_by_reference_name[module_reference_name] = error_message
226
+
227
+      return false
228
+    end
229
+
230
+    metasploit_class = namespace_module.metasploit_class
231
+
232
+    unless metasploit_class
233
+      error_message = "Missing Metasploit class constant"
234
+
235
+      elog(error_message)
236
+      module_manager.module_load_error_by_reference_name[module_reference_name] = error_message
237
+    end
238
+
239
+    unless usable?(metasploit_class)
240
+      ilog("Skipping module #{module_reference_name} under #{parent_path} because is_usable returned false.", 'core', LEV_1)
241
+
242
+      return false
243
+    end
244
+
245
+    ilog("Loaded #{type} module #{module_reference_name} under #{parent_path}", 'core', LEV_2)
246
+
247
+    # if this is a reload, then there may be a pre-existing error that should now be cleared
248
+    module_manager.module_load_error_by_reference_name.delete(module_reference_name)
249
+
250
+    # Do some processing on the loaded module to get it into the right associations
251
+    module_manager.on_module_load(
252
+        metasploit_class,
253
+        type,
254
+        module_reference_name,
255
+        {
256
+            # files[0] is stored in the {Msf::Module#file_path} and is used to reload the module, so it needs to be a
257
+            # full path
258
+            'files' => [
259
+                module_path
260
+            ],
261
+            'paths' => [
262
+                module_reference_name
263
+            ],
264
+            'type' => type
265
+        }
266
+    )
267
+
268
+    # Set this module type as needing recalculation
269
+    recalculate_by_type = options[:recalculate_by_type]
270
+
271
+    if recalculate_by_type
272
+      recalculate_by_type[type] = true
273
+    end
274
+
275
+    # The number of loaded modules this round
276
+    count_by_type = options[:count_by_type]
277
+
278
+    if count_by_type
279
+      count_by_type[type] ||= 0
280
+      count_by_type[type] += 1
281
+    end
282
+
283
+    return true
284
+  end
285
+
286
+  attr_reader :module_manager
287
+
288
+  # Returns path to module that can be used for reporting errors in evaluating the
289
+  # {#read_module_content module_content}.
290
+  #
291
+  # @param path (see #load_module)
292
+  # @param type (see #load_module)
293
+  # @param module_reference_name (see #load_module)
294
+  # @return [String] The path to module.
295
+  def module_path(parent_path, type, module_reference_name)
296
+    raise ::NotImplementedError
297
+  end
298
+
299
+  # Returns whether the path could refer to a module.  The path would still need to be loaded in order to check if it
300
+  # actually is a valid module.
301
+  #
302
+  # @param [String] path to module without the type directory.
303
+  # @return [true] if the extname is {MODULE_EXTENSION} AND
304
+  #                   the path does not match {UNIT_TEXT_REGEX} AND
305
+  #                   the path is not hidden (starts with '.')
306
+  # @return [false] otherwise
307
+  def module_path?(path)
308
+    module_path = false
309
+
310
+    extension = File.extname(path)
311
+
312
+    unless (path.starts_with?('.') or
313
+            extension != MODULE_EXTENSION or
314
+            path =~ UNIT_TEST_REGEX)
315
+      module_path = true
316
+    end
317
+
318
+    module_path
319
+  end
320
+
321
+  # Changes a file name path to a canonical module reference name.
322
+  #
323
+  # @param [String] path Relative path to module.
324
+  # @return [String] MODULE_EXTENSION removed from path.
325
+  def module_reference_name_from_path(path)
326
+    path.gsub(/#{MODULE_EXTENSION}$/, '')
327
+  end
328
+
329
+  # Returns a nested module to wrap the Metasploit(1|2|3) class so that it doesn't overwrite other (metasploit)
330
+  # module's classes.  The wrapper module must be named so that active_support's autoloading code doesn't break when
331
+  # searching constants from inside the Metasploit(1|2|3) class.
332
+  def namespace_module(module_reference_name)
333
+    namespace_module_names = self.namespace_module_names(module_reference_name)
334
+
335
+    # If this is a reload, then the pre-existing namespace_module needs to be removed.
336
+
337
+    # don't check ancestors for the constants
338
+    inherit = false
339
+
340
+    # Don't want to trigger ActiveSupport's const_missing, so can't use constantize.
341
+    namespace_module = namespace_module_names.inject(Object) { |parent, module_name|
342
+      if parent.const_defined?(module_name, inherit)
343
+        parent.const_get(module_name, inherit)
344
+      else
345
+        break
346
+      end
347
+    }
348
+
349
+    # if a reload
350
+    unless namespace_module.nil?
351
+      parent_module = namespace_module.parent
352
+
353
+      # remove_const is private, so invoke in instance context
354
+      parent_module.instance_eval do
355
+        remove_const namespace_module_names.last
356
+      end
357
+    end
358
+
359
+    # In order to have constants defined in Msf resolve without the Msf qualifier in the module_content, the
360
+    # Module.nesting must resolve for the entire nesting.  Module.nesting is strictly lexical, and can't be faked with
361
+    # module_eval(&block). (There's actually code in ruby's implementation to stop module_eval from being added to
362
+    # Module.nesting when using the block syntax.) All this means is the modules have to be declared as a string that
363
+    # gets module_eval'd.
364
+
365
+    nested_module_names = namespace_module_names.reverse
366
+
367
+    namespace_module_content = nested_module_names.inject(NAMESPACE_MODULE_CONTENT) { |wrapped_content, module_name|
368
+      lines = []
369
+      lines << "module #{module_name}"
370
+      lines << wrapped_content
371
+      lines << "end"
372
+
373
+      lines.join("\n")
374
+    }
375
+
376
+    Object.module_eval(namespace_module_content, __FILE__, NAMESPACE_MODULE_LINE)
377
+
378
+    # The namespace_module exists now, so no need to use constantize to do const_missing
379
+    namespace_module = namespace_module_names.inject(Object) { |parent, module_name|
380
+      parent.const_get(module_name, inherit)
381
+    }
382
+    # record the loader, so that the namespace module and its metasploit_class can be reloaded
383
+    namespace_module.loader = self
384
+
385
+    namespace_module
386
+  end
387
+
388
+  def namespace_module_name(module_reference_name)
389
+    namespace_module_names = self.namespace_module_names(module_reference_name)
390
+    namespace_module_name = namespace_module_names.join(MODULE_SEPARATOR)
391
+
392
+    namespace_module_name
393
+  end
394
+
395
+  # Returns a fully qualified module name to wrap the Metasploit(1|2|3) class so that it doesn't overwrite other
396
+  # (metasploit) module's classes.  Invalid module name characters are escaped by using 'H*' unpacking and prefixing
397
+  # each code with X so the code remains a valid module name when it starts with a digit.
398
+  #
399
+  # @param [String] module_reference_name The canonical name for the module.
400
+  # @return [Module] Msf::Modules::<name>
401
+  #
402
+  # @see namespace_module
403
+  def namespace_module_names(module_reference_name)
404
+    relative_module_name = module_reference_name.camelize
405
+
406
+    module_names = relative_module_name.split('::')
407
+
408
+    # The module_reference_name is path-like, so it can include characters that are invalid in module names
409
+    valid_module_names = module_names.collect { |module_name|
410
+      valid_module_name = module_name.gsub(/^[0-9]|[^A-Za-z0-9]/) { |invalid_constant_name_character|
411
+        unpacked = invalid_constant_name_character.unpack('H*')
412
+        # unpack always returns an array, so get first value to get character's encoding
413
+        hex_code = unpacked[0]
414
+
415
+        # as a convention start each hex-code with X so that it'll make a valid constant name since constants can't
416
+        # start with digits.
417
+        "X#{hex_code}"
418
+      }
419
+
420
+      valid_module_name
421
+    }
422
+
423
+    namespace_module_names = NAMESPACE_MODULE_NAMES + valid_module_names
424
+
425
+    namespace_module_names
426
+  end
427
+
428
+  # Read the content of the module from under path.
429
+  #
430
+  # @param parent_path (see #load_module)
431
+  # @param type (see #load_module)
432
+  # @param module_reference_name (see #load_module)
433
+  # @return [String] module content that can be module_evaled into the {#namespace_module}
434
+  def read_module_content(parent_path, type, module_reference_name)
435
+    raise ::NotImplementedError
436
+  end
437
+
438
+
439
+  # The path to the module qualified by the type directory.
440
+  #
441
+  # @note To get the full path to the module, use {#module_path}
442
+  #
443
+  # @param [String] type The type of the module.
444
+  # @param [String] module_reference_name The canonical name for the module.
445
+  # @return [String] path to the module starting with the type directory.
446
+  #
447
+  # @see DIRECTORY_BY_TYPE
448
+  def typed_path(type, module_reference_name)
449
+    file_name = module_reference_name + MODULE_EXTENSION
450
+    type_directory = DIRECTORY_BY_TYPE[type]
451
+    typed_path = File.join(type_directory, file_name)
452
+
453
+    typed_path
454
+  end
455
+
456
+  # Returns whether the metasploit_class is usable on the current system.  Defer's to metasploit_class's #is_usable if
457
+  # it is defined.
458
+  #
459
+  # @param [Msf::Module] metasploit_class As returned by {Msf::Modules::Namespace#metasploit_class}
460
+  def usable?(metasploit_class)
461
+    # If the module indicates that it is not usable on this system, then we
462
+    # will not try to use it.
463
+    usable = false
464
+
465
+    if metasploit_class.respond_to? :is_usable
466
+      begin
467
+        usable = metasploit_class.is_usable
468
+      rescue => error
469
+        elog("Exception caught during is_usable check: #{error}")
470
+      end
471
+    else
472
+      usable = true
473
+    end
474
+
475
+    usable
476
+  end
477
+end

+ 79
- 0
lib/msf/core/modules/loader/directory.rb View File

@@ -0,0 +1,79 @@
1
+# Concerns loading module from a directory
2
+class Msf::Modules::Loader::Directory < Msf::Modules::Loader::Base
3
+  # Returns true if the path is a directory
4
+  #
5
+  # @param (see Msf::Modules::Loader::Base#loadable?)
6
+  # @return [true] if path is a directory
7
+  # @return [false] otherwise
8
+  def loadable?(path)
9
+    if File.directory?(path)
10
+      true
11
+    else
12
+      false
13
+    end
14
+  end
15
+
16
+  protected
17
+
18
+  # Yields the module_reference_name for each module file found under the directory path.
19
+  #
20
+  # @param [String] path The path to the directory.
21
+  # @yield (see Msf::Modules::Loader::Base#each_module_reference_name)
22
+  # @yieldparam [String] path The path to the directory.
23
+  # @yieldparam [String] type The type correlated with the directory under path.
24
+  # @yieldparam module_reference_name (see Msf::Modules::Loader::Base#each_module_reference_name)
25
+  # @return (see Msf::Modules::Loader::Base#each_module_reference_name)
26
+  def each_module_reference_name(path)
27
+    ::Dir.foreach(path) do |entry|
28
+      if entry.downcase == '.svn'
29
+        next
30
+      end
31
+
32
+      full_entry_path = ::File.join(path, entry)
33
+      type = entry.singularize
34
+
35
+      unless ::File.directory?(full_entry_path) and
36
+             module_manager.type_enabled? type
37
+        next
38
+      end
39
+
40
+      full_entry_pathname = Pathname.new(full_entry_path)
41
+
42
+      # Try to load modules from all the files in the supplied path
43
+      Rex::Find.find(full_entry_path) do |entry_descendant_path|
44
+        if module_path?(entry_descendant_path)
45
+          entry_descendant_pathname = Pathname.new(entry_descendant_path)
46
+          relative_entry_descendant_pathname = entry_descendant_pathname.relative_path_from(full_entry_pathname)
47
+          relative_entry_descendant_path = relative_entry_descendant_pathname.to_path
48
+
49
+          # The module_reference_name doesn't have a file extension
50
+          module_reference_name = module_reference_name_from_path(relative_entry_descendant_path)
51
+
52
+          yield path, type, module_reference_name
53
+        end
54
+      end
55
+    end
56
+  end
57
+
58
+  # Returns the full path to the module file on disk.
59
+  #
60
+  # @param (see Msf::Modules::Loader::Base#module_path)
61
+  # @return [String] Path to module file on disk.
62
+  def module_path(parent_path, type, module_reference_name)
63
+    file_name = module_reference_name + MODULE_EXTENSION
64
+    type_directory = DIRECTORY_BY_TYPE[type]
65
+    full_path = File.join(parent_path, type_directory, file_name)
66
+
67
+    full_path
68
+  end
69
+
70
+  # Loads the module content from the on disk file.
71
+  #
72
+  # @param (see Msf::Modules::Loader::Base#read_module_content)
73
+  # @return (see Msf::Modules::Loader::Base#read_module_content)
74
+  def read_module_content(parent_path, type, module_reference_name)
75
+    full_path = module_path(parent_path, type, module_reference_name)
76
+
77
+    ::File.read(full_path)
78
+  end
79
+end

+ 51
- 0
lib/msf/core/modules/namespace.rb View File

@@ -0,0 +1,51 @@
1
+# Concern for behavior that all namespace modules that wrap Msf::Modules must support like version checking and
2
+# grabbing the version specific-Metasploit* class.
3
+module Msf::Modules::Namespace
4
+  # Returns the Metasploit(3|2|1) class from the module_evalled content.
5
+  #
6
+  # @note The module content must be module_evalled into this namespace module before the return of
7
+  #   {metasploit_class} is valid.
8
+  #
9
+  # @return [Msf::Module] if a Metasploit(3|2|1) class exists in this module
10
+  # @return [nil] if such as class is not defined.
11
+  def metasploit_class
12
+    metasploit_class = nil
13
+    # don't search ancestors for the metasploit_class
14
+    inherit = false
15
+
16
+    ::Msf::Framework::Major.downto(1) do |major|
17
+      if const_defined?("Metasploit#{major}", inherit)
18
+        metasploit_class = const_get("Metasploit#{major}")
19
+
20
+        break
21
+      end
22
+    end
23
+
24
+    metasploit_class
25
+  end
26
+
27
+  # Raises an error unless {Msf::Framework::VersionCore} and {Msf::Framework::VersionAPI} meet the minimum required
28
+  # versions defined in RequiredVersions in the module content.
29
+  #
30
+  # @note The module content must be module_evalled into this namespace module using module_eval_with_lexical_scope
31
+  #   before calling {version_compatible!} is valid.
32
+  #
33
+  # @raise [Msf::Modules::VersionCompatibilityError] if RequiredVersion[0] > Msf::Framework::VersionCore or
34
+  #   RequiredVersion[1] > Msf::Framework::VersionApi
35
+  # @return [void]
36
+  def version_compatible!
37
+    if const_defined?(:RequiredVersions)
38
+      required_versions = const_get(:RequiredVersions)
39
+      minimum_core_version = required_versions[0]
40
+      minimum_api_version = required_versions[1]
41
+
42
+      if (minimum_core_version > ::Msf::Framework::VersionCore or
43
+          minimum_api_version > ::Msf::Framework::VersionAPI)
44
+        raise Msf::Modules::VersionCompatibilityError.new(
45
+                  :minimum_api_version => minimum_api_version,
46
+                  :minimum_core_version => minimum_core_version
47
+              )
48
+      end
49
+    end
50
+  end
51
+end

+ 12
- 0
lib/msf/core/modules/version_compatibility_error.rb View File

@@ -0,0 +1,12 @@
1
+class Msf::Modules::VersionCompatibilityError < StandardError
2
+  def initialize(attributes={})
3
+    @minimum_api_version = attributes[:minimum_api_version]
4
+    @minimum_core_version = attributes[:minimum_core_version]
5
+
6
+    super("Failed to reload module (#{name}) due to version check " \
7
+          "(requires API:#{minimum_api_version} Core:#{minimum_core_version})")
8
+  end
9
+
10
+  attr_reader :minimum_api_version
11
+  attr_reader :minimum_core_version
12
+end

Loading…
Cancel
Save