319 lines
9.6 KiB
Ruby
319 lines
9.6 KiB
Ruby
require 'msf/core/module'
|
|
|
|
module Msf
|
|
class Evasion < Msf::Module
|
|
|
|
include Msf::Auxiliary::Report
|
|
|
|
class Complete < RuntimeError ; end
|
|
|
|
class Failed < RuntimeError ; end
|
|
|
|
def initialize(info={})
|
|
if (info['Payload'] and info['Payload']['Compat'])
|
|
info['Compat'] = Hash.new if (info['Compat'] == nil)
|
|
info['Compat']['Payload'] = Hash.new if (info['Compat']['Payload'] == nil)
|
|
info['Compat']['Payload'].update(info['Payload']['Compat'])
|
|
end
|
|
|
|
super(info)
|
|
|
|
self.payload_info = info['Payload'] || {}
|
|
self.targets = Rex::Transformer.transform(info['Targets'], Array, [ Target ], 'Targets')
|
|
|
|
if info.key? 'DefaultTarget'
|
|
self.default_target = info['DefaultTarget']
|
|
else
|
|
self.default_target = 0
|
|
# Add an auto-target to the evasion if it doesn't have one
|
|
if info['Targets'] && info['Targets'].count > 1 && !has_auto_target?(info['Targets'])
|
|
# Finally, only add the target if there is a remote host option
|
|
if self.respond_to?(:rhost) && self.respond_to?(:auto_targeted_index)
|
|
auto = ["Automatic", {'AutoGenerated' => true}.merge(info['Targets'][self.default_target][1])]
|
|
info['Targets'].unshift(auto)
|
|
end
|
|
end
|
|
end
|
|
|
|
if (info['Payload'] and info['Payload']['ActiveTimeout'])
|
|
self.active_timeout = info['Payload']['ActiveTimeout'].to_i
|
|
end
|
|
|
|
register_options([
|
|
OptString.new(
|
|
'FILENAME',
|
|
[
|
|
true,
|
|
'Filename for the evasive file (default: random)',
|
|
"#{Rex::Text.rand_text_alpha(3..10)}.exe"
|
|
])
|
|
], self.class)
|
|
end
|
|
|
|
def self.type
|
|
Msf::MODULE_EVASION
|
|
end
|
|
|
|
def type
|
|
Msf::MODULE_EVASION
|
|
end
|
|
|
|
def setup
|
|
end
|
|
|
|
def file_format_filename
|
|
datastore['FILENAME']
|
|
end
|
|
|
|
def file_create(data)
|
|
fname = file_format_filename
|
|
ltype = "evasion.fileformat.#{self.shortname}"
|
|
full_path = store_local(ltype, nil, data, fname)
|
|
print_good "#{fname} stored at #{full_path}"
|
|
end
|
|
|
|
#
|
|
# Returns the target's platform, or the one assigned to the module itself.
|
|
#
|
|
def target_platform
|
|
(target and target.platform) ? target.platform : platform
|
|
end
|
|
|
|
#
|
|
# Returns the target's architecture, or the one assigned to the module
|
|
# itself.
|
|
#
|
|
def target_arch
|
|
(target and target.arch) ? target.arch : (arch == []) ? nil : arch
|
|
end
|
|
|
|
def normalize_platform_arch
|
|
c_platform = (target && target.platform) ? target.platform : platform
|
|
c_arch = (target && target.arch) ? target.arch : (arch == []) ? nil : arch
|
|
c_arch ||= [ ARCH_X86 ]
|
|
return c_platform, c_arch
|
|
end
|
|
|
|
# Returns whether the requested payload is compatible with the module
|
|
#
|
|
# @param [String] name The payload name
|
|
# @param [TrueClass] Payload is compatible.
|
|
# @param [FlaseClass] Payload is not compatible.
|
|
def is_payload_compatible?(name)
|
|
p = framework.payloads[name]
|
|
|
|
pi = p.new
|
|
|
|
# Are we compatible in terms of conventions and connections and
|
|
# what not?
|
|
return false if !compatible?(pi)
|
|
|
|
# If the payload is privileged but the evasion does not give
|
|
# privileged access, then fail it.
|
|
return false if !self.privileged && pi.privileged
|
|
|
|
return true
|
|
end
|
|
|
|
# Returns a list of compatible payloads based on platform, architecture,
|
|
# and size requirements.
|
|
def compatible_payloads
|
|
payloads = []
|
|
|
|
c_platform, c_arch = normalize_platform_arch
|
|
|
|
framework.payloads.each_module(
|
|
'Arch' => c_arch, 'Platform' => c_platform) { |name, mod|
|
|
payloads << [ name, mod ] if is_payload_compatible?(name)
|
|
}
|
|
|
|
return payloads
|
|
end
|
|
|
|
def run
|
|
raise NotImplementedError
|
|
end
|
|
|
|
def cleanup
|
|
end
|
|
|
|
def fail_with(reason, msg=nil)
|
|
raise Msf::Evasion::Failed, "#{reason}: #{msg}"
|
|
end
|
|
|
|
def evasion_commands
|
|
{}
|
|
end
|
|
|
|
def stance
|
|
'passive'
|
|
end
|
|
|
|
def passive?
|
|
true
|
|
end
|
|
|
|
def aggressive?
|
|
false
|
|
end
|
|
|
|
# Generates the encoded version of the supplied payload using the payload
|
|
# requirements specific to this evasion module. The encoded instance is returned
|
|
# to the caller. This method is exposed in the manner that it is such that passive
|
|
# evasions and re-generate an encoded payload on the fly rather than having to use
|
|
# the pre-generated one.
|
|
def generate_payload(pinst = nil)
|
|
# Set the encoded payload to the result of the encoding process
|
|
self.payload = generate_single_payload(pinst)
|
|
|
|
# Save the payload instance
|
|
self.payload_instance = (pinst) ? pinst : self.payload_instance
|
|
|
|
return self.payload
|
|
end
|
|
|
|
def generate_single_payload(pinst = nil, platform = nil, arch = nil, explicit_target = nil)
|
|
explicit_target ||= target
|
|
|
|
# If a payload instance was supplied, use it, otherwise
|
|
# use the active payload instance
|
|
real_payload = (pinst) ? pinst : self.payload_instance
|
|
|
|
if (real_payload == nil)
|
|
raise MissingPayloadError, "No payload has been selected.",
|
|
caller
|
|
end
|
|
|
|
# If this is a generic payload, then we should specify the platform
|
|
# and architecture so that it knows how to pass things on.
|
|
if real_payload.kind_of?(Msf::Payload::Generic)
|
|
# Convert the architecture specified into an array.
|
|
if arch and arch.kind_of?(String)
|
|
arch = [ arch ]
|
|
end
|
|
|
|
# Define the explicit platform and architecture information only if
|
|
# it's been specified.
|
|
if platform
|
|
real_payload.explicit_platform = Msf::Module::PlatformList.transform(platform)
|
|
end
|
|
|
|
if arch
|
|
real_payload.explicit_arch = arch
|
|
end
|
|
|
|
# Force it to reset so that it will find updated information.
|
|
real_payload.reset
|
|
end
|
|
|
|
# Duplicate the evasion payload requirements
|
|
reqs = self.payload_info.dup
|
|
|
|
# Pass save register requirements to the NOP generator
|
|
reqs['Space'] = payload_info['Space'].to_i
|
|
reqs['SaveRegisters'] = module_info['SaveRegisters']
|
|
reqs['Prepend'] = payload_info['Prepend']
|
|
reqs['PrependEncoder'] = payload_info['PrependEncoder']
|
|
reqs['BadChars'] = payload_info['BadChars']
|
|
reqs['Append'] = payload_info['Append']
|
|
reqs['AppendEncoder'] = payload_info['AppendEncoder']
|
|
reqs['MaxNops'] = payload_info['MaxNops']
|
|
reqs['MinNops'] = payload_info['MinNops']
|
|
reqs['Encoder'] = datastore['ENCODER'] || payload_info['Encoder']
|
|
reqs['Nop'] = datastore['NOP'] || payload_info['Nop']
|
|
reqs['EncoderType'] = payload_info['EncoderType']
|
|
reqs['EncoderOptions'] = payload_info['EncoderOptions']
|
|
reqs['ExtendedOptions'] = payload_info['ExtendedOptions']
|
|
reqs['Evasion'] = self
|
|
|
|
# Pass along the encoder don't fall through flag
|
|
reqs['EncoderDontFallThrough'] = datastore['EncoderDontFallThrough']
|
|
|
|
# Incorporate any context encoding requirements that are needed
|
|
define_context_encoding_reqs(reqs)
|
|
|
|
# Call the encode begin routine.
|
|
encode_begin(real_payload, reqs)
|
|
|
|
# Generate the encoded payload.
|
|
encoded = EncodedPayload.create(real_payload, reqs)
|
|
|
|
# Call the encode end routine which is expected to return the actual
|
|
# encoded payload instance.
|
|
return encode_end(real_payload, reqs, encoded)
|
|
end
|
|
|
|
def define_context_encoding_reqs(reqs)
|
|
return unless datastore['EnableContextEncoding']
|
|
|
|
# At present, we don't support any automatic methods of obtaining
|
|
# context information. In the future, we might support obtaining
|
|
# temporal information remotely.
|
|
|
|
# Pass along the information specified in our evasion datastore as
|
|
# encoder options
|
|
reqs['EncoderOptions'] = {} if reqs['EncoderOptions'].nil?
|
|
reqs['EncoderOptions']['EnableContextEncoding'] = datastore['EnableContextEncoding']
|
|
reqs['EncoderOptions']['ContextInformationFile'] = datastore['ContextInformationFile']
|
|
end
|
|
|
|
def encode_begin(real_payload, reqs)
|
|
end
|
|
|
|
def encode_end(real_payload, reqs, encoded)
|
|
encoded
|
|
end
|
|
|
|
def target
|
|
if self.respond_to?(:auto_targeted_index)
|
|
if auto_target?
|
|
auto_idx = auto_targeted_index
|
|
if auto_idx.present?
|
|
datastore['TARGET'] = auto_idx
|
|
else
|
|
# If our inserted Automatic Target was selected but we failed to
|
|
# find a suitable target, we just grab the original first target.
|
|
datastore['TARGET'] = 1
|
|
end
|
|
end
|
|
end
|
|
|
|
target_idx = target_index
|
|
return (target_idx) ? targets[target_idx.to_i] : nil
|
|
end
|
|
|
|
def target_index
|
|
target_idx = Integer(datastore['TARGET']) rescue datastore['TARGET']
|
|
|
|
default_idx = default_target || 0
|
|
# Use the default target if one was not supplied.
|
|
if (target_idx == nil and default_idx and default_idx >= 0)
|
|
target_idx = default_idx
|
|
elsif target_idx.is_a?(String)
|
|
target_idx = targets.index { |target| target.name == target_idx }
|
|
end
|
|
|
|
return (target_idx) ? target_idx.to_i : nil
|
|
end
|
|
|
|
def has_auto_target?(targets=[])
|
|
target_names = targets.collect { |target| target.first}
|
|
target_names.each do |target|
|
|
return true if target =~ /Automatic/
|
|
end
|
|
return false
|
|
end
|
|
|
|
attr_accessor :default_target
|
|
|
|
attr_accessor :targets
|
|
|
|
attr_reader :payload_info
|
|
|
|
attr_accessor :payload_info
|
|
|
|
attr_accessor :payload_instance
|
|
|
|
attr_accessor :payload
|
|
end
|
|
end |