@@ -17,8 +17,10 @@ class PayloadCachedSize
1717 OPTS = {
1818 'Format' => 'raw' ,
1919 'Options' => {
20+ 'VERBOSE' => false ,
2021 'CPORT' => 4444 ,
2122 'LPORT' => 4444 ,
23+ 'RPORT' => 4444 ,
2224 'CMD' => '/bin/sh' ,
2325 'URL' => 'http://a.com' ,
2426 'PATH' => '/' ,
@@ -46,88 +48,196 @@ class PayloadCachedSize
4648
4749 OPTS_IPV4 = {
4850 'LHOST' => '255.255.255.255' ,
51+ 'RHOST' => '255.255.255.255' ,
4952 'KHOST' => '255.255.255.255' ,
5053 'AHOST' => '255.255.255.255'
5154 } . freeze
5255
5356 OPTS_IPV6 = {
5457 'LHOST' => 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' ,
58+ 'RHOST' => 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' ,
5559 'KHOST' => 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff' ,
5660 'AHOST' => 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'
5761 } . freeze
5862
59- # Insert a new CachedSize value into the text of a payload module
63+ # Inserts or updates the CachedSize constant in the text of a payload module.
6064 #
6165 # @param data [String] The source code of a payload module
62- # @param cached_size [String] The new value for cached_size, which
63- # which should be either numeric or the string :dynamic
64- # @return [String]
66+ # @param cached_size [String, Integer] The new value for CachedSize, which should be either an integer or the string ":dynamic"
67+ # @return [String] The updated source code with the new CachedSize value
6568 def self . update_cache_constant ( data , cached_size )
6669 data .
6770 gsub ( /^\s *CachedSize\s *=\s *(\d +|:dynamic).*/ , '' ) .
6871 gsub ( /^(module MetasploitModule)\s *\n / ) do |m |
69- "#{ m . strip } \n \n CachedSize = #{ cached_size } \n \n "
72+ "#{ m . strip } \n CachedSize = #{ cached_size } \n \n "
7073 end
7174 end
7275
73- # Insert a new CachedSize value into a payload module file
76+ # Inserts or updates the CachedSizeOverrides constant in the text of a payload module,
77+ # removing any previous CachedSizeStages, # Other stager sizes, or CachedSizeOverrides lines.
78+ #
79+ # @param data [String] The source code of a payload module
80+ # @param stages_with_sizes [Array<{:stage => Msf::Payload::Stager, :size => Integer}>] Array of hashes with :stage (an Msf::Payload::Stager instance) and :size (Integer)
81+ # @return [String] The updated source code with the new CachedSizeOverrides value
82+ def self . update_stage_sizes_constant ( data , stages_with_sizes )
83+ sizes = stages_with_sizes . sort_by { |stage_with_size | stage_with_size [ :stage ] . refname } . map do |stage_with_size |
84+ [ stage_with_size [ :stage ] . refname , stage_with_size [ :size ] ]
85+ end
86+ data_without_other_stages = data . gsub ( /^\s *CachedSizeOverrides\s *=.*\n / , '' )
87+ return data_without_other_stages if sizes . empty?
88+
89+ data_without_other_stages . gsub ( /^\s *(CachedSize\s *=\s *(\d +|:dynamic))\s *\n / ) do |m |
90+ " #{ m . strip } \n CachedSizeOverrides = {#{ sizes . map { |( k , v ) | %Q{"#{ k } " => #{ v } } } . join ( ', ' ) } }\n \n "
91+ end
92+ end
93+
94+ # Insert or update the CachedSize value into a payload module file
7495 #
7596 # @param mod [Msf::Payload] The class of the payload module to update
76- # @param cached_size [String] The new value for cached_size, which
77- # which should be either numeric or the string :dynamic
97+ # @param cached_size [String, Integer ] The new value for cached_size, which
98+ # should be either an integer or the string " :dynamic"
7899 # @return [void]
79100 def self . update_cached_size ( mod , cached_size )
80101 mod_data = ""
81102
82- ::File . open ( mod . file_path , 'rb' ) do |fd |
103+ file_path = mod . file_path
104+
105+ ::File . open ( file_path , 'rb' ) do |fd |
83106 mod_data = fd . read ( fd . stat . size )
84107 end
85108
86- ::File . open ( mod . file_path , 'wb' ) do |fd |
109+ ::File . open ( file_path , 'wb' ) do |fd |
87110 fd . write update_cache_constant ( mod_data , cached_size )
88111 end
89112 end
90113
91- # Updates the payload module specified with the current CachedSize
114+ # Insert or update the CachedSize value into a payload module file
92115 #
93116 # @param mod [Msf::Payload] The class of the payload module to update
117+ # @param stages_with_sizes [Array<{:stage => Msf::Payload::Stager, :size => Integer}>] Array of hashes with :stage (an Msf::Payload::Stager instance) and :size (Integer)
94118 # @return [void]
95- def self . update_module_cached_size ( mod )
96- update_cached_size ( mod , compute_cached_size ( mod ) )
119+ def self . update_stager_cached_sizes ( mod , stages_with_sizes )
120+ mod_data = ""
121+
122+ file_path = mod . file_path
123+
124+ ::File . open ( file_path , 'rb' ) do |fd |
125+ mod_data = fd . read ( fd . stat . size )
126+ end
127+
128+ ::File . open ( file_path , 'wb' ) do |fd |
129+ fd . write update_stage_sizes_constant ( mod_data , stages_with_sizes )
130+ end
131+ end
132+
133+ # Updates the payload module specified with the current CachedSize
134+ #
135+ # @param framework [Msf::Framework] The Metasploit framework instance used for payload generation
136+ # @param mod [Msf::Payload] The class of the payload module to update
137+ # @return [String, Integer] The updated CachedSize value
138+ def self . update_module_cached_size ( framework , mod )
139+ cached_size = compute_cached_size ( framework , mod )
140+ update_cached_size ( mod , cached_size )
141+ cached_size
142+ end
143+
144+ # Updates the stager payload module with the most frequent CachedSize value and sets CachedSizeOverrides for other stages.
145+ #
146+ # @param framework [Msf::Framework] The Metasploit framework instance used for payload generation
147+ # @param stages [Array<Msf::Payload>] Array of stager modules to update
148+ # @return [Integer, String] The new CachedSize value set for the stager
149+ def self . update_stager_module_cached_size ( framework , stages )
150+ stages_with_sizes = stages . map do |stage |
151+ { stage : stage , size : compute_cached_size ( framework , stage ) }
152+ end
153+ most_frequent_cached_size = stages_with_sizes . map { |stage_with_size | stage_with_size [ :size ] }
154+ . select { |size | size . is_a? ( Numeric ) } . tally . sort_by ( &:last ) . to_h . keys . last
155+
156+ new_size = most_frequent_cached_size || stages_with_sizes . first [ :size ]
157+ other_sizes = stages_with_sizes . select { |stage_with_size | stage_with_size [ :size ] != new_size }
158+
159+ update_cached_size ( stages . first , new_size )
160+ update_stager_cached_sizes ( stages . first , other_sizes )
161+
162+ new_size
97163 end
98164
99165 # Calculates the CachedSize value for a payload module
100166 #
101167 # @param mod [Msf::Payload] The class of the payload module to update
102- # @return [Integer]
103- def self . compute_cached_size ( mod )
104- return ":dynamic" if is_dynamic? ( mod )
168+ # @return [Integer, String ]
169+ def self . compute_cached_size ( framework , mod )
170+ return ":dynamic" if is_dynamic? ( framework , mod )
105171
106- mod . generate_simple ( module_options ( mod ) ) . size
172+ mod . replicant . generate_simple ( module_options ( mod ) ) . bytesize
107173 end
108174
109175 # Determines whether a payload generates a static sized output
110176 #
111177 # @param mod [Msf::Payload] The class of the payload module to update
112178 # @param generation_count [Integer] The number of iterations to use to
113179 # verify that the size is static.
114- # @return [Integer]
115- def self . is_dynamic? ( mod , generation_count = 5 )
180+ # @return [Boolean]
181+ def self . is_dynamic? ( framework , mod , generation_count = 10 )
182+ return true if mod . class . const_defined? ( 'ForceDynamicCachedSize' ) && mod . class ::ForceDynamicCachedSize
116183 opts = module_options ( mod )
117- [ *( 1 ..generation_count ) ] . map do |x |
118- mod . generate_simple ( opts ) . size
119- end . uniq . length != 1
184+ last_bytesize = nil
185+ generation_count . times do
186+ # Ensure a new module instance is created for each attempt, as some options are randomized on load - such as tmp file path names etc
187+ new_mod = framework . payloads . create ( mod . refname )
188+ bytesize = new_mod . generate_simple ( opts ) . bytesize
189+ last_bytesize ||= bytesize
190+ if last_bytesize != bytesize
191+ return true
192+ end
193+ end
194+
195+ false
120196 end
121197
122198 # Determines whether a payload's CachedSize is up to date
123199 #
124200 # @param mod [Msf::Payload] The class of the payload module to update
125201 # @return [Boolean]
126- def self . is_cached_size_accurate? ( mod )
127- return true if mod . dynamic_size? && is_dynamic? ( mod )
202+ def self . is_cached_size_accurate? ( framework , mod )
203+ return true if mod . dynamic_size? && is_dynamic? ( framework , mod )
128204 return false if mod . cached_size . nil?
129205
130- mod . cached_size == mod . generate_simple ( module_options ( mod ) ) . size
206+ mod . cached_size == mod . replicant . generate_simple ( module_options ( mod ) ) . bytesize
207+ end
208+
209+ # Checks for errors or inconsistencies in the CachedSize value for a payload module.
210+ # Returns nil if the cache is correct, or a string describing the error if not.
211+ #
212+ # @param framework [Msf::Framework] The Metasploit framework instance used for payload generation
213+ # @param mod [Msf::Payload] The payload module to check
214+ # @return [String, nil] Error message if there is a problem, or nil if the cache is correct
215+ def self . cache_size_errors_for ( framework , mod )
216+ is_payload_size_different_on_each_generation = is_dynamic? ( framework , mod )
217+ module_marked_as_dynamic = mod . dynamic_size?
218+ payload_cached_static_size = mod . cached_size
219+
220+ # Validate dynamic scenario
221+ return if is_payload_size_different_on_each_generation && module_marked_as_dynamic
222+
223+ if is_payload_size_different_on_each_generation && !module_marked_as_dynamic
224+ return 'Module generated different sizes for each generation attempt. CacheSize must be set to :dynamic'
225+ end
226+
227+ if payload_cached_static_size . nil?
228+ return 'Module missing CachedSize and not marked as dynamic'
229+ end
230+
231+ payload_size_after_one_generation = mod . replicant . generate_simple ( module_options ( mod ) ) . bytesize
232+
233+ # Validate static scenario
234+ return if payload_cached_static_size == payload_size_after_one_generation
235+
236+ if payload_cached_static_size != payload_size_after_one_generation
237+ return "Module marked as having size #{ payload_cached_static_size } but after one generation was #{ payload_size_after_one_generation } "
238+ end
239+
240+ raise "unhandled scenario"
131241 end
132242
133243 # Get a set of sane default options for the module so it can generate a
0 commit comments