Skip to content

Commit a808c9f

Browse files
committed
Bring some sanity to the datastore
Before, the datastore would store options case-sensitive, but would access them case-insensitive, resulting is a number of string compares. This commit stores options in their downcase form to reduce update/lookup time. This adds up to reducing msfconsole boot time by about 10% and rspec time by about 45 sec. (!) on my box. One tricky part of this conversion is that there are several places (in pro and framework) where we export or otherwise access the datastore as a plain hash (case-sensitive). I believe I have caught all the ways we access the datastore that are case-sensitive and substituted the original key capitalization in those cases.
1 parent 85ab9d3 commit a808c9f

File tree

2 files changed

+48
-67
lines changed

2 files changed

+48
-67
lines changed

lib/msf/core/data_store.rb

Lines changed: 46 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# -*- coding: binary -*-
2+
3+
require 'set'
4+
25
module Msf
36

47
###
@@ -13,17 +16,19 @@ class DataStore < Hash
1316
# Initializes the data store's internal state.
1417
#
1518
def initialize()
16-
@options = Hash.new
17-
@imported = Hash.new
18-
@imported_by = Hash.new
19+
@options = Hash.new
20+
@imported = Hash.new
21+
@imported_by = Hash.new
22+
@original_keys = Set.new
1923
end
2024

2125
#
2226
# Clears the imported flag for the supplied key since it's being set
2327
# directly.
2428
#
2529
def []=(k, v)
26-
k = find_key_case(k)
30+
add_key(k)
31+
k = k.downcase
2732
@imported[k] = false
2833
@imported_by[k] = nil
2934

@@ -44,31 +49,32 @@ def []=(k, v)
4449
# Case-insensitive wrapper around hash lookup
4550
#
4651
def [](k)
47-
super(find_key_case(k))
52+
super(k.downcase)
4853
end
4954

5055
#
5156
# Case-insensitive wrapper around store
5257
#
5358
def store(k,v)
54-
super(find_key_case(k), v)
59+
add_key(k)
60+
super(k.downcase, v)
5561
end
5662

5763
#
5864
# Case-insensitive wrapper around delete
5965
#
6066
def delete(k)
61-
super(find_key_case(k))
67+
super(k.downcase)
6268
end
6369

64-
65-
#
66-
# Updates a value in the datastore with the specified name, k, to the
67-
# specified value, v. This update does not alter the imported status of
68-
# the value.
69-
#
70-
def update_value(k, v)
71-
self.store(k, v)
70+
# Override Hash's to_h method so we can include the original case of each key
71+
# (failing to do this breaks a number of places in framework and pro that use
72+
# serialized datastores)
73+
def to_h
74+
@original_keys.reduce({}) do |acc, key|
75+
acc[key] = self[key]
76+
acc
77+
end
7278
end
7379

7480
#
@@ -128,15 +134,16 @@ def import_options_from_s(option_str, delim = nil)
128134
# Imports options from a hash and stores them in the datastore.
129135
#
130136
def import_options_from_hash(option_hash, imported = true, imported_by = nil)
131-
option_hash.each_pair { |key, val|
137+
option_hash.each_pair do |key, val|
132138
import_option(key, val, imported, imported_by)
133-
}
139+
end
134140
end
135141

136142
def import_option(key, val, imported=true, imported_by=nil, option=nil)
137143
self.store(key, val)
138144

139-
@options[key] = option
145+
key = key.downcase
146+
@options[key] = option
140147
@imported[key] = imported
141148
@imported_by[key] = imported_by
142149
end
@@ -145,21 +152,9 @@ def import_option(key, val, imported=true, imported_by=nil, option=nil)
145152
# Serializes the options in the datastore to a string.
146153
#
147154
def to_s(delim = ' ')
148-
str = ''
149-
150-
keys.sort.each { |key|
151-
str << "#{key}=#{self[key]}" + ((str.length) ? delim : '')
152-
}
153-
154-
return str
155-
end
156-
157-
def to_h
158-
datastore_hash = {}
159-
self.keys.each do |k|
160-
datastore_hash[k.to_s] = self[k].to_s
155+
@original_keys.reduce('') do |acc, key|
156+
acc << "#{key}=#{self[key]}#{delim}"
161157
end
162-
datastore_hash
163158
end
164159

