Class: Msf::Util::PayloadCachedSize

Inherits:
Object
  • Object
show all
Defined in:
lib/msf/util/payload_cached_size.rb

Overview

The class provides helper methods for verifying and updating the embedded CachedSize constant within payload modules.

Constant Summary collapse

OPTS =
{
  'Format'      => 'raw',
  'Options'     => {
    'VERBOSE' => false,
    'CPORT' => 4444,
    'LPORT' => 4444,
    'RPORT' => 4444,
    'CMD' => '/bin/sh',
    'URL' => 'http://a.com',
    'PATH' => '/',
    'BUNDLE' => 'data/isight.bundle',
    'DLL' => 'external/source/byakugan/bin/XPSP2/detoured.dll',
    'RC4PASSWORD' => 'Metasploit',
    'DNSZONE' => 'corelan.eu',
    'PEXEC' => '/bin/sh',
    'HttpUserAgent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:94.0) Gecko/20100101 Firefox/94.0',
    'StagerURILength' => 5,
    'FD' => 100,
    'MeterpreterDebugBuild' => false
  },
  'Encoder'     => nil,
  'DisableNops' => true
}
OPTS_ARCH_X64 =
{
  'DLL' => 'data/vncdll.x64.dll',
  'PE' => 'data/vncdll.x64.dll'
}.freeze
OPTS_ARCH_X86 =
{
  'DLL' => 'data/vncdll.x86.dll',
  'PE' => 'data/vncdll.x86.dll'
}.freeze
OPTS_IPV4 =
{
  'LHOST' => '223.255.255.255',
  'RHOST' => '255.255.255.255',
  'KHOST' => '255.255.255.255',
  'AHOST' => '255.255.255.255'
}.freeze
OPTS_IPV6 =
{
  'LHOST' => 'fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
  'RHOST' => 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
  'KHOST' => 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
  'AHOST' => 'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff'
}.freeze

Class Method Summary collapse

Class Method Details

.cache_size_errors_for(framework, mod) ⇒ String?

Checks for errors or inconsistencies in the CachedSize value for a payload module. Returns nil if the cache is correct, or a string describing the error if not.

Parameters:

  • framework (Msf::Framework)

    The Metasploit framework instance used for payload generation

  • mod (Msf::Payload)

    The payload module to check

Returns:

  • (String, nil)

    Error message if there is a problem, or nil if the cache is correct



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/msf/util/payload_cached_size.rb', line 217

def self.cache_size_errors_for(framework, mod)
  is_payload_size_different_on_each_generation = is_dynamic?(framework,mod)
  module_marked_as_dynamic = mod.dynamic_size?
  payload_cached_static_size = mod.cached_size

  # Validate dynamic scenario
  return if is_payload_size_different_on_each_generation && module_marked_as_dynamic

  if is_payload_size_different_on_each_generation && !module_marked_as_dynamic
    return 'Module generated different sizes for each generation attempt. CacheSize must be set to :dynamic'
  end

  if payload_cached_static_size.nil?
    return 'Module missing CachedSize and not marked as dynamic'
  end

  payload_size_after_one_generation = mod.replicant.generate_simple(module_options(mod)).bytesize

  # Validate static scenario
  return if payload_cached_static_size == payload_size_after_one_generation

  if payload_cached_static_size != payload_size_after_one_generation
    return "Module marked as having size #{payload_cached_static_size} but after one generation was #{payload_size_after_one_generation}"
  end

  raise "unhandled scenario"
end

.compute_cached_size(framework, mod) ⇒ Integer, String

Calculates the CachedSize value for a payload module

Parameters:

  • mod (Msf::Payload)

    The class of the payload module to update

Returns:

  • (Integer, String)


171
172
173
174
175
# File 'lib/msf/util/payload_cached_size.rb', line 171

def self.compute_cached_size(framework, mod)
  return ":dynamic" if is_dynamic?(framework, mod)

  mod.replicant.generate_simple(module_options(mod)).bytesize
end

.is_cached_size_accurate?(framework, mod) ⇒ Boolean

Determines whether a payload’s CachedSize is up to date

