Skip to content

Commit 43ddc66

Browse files
committed
Initial fix for non db cache
1 parent 697b893 commit 43ddc66

File tree

9 files changed

+353
-8
lines changed

9 files changed

+353
-8
lines changed

lib/msf/core/framework.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ module Offspring
6161
require 'msf/core/db_manager'
6262
require 'msf/core/event_dispatcher'
6363
require 'rex/json_hash_file'
64+
require 'msf/core/modules/metadata/cache'
6465

6566
#
6667
# Creates an instance of the framework context.
@@ -90,6 +91,8 @@ def initialize(options={})
9091
events.add_general_subscriber(subscriber)
9192
events.add_db_subscriber(subscriber)
9293
events.add_ui_subscriber(subscriber)
94+
95+
Msf::Modules::Metadata::Cache.instance.init(self)
9396
end
9497

9598
def inspect

lib/msf/core/module/full_name.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ module ClassMethods
2121
#
2222

2323
def fullname
24-
type + '/' + refname
24+
type + '/' + (refname.nil? ? "" : refname)
2525
end
2626

2727
def shortname

lib/msf/core/module_set.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ def add_module(klass, reference_name, info = {})
205205

206206
self[reference_name] = klass
207207

208+
Msf::Modules::Metadata::Cache.instance.cache_module_metadata(klass)
209+
208210
klass
209211
end
210212

