# -*- coding: binary -*- # # Gems # require 'active_support/concern' # Concerns the module cache maintained by the {Msf::ModuleManager}. module Msf::ModuleManager::Cache extend ActiveSupport::Concern MEMORY = :memory FILESYSTEM = :filesystem # Returns whether the cache is empty # # @return [true] if the cache has no entries. # @return [false] if the cache has any entries. def cache_empty? module_info_by_path.empty? end # @note path, reference_name, and type must be passed as options because when +class_or_module+ is a payload Module, # those attributes will either not be set or not exist on the module. # # Updates the in-memory cache so that {#file_changed?} will report +false+ if # the module is loaded again. # # @param class_or_module [Class, ::Module] either a module Class # or a payload Module. # @param options [Hash{Symbol => String}] # @option options [String] :path the path to the file from which # +class_or_module+ was loaded. # @option options [String] :reference_name the reference name for # +class_or_module+. # @option options [String] :type the module type # @return [void] # @raise [KeyError] unless +:path+ is given. # @raise [KeyError] unless +:reference_name+ is given. # @raise [KeyError] unless +:type+ is given. def cache_in_memory(class_or_module, options={}) options.assert_valid_keys(:path, :reference_name, :type) path = options.fetch(:path) begin modification_time = File.mtime(path) rescue Errno::ENOENT => error log_lines = [] log_lines << "Could not find the modification of time of #{path}:" log_lines << error.class.to_s log_lines << error.to_s log_lines << "Call stack:" log_lines += error.backtrace log_message = log_lines.join("\n") elog(log_message) else parent_path = class_or_module.module_parent.parent_path reference_name = options.fetch(:reference_name) type = options.fetch(:type) module_info_by_path[path] = { :modification_time => modification_time, :parent_path => parent_path, :reference_name => reference_name, :type => type } end end # Forces loading of the module with the given type and module reference name from the cache. # # @param [String] type the type of the module. # @param [String] reference_name the module reference name. # @return [false] if a module with the given type and reference name does not exist in the cache. # @return (see Msf::Modules::Loader::Base#load_module) def load_cached_module(type, reference_name, cache_type: Msf::ModuleManager::Cache::MEMORY) case cache_type when Msf::ModuleManager::Cache::FILESYSTEM cached_metadata = Msf::Modules::Metadata::Cache.instance.get_module_reference(type: type, reference_name: reference_name) return false unless cached_metadata parent_path = get_parent_path(cached_metadata.path, type) when Msf::ModuleManager::Cache::MEMORY cached_metadata = nil module_info = self.module_info_by_path.values.find { |inner_info| inner_info[:type] == type and inner_info[:reference_name] == reference_name } return false unless module_info parent_path = module_info[:parent_path] else raise ArgumentError, "#{cache_type} is not a valid cache type." end try_load_module(parent_path, reference_name, type, cached_metadata: cached_metadata) end def try_load_module(parent_path, reference_name, type, cached_metadata: nil) loaded = false # XXX borked loaders.each do |loader| next unless cached_metadata || loader.loadable_module?(parent_path, type, reference_name) loaded = loader.load_module(parent_path, type, reference_name, force: true, cached_metadata: cached_metadata) break if loaded end loaded end # @overload refresh_cache_from_module_files # Rebuilds module metadata store and in-memory cache for all modules. # # @return [void] # @overload refresh_cache_from_module_files(module_class_or_instance) # Rebuilds database and in-memory cache for given module_class_or_instance. # # @param (see Msf::DBManager#update_module_details) # @return [void] def refresh_cache_from_module_files(module_class_or_instance = nil) if module_class_or_instance Msf::Modules::Metadata::Cache.instance.refresh_metadata_instance(module_class_or_instance) else module_sets = [ ['exploit', @framework.exploits], ['auxiliary', @framework.auxiliary], ['post', @framework.post], ['payload', @framework.payloads], ['encoder', @framework.encoders], ['nop', @framework.nops], ['evasion', @framework.evasion] ] @framework.payloads.recalculate # Ensure all payloads are calculated before refreshing metadata Msf::Modules::Metadata::Cache.instance.refresh_metadata(module_sets) end refresh_cache_from_database(self.module_paths) end # Refreshes the in-memory cache from the database cache. # # @return [void] def refresh_cache_from_database(allowed_paths=[""]) self.module_info_by_path_from_database!(allowed_paths) end protected # @!attribute [rw] module_info_by_path # @return (see #module_info_by_path_from_store!) attr_accessor :module_info_by_path # Return a module info from Msf::Modules::Metadata::Obj. # # @note Also sets module_set(module_type)[module_reference_name] to nil if it is not already set. # # @return [Hash{String => Hash{Symbol => Object}}] Maps path (Mdm::Module::Detail#file) to module information. Module # information is a Hash derived from Mdm::Module::Detail. It includes :modification_time, :parent_path, :type, # :reference_name. def module_info_by_path_from_database!(allowed_paths=[""]) self.module_info_by_path = {} allowed_paths = allowed_paths.map{|x| x + "/"} metadata = Msf::Modules::Metadata::Cache.instance.get_metadata metadata.each do |module_metadata| path = module_metadata.path type = module_metadata.type reference_name = module_metadata.ref_name # Skip cached modules that are not in our allowed load paths next if allowed_paths.select{|x| path.index(x) == 0}.empty? parent_path = get_parent_path(path, type) module_info_by_path[path] = { :reference_name => reference_name, :type => type, :parent_path => parent_path, :modification_time => module_metadata.mod_time } module_metadata.aliases.each do |a| self.aliases[a] = module_metadata.fullname end self.inv_aliases[module_metadata.fullname] = module_metadata.aliases unless module_metadata.aliases.empty? typed_module_set = module_set(type) # Don't want to trigger as {Msf::ModuleSet#create} so check for # key instead of using ||= which would call {Msf::ModuleSet#[]} # which would potentially call {Msf::ModuleSet#create}. next unless typed_module_set unless typed_module_set.has_key?(reference_name) typed_module_set[reference_name] = nil end end self.module_info_by_path end def get_parent_path(module_path, type) # The load path is assumed to be the next level above the type directory type_dir = File.join('', Mdm::Module::Detail::DIRECTORY_BY_TYPE[type], '') module_path.split(type_dir)[0..-2].join(type_dir) # TODO: rewrite end end