Parameters:

  • mod (Msf::Payload)

    The class of the payload module to update

Returns:

  • (Boolean)


204
205
206
207
208
209
# File 'lib/msf/util/payload_cached_size.rb', line 204

def self.is_cached_size_accurate?(framework, mod)
  return true if mod.dynamic_size? && is_dynamic?(framework, mod)
  return false if mod.cached_size.nil?

  mod.cached_size == mod.replicant.generate_simple(module_options(mod)).bytesize
end

.is_dynamic?(framework, mod, generation_count = 10) ⇒ Boolean

Determines whether a payload generates a static sized output

Parameters:

  • mod (Msf::Payload)

    The class of the payload module to update

  • generation_count (Integer) (defaults to: 10)

    The number of iterations to use to verify that the size is static.

Returns:

  • (Boolean)


183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/msf/util/payload_cached_size.rb', line 183

def self.is_dynamic?(framework, mod, generation_count=10)
  return true if mod.class.const_defined?('ForceDynamicCachedSize') && mod.class::ForceDynamicCachedSize
  opts = module_options(mod)
  last_bytesize = nil
  generation_count.times do
    # Ensure a new module instance is created for each attempt, as some options are randomized on load - such as tmp file path names etc
    new_mod = framework.payloads.create(mod.refname)
    bytesize = new_mod.generate_simple(opts).bytesize
    last_bytesize ||= bytesize
    if last_bytesize != bytesize
      return true
    end
  end

  false
end

.module_options(mod) ⇒ Hash

Get a set of sane default options for the module so it can generate a payload for size analysis.

Parameters:

  • mod (Msf::Payload)

    The class of the payload module to get options for

Returns:

  • (Hash)


250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
# File 'lib/msf/util/payload_cached_size.rb', line 250

def self.module_options(mod)
  opts = OPTS.clone
  # Assign this way to overwrite the Options key of the newly cloned hash
  opts['Options'] = opts['Options'].merge(mod.shortname =~ /6/ ? OPTS_IPV6 : OPTS_IPV4)
  # Extract the AdaptedArch for adaptor payloads, note `mod.adapted_arch` is not part of the public API
  # at this time, but could be in the future. The use of send is safe for now as it is an internal tool
  # with automated tests if the API were to change in the future
  adapted_arch = mod.send(:module_info)['AdaptedArch']
  if adapted_arch == ARCH_X64 || mod.arch_to_s == ARCH_X64
    opts['Options'].merge!(OPTS_ARCH_X64)
  elsif adapted_arch == ARCH_X86 || mod.arch_to_s == ARCH_X86
    opts['Options'].merge!(OPTS_ARCH_X86)
  end
  opts
end

.update_cache_constant(data, cached_size) ⇒ String

Inserts or updates the CachedSize constant in the text of a payload module.

Parameters:

  • data (String)

    The source code of a payload module

  • cached_size (String, Integer)

    The new value for CachedSize, which should be either an integer or the string ":dynamic"

Returns:

  • (String)

    The updated source code with the new CachedSize value



70
71
72
73
74
75
76
# File 'lib/msf/util/payload_cached_size.rb', line 70

def self.update_cache_constant(data, cached_size)
  data.
    gsub(/^\s*CachedSize\s*=\s*(\d+|:dynamic).*/, '').
    gsub(/^(module MetasploitModule)\s*\n/) do |m|
      "#{m.strip}\n  CachedSize = #{cached_size}\n\n"
    end
end

.update_cached_size(mod, cached_size) ⇒ void

This method returns an undefined value.

Insert or update the CachedSize value into a payload module file

Parameters:

  • mod (Msf::Payload)

    The class of the payload module to update

  • cached_size (String, Integer)

    The new value for cached_size, which should be either an integer or the string ":dynamic"



102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/msf/util/payload_cached_size.rb', line 102

def self.update_cached_size(mod, cached_size)
  mod_data = ""

  file_path = mod.file_path

  ::File.open(file_path, 'rb') do |fd|
    mod_data = fd.read(fd.stat.size)
  end

  ::File.open(file_path, 'wb') do |fd|
    fd.write update_cache_constant(mod_data, cached_size)
  end
