Skip to content

Commit 48221e5

Browse files
authored
Land rapid7#18704, Leverage the module metadata cache in the module_sets
2 parents 7ac4387 + 82e9c27 commit 48221e5

File tree

29 files changed

+357
-245
lines changed

29 files changed

+357
-245
lines changed

.github/workflows/verify.yml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ jobs:
7676
include:
7777
- os: ubuntu-latest
7878
ruby: '3.1'
79-
test_cmd: 'bundle exec rake rspec-rerun:spec SPEC_OPTS="--tag content" DATASTORE_FALLBACKS=1'
79+
test_cmd: 'bundle exec rake rspec-rerun:spec SPEC_OPTS="--tag content" MSF_FEATURE_DATASTORE_FALLBACKS=1'
80+
- os: ubuntu-latest
81+
ruby: '3.1'
82+
test_cmd: 'bundle exec rake rspec-rerun:spec SPEC_OPTS="--tag content" MSF_FEATURE_DEFER_MODULE_LOADS=1'
8083
test_cmd:
8184
- bundle exec rake rspec-rerun:spec SPEC_OPTS="--tag content"
8285
- bundle exec rake rspec-rerun:spec SPEC_OPTS="--tag ~content"

lib/msf/core/encoded_payload.rb

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def encode
140140
# as the framework's list of encoder names so we can compare them later.
141141
# This is important for when we get input from RPC.
142142
if reqs['Encoder']
143-
reqs['Encoder'] = reqs['Encoder'].encode(framework.encoders.keys[0].encoding)
143+
reqs['Encoder'] = reqs['Encoder'].encode(framework.encoders.module_refnames[0].encoding)
144144
end
145145

146146
# If the caller had a preferred encoder, use this encoder only
@@ -237,9 +237,9 @@ def encode
237237

238238
begin
239239
eout = self.encoder.encode(eout, reqs['BadChars'], nil, pinst.platform)
240-
rescue EncodingError
241-
wlog("#{err_start}: Encoder #{encoder.refname} failed: #{$!}", 'core', LEV_1)
242-
dlog("#{err_start}: Call stack\n#{$@.join("\n")}", 'core', LEV_3)
240+
rescue EncodingError => e
241+
wlog("#{err_start}: Encoder #{encoder.refname} failed: #{e}", 'core', LEV_1)
242+
dlog("#{err_start}: Call stack\n#{e.backtrace}", 'core', LEV_3)
243243
next_encoder = true
244244
break
245245

@@ -342,7 +342,7 @@ def generate_sled
342342
wlog("#{pinst.refname}: Failed to find preferred nop #{reqs['Nop']}")
343343
end
344344