165160
#
@@ -199,9 +194,10 @@ def from_file(path, name = 'global')
199194
# not include default option values.
200195
#
201196
def user_defined
202-
reject { |k, v|
203-
@imported[k] == true
204-
}
197+
@original_keys.reduce({}) do |acc, k|
198+
acc[k] = self[k] unless @imported[k.downcase]
199+
acc
200+
end
205201
end
206202

207203
#
@@ -222,40 +218,26 @@ def clear_non_user_defined
222218
# Completely clear all values in the hash
223219
#
224220
def clear
225-
self.keys.each {|k| self.delete(k) }
226-
self
221+
@options.clear
222+
@imported.clear
223+
@imported_by.clear
224+
@original_keys.clear
225+
super
227226
end
228227

229-
#
230-
# Overrides the builtin 'each' operator to avoid the following exception on Ruby 1.9.2+
231-
# "can't add a new key into hash during iteration"
232-
#
228+
# Yield the original-cased key
233229
def each(&block)
234-
list = []
235-
self.keys.sort.each do |sidx|
236-
list << [sidx, self[sidx]]
230+
@original_keys.each do |key|
231+
block.call(key, self[key])
237232
end
238-
list.each(&block)
239233
end
240234

241-
protected
242-
243-
#
244-
# Case-insensitive key lookup
245-
#
246-
def find_key_case(k)
235+
protected
247236

248-
# Scan each key looking for a match
249-
self.each_key do |rk|
250-
if (rk.downcase == k.downcase)
251-
return rk
252-
end
253-
end
254-
255-
# Fall through to the non-existent value
256-
return k
237+
# Keep track of the original, case-sensitive key
238+
def add_key(k)
239+
@original_keys.add(k) unless include? k.downcase
257240
end
258-
259241
end
260242

261243
###
@@ -278,7 +260,7 @@ def initialize(m)
278260
# if we can't directly find it
279261
#
280262
def fetch(key)
281-
key = find_key_case(key)
263+
key = key.downcase
282264
val = nil
283265
val = super if(@imported_by[key] != 'self')
284266
if (val.nil? and @_module and @_module.framework)
@@ -292,7 +274,7 @@ def fetch(key)
292274
# Same as fetch
293275
#
294276
def [](key)
295-
key = find_key_case(key)
277+
key = key.downcase
296278
val = nil
297279
val = super if(@imported_by[key] != 'self')
298280
if (val.nil? and @_module and @_module.framework)
@@ -315,11 +297,10 @@ def default?(key)
315297
def copy
316298
clone = self.class.new(@_module)
317299
self.keys.each do |k|
318-
clone.import_option(k, self[k].kind_of?(String) ? self[k].dup : self[k], @imported[k], @imported_by[k])
300+
clone.import_option(k, self[k].kind_of?(String) ? self[k].dup : self[k], @imported[k.downcase], @imported_by[k.downcase])
319301
end
320302
clone
321303
end
322304
end
323305

324306
end
325-

lib/msf/core/exploit/powershell.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def make_subs(script, subs)
5858
# @return [String] Encoded script
5959
def encode_script(script_in, eof = nil)
6060
opts = {}
61-
datastore.select { |k, v| k =~ /^Powershell::(strip|sub)/ && v }.keys.map do |k|
61+
datastore.select { |k, v| k =~ /^Powershell::(strip|sub)/i && v }.keys.map do |k|
6262
mod_method = k.split('::').last.intern
6363
opts[mod_method.to_sym] = true
6464
end
@@ -76,7 +76,7 @@ def encode_script(script_in, eof = nil)
7676
# @return [String] Compressed script with decompression stub
7777
def compress_script(script_in, eof=nil)
7878
opts = {}
79-
datastore.select { |k, v| k =~ /^Powershell::(strip|sub)/ && v }.keys.map do |k|
79+
datastore.select { |k, v| k =~ /^Powershell::(strip|sub)/i && v }.keys.map do |k|
8080
mod_method = k.split('::').last.intern
8181
opts[mod_method.to_sym] = true
8282
end

0 commit comments

Comments
 (0)