end

.update_module_cached_size(framework, mod) ⇒ String, Integer

Updates the payload module specified with the current CachedSize

Parameters:

  • framework (Msf::Framework)

    The Metasploit framework instance used for payload generation

  • mod (Msf::Payload)

    The class of the payload module to update

Returns:

  • (String, Integer)

    The updated CachedSize value



140
141
142
143
144
# File 'lib/msf/util/payload_cached_size.rb', line 140

def self.update_module_cached_size(framework, mod)
  cached_size = compute_cached_size(framework, mod)
  update_cached_size(mod, cached_size)
  cached_size
end

.update_stage_sizes_constant(data, stages_with_sizes) ⇒ String

Inserts or updates the CachedSizeOverrides constant in the text of a payload module, removing any previous CachedSizeStages, # Other stager sizes, or CachedSizeOverrides lines.

Parameters:

  • data (String)

    The source code of a payload module

  • stages_with_sizes (Array<{:stage => Msf::Payload::Stager, :size => Integer}>)

    Array of hashes with :stage (an Msf::Payload::Stager instance) and :size (Integer)

Returns:

  • (String)

    The updated source code with the new CachedSizeOverrides value



84
85
86
87
88
89
90
91
92
93
94
# File 'lib/msf/util/payload_cached_size.rb', line 84

def self.update_stage_sizes_constant(data, stages_with_sizes)
  sizes = stages_with_sizes.sort_by { |stage_with_size| stage_with_size[:stage].refname }.map do |stage_with_size|
    [stage_with_size[:stage].refname, stage_with_size[:size]]
  end
    data_without_other_stages = data.gsub(/^\s*CachedSizeOverrides\s*=.*\n/, '')
  return data_without_other_stages if sizes.empty?

  data_without_other_stages.gsub(/^\s*(CachedSize\s*=\s*(\d+|:dynamic))\s*\n/) do |m|
    "  #{m.strip}\n  CachedSizeOverrides = {#{sizes.map { |(k, v)| %Q{"#{k}" => #{v}}}.join(', ')}}\n\n"
  end
end

.update_stager_cached_sizes(mod, stages_with_sizes) ⇒ void

This method returns an undefined value.

Insert or update the CachedSize value into a payload module file

Parameters:

  • mod (Msf::Payload)

    The class of the payload module to update

  • stages_with_sizes (Array<{:stage => Msf::Payload::Stager, :size => Integer}>)

    Array of hashes with :stage (an Msf::Payload::Stager instance) and :size (Integer)



121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/msf/util/payload_cached_size.rb', line 121

def self.update_stager_cached_sizes(mod, stages_with_sizes)
  mod_data = ""

  file_path = mod.file_path

  ::File.open(file_path, 'rb') do |fd|
    mod_data = fd.read(fd.stat.size)
  end

  ::File.open(file_path, 'wb') do |fd|
    fd.write update_stage_sizes_constant( mod_data, stages_with_sizes)
  end
end

.update_stager_module_cached_size(framework, stages) ⇒ Integer, String

Updates the stager payload module with the most frequent CachedSize value and sets CachedSizeOverrides for other stages.

Parameters:

  • framework (Msf::Framework)

    The Metasploit framework instance used for payload generation

  • stages (Array<Msf::Payload>)

    Array of stager modules to update

Returns:

  • (Integer, String)

    The new CachedSize value set for the stager



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/msf/util/payload_cached_size.rb', line 151

def self.update_stager_module_cached_size(framework, stages)
  stages_with_sizes = stages.map do |stage|
    { stage: stage, size: compute_cached_size(framework, stage) }
  end
  most_frequent_cached_size = stages_with_sizes.map { |stage_with_size| stage_with_size[:size] }
                          .select { |size| size.is_a?(Numeric) }.tally.sort_by(&:last).to_h.keys.last

  new_size = most_frequent_cached_size || stages_with_sizes.first[:size]
  other_sizes = stages_with_sizes.select { |stage_with_size| stage_with_size[:size] != new_size }

  update_cached_size(stages.first, new_size)
  update_stager_cached_sizes(stages.first, other_sizes)

  new_size
end