Class: Msf::EncodedPayload

Inherits:
Object
  • Object
show all
Includes:
Framework::Offspring
Defined in:
lib/msf/core/encoded_payload.rb

Overview

This class wrappers an encoded payload buffer and the means used to create one.

Instance Attribute Summary collapse

Attributes included from Framework::Offspring

#framework

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(framework, pinst, reqs) ⇒ EncodedPayload

Creates an instance of an EncodedPayload.



32
33
34
35
36
37
# File 'lib/msf/core/encoded_payload.rb', line 32

def initialize(framework, pinst, reqs)
  self.framework = framework
  self.pinst     = pinst
  self.reqs      = reqs
  self.space     = reqs['Space']
end

Instance Attribute Details

#encodedObject

The encoded version of the raw payload plus the NOP sled if one was generated.



494
495
496
# File 'lib/msf/core/encoded_payload.rb', line 494

def encoded
  @encoded
end

#encoderObject

The encoder that was used



506
507
508
# File 'lib/msf/core/encoded_payload.rb', line 506

def encoder
  @encoder
end

#iterationsObject

The number of encoding iterations used



514
515
516
# File 'lib/msf/core/encoded_payload.rb', line 514

def iterations
  @iterations
end

#nopObject

The NOP generator that was used



510
511
512
# File 'lib/msf/core/encoded_payload.rb', line 510

def nop
  @nop
end

#nop_sledObject

The NOP sled itself



502
503
504
# File 'lib/msf/core/encoded_payload.rb', line 502

def nop_sled
  @nop_sled
end

#nop_sled_sizeObject

The size of the NOP sled



498
499
500
# File 'lib/msf/core/encoded_payload.rb', line 498

def nop_sled_size
  @nop_sled_size
end

#payload=(value) ⇒ Object (writeonly, protected)

:nodoc:



525
526
527
# File 'lib/msf/core/encoded_payload.rb', line 525

def payload=(value)
  @payload = value
end

#pinstObject (protected)

The payload instance used to generate the payload



534
535
536
# File 'lib/msf/core/encoded_payload.rb', line 534

def pinst
  @pinst
end

#rawObject

The raw version of the payload



489
490
491
# File 'lib/msf/core/encoded_payload.rb', line 489

def raw
  @raw
end

#reqsObject (protected)

The requirements used for generation



538
539
540
# File 'lib/msf/core/encoded_payload.rb', line 538

def reqs
  @reqs
end

#spaceObject

The maximum number of bytes acceptable for the encoded payload



518
519
520
# File 'lib/msf/core/encoded_payload.rb', line 518

def space
  @space
end

Class Method Details

.create(pinst, reqs = {}) ⇒ Object

This method creates an encoded payload instance and returns it to the caller.



20
21
22
23
24
25
26
27
# File 'lib/msf/core/encoded_payload.rb', line 20

def self.create(pinst, reqs = {})
  # Create the encoded payload instance
  p = EncodedPayload.new(pinst.framework, pinst, reqs)

  p.generate(reqs['Raw'])

  return p
end

Instance Method Details

#archObject

An array containing the architecture(s) that this payload was made to run on



471
472
473
474
475
# File 'lib/msf/core/encoded_payload.rb', line 471

def arch
  if pinst
    pinst.arch
  end
end

#compatible_encodersObject (protected)



562
563
564
565
566
567
568
569
570
571
572
573
574
# File 'lib/msf/core/encoded_payload.rb', line 562

def compatible_encoders
  arch = reqs['Arch'] || pinst.arch
  platform = reqs['Platform'] || pinst.platform

  encoders = []

  framework.encoders.each_module_ranked(
    'Arch' => arch, 'Platform' => platform) { |name, mod|
    encoders << [ name, mod ]
  }

  encoders
end

#encodeObject

Scans for a compatible encoder using ranked precedence and populates the encoded attribute.



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/msf/core/encoded_payload.rb', line 131