lib/msf/core/modules/metadata.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module Msf::Modules::Metadata
2+
3+
end
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
require 'singleton'
2+
require 'msf/events'
3+
require 'rex/ui/text/output/stdio'
4+
require 'msf/core/constants'
5+
require 'msf/core/modules/metadata/obj'
6+
require 'msf/core/modules/metadata/search'
7+
8+
#
9+
# Core service class that provides storage of module metadata as well as operations on the metadata.
10+
# Note that operations on this metadata are included as separate modules.
11+
#
12+
# To prevent excessive startup times module definitions are not parsed for metadata until startup
13+
# is complete. Once startup is complete to prevent CPU spin loading is then done gradually and
14+
# only when an operation using the cache is called is CPU use maximized.
15+
#
16+
module Msf
17+
module Modules
18+
module Metadata
19+
20+
class Cache
21+
include Singleton
22+
include ::Msf::UiEventSubscriber
23+
include Msf::Modules::Metadata::Search
24+
25+
#
26+
# Init registers this class as a listener to be notified when the console is done loading,
27+
# this acts a hint to this class to start trickle loading the metadata
28+
#
29+
def init(framework)
30+
register_ui_listener(framework)
31+
@framework = framework
32+
end
33+
34+
#
35+
# Parses module metadata into an in memory cache.
36+
#
37+
# @param klass_or_instance - An instantiated or class instance of a module.
38+
#
39+
def cache_module_metadata(klass_or_instance)
40+
# Only use cache if db is not in use for now
41+
return if @framework.db.active
42+
43+
if klass_or_instance.is_a?(Class)
44+
if @module_load_complete
45+
add_module_metadata_from_class(klass_or_instance)
46+
else
47+
@module_definitions << klass_or_instance
48+
end
49+
else
50+
add_module_metadata_from_instance(klass_or_instance)
51+
end
52+
end
53+
54+
#########
55+
protected
56+
#########
57+
58+
#
59+
# Notify the thread responsible for loading metadata that it can start loading.
60+
#
61+
def on_ui_start(rev)
62+
return if @module_load_complete
63+
@startup_called = true
64+
end
65+
66+
#
67+
# Returns the module data cache, but first ensures all the metadata is loaded
68+
#
69+
def get_module_metadata_cache
70+
wait_for_load
71+
return @module_metadata_cache
72+
end
73+
74+
#######
75+
private
76+
#######
77+
78+
def register_ui_listener(framework)
79+
begin
80+
framework.events.add_ui_subscriber(self)
81+
rescue Exception => e
82+
elog('Unable to register metadata cache service as UI listener')
83+
end
84+
end
85+
86+
def wait_for_load
87+
if (!@module_load_complete)
88+
@trickle_load = false
89+
@console.print_warning('Waiting to finish parsing module metadata')
90+
@module_load_thread.join
91+
end
92+
end
93+
94+
def add_module_metadata_from_class(module_class_definition)
95+
begin
96+
instance = module_class_definition.new
97+
add_module_metadata_from_instance(instance)
98+
rescue Exception => e
99+
elog("Error adding module metadata: #{e.message}")
100+
end
101+
end
102+
103+
def add_module_metadata_from_instance(module_instance)
104+
module_metadata = Obj.new(module_instance)
105+
@module_metadata_cache[get_cache_key(module_instance)] = module_metadata
106+
end
107+
108+
def get_cache_key(module_instance)
109+
key = ''
110+
key << (module_instance.type.nil? ? '' : module_instance.type)
111+
key << '_'
112+
key << module_instance.name
113+
return key
114+
end
115+
116+
#
117+
# This method is used by the @module_load_thread
118+
#
119+
def load_module_definitions
120+
loop do
121+
if @startup_called
122+
break;
123+
end
124+
125+
sleep 0.3
126+
end
127+
128+
count = 0
129+
@module_definitions.each {|module_definition|
130+
add_module_metadata_from_class(module_definition)
131+
count = count + 1
132+
if (@trickle_load && count > @trickle_load_batch)
133+
sleep @trickle_load_interval
134+
count = 0
135+
end
136+
}
137+
138+
@module_load_complete = true
139+
GC.start(full_mark: true, immediate_sweep: true)
140+
end
141+
142+
def initialize
143+
@module_load_complete = false
144+
@startup_called = false;
145+
@trickle_load = true
146+
@trickle_load_batch = 200
147+
@trickle_load_interval = 0.5
148+
@module_metadata_cache = {}
149+
@module_definitions = []
150+
@module_load_thread = Thread.new {
151+
load_module_definitions
152+
}
153+
154+
@console = Rex::Ui::Text::Output::Stdio.new
155+
end
156+
end
157+
158+
end
159+
end
160+
end

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

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#
2+
# Simple accessor object for storing module metadata.
3+
#
4+
module Msf
5+
module Modules
6+
module Metadata
7+
8+
class Obj
9+
attr_reader :name
10+
attr_reader :full_name
11+
attr_reader :rank
12+
attr_reader :disclosure_date
13+
attr_reader :type
14+
attr_reader :author
15+
attr_reader :description
16+
attr_reader :references
17+
attr_reader :is_server
18+
attr_reader :is_client
19+
attr_reader :platform
20+
attr_reader :arch
21+
attr_reader :rport
22+
attr_reader :targets
23+
24+
def initialize(module_instance)
25+
@name = module_instance.name
26+
@full_name = module_instance.fullname
27+
@disclosure_date = module_instance.disclosure_date
28+
@rank = module_instance.rank
29+
@type = module_instance.type
30+
@description = module_instance.description
31+
@author = module_instance.author.map{|x| x.to_s}
32+
@references = module_instance.references.map{|x| [x.ctx_id, x.ctx_val].join("-") }
33+
@is_server = (module_instance.respond_to?(:stance) and module_instance.stance == "aggressive")
34+
@is_client = (module_instance.respond_to?(:stance) and module_instance.stance == "passive")
35+
@platform = module_instance.platform_to_s
36+
@arch = module_instance.arch_to_s
37+
@rport = module_instance.datastore['RPORT'].to_s
38+
39+
if module_instance.respond_to?(:targets) and module_instance.targets
40+
@targets = module_instance.targets.map{|x| x.name}
41+
end
42+
end
43+
end
44+
end
45+
end
46+
end
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
#
2+
# Provides search operations on the module metadata cache.
3+
#
4+
module Msf::Modules::Metadata::Search
5+
#
6+
# Searches the module metadata using the passed search string.
7+
#
8+
def find(search_string)
9+
search_results = []
10+
11+
get_module_metadata_cache.values.each { |module_metadata|
12+
if is_match(search_string, module_metadata)
13+
search_results << module_metadata
14+
end
15+
}
16+
17+
return search_results
18+
end
19+
20+
#######
21+
private
22+
#######
23+
24+
def is_match(search_string, module_metadata)
25+
return false if not search_string
26+
27+
search_string += ' '
28+
29+
# Split search terms by space, but allow quoted strings
30+
terms = search_string.split(/\"/).collect{|t| t.strip==t ? t : t.split(' ')}.flatten
31+
terms.delete('')
32+
33+
# All terms are either included or excluded
34+
res = {}
35+
36+
terms.each do |t|
37+
f,v = t.split(":", 2)
38+
if not v
39+
v = f
40+
f = 'text'
41+
end
42+
next if v.length == 0
43+
f.downcase!
44+
v.downcase!
45+
res[f] ||=[ [], [] ]
46+
if v[0,1] == "-"
47+
next if v.length == 1
48+
res[f][1] << v[1,v.length-1]
49+
else
50+
res[f][0] << v
51+
end
52+
end
53+
54+
k = res
55+
56+
[0,1].each do |mode|
57+
match = false
58+
k.keys.each do |t|
59+
next if k[t][mode].length == 0
60+
61+
k[t][mode].each do |w|
62+
# Reset the match flag for each keyword for inclusive search
63+
match = false if mode == 0
64+
65+
# Convert into a case-insensitive regex
66+
r = Regexp.new(Regexp.escape(w), true)
67+
68+
case t
69+
when 'text'
70+
terms = [module_metadata.name, module_metadata.full_name, module_metadata.description] + module_metadata.references + module_metadata.author
71+
72+
if module_metadata.targets
73+
terms = terms + module_metadata.targets
74+
end
75+
match = [t,w] if terms.any? { |x| x =~ r }
76+
when 'name'
77+
match = [t,w] if module_metadata.name =~ r
78+
when 'path'
79+
match = [t,w] if module_metadata.full_name =~ r
80+
when 'author'
81+
match = [t,w] if module_metadata.author.any? { |a| a =~ r }
82+
when 'os', 'platform'
83+
match = [t,w] if module_metadata.platform =~ r or module_metadata.arch =~ r
84+
85+
if module_metadata.targets
86+
match = [t,w] if module_metadata.targets.any? { |t| t =~ r }
87+
end
88+
when 'port'
89+
match = [t,w] if module_metadata.rport =~ r
90+
when 'type'
91+
match = [t,w] if Msf::MODULE_TYPES.any? { |modt| w == modt and module_metadata.type == modt }
92+
when 'app'
93+
match = [t,w] if (w == "server" and module_metadata.is_server)
94+
match = [t,w] if (w == "client" and module_metadata.is_client)
95+
when 'cve'
96+
match = [t,w] if module_metadata.references.any? { |ref| ref =~ /^cve\-/i and ref =~ r }
97+
when 'bid'
98+
match = [t,w] if module_metadata.references.any? { |ref| ref =~ /^bid\-/i and ref =~ r }
99+
when 'edb'
100+
match = [t,w] if module_metadata.references.any? { |ref| ref =~ /^edb\-/i and ref =~ r }
101+
end
102+
break if match
103+
end
104+
# Filter this module if no matches for a given keyword type
105+
if mode == 0 and not match
106+
return false
107+
end
108+
end
109+
# Filter this module if we matched an exclusion keyword (-value)
110+
if mode == 1 and match
111+
return false
112+
end
113+
end
114+
115+
true
116+
end
117+
end

lib/msf/core/payload_set.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ def add_module(payload_module, reference_name, modinfo={})
197197
# our own evil purposes.
198198
instance = build_payload(payload_module).new
199199

200+
Msf::Modules::Metadata::Cache.instance.cache_module_metadata(instance)
201+
200202
# Create an array of information about this payload module
201203
pinfo =
202204
[

0 commit comments

Comments
 (0)