345-
nops.each { |nopname, nopmod|
345+
nops.each_module { |nopname, nopmod|
346346
# Create an instance of the nop module
347347
self.nop = nopmod.new
348348

lib/msf/core/exploit/remote/browser_autopwn2.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,8 @@ module Exploit::Remote::BrowserAutopwn2
4848
# @return [void]
4949
def init_exploits
5050
# First we're going to avoid using #find_all because that gets very slow.
51-
framework.exploits.each_pair do |fullname, place_holder|
52-
# If the place holder isn't __SYMBOLIC__, then that means the module is initialized,
53-
# and that's gotta be the active browser autopwn.
51+
framework.exploits.module_refnames.each do |fullname|
52+
5453
next if !fullname.include?('browser') || self.fullname == "exploit/#{fullname}"
5554

5655
# The user gets to specify which modules to include/exclude
@@ -269,7 +268,7 @@ def get_selected_payload_name(platform)
269268

270269
# The payload is legit, we can use it.
271270
# Avoid #create seems faster
272-
return payload_name if framework.payloads.keys.include?(payload_name)
271+
return payload_name if framework.payloads.module_refnames.include?(payload_name)
273272

274273
default = DEFAULT_PAYLOADS[platform][:payload]
275274

lib/msf/core/module/platform.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,6 @@ def self.build_child_platform_abbrev(mod)
131131
# the string).
132132
#
133133
def self.find_portion(mod, str)
134-
135134
# Check to see if we've built the abbreviated cache
136135
if (not (
137136
mod.const_defined?('Abbrev') and

lib/msf/core/module/platform_list.rb

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,7 @@ def self.win32
2525
# convenient.
2626
#
2727
def self.transform(src)
28-
if (src.kind_of?(Array))
29-
from_a(src)
30-
else
31-
from_a([src])
32-
end
28+
from_a(Array.wrap(src))
3329
end
3430

3531
#

lib/msf/core/module_set.rb

Lines changed: 37 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ class Msf::ModuleSet < Hash
2020
# and then returns the now-loaded class afterwards.
2121
#
2222
# @param [String] name the module reference name
23-
# @return [Msf::Module] instance of the of the Msf::Module subclass with the given reference name
23+
# @return [Msf::Module] Class of the of the Msf::Module with the given reference name
2424
def [](name)
25-
module_instance = super
26-
if module_instance == Msf::SymbolicModule || module_instance.nil?
27-
create(name)
25+
module_class = super
26+
if module_class == Msf::SymbolicModule || module_class.nil?
27+
load_module_class(name)
2828
end
2929

3030
super
@@ -36,14 +36,8 @@ def [](name)
3636
# @return [Msf::Module,nil] Instance of the named module or nil if it
3737
# could not be created.
3838
def create(reference_name, cache_type: Msf::ModuleManager::Cache::FILESYSTEM)
39-
klass = fetch(reference_name, nil)
39+
klass = load_module_class(reference_name, cache_type: cache_type)
4040
instance = nil
41-
# If there is no module associated with this class, then try to demand load it.
42-
if klass.nil? or klass == Msf::SymbolicModule
43-
framework.modules.load_cached_module(module_type, reference_name, cache_type: cache_type)
44-
klass = fetch(reference_name, nil)
45-
end
46-
4741
# If the klass is valid for this reference_name, try to create it
4842
unless klass.nil? or klass == Msf::SymbolicModule
4943
instance = klass.new
@@ -56,7 +50,7 @@ def create(reference_name, cache_type: Msf::ModuleManager::Cache::FILESYSTEM)
5650
self.delete(reference_name)
5751
end
5852

59-
return instance
53+
instance
6054
end
6155

6256
# Overrides the builtin 'each' operator to avoid the following exception on Ruby 1.9.2+
@@ -68,7 +62,7 @@ def create(reference_name, cache_type: Msf::ModuleManager::Cache::FILESYSTEM)
6862
# @return [void]
6963
def each(&block)
7064
list = []
71-
self.keys.sort.each do |sidx|
65+
module_metadata.keys.sort.each do |sidx|
7266
list << [sidx, self[sidx]]
7367
end
7468
list.each(&block)
@@ -81,9 +75,7 @@ def each(&block)
8175
# @yieldparam (see #each_module_list)
8276
# @return (see #each_module_list)
8377
def each_module(opts = {}, &block)
84-
demand_load_modules
85-
86-
self.mod_sorted = self.sort
78+
self.mod_sorted = module_metadata.sort
8779

8880
each_module_list(mod_sorted, opts, &block)
8981
end
@@ -107,8 +99,6 @@ def each_module_filter(opts, name, entry)
10799
# @yieldparam (see #each_module_list)
108100
# @return (see #each_module_list)
109101
def each_module_ranked(opts = {}, &block)
110-
demand_load_modules
111-
112102
each_module_list(rank_modules, opts, &block)
113103
end
114104

@@ -166,7 +156,6 @@ def recalculate
166156
# @return [true] if the module can be {#create created} and cached.
167157
# @return [false] otherwise
168158
def valid?(reference_name)
169-
create(reference_name)
170159
(self[reference_name]) ? true : false
171160
end
172161

@@ -203,28 +192,12 @@ def add_module(klass, reference_name, info = {})
203192
klass
204193
end
205194

206-
protected
207-
208-
# Load all modules that are marked as being symbolic.
209-
#
210-
# @return [void]
211-
def demand_load_modules
212-
found_symbolics = false
213-
# Pre-scan the module list for any symbolic modules
214-
self.each_pair { |name, mod|
215-
if (mod == Msf::SymbolicModule)
216-
found_symbolics = true
217-
mod = create(name)
218-
next if (mod.nil?)
219-
end
220-
}
221-
222-
# If we found any symbolic modules, then recalculate.
223-
if (found_symbolics)
224-
recalculate
225-
end
195+
def module_refnames
196+
module_metadata.keys
226197
end
227198

199+
protected
200+
228201
# Enumerates the modules in the supplied array with possible limiting factors.
229202
#
230203
# @param [Array<Array<String, Class>>] ary Array of module reference name and module class pairs
@@ -238,35 +211,32 @@ def demand_load_modules
238211
# @yieldparam [Class] module The module class: a subclass of {Msf::Module}.
239212
# @return [void]
240213
def each_module_list(ary, opts, &block)
241-
ary.each { |entry|
242-
name, mod = entry
243-
244-
# Skip any lingering symbolic modules.
245-
next if (mod == Msf::SymbolicModule)
214+
ary.each do |entry|
215+
name, module_metadata = entry
246216

247217
# Filter out incompatible architectures
248218
if (opts['Arch'])
249-
if (!architectures_by_module[mod])
250-
architectures_by_module[mod] = mod.new.arch
219+
if (!architectures_by_module[name])
220+
architectures_by_module[name] = Array.wrap(module_metadata.arch)
251221
end
252222

253-
next if ((architectures_by_module[mod] & opts['Arch']).empty? == true)
223+
next if ((architectures_by_module[name] & opts['Arch']).empty? == true)
254224
end
255225

256226
# Filter out incompatible platforms
257227
if (opts['Platform'])
258-
if (!platforms_by_module[mod])
259-
platforms_by_module[mod] = mod.new.platform
228+
if (!platforms_by_module[name])
229+
platforms_by_module[name] = module_metadata.platform_list
260230
end
261231

262-
next if ((platforms_by_module[mod] & opts['Platform']).empty? == true)
232+
next if ((platforms_by_module[name] & opts['Platform']).empty? == true)
263233
end
264234

265235
# Custom filtering
266236
next if (each_module_filter(opts, name, entry) == true)
267237

268-
block.call(name, mod)
269-
}
238+
block.call(name, self[name])
239+
end
270240
end
271241

272242
# @!attribute [rw] ambiguous_module_reference_name_set
@@ -304,33 +274,23 @@ def each_module_list(ary, opts, &block)
304274
# @return [Array<Array<String, Class>>] Array of arrays where the inner array is a pair of the module reference name
305275
# and the module class.
306276
def rank_modules
307-
self.sort_by { |pair| module_rank(*pair) }.reverse!
277+
module_metadata.sort_by do |refname, metadata|
278+
[metadata.rank || Msf::NormalRanking, refname]
279+
end.reverse!
308280
end
309281

310-
# Retrieves the rank from a loaded, not-yet-loaded, or unloadable Metasploit Module.
311-
#
312-
# @param reference_name [String] The reference name of the Metasploit Module
313-
# @param metasploit_module_class [Class<Msf::Module>, Msf::SymbolicModule] The loaded `Class` for the Metasploit
314-
# Module, or {Msf::SymbolicModule} if the Metasploit Module is not loaded yet.
315-
# @return [Integer] an `Msf::*Ranking`. `Msf::ManualRanking` if `metasploit_module_class` is `nil` or
316-
# {Msf::SymbolicModule} and it could not be loaded by {#create}. Otherwise, the `Rank` constant of the
317-
# `metasploit_module_class` or {Msf::NormalRanking} if `metasploit_module_class` does not define `Rank`.
318-
def module_rank(reference_name, metasploit_module_class)
319-
if metasploit_module_class.nil?
320-
Msf::ManualRanking
321-
elsif metasploit_module_class == Msf::SymbolicModule
322-
# TODO don't create an instance just to get the Class.
323-
created_metasploit_module_instance = create(reference_name)
324-
325-
if created_metasploit_module_instance.nil?
326-
module_rank(reference_name, nil)
327-
else
328-
module_rank(reference_name, created_metasploit_module_instance.class)
329-
end
330-
elsif metasploit_module_class.const_defined? :Rank
331-
metasploit_module_class.const_get :Rank
332-
else
333-
Msf::NormalRanking
282+
def module_metadata
283+
Msf::Modules::Metadata::Cache.instance.module_metadata(module_type)
284+
end
285+
286+
def load_module_class(reference_name, cache_type: Msf::ModuleManager::Cache::FILESYSTEM)
287+
klass = fetch(reference_name, nil)
288+
289+
# If there is no module associated with this class, then try to demand load it.
290+
if klass.nil? || klass == Msf::SymbolicModule
291+
framework.modules.load_cached_module(module_type, reference_name, cache_type: cache_type)
292+
klass = fetch(reference_name, nil)
334293
end
294+
klass
335295
end
336296
end

lib/msf/core/modules/metadata/cache.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ def refresh_metadata(module_sets)
8686
end
8787
end
8888

89+
def module_metadata(type)
90+
@mutex.synchronize do
91+
wait_for_load
92+
# TODO: Should probably figure out a way to cache this
93+
@module_metadata_cache.filter_map { |_, metadata| [metadata.ref_name, metadata] if metadata.type == type }.to_h
94+
end
95+
end
96+
8997
#######
9098
private
9199
#######
@@ -154,7 +162,7 @@ def initialize
154162
@module_metadata_cache = {}
155163
@store_loaded = false
156164
@console = Rex::Ui::Text::Output::Stdio.new
157-
@load_thread = Thread.new {
165+
@load_thread = Thread.new {
158166
init_store
159167
@store_loaded = true
160168
}

lib/msf/core/modules/metadata/obj.rb

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ class Obj
2828
attr_reader :description
2929
# @return [Array<String>]
3030
attr_reader :references
31-
# @return [Boolean]
31+
# @return [String]
3232
attr_reader :platform
33+
# @return [Msf::Module::PlatformList]
34+
attr_reader :platform_list
3335
# @return [String]
3436
attr_reader :arch
3537
# @return [Integer]
@@ -90,6 +92,7 @@ def initialize(module_instance, obj_hash = nil)
9092
@default_credential = module_instance.default_cred?
9193

9294
@platform = module_instance.platform_to_s
95+
@platform_list = module_instance.platform
9396
# Done to ensure that differences do not show up for the same array grouping
9497
sort_platform_string
9598

@@ -235,6 +238,7 @@ def init_from_hash(obj_hash)
235238
@author = obj_hash['author'].nil? ? [] : obj_hash['author']
236239
@references = obj_hash['references']
237240
@platform = obj_hash['platform']
241+
@platform_list = parse_platform_list(@platform)
238242
@arch = obj_hash['arch']
239243
@rport = obj_hash['rport']
240244
@mod_time = Time.parse(obj_hash['mod_time'])
@@ -288,6 +292,18 @@ def force_encoding(encoding)
288292
@references = @references.map {|r| r.dup.force_encoding(encoding)}
289293
end
290294

295+
def parse_platform_list(platform_string)
296+
return nil if platform_string.nil?
297+
298+
if platform_string.casecmp('All')
299+
# empty string represents all platforms in Msf::Module::PlatformList
300+
platforms = ['']
301+
else
302+
platforms = platform_string.split(',')
303+
end
304+
Msf::Module::PlatformList.transform(platforms)
305+
end
306+
291307
end
292308
end
293309
end

lib/msf/core/payload_generator.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,7 @@ def format_is_valid?
598598
# @return [True] if the payload is a valid Metasploit Payload
599599
# @return [False] if the payload is not a valid Metasploit Payload
600600
def payload_is_valid?
601-
(framework.payloads.keys + ['stdin']).include? payload
601+
(framework.payloads.module_refnames + ['stdin']).include? payload
602602
end
603603

604604
end

0 commit comments

Comments
 (0)