def encode
  # Get the minimum number of nops to use
  min = (reqs['MinNops'] || 0).to_i
  min = 0 if reqs['DisableNops']

  # If the exploit needs the payload to be encoded, we need to run the list of
  # encoders in ranked precedence and try to encode with them.
  if needs_encoding
    # Make sure the encoder name from the user has the same String#encoding
    # as the framework's list of encoder names so we can compare them later.
    # This is important for when we get input from RPC.
    if reqs['Encoder']
      reqs['Encoder'] = reqs['Encoder'].encode(framework.encoders.keys[0].encoding)
    end

    # If the caller had a preferred encoder, use this encoder only
    if ((reqs['Encoder']) and (preferred = framework.encoders[reqs['Encoder']]))
      encoders = [ [reqs['Encoder'], preferred] ]
    elsif (reqs['Encoder'])
      wlog("#{pinst.refname}: Failed to find preferred encoder #{reqs['Encoder']}")
      raise NoEncodersSucceededError, "Failed to find preferred encoder #{reqs['Encoder']}"
    else
      encoders = compatible_encoders
    end

    encoders.each { |encname, encmod|
      self.encoder = encmod.new
      self.encoded = nil

      # If the encoding is requested by an exploit check compatibility
      # options first of all. For the 'generic/none' encoder compatibility
      # options don't apply.
      if (reqs['Exploit'] &&
          !reqs['Exploit'].compatible?(self.encoder) &&
          encname !~ /generic\/none/)
        wlog("#{pinst.refname}: Encoder #{encoder.refname} doesn't match the exploit Compat options",
          'core', LEV_1)
        next
      end

      # If there is an encoder type restriction, check to see if this
      # encoder matches with what we're searching for.
      if ((reqs['EncoderType']) and
          (self.encoder.encoder_type.split(/\s+/).include?(reqs['EncoderType']) == false))
        wlog("#{pinst.refname}: Encoder #{encoder.refname} is not a compatible encoder type: #{reqs['EncoderType']} != #{self.encoder.encoder_type}",
          'core', LEV_1)
        next
      end

      # If the exploit did not explicitly request a kind of encoder and
      # the current encoder has a manual ranking, then it should not be
      # considered as a valid encoder.  A manual ranking tells the
      # framework that an encoder must be explicitly defined as the
      # encoder of choice for an exploit.
      if ((reqs['EncoderType'].nil?) and
          (reqs['Encoder'].nil?) and
          (self.encoder.rank == ManualRanking))
        wlog("#{pinst.refname}: Encoder #{encoder.refname} is manual ranked and was not defined as a preferred encoder.",
          'core', LEV_1)
        next
      end

      # If the caller explicitly requires register preservation, make sure
      # that the module in question can handle it. This is mostly used by
      # the stage encoder path.
      if (reqs['ForceSaveRegisters'] and
          reqs['EncoderOptions'] and
          (reqs['EncoderOptions']['SaveRegisters'].to_s.length > 0) and
          (! self.encoder.can_preserve_registers?))
        wlog("#{pinst.refname}: Encoder #{encoder.refname} does not preserve registers and the caller needs #{reqs['EncoderOptions']['SaveRegisters']} preserved.",
          'core', LEV_1)
        next
      end

      # Import the datastore from payload (and likely exploit by proxy)
      self.encoder.share_datastore(pinst.datastore)

      # If we have any encoder options, import them into the datastore
      # of the encoder.
      if (reqs['EncoderOptions'])
        self.encoder.datastore.import_options_from_hash(reqs['EncoderOptions'])
      end

      # Validate the encoder to make sure it's properly initialized.
      begin
        self.encoder.validate
      rescue ::Exception
        wlog("#{pinst.refname}: Failed to validate encoder #{encoder.refname}: #{$!}",
          'core', LEV_1)
        next
      end

      # Tell the encoder how much space is available
      self.encoder.available_space = self.space

      eout = self.raw.dup

      next_encoder = false

      # Try encoding with the current encoder
      #
      # NOTE: Using more than one iteration may cause successive iterations to switch
      # to using a different encoder.
      #
      1.upto(self.iterations) do |iter|
        err_start = "#{pinst.refname}: iteration #{iter}"

        begin
          eout = self.encoder.encode(eout, reqs['BadChars'], nil, pinst.platform)
        rescue EncodingError
          wlog("#{err_start}: Encoder #{encoder.refname} failed: #{$!}", 'core', LEV_1)
          dlog("#{err_start}: Call stack\n#{$@.join("\n")}", 'core', LEV_3)
          next_encoder = true
          break

        rescue ::Exception => e
          elog("Broken encoder #{encoder.refname}", error: e)
          next_encoder = true
          break
        end

        # Check to see if we have enough room for the minimum requirements
        if ((reqs['Space']) and (reqs['Space'] < eout.length + min))
          wlog("#{err_start}: Encoded payload version is too large (#{eout.length} bytes) with encoder #{encoder.refname}",
            'core', LEV_1)
          next_encoder = true
          break
        end

        ilog("#{err_start}: Successfully encoded with encoder #{encoder.refname} (size is #{eout.length})",
          'core', LEV_0)
      end

      next if next_encoder

      self.encoded = eout
      break
    }

    # If the encoded payload is nil, raise an exception saying that we
    # suck at life.
    if (self.encoded == nil)
      self.encoder = nil
      raise NoEncodersSucceededError,
        "#{pinst.refname}: All encoders failed to encode.",
        caller
    end

  # If there are no bad characters, then the raw is the same as the
  # encoded
  else
    # NOTE: BadChars can contain whitespace, so don't use String#blank?
    unless reqs['BadChars'].nil? || reqs['BadChars'].empty?
      ilog("#{pinst.refname}: payload contains no badchars, skipping automatic encoding", 'core', LEV_0)
    end

    # Space = 0 is a special value used by msfvenom to generate the smallest
    # payload possible. In that case do not raise an exception indicating
    # that the payload is too large.
    if reqs['Space'] && reqs['Space'] > 0 && reqs['Space'] < raw.length + min
      wlog("#{pinst.refname}: Raw (unencoded) payload is too large (#{raw.length} bytes)", 'core', LEV_1)
      raise PayloadSpaceViolation, 'The payload exceeds the specified space', caller
    end

    self.encoded = raw
  end

  # Prefix the prepend encoder value
  self.encoded = (reqs['PrependEncoder'] || '') + self.encoded
  self.encoded << (reqs['AppendEncoder'] || '')
