Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/msf/util/payload_cached_size.rb
57888 views
1
# -*- coding: binary -*-
2
###
3
#
4
#
5
###
6
7
module Msf
8
module Util
9
10
#
11
# The class provides helper methods for verifying and updating the embedded CachedSize
12
# constant within payload modules.
13
#
14
15
class PayloadCachedSize
16
17
OPTS = {
18
'Format' => 'raw',
19
'Options' => {
20
'VERBOSE' => false,
21
'CPORT' => 4444,
22
'LPORT' => 4444,
23
'RPORT' => 4444,
24
'CMD' => '/bin/sh',
25
'URL' => 'http://a.com',
26
'PATH' => '/',
27
'BUNDLE' => 'data/isight.bundle',
28
'DLL' => 'external/source/byakugan/bin/XPSP2/detoured.dll',
29
'RC4PASSWORD' => 'Metasploit',
30
'DNSZONE' => 'corelan.eu',
31
'PEXEC' => '/bin/sh',
32
'HttpUserAgent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0',
33
'StagerURILength' => 5
34
},
35
'Encoder' => nil,
36
'DisableNops' => true
37
}
38
39
OPTS_ARCH_X64 = {
40
'DLL' => 'data/vncdll.x64.dll',
41
'PE' => 'data/vncdll.x64.dll'
42
}.freeze
43
44
OPTS_ARCH_X86 = {
45
'DLL' => 'data/vncdll.x86.dll',
46
'PE' => 'data/vncdll.x86.dll'
47
}.freeze
48
49
OPTS_IPV4 = {
50
'LHOST' => '223.255.255.255',
51
'RHOST' => '255.255.255.255',
52
'KHOST' => '255.255.255.255',
53
'AHOST' => '255.255.255.255'
54
}.freeze
55
56
OPTS_IPV6 = {
57
'LHOST' => 'fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
58
'RHOST' => 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
59
'KHOST' => 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
60
'AHOST' => 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'
61
}.freeze
62
63
# Inserts or updates the CachedSize constant in the text of a payload module.
64
#
65
# @param data [String] The source code of a payload module
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
68
def self.update_cache_constant(data, cached_size)
69
data.
70
gsub(/^\s*CachedSize\s*=\s*(\d+|:dynamic).*/, '').
71
gsub(/^(module MetasploitModule)\s*\n/) do |m|
72
"#{m.strip}\n CachedSize = #{cached_size}\n\n"
73
end
74
end
75
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
95
#
96
# @param mod [Msf::Payload] The class of the payload module to update
97
# @param cached_size [String, Integer] The new value for cached_size, which
98
# should be either an integer or the string ":dynamic"
99
# @return [void]
100
def self.update_cached_size(mod, cached_size)
101
mod_data = ""
102
103
file_path = mod.file_path
104
105
::File.open(file_path, 'rb') do |fd|
106
mod_data = fd.read(fd.stat.size)
107
end
108
109
::File.open(file_path, 'wb') do |fd|
110
fd.write update_cache_constant(mod_data, cached_size)
111
end
112
end
113
114
# Insert or update the CachedSize value into a payload module file
115
#
116
# @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)
118
# @return [void]
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
163
end
164
165
# Calculates the CachedSize value for a payload module
166
#
167
# @param mod [Msf::Payload] The class of the payload module to update
168
# @return [Integer, String]
169
def self.compute_cached_size(framework, mod)
170
return ":dynamic" if is_dynamic?(framework, mod)
171
172
mod.replicant.generate_simple(module_options(mod)).bytesize
173
end
174
175
# Determines whether a payload generates a static sized output
176
#
177
# @param mod [Msf::Payload] The class of the payload module to update
178
# @param generation_count [Integer] The number of iterations to use to
179
# verify that the size is static.
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
183
opts = module_options(mod)
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
196
end
197
198
# Determines whether a payload's CachedSize is up to date
199
#
200
# @param mod [Msf::Payload] The class of the payload module to update
201
# @return [Boolean]
202
def self.is_cached_size_accurate?(framework, mod)
203
return true if mod.dynamic_size? && is_dynamic?(framework, mod)
204
return false if mod.cached_size.nil?
205
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"
241
end
242
243
# Get a set of sane default options for the module so it can generate a
244
# payload for size analysis.
245
#
246
# @param mod [Msf::Payload] The class of the payload module to get options for
247
# @return [Hash]
248
def self.module_options(mod)
249
opts = OPTS.clone
250
# Assign this way to overwrite the Options key of the newly cloned hash
251
opts['Options'] = opts['Options'].merge(mod.shortname =~ /6/ ? OPTS_IPV6 : OPTS_IPV4)
252
# Extract the AdaptedArch for adaptor payloads, note `mod.adapted_arch` is not part of the public API
253
# at this time, but could be in the future. The use of send is safe for now as it is an internal tool
254
# with automated tests if the API were to change in the future
255
adapted_arch = mod.send(:module_info)['AdaptedArch']
256
if adapted_arch == ARCH_X64 || mod.arch_to_s == ARCH_X64
257
opts['Options'].merge!(OPTS_ARCH_X64)
258
elsif adapted_arch == ARCH_X86 || mod.arch_to_s == ARCH_X86
259
opts['Options'].merge!(OPTS_ARCH_X86)
260
end
261
opts
262
end
263
end
264
265
end
266
end
267
268