end

#encoded_exe(opts = {}) ⇒ Object

Convert the payload to an executable appropriate for its arch and platform.

opts are passed directly to Msf::Util::EXE.to_executable

see Msf::Exploit::EXE



399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
# File 'lib/msf/core/encoded_payload.rb', line 399

def encoded_exe(opts={})
  # Ensure arch and platform are in the format that to_executable expects
  if opts[:arch] and not opts[:arch].kind_of? Array
    opts[:arch] = [ opts[:arch] ]
  end
  if (opts[:platform].kind_of? Msf::Module::PlatformList)
    opts[:platform] = opts[:platform].platforms
  end

  emod = pinst.assoc_exploit if pinst.respond_to? :assoc_exploit

  if emod
    if (emod.datastore["EXE::Custom"] and emod.respond_to? :get_custom_exe)
      return emod.get_custom_exe
    end
    # This is a little ghetto, grabbing datastore options from the
    # associated exploit, but it doesn't really make sense for the
    # payload to have exe options if the exploit doesn't need an exe.
    # Msf::Util::EXE chooses reasonable defaults if these aren't given,
    # so it's not that big of an issue.
    opts.merge!({
      :template_path => emod.datastore['EXE::Path'],
      :template => emod.datastore['EXE::Template'],
      :inject => emod.datastore['EXE::Inject'],
      :fallback => emod.datastore['EXE::FallBack'],
      :sub_method => emod.datastore['EXE::OldMethod']
    })
    # Prefer the target's platform/architecture information, but use
    # the exploit module's if no target specific information exists.
    opts[:platform] ||= emod.target_platform  if emod.respond_to? :target_platform
    opts[:platform] ||= emod.platform         if emod.respond_to? :platform
    opts[:arch] ||= emod.target_arch          if emod.respond_to? :target_arch
    opts[:arch] ||= emod.arch                 if emod.respond_to? :arch
  end
  # Lastly, try the payload's. This always happens if we don't have an
  # associated exploit module.
  opts[:platform] ||= pinst.platform if pinst.respond_to? :platform
  opts[:arch] ||= pinst.arch         if pinst.respond_to? :arch

  Msf::Util::EXE.to_executable(framework, opts[:arch], opts[:platform], encoded, opts)
end

#encoded_jar(opts = {}) ⇒ Object

Generate a jar file containing the encoded payload.

Uses the payload’s generate_jar method if it is implemented (Java payloads should all have it). Otherwise, converts the payload to an executable and uses Msf::Util::EXE.to_jar to create a jar file that dumps the exe out to a random file name in the system’s temporary directory and executes it.



450
451
452
453
454
455
456
# File 'lib/msf/core/encoded_payload.rb', line 450

def encoded_jar(opts={})
  return pinst.generate_jar(opts) if pinst.respond_to? :generate_jar

  opts[:spawn] ||= pinst.datastore["Spawn"]

  Msf::Util::EXE.to_jar(encoded_exe(opts), opts)
end

#encoded_war(opts = {}) ⇒ Object

Similar to encoded_jar but builds a web archive for use in servlet containers such as Tomcat.



462
463
464
465
466
# File 'lib/msf/core/encoded_payload.rb', line 462

def encoded_war(opts={})
  return pinst.generate_war(opts) if pinst.respond_to? :generate_war

  Msf::Util::EXE.to_jsp_war(encoded_exe(opts), opts)
end

#generate(raw = nil) ⇒ String

This method generates the full encoded payload and returns the encoded payload buffer.

Returns:

  • (String)

    The encoded payload.



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/msf/core/encoded_payload.rb', line 44

def generate(raw = nil)
  self.raw           = raw
  self.encoded       = nil
  self.nop_sled_size = 0
  self.nop_sled      = nil
  self.encoder       = nil
  self.nop           = nil

  # Increase thread priority as necessary.  This is done
  # to ensure that the encoding and sled generation get
  # enough time slices from the ruby thread scheduler.
  priority = Thread.current.priority

  if (priority == 0)
    Thread.current.priority = 1
  end

  begin
    # First, validate
    pinst.validate()

    # Propagate space information when set
    unless self.space.nil?
      # Tell the payload how much space is available
      pinst.available_space = self.space
      # Reserve 10% of the available space if encoding is required
      pinst.available_space -= (self.space * 0.1).ceil if needs_encoding
    end

    # Generate the raw version of the payload first
    generate_raw() if self.raw.nil?

    # If encoder is set, it could be an encoders list
    # The form is "<encoder>:<iteration>, <encoder2>:<iteration>"...
    unless reqs['Encoder'].blank?
      encoder_str = reqs['Encoder']
      encoder_str.scan(/([^:, ]+):?([^,]+)?/).map do |encoder_opt|
        reqs['Encoder'] = encoder_opt[0]

        self.iterations = (encoder_opt[1] || reqs['Iterations']).to_i
        self.iterations = 1 if self.iterations < 1

        # Encode the payload with every encoders in the list
        encode()
        # Encoded payload is now the raw payload to be encoded by the next encoder
        self.raw = self.encoded
      end
    else
      self.iterations = reqs['Iterations'].to_i
      self.iterations = 1 if self.iterations < 1
      # No specified encoder, let BadChars or ForceEncode do their job
      encode()
    end

    # Build the NOP sled
    generate_sled()

    # Finally, set the complete payload definition
    self.encoded = (self.nop_sled || '') + self.encoded
  ensure
    # Restore the thread priority
    Thread.current.priority = priority
  end

  # Return the complete payload
  return encoded
end

#generate_rawString

Generates the raw payload from the payload instance. This populates the #raw attribute.

Returns:

  • (String)

    The raw, unencoded payload.



117
118
119
120
121
122
123
124
125
# File 'lib/msf/core/encoded_payload.rb', line 117

def generate_raw
  self.raw = (reqs['Prepend'] || '') + pinst.generate_complete + (reqs['Append'] || '')

  # If an encapsulation routine was supplied, then we should call it so
  # that we can get the real raw payload.
  if reqs['EncapsulationRoutine']
    self.raw = reqs['EncapsulationRoutine'].call(reqs, raw)
  end
end

#generate_sledObject

Construct a NOP sled if necessary



306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
# File 'lib/msf/core/encoded_payload.rb', line 306

def generate_sled
  min   = reqs['MinNops'] || 0
  space = reqs['Space']
  pad_nops = reqs['PadNops']

  self.nop_sled_size = min

  # Calculate the number of NOPs to pad out the buffer with based on the
  # requirements.  If there was a space requirement, check to see if
  # there's any room at all left for a sled.
  if ((space) and
     (space > encoded.length))
    self.nop_sled_size = reqs['Space'] - self.encoded.length
  end

  # If the maximum number of NOPs has been exceeded, wrap it back down.
  if ((reqs['MaxNops']) and
     (reqs['MaxNops'] < self.nop_sled_size))
    self.nop_sled_size = reqs['MaxNops']
  end

  # Check for the DisableNops setting
  self.nop_sled_size = 0 if reqs['DisableNops']

  # Check for the PadNops setting
  self.nop_sled_size = (pad_nops - self.encoded.length) if reqs['PadNops']

  # Now construct the actual sled
  if (self.nop_sled_size > 0)
    nops = pinst.compatible_nops

    # If the caller had a preferred nop, try to find it and prefix it
    if ((reqs['Nop']) and
        (preferred = framework.nops[reqs['Nop']]))
      nops.unshift([reqs['Nop'], preferred ])
    elsif (reqs['Nop'])
      wlog("#{pinst.refname}: Failed to find preferred nop #{reqs['Nop']}")
    end

    nops.each { |nopname, nopmod|
      # Create an instance of the nop module
      self.nop = nopmod.new

      # Propagate options from the payload and possibly exploit
      self.nop.share_datastore(pinst.datastore)

      # The list of save registers
      save_regs = (reqs['SaveRegisters'] || []) + (pinst.save_registers || [])

      if (save_regs.empty? == true)
        save_regs = nil
      end

      begin
        nop.copy_ui(pinst)
        self.nop_sled = nop.generate_sled(self.nop_sled_size,
          'BadChars'      => reqs['BadChars'],
          'SaveRegisters' => save_regs)

        if nop_sled && nop_sled.length == nop_sled_size
          break
        else
          dlog("#{pinst.refname}: Nop generator #{nop.refname} failed to generate sled for payload", 'core', LEV_1)
        end
      rescue
        dlog("#{pinst.refname}: Nop generator #{nop.refname} failed to generate sled for payload: #{$!}",
          'core', LEV_1)

        self.nop = nil
      end
    }

    if (self.nop_sled == nil)
      raise NoNopsSucceededError,
        "#{pinst.refname}: All NOP generators failed to construct sled for.",
        caller
    end
  else
    self.nop_sled = ''
  end

  return self.nop_sled
end

#has_chars?(chars) ⇒ Boolean (protected)

Returns:

  • (Boolean)


544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
# File 'lib/msf/core/encoded_payload.rb', line 544

def has_chars?(chars)
  # NOTE: BadChars can contain whitespace, so don't use String#blank?
  if chars.nil? || chars.empty?
    return false
  end

  # payload hasn't been set yet but we have bad characters so assume they'll need to be encoded
  return true if self.raw.nil?

  return false if self.raw.empty?

  chars.each_byte do |bad|
    return true if self.raw.index(bad.chr(::Encoding::ASCII_8BIT))
  end

  false
end

#needs_encodingObject (protected)



540
541
542
# File 'lib/msf/core/encoded_payload.rb', line 540

def needs_encoding
  !reqs['Encoder'].blank? || reqs['ForceEncode'] || has_chars?(reqs['BadChars'])
end

#platformObject

An array containing the platform(s) that this payload was made to run on



480
481
482
483
484
# File 'lib/msf/core/encoded_payload.rb', line 480

def platform
  if pinst
    pinst.platform
  end
end