Land #2950 - New Payload Generator for MsfVenom
commit
4dd60631cb
|
@ -100,7 +100,7 @@ module Framework
|
|||
|
||||
# Initialize configuration and logging
|
||||
Msf::Config.init
|
||||
Msf::Logging.init
|
||||
Msf::Logging.init unless opts['DisableLogging']
|
||||
|
||||
# Load the configuration
|
||||
framework.load_config
|
||||
|
|
|
@ -0,0 +1,394 @@
|
|||
require 'active_support/core_ext/numeric/bytes'
|
||||
module Msf
|
||||
|
||||
class PayloadGeneratorError < StandardError
|
||||
end
|
||||
|
||||
class EncoderSpaceViolation < PayloadGeneratorError
|
||||
end
|
||||
|
||||
class IncompatibleArch < PayloadGeneratorError
|
||||
end
|
||||
|
||||
class IncompatibleEndianess < PayloadGeneratorError
|
||||
end
|
||||
|
||||
class IncompatiblePlatform < PayloadGeneratorError
|
||||
end
|
||||
|
||||
class InvalidFormat < PayloadGeneratorError
|
||||
end
|
||||
|
||||
class PayloadGenerator
|
||||
|
||||
# @!attribute add_code
|
||||
# @return [String] The path to a shellcode file to execute in a seperate thread
|
||||
attr_accessor :add_code
|
||||
# @!attribute arch
|
||||
# @return [String] The CPU architecture to build the payload for
|
||||
attr_accessor :arch
|
||||
# @!attribute badchars
|
||||
# @return [String] The bad characters that can't be in the payload
|
||||
attr_accessor :badchars
|
||||
# @!attribute cli
|
||||
# @return [Boolean] Whether this is being run by a CLI script
|
||||
attr_accessor :cli
|
||||
# @!attribute datastore
|
||||
# @return [Hash] The datastore to apply to the payload module
|
||||
attr_accessor :datastore
|
||||
# @!attribute encoder
|
||||
# @return [String] The encoder(s) you want applied to the payload
|
||||
attr_accessor :encoder
|
||||
# @!attribute format
|
||||
# @return [String] The format you want the payload returned in
|
||||
attr_accessor :format
|
||||
# @!attribute framework
|
||||
# @return [Msf::Framework] The framework instance to use for generation
|
||||
attr_accessor :framework
|
||||
# @!attribute iterations
|
||||
# @return [Fixnum] The number of iterations to run the encoder
|
||||
attr_accessor :iterations
|
||||
# @!attribute keep
|
||||
# @return [Boolean] Whether or not to preserve the original functionality of the template
|
||||
attr_accessor :keep
|
||||
# @!attribute nops
|
||||
# @return [Fixnum] The size in bytes of NOP sled to prepend the payload with
|
||||
attr_accessor :nops
|
||||
# @!attribute payload
|
||||
# @return [String] The refname of the payload to generate
|
||||
attr_accessor :payload
|
||||
# @!attribute platform
|
||||
# @return [String] The platform to build the payload for
|
||||
attr_accessor :platform
|
||||
# @!attribute space
|
||||
# @return [Fixnum] The maximum size in bytes of the payload
|
||||
attr_accessor :space
|
||||
# @!attribute stdin
|
||||
# @return [String] The raw bytes of a payload taken from STDIN
|
||||
attr_accessor :stdin
|
||||
# @!attribute template
|
||||
# @return [String] The path to an executable template to use
|
||||
attr_accessor :template
|
||||
|
||||
|
||||
# @param opts [Hash] The options hash
|
||||
# @option opts [String] :payload (see #payload)
|
||||
# @option opts [String] :format (see #format)
|
||||
# @option opts [String] :encoder (see #encoder)
|
||||
# @option opts [Fixnum] :iterations (see #iterations)
|
||||
# @option opts [String] :arch (see #arch)
|
||||
# @option opts [String] :platform (see #platform)
|
||||
# @option opts [String] :badchars (see #badchars)
|
||||
# @option opts [String] :template (see #template)
|
||||
# @option opts [Fixnum] :space (see #space)
|
||||
# @option opts [Fixnum] :nops (see #nops)
|
||||
# @option opts [String] :add_code (see #add_code)
|
||||
# @option opts [Boolean] :keep (see #keep)
|
||||
# @option opts [Hash] :datastore (see #datastore)
|
||||
# @option opts [Msf::Framework] :framework (see #framework)
|
||||
# @option opts [Boolean] :cli (see #cli)
|
||||
# @raise [KeyError] if framework is not provided in the options hash
|
||||
def initialize(opts={})
|
||||
@add_code = opts.fetch(:add_code, '')
|
||||
@arch = opts.fetch(:arch, '')
|
||||
@badchars = opts.fetch(:badchars, '')
|
||||
@cli = opts.fetch(:cli, false)
|
||||
@datastore = opts.fetch(:datastore, {})
|
||||
@encoder = opts.fetch(:encoder, '')
|
||||
@format = opts.fetch(:format, 'raw')
|
||||
@iterations = opts.fetch(:iterations, 1)
|
||||
@keep = opts.fetch(:keep, false)
|
||||
@nops = opts.fetch(:nops, 0)
|
||||
@payload = opts.fetch(:payload, '')
|
||||
@platform = opts.fetch(:platform, '')
|
||||
@space = opts.fetch(:space, 1.gigabyte)
|
||||
@stdin = opts.fetch(:stdin, nil)
|
||||
@template = opts.fetch(:template, '')
|
||||
|
||||
@framework = opts.fetch(:framework)
|
||||
|
||||
raise ArgumentError, "Invalid Payload Selected" unless payload_is_valid?
|
||||
raise ArgumentError, "Invalid Format Selected" unless format_is_valid?
|
||||
end
|
||||
|
||||
# This method takes the shellcode generated so far and adds shellcode from
|
||||
# a supplied file. The added shellcode is executed in a seperate thread
|
||||
# from the main payload.
|
||||
# @param shellcode [String] The shellcode to add to
|
||||
# @return [String] the combined shellcode which executes the added code in a seperate thread
|
||||
def add_shellcode(shellcode)
|
||||
if add_code.present? and platform_list.platforms.include? Msf::Module::Platform::Windows and arch == "x86"
|
||||
cli_print "Adding shellcode from #{add_code} to the payload"
|
||||
shellcode_file = File.open(add_code)
|
||||
shellcode_file.binmode
|
||||
added_code = shellcode_file.read
|
||||
shellcode_file.close
|
||||
shellcode = ::Msf::Util::EXE.win32_rwx_exec_thread(shellcode,0,'end')
|
||||
shellcode << added_code
|
||||
else
|
||||
shellcode.dup
|
||||
end
|
||||
end
|
||||
|
||||
# This method takes a payload module and tries to reconcile a chosen
|
||||
# arch with the arches supported by the module.
|
||||
# @param mod [Msf::Payload] The module class to choose an arch for
|
||||
# @return [String] String form of the Arch if a valid arch found
|
||||
# @return [Nil] if no valid arch found
|
||||
def choose_arch(mod)
|
||||
if arch.blank?
|
||||
@arch = mod.arch.first
|
||||
cli_print "No Arch selected, selecting Arch: #{arch} from the payload"
|
||||
return mod.arch.first
|
||||
elsif mod.arch.include? arch
|
||||
return arch
|
||||
else
|
||||
return nil
|
||||
end
|
||||
end
|
||||
|
||||
# This method takes a payload module and tries to reconcile a chosen
|
||||
# platform with the platforms supported by the module.
|
||||
# @param mod [Msf::Payload] The module class to choose a platform for
|
||||
# @return [Msf::Module::PlatformList] The selected platform list
|
||||
def choose_platform(mod)
|
||||
chosen_platform = platform_list
|
||||
if chosen_platform.platforms.empty?
|
||||
chosen_platform = mod.platform
|
||||
cli_print "No platform was selected, choosing #{chosen_platform.platforms.first} from the payload"
|
||||
@platform = mod.platform.platforms.first.to_s.split("::").last
|
||||
elsif (chosen_platform & mod.platform).empty?
|
||||
chosen_platform = Msf::Module::PlatformList.new
|
||||
end
|
||||
chosen_platform
|
||||
end
|
||||
|
||||
# This method takes the shellcode generated so far and iterates through
|
||||
# the chosen or compatible encoders. It attempts to encode the payload
|
||||
# with each encoder until it finds one that works.
|
||||
# @param shellcode [String] The shellcode to encode
|
||||
# @return [String] The encoded shellcode
|
||||
def encode_payload(shellcode)
|
||||
shellcode = shellcode.dup
|
||||
encoder_list = get_encoders
|
||||
cli_print "Found #{encoder_list.count} compatible encoders"
|
||||
if encoder_list.empty?
|
||||
shellcode
|
||||
else
|
||||
encoder_list.each do |encoder_mod|
|
||||
cli_print "Attempting to encode payload with #{iterations} iterations of #{encoder_mod.refname}"
|
||||
begin
|
||||
return run_encoder(encoder_mod, shellcode.dup)
|
||||
rescue ::Msf::EncoderSpaceViolation => e
|
||||
cli_print "#{encoder_mod.refname} failed with #{e.message}"
|
||||
next
|
||||
rescue ::Msf::EncodingError => e
|
||||
cli_print "#{encoder_mod.refname} failed with #{e.message}"
|
||||
next
|
||||
end
|
||||
end
|
||||
raise ::Msf::EncodingError, "No Encoder Succeeded"
|
||||
end
|
||||
end
|
||||
|
||||
# This returns a hash for the exe format generation of payloads
|
||||
# @return [Hash] The hash needed for generating an executable format
|
||||
def exe_options
|
||||
opts = { inject: keep }
|
||||
unless template.blank?
|
||||
opts[:template_path] = File.dirname(template)
|
||||
opts[:template] = File.basename(template)
|
||||
end
|
||||
opts
|
||||
end
|
||||
|
||||
# This method takes the payload shellcode and formats it appropriately based
|
||||
# on the selected output format.
|
||||
# @param shellcode [String] the processed shellcode to be formatted
|
||||
# @return [String] The final formatted form of the payload
|
||||
def format_payload(shellcode)
|
||||
case format.downcase
|
||||
when "js_be"
|
||||
if Rex::Arch.endian(arch) != ENDIAN_BIG
|
||||
raise IncompatibleEndianess, "Big endian format selected for a non big endian payload"
|
||||
else
|
||||
::Msf::Simple::Buffer.transform(shellcode, format)
|
||||
end
|
||||
when *::Msf::Simple::Buffer.transform_formats
|
||||
::Msf::Simple::Buffer.transform(shellcode, format)
|
||||
when *::Msf::Util::EXE.to_executable_fmt_formats
|
||||
::Msf::Util::EXE.to_executable_fmt(framework, arch, platform, shellcode, format, exe_options)
|
||||
else
|
||||
raise InvalidFormat, "you have selected an invalid payload format"
|
||||
end
|
||||
end
|
||||
|
||||
# This method generates Java payloads which are a special case.
|
||||
# They can be generated in raw or war formats, which respectively
|
||||
# produce a JAR or WAR file for the java payload.
|
||||
# @return [String] Java payload as a JAR or WAR file
|
||||
def generate_java_payload
|
||||
payload_module = framework.payloads.create(payload)
|
||||
case format
|
||||
when "raw"
|
||||
if payload_module.respond_to? :generate_jar
|
||||
payload_module.generate_jar.pack
|
||||
else
|
||||
raise InvalidFormat, "#{payload} is not a Java payload"
|
||||
end
|
||||
when "war"
|
||||
if payload_module.respond_to? :generate_war
|
||||
payload_module.generate_war.pack
|
||||
else
|
||||
raise InvalidFormat, "#{payload} is not a Java payload"
|
||||
end
|
||||
else
|
||||
raise InvalidFormat, "#{format} is not a valid format for Java payloads"
|
||||
end
|
||||
end
|
||||
|
||||
# This method is a wrapper around all of the other methods. It calls the correct
|
||||
# methods in order based on the supplied options and returns the finished payload.
|
||||
# @return [String] A string containing the bytes of the payload in the format selected
|
||||
def generate_payload
|
||||
if platform == "java" or arch == "java" or payload.start_with? "java/"
|
||||
generate_java_payload
|
||||
else
|
||||
raw_payload = generate_raw_payload
|
||||
raw_payload = add_shellcode(raw_payload)
|
||||
encoded_payload = encode_payload(raw_payload)
|
||||
encoded_payload = prepend_nops(encoded_payload)
|
||||
format_payload(encoded_payload)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# This method generates the raw form of the payload as generated by the payload module itself.
|
||||
# @raise [Msf::IncompatiblePlatform] if no platform was selected for a stdin payload
|
||||
# @raise [Msf::IncompatibleArch] if no arch was selected for a stdin payload
|
||||
# @raise [Msf::IncompatiblePlatform] if the platform is incompatible with the payload
|
||||
# @raise [Msf::IncompatibleArch] if the arch is incompatible with the payload
|
||||
# @return [String] the raw bytes of the payload to be generated
|
||||
def generate_raw_payload
|
||||
if payload == 'stdin'
|
||||
if arch.blank?
|
||||
raise IncompatibleArch, "You must select an arch for a custom payload"
|
||||
elsif platform.blank?
|
||||
raise IncompatiblePlatform, "You must select a platform for a custom payload"
|
||||
end
|
||||
stdin
|
||||
else
|
||||
payload_module = framework.payloads.create(payload)
|
||||
|
||||
chosen_platform = choose_platform(payload_module)
|
||||
if chosen_platform.platforms.empty?
|
||||
raise IncompatiblePlatform, "The selected platform is incompatible with the payload"
|
||||
end
|
||||
|
||||
chosen_arch = choose_arch(payload_module)
|
||||
unless chosen_arch
|
||||
raise IncompatibleArch, "The selected arch is incompatible with the payload"
|
||||
end
|
||||
|
||||
payload_module.generate_simple(
|
||||
'Format' => 'raw',
|
||||
'Options' => datastore,
|
||||
'Encoder' => nil
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
# This method returns an array of encoders that either match the
|
||||
# encoders selected by the user, or match the arch selected.
|
||||
# @return [Array<Msf::Encoder>] An array of potential encoders to use
|
||||
def get_encoders
|
||||
encoders = []
|
||||
if encoder.present?
|
||||
# Allow comma seperated list of encoders so users can choose several
|
||||
encoder.split(',').each do |chosen_encoder|
|
||||
encoders << framework.encoders.create(chosen_encoder)
|
||||
end
|
||||
encoders.sort_by { |my_encoder| my_encoder.rank }.reverse
|
||||
elsif badchars.present?
|
||||
framework.encoders.each_module_ranked('Arch' => [arch]) do |name, mod|
|
||||
encoders << framework.encoders.create(name)
|
||||
end
|
||||
encoders.sort_by { |my_encoder| my_encoder.rank }.reverse
|
||||
else
|
||||
encoders
|
||||
end
|
||||
end
|
||||
|
||||
# Returns a PlatformList object based on the platform string given at creation.
|
||||
# @return [Msf::Module::PlatformList] It will be empty if no valid platforms found
|
||||
def platform_list
|
||||
if platform.blank?
|
||||
list = Msf::Module::PlatformList.new
|
||||
else
|
||||
begin
|
||||
list = ::Msf::Module::PlatformList.transform(platform)
|
||||
rescue
|
||||
list = Msf::Module::PlatformList.new
|
||||
end
|
||||
end
|
||||
list
|
||||
end
|
||||
|
||||
# This method takes an encoded payload and prepends a NOP Sled to it
|
||||
# with a size based on the nops value given to the generator.
|
||||
# @param shellcode [String] The shellcode to prepend the NOPs to
|
||||
# @return [String] the shellcode with the appropriate nopsled affixed
|
||||
def prepend_nops(shellcode)
|
||||
if nops > 0
|
||||
framework.nops.each_module_ranked('Arch' => [arch]) do |name, mod|
|
||||
nop = framework.nops.create(name)
|
||||
raw = nop.generate_sled(nops, {'BadChars' => badchars, 'SaveRegisters' => [ 'esp', 'ebp', 'esi', 'edi' ] })
|
||||
if raw
|
||||
cli_print "Successfully added NOP sled from #{name}"
|
||||
return raw + shellcode
|
||||
end
|
||||
end
|
||||
else
|
||||
shellcode
|
||||
end
|
||||
end
|
||||
|
||||
# This method runs a specified encoder, for a number of defined iterations against the shellcode.
|
||||
# @param encoder_module [Msf::Encoder] The Encoder to run against the shellcode
|
||||
# @param shellcode [String] The shellcode to be encoded
|
||||
# @return [String] The encoded shellcode
|
||||
# @raise [Msf::EncoderSpaceViolation] If the Encoder makes the shellcode larger than the supplied space limit
|
||||
def run_encoder(encoder_module, shellcode)
|
||||
iterations.times do |x|
|
||||
shellcode = encoder_module.encode(shellcode.dup, badchars, nil, platform_list)
|
||||
cli_print "#{encoder_module.refname} succeeded with size #{shellcode.length} (iteration=#{x})"
|
||||
raise EncoderSpaceViolation, "encoder has made a buffer that is too big" if shellcode.length > space
|
||||
end
|
||||
shellcode
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# This method prints output to the console if running in CLI mode
|
||||
# @param [String] message The message to print to the console.
|
||||
def cli_print(message= '')
|
||||
$stderr.puts message if cli
|
||||
end
|
||||
|
||||
# This method checks if the Generator's selected format is valid
|
||||
# @return [True] if the format is valid
|
||||
# @return [False] if the format is not valid
|
||||
def format_is_valid?
|
||||
formats = (::Msf::Util::EXE.to_executable_fmt_formats + ::Msf::Simple::Buffer.transform_formats).uniq
|
||||
formats.include? format.downcase
|
||||
end
|
||||
|
||||
# This method checks if the Generator's selected payload is valid
|
||||
# @return [True] if the payload is a valid Metasploit Payload
|
||||
# @return [False] if the payload is not a valid Metasploit Payload
|
||||
def payload_is_valid?
|
||||
(framework.payloads.keys + ['stdin']).include? payload
|
||||
end
|
||||
|
||||
end
|
||||
end
|
352
msfvenom
352
msfvenom
|
@ -15,31 +15,17 @@ $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB']
|
|||
require 'rex'
|
||||
require 'msf/ui'
|
||||
require 'msf/base'
|
||||
require 'msf/core/payload_generator'
|
||||
|
||||
|
||||
# Mad payload generation
|
||||
#
|
||||
# @example
|
||||
# venom = MsfVenom.new
|
||||
# # ARGV will be parsed destructively!
|
||||
# venom.parse_args(ARGV)
|
||||
# $stdout.puts venom.generate
|
||||
class MsfVenom
|
||||
class MsfVenomError < StandardError; end
|
||||
class UsageError < MsfVenomError; end
|
||||
class NoTemplateError < MsfVenomError; end
|
||||
class IncompatibleError < MsfVenomError; end
|
||||
|
||||
Status = "[*] "
|
||||
Error = "[-] "
|
||||
|
||||
require 'optparse'
|
||||
|
||||
def initialize(in_stream=$stdin, out_stream=$stdout, err_stream=$stderr, framework=nil)
|
||||
@in = in_stream
|
||||
@out = out_stream
|
||||
@err = err_stream
|
||||
@framework = framework
|
||||
end
|
||||
|
||||
# Creates a new framework object.
|
||||
#
|
||||
|
@ -64,13 +50,10 @@ class MsfVenom
|
|||
@framework
|
||||
end
|
||||
|
||||
# Initialize the options for this run from ARGV
|
||||
# @param args [Array] Usually ARGV. Parsed destructively.
|
||||
# @return [void]
|
||||
# @raise [UsageError] When given invalid options
|
||||
|
||||
def parse_args(args)
|
||||
@opts = {}
|
||||
@datastore = {}
|
||||
opts = {}
|
||||
datastore = {}
|
||||
opt = OptionParser.new
|
||||
opt.banner = "Usage: #{$0} [options] <var=val>"
|
||||
opt.separator('')
|
||||
|
@ -78,9 +61,9 @@ class MsfVenom
|
|||
|
||||
opt.on('-p', '--payload <payload>', String, 'Payload to use. Specify a \'-\' or stdin to use custom payloads') do |p|
|
||||
if p == '-'
|
||||
@opts[:payload] = 'stdin'
|
||||
opts[:payload] = 'stdin'
|
||||
else
|
||||
@opts[:payload] = p
|
||||
opts[:payload] = p
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -88,56 +71,55 @@ class MsfVenom
|
|||
if l.nil? or l.empty?
|
||||
l = ["all"]
|
||||
end
|
||||
@opts[:list] = l
|
||||
opts[:list] = l
|
||||
end
|
||||
|
||||
opt.on('-n', '--nopsled <length>', Integer, 'Prepend a nopsled of [length] size on to the payload') do |n|
|
||||
@opts[:nopsled] = n.to_i
|
||||
opts[:nops] = n.to_i
|
||||
end
|
||||
|
||||
opt.on('-f', '--format <format>', String, "Output format (use --help-formats for a list)") do |f|
|
||||
@opts[:format] = f
|
||||
opts[:format] = f
|
||||
end
|
||||
|
||||
opt.on('-e', '--encoder [encoder]', String, 'The encoder to use') do |e|
|
||||
@opts[:encode] = true
|
||||
@opts[:encoder] = e
|
||||
opts[:encoder] = e
|
||||
end
|
||||
|
||||
opt.on('-a', '--arch <architecture>', String, 'The architecture to use') do |a|
|
||||
@opts[:arch] = a
|
||||
opts[:arch] = a
|
||||
end
|
||||
|
||||
opt.on('--platform <platform>', String, 'The platform of the payload') do |l|
|
||||
@opts[:platform] = l
|
||||
opts[:platform] = l
|
||||
end
|
||||
|
||||
opt.on('-s', '--space <length>', Integer, 'The maximum size of the resulting payload') do |s|
|
||||
@opts[:space] = s
|
||||
opts[:space] = s
|
||||
end
|
||||
|
||||
opt.on('-b', '--bad-chars <list>', String, 'The list of characters to avoid example: \'\x00\xff\'') do |b|
|
||||
@opts[:badchars] = b
|
||||
opts[:badchars] = b
|
||||
end
|
||||
|
||||
opt.on('-i', '--iterations <count>', Integer, 'The number of times to encode the payload') do |i|
|
||||
@opts[:iterations] = i
|
||||
opts[:iterations] = i
|
||||
end
|
||||
|
||||
opt.on('-c', '--add-code <path>', String, 'Specify an additional win32 shellcode file to include') do |x|
|
||||
@opts[:addshellcode] = x
|
||||
opts[:add_code] = x
|
||||
end
|
||||
|
||||
opt.on('-x', '--template <path>', String, 'Specify a custom executable file to use as a template') do |x|
|
||||
@opts[:template] = x
|
||||
opts[:template] = x
|
||||
end
|
||||
|
||||
opt.on('-k', '--keep', 'Preserve the template behavior and inject the payload as a new thread') do
|
||||
@opts[:inject] = true
|
||||
opts[:keep] = true
|
||||
end
|
||||
|
||||
opt.on('-o', '--options', "List the payload's standard options") do
|
||||
@opts[:list_options] = true
|
||||
opts[:list_options] = true
|
||||
end
|
||||
|
||||
opt.on_tail('-h', '--help', 'Show this message') do
|
||||
|
@ -161,76 +143,49 @@ class MsfVenom
|
|||
raise UsageError, "Missing required argument for option\n#{opt}"
|
||||
end
|
||||
|
||||
if @opts.empty?
|
||||
if opts.empty?
|
||||
raise UsageError, "No options\n#{opt}"
|
||||
end
|
||||
|
||||
if args
|
||||
args.each do |x|
|
||||
k,v = x.split('=', 2)
|
||||
@datastore[k.upcase] = v.to_s
|
||||
datastore[k.upcase] = v.to_s
|
||||
end
|
||||
end
|
||||
|
||||
if @opts[:payload].nil? # if no payload option is selected assume we are reading it from stdin
|
||||
@opts[:payload] = "stdin"
|
||||
end
|
||||
end
|
||||
|
||||
def print_status(msg)
|
||||
@err.puts(Status + msg)
|
||||
end
|
||||
|
||||
def print_error(msg)
|
||||
@err.puts(Error + msg)
|
||||
end
|
||||
|
||||
def get_encoders(arch, encoder)
|
||||
encoders = []
|
||||
|
||||
if (encoder)
|
||||
encoders << framework.encoders.create(encoder)
|
||||
else
|
||||
framework.encoders.each_module_ranked(
|
||||
'Arch' => arch ? arch.split(',') : nil) { |name, mod|
|
||||
encoders << mod.new
|
||||
}
|
||||
if opts[:payload].nil? # if no payload option is selected assume we are reading it from stdin
|
||||
opts[:payload] = "stdin"
|
||||
end
|
||||
|
||||
encoders
|
||||
if opts[:payload] == 'stdin' and not opts[:list]
|
||||
$stderr.puts "Attempting to read payload from STDIN..."
|
||||
begin
|
||||
::Timeout.timeout(30) do
|
||||
opts[:stdin] = payload_stdin
|
||||
end
|
||||
rescue Timeout::Error
|
||||
opts[:stdin] = ''
|
||||
end
|
||||
end
|
||||
|
||||
opts[:datastore] = datastore
|
||||
|
||||
opts
|
||||
end
|
||||
|
||||
|
||||
# Read a raw payload from stdin (or whatever IO object we're currently
|
||||
# using as stdin, see {#initialize})
|
||||
#
|
||||
# @return [String]
|
||||
def payload_stdin
|
||||
@in = $stdin
|
||||
@in.binmode
|
||||
payload = @in.read
|
||||
payload
|
||||
end
|
||||
|
||||
def generate_nops(arch, len, nop_mod=nil, nop_opts={})
|
||||
nop_opts['BadChars'] ||= ''
|
||||
nop_opts['SaveRegisters'] ||= [ 'esp', 'ebp', 'esi', 'edi' ]
|
||||
|
||||
if nop_mod
|
||||
nop = framework.nops.create(nop_mod)
|
||||
raw = nop.generate_sled(len, nop_opts)
|
||||
return raw if raw
|
||||
end
|
||||
|
||||
framework.nops.each_module_ranked('Arch' => arch) do |name, mod|
|
||||
begin
|
||||
nop = framework.nops.create(name)
|
||||
raw = nop.generate_sled(len, nop_opts)
|
||||
return raw if raw
|
||||
rescue
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def dump_payloads
|
||||
init_framework(:module_types => [ ::Msf::MODULE_PAYLOAD ])
|
||||
tbl = Rex::Ui::Text::Table.new(
|
||||
|
@ -290,219 +245,58 @@ class MsfVenom
|
|||
"\n" + tbl.to_s + "\n"
|
||||
end
|
||||
|
||||
# @return [String] A raw shellcode blob
|
||||
# @return [nil] When commandline options conspire to produce no output
|
||||
def generate_raw_payload
|
||||
|
||||
if @opts[:payload] == 'stdin'
|
||||
payload_raw = payload_stdin
|
||||
if @opts[:encode] and (@opts[:arch].nil? or @opts[:platform].nil?)
|
||||
print_error("Cannot encode stdin payload without specifying the proper architecture and platform")
|
||||
@opts[:encode] = false
|
||||
end
|
||||
# defaults for stdin payloads users should define them
|
||||
unless @opts[:arch]
|
||||
print_error("Defaulting to x86 architecture for stdin payload, use -a to change")
|
||||
@opts[:arch] = "x86"
|
||||
end
|
||||
unless @opts[:platform]
|
||||
print_error("Defaulting to Windows platform for stdin payload, use --platform to change")
|
||||
@opts[:platform] = ::Msf::Module::PlatformList.transform("Windows")
|
||||
end
|
||||
else
|
||||
payload = framework.payloads.create(@opts[:payload])
|
||||
if payload.nil?
|
||||
raise UsageError, "Invalid payload: #{@opts[:payload]}"
|
||||
end
|
||||
@opts[:arch] ||= payload.arch[0]
|
||||
# If it's not stdin, we'll already have a PlatformList
|
||||
@opts[:platform] ||= payload.platform
|
||||
payload.datastore.merge! @datastore
|
||||
|
||||
if @opts[:list_options]
|
||||
print_status("Options for #{payload.fullname}\n\n" +
|
||||
::Msf::Serializer::ReadableText.dump_module(payload,' '))
|
||||
return
|
||||
end
|
||||
payload_raw = payload.generate_simple(
|
||||
'Format' => 'raw',
|
||||
'Options' => @datastore,
|
||||
'Encoder' => nil)
|
||||
end
|
||||
if __FILE__ == $0
|
||||
|
||||
payload_raw
|
||||
begin
|
||||
generator_opts = parse_args(ARGV)
|
||||
rescue MsfVenomError, Msf::OptionValidateError => e
|
||||
$stderr.puts e.message
|
||||
exit(-1)
|
||||
end
|
||||
|
||||
|
||||
# Main dispatch method to do the right thing with the given options.
|
||||
def generate
|
||||
if @opts[:list]
|
||||
@opts[:list].each do |mod|
|
||||
case mod.downcase
|
||||
if generator_opts[:list]
|
||||
generator_opts[:list].each do |mod|
|
||||
case mod.downcase
|
||||
when "payloads"
|
||||
@err.puts dump_payloads
|
||||
$stderr.puts dump_payloads
|
||||
when "encoders"
|
||||
@err.puts dump_encoders(@opts[:arch])
|
||||
$stderr.puts dump_encoders(opts[:arch])
|
||||
when "nops"
|
||||
@err.puts dump_nops
|
||||
$stderr.puts dump_nops
|
||||
when "all"
|
||||
# Init here so #dump_payloads doesn't create a framework with
|
||||
# only payloads, etc.
|
||||
init_framework
|
||||
@err.puts dump_payloads
|
||||
@err.puts dump_encoders
|
||||
@err.puts dump_nops
|
||||
$stderr.puts dump_payloads
|
||||
$stderr.puts dump_encoders
|
||||
$stderr.puts dump_nops
|
||||
else
|
||||
raise UsageError, "Invalid module type"
|
||||
end
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
# Normalize the options
|
||||
@opts[:platform] = ::Msf::Module::PlatformList.transform(@opts[:platform]) if @opts[:platform]
|
||||
@opts[:badchars] = Rex::Text.hex_to_raw(@opts[:badchars]) if @opts[:badchars]
|
||||
@opts[:format] ||= 'ruby'
|
||||
@opts[:encoder] ||= nil
|
||||
@opts[:encode] ||= !(@opts[:badchars].nil? or @opts[:badchars].empty?)
|
||||
|
||||
|
||||
payload_raw = generate_raw_payload
|
||||
return unless payload_raw
|
||||
|
||||
if @opts[:template]
|
||||
unless File.exist?(@opts[:template])
|
||||
raise NoTemplateError, "Template file (#{@opts[:template]}) does not exist"
|
||||
end
|
||||
path = File.dirname(@opts[:template])
|
||||
altexe = File.basename(@opts[:template])
|
||||
end
|
||||
exeopts = { :inject => @opts[:inject], :template_path => path, :template => altexe }
|
||||
|
||||
# If we were given addshellcode for a win32 payload,
|
||||
# create a double-payload; one running in one thread, one running in the other
|
||||
if @opts[:addshellcode] and @opts[:platform].platforms.include?(::Msf::Module::Platform::Windows) and @opts[:arch] == 'x86'
|
||||
payload_raw = ::Msf::Util::EXE.win32_rwx_exec_thread(payload_raw,0,'end')
|
||||
file = ::File.new(@opts[:addshellcode])
|
||||
file.binmode
|
||||
payload_raw << file.read
|
||||
file.close
|
||||
end
|
||||
|
||||
if @opts[:encode]
|
||||
done = false
|
||||
encoders = get_encoders(@opts[:arch], @opts[:encoder])
|
||||
encoders.each do |enc|
|
||||
next if not enc
|
||||
begin
|
||||
break if done
|
||||
enc.datastore.import_options_from_hash(@datastore)
|
||||
skip = false
|
||||
raw = nil
|
||||
|
||||
@opts[:iterations] ||= 1
|
||||
|
||||
1.upto(@opts[:iterations].to_i) do |iteration|
|
||||
begin
|
||||
raw = enc.encode(payload_raw.dup, @opts[:badchars], nil, @opts[:platform])
|
||||
rescue ::Msf::EncodingError
|
||||
print_error("#{enc.refname} failed: #{$!.class} : #{$!}")
|
||||
skip = true
|
||||
break
|
||||
end
|
||||
if @opts[:space] and @opts[:space] > 0 and raw.length > @opts[:space]
|
||||
print_error("#{enc.refname} created buffer that is too big (#{raw.length})\n\n")
|
||||
skip = true
|
||||
break
|
||||
end
|
||||
|
||||
print_status("#{enc.refname} succeeded with size #{raw.length} (iteration=#{iteration})\n")
|
||||
payload_raw = raw.dup
|
||||
if iteration == @opts[:iterations]
|
||||
done = true
|
||||
break
|
||||
end
|
||||
end
|
||||
next if skip
|
||||
|
||||
rescue ::Errno::ENOENT, ::Errno::EINVAL
|
||||
print_error("#{enc.refname} failed: #{$!}")
|
||||
break
|
||||
|
||||
rescue => e
|
||||
print_error("#{enc.refname} failed: #{e.class} #{e}")
|
||||
e.backtrace.each { |el|
|
||||
@err.puts(el.to_s)
|
||||
}
|
||||
end
|
||||
$stderr.puts "Invalid module type"
|
||||
end
|
||||
end
|
||||
|
||||
if @opts[:nopsled]
|
||||
nopts = { 'BadChars' => @opts[:badchars] }
|
||||
nops = generate_nops([@opts[:arch]], @opts[:nopsled], nil, nopts)
|
||||
payload_raw = nops + payload_raw
|
||||
end
|
||||
|
||||
@out.binmode
|
||||
|
||||
case @opts[:format].downcase
|
||||
# Special-case this to check endianness
|
||||
when "js_be"
|
||||
|
||||
if Rex::Arch.endian(payload.arch) != ENDIAN_BIG
|
||||
raise IncompatibleError, "Big endian format selected for a non big endian payload"
|
||||
end
|
||||
@out.write ::Msf::Simple::Buffer.transform(payload_raw, @opts[:format])
|
||||
|
||||
# Special-case this so we can build a war directly from the payload if
|
||||
# possible
|
||||
when "war"
|
||||
exe = ::Msf::Util::EXE.to_executable_fmt(framework, @opts[:arch], @opts[:platform], payload_raw, @opts[:format], exeopts)
|
||||
if (!exe && payload.respond_to?(:generate_war))
|
||||
exe = payload.generate_war.pack
|
||||
end
|
||||
@out.write exe
|
||||
|
||||
# Same as war, special-case this so we can build a jar directly from the
|
||||
# payload if possible
|
||||
when "java"
|
||||
exe = ::Msf::Util::EXE.to_executable_fmt(framework, @opts[:arch], @opts[:platform], payload_raw, @opts[:format], exeopts)
|
||||
if (!exe && payload.respond_to?(:generate_jar))
|
||||
exe = payload.generate_jar.pack
|
||||
end
|
||||
if exe
|
||||
@out.write exe
|
||||
else
|
||||
print_error("Could not generate payload format")
|
||||
end
|
||||
|
||||
when *::Msf::Simple::Buffer.transform_formats
|
||||
buf = ::Msf::Simple::Buffer.transform(payload_raw, @opts[:format])
|
||||
@out.write buf
|
||||
|
||||
when *::Msf::Util::EXE.to_executable_fmt_formats
|
||||
exe = ::Msf::Util::EXE.to_executable_fmt(framework, @opts[:arch], @opts[:platform], payload_raw, @opts[:format], exeopts)
|
||||
if exe.nil?
|
||||
raise IncompatibleError, "This format does not support that platform/architecture"
|
||||
end
|
||||
@out.write exe
|
||||
|
||||
else
|
||||
raise IncompatibleError, "Unsupported format"
|
||||
return
|
||||
end
|
||||
exit(0)
|
||||
end
|
||||
end
|
||||
|
||||
if generator_opts[:list_options]
|
||||
payload_mod = framework.payloads.create(generator_opts[:payload])
|
||||
$stderr.puts "Options for #{payload_mod.fullname}\n\n" + ::Msf::Serializer::ReadableText.dump_module(payload_mod,' ')
|
||||
exit(0)
|
||||
end
|
||||
|
||||
generator_opts[:framework] = framework
|
||||
generator_opts[:cli] = true
|
||||
|
||||
if __FILE__ == $0
|
||||
begin
|
||||
venom = MsfVenom.new
|
||||
venom.parse_args(ARGV)
|
||||
venom.generate
|
||||
rescue MsfVenom::MsfVenomError, Msf::OptionValidateError => e
|
||||
venom_generator = Msf::PayloadGenerator.new(generator_opts)
|
||||
payload = venom_generator.generate_payload
|
||||
rescue ::Exception => e
|
||||
$stderr.puts e.message
|
||||
exit(-1)
|
||||
end
|
||||
|
||||
output_stream = $stdout
|
||||
output_stream.binmode
|
||||
output_stream.write payload
|
||||
|
||||
end
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
瑞瑞
|
|
@ -0,0 +1,521 @@
|
|||
require 'spec_helper'
|
||||
require 'msf/core/payload_generator'
|
||||
|
||||
describe Msf::PayloadGenerator do
|
||||
|
||||
PAYLOAD_FRAMEWORK = Msf::Simple::Framework.create(
|
||||
:module_types => [ ::Msf::MODULE_PAYLOAD, ::Msf::MODULE_ENCODER, ::Msf::MODULE_NOP],
|
||||
'DisableDatabase' => true,
|
||||
'DisableLogging' => true
|
||||
)
|
||||
|
||||
let(:lhost) { "192.168.172.1"}
|
||||
let(:lport) { "8443" }
|
||||
let(:datastore) { { "LHOST" => lhost, "LPORT" => lport } }
|
||||
let(:add_code) { false }
|
||||
let(:arch) { "x86" }
|
||||
let(:badchars) { "\x20\x0D\x0A" }
|
||||
let(:encoder) { 'x86/shikata_ga_nai' }
|
||||
let(:format) { "raw" }
|
||||
let(:framework) { PAYLOAD_FRAMEWORK }
|
||||
let(:iterations) { 1 }
|
||||
let(:keep) { false }
|
||||
let(:nops) { 0 }
|
||||
let(:payload) { "windows/meterpreter/reverse_tcp"}
|
||||
let(:platform) { "Windows" }
|
||||
let(:space) { 1073741824 }
|
||||
let(:stdin) { nil }
|
||||
let(:template) { File.join(Msf::Config.data_directory, "templates", "template_x86_windows.exe") }
|
||||
let(:generator_opts) {
|
||||
{
|
||||
add_code: add_code,
|
||||
arch: arch,
|
||||
badchars: badchars,
|
||||
encoder: encoder,
|
||||
datastore: datastore,
|
||||
format: format,
|
||||
framework: framework,
|
||||
iterations: iterations,
|
||||
keep: keep,
|
||||
nops: nops,
|
||||
payload: payload,
|
||||
platform: platform,
|
||||
space: space,
|
||||
stdin: stdin,
|
||||
template: template
|
||||
}
|
||||
}
|
||||
let(:payload_module) { framework.payloads.create(payload)}
|
||||
let(:shellcode) { "\x50\x51\x58\x59" }
|
||||
let(:encoder_module) { framework.encoders.create('x86/shikata_ga_nai') }
|
||||
|
||||
subject(:payload_generator) { described_class.new(generator_opts) }
|
||||
|
||||
it { should respond_to :add_code }
|
||||
it { should respond_to :arch }
|
||||
it { should respond_to :badchars }
|
||||
it { should respond_to :cli }
|
||||
it { should respond_to :encoder }
|
||||
it { should respond_to :datastore }
|
||||
it { should respond_to :format }
|
||||
it { should respond_to :framework }
|
||||
it { should respond_to :iterations }
|
||||
it { should respond_to :keep }
|
||||
it { should respond_to :nops }
|
||||
it { should respond_to :payload }
|
||||
it { should respond_to :platform }
|
||||
it { should respond_to :space }
|
||||
it { should respond_to :stdin }
|
||||
it { should respond_to :template }
|
||||
|
||||
context 'when creating a new generator' do
|
||||
subject(:new_payload_generator) { -> { described_class.new(generator_opts) } }
|
||||
|
||||
context 'when not given a framework instance' do
|
||||
let(:generator_opts) {
|
||||
{
|
||||
add_code: add_code,
|
||||
arch: arch,
|
||||
badchars: badchars,
|
||||
encoder: encoder,
|
||||
datastore: datastore,
|
||||
format: format,
|
||||
iterations: iterations,
|
||||
keep: keep,
|
||||
nops: nops,
|
||||
payload: payload,
|
||||
platform: platform,
|
||||
space: space,
|
||||
stdin: stdin,
|
||||
template: template
|
||||
}
|
||||
}
|
||||
|
||||
it { should raise_error(KeyError, "key not found: :framework") }
|
||||
end
|
||||
|
||||
context 'when not given a payload' do
|
||||
let(:payload) { nil }
|
||||
|
||||
it { should raise_error(ArgumentError, "Invalid Payload Selected") }
|
||||
end
|
||||
|
||||
context 'when given an invalid payload' do
|
||||
let(:payload) { "beos/meterpreter/reverse_gopher" }
|
||||
|
||||
it { should raise_error(ArgumentError, "Invalid Payload Selected") }
|
||||
end
|
||||
|
||||
context 'when given a payload through stdin' do
|
||||
let(:payload) { "stdin" }
|
||||
|
||||
it { should_not raise_error }
|
||||
end
|
||||
|
||||
context 'when given an invalid format' do
|
||||
let(:format) { "foobar" }
|
||||
|
||||
it { should raise_error(ArgumentError, "Invalid Format Selected") }
|
||||
end
|
||||
|
||||
context 'when given any valid transform format' do
|
||||
let(:format) { ::Msf::Simple::Buffer.transform_formats.sample }
|
||||
|
||||
it { should_not raise_error }
|
||||
end
|
||||
|
||||
context 'when given any valid executable format' do
|
||||
let(:format) { ::Msf::Util::EXE.to_executable_fmt_formats.sample }
|
||||
|
||||
it { should_not raise_error }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when not given a platform' do
|
||||
let(:platform) { '' }
|
||||
|
||||
context '#platform_list' do
|
||||
it 'returns an empty PlatformList' do
|
||||
expect(payload_generator.platform_list.platforms).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context '#choose_platform' do
|
||||
it 'chooses the platform list for the module' do
|
||||
expect(payload_generator.choose_platform(payload_module).platforms).to eq [Msf::Module::Platform::Windows]
|
||||
end
|
||||
|
||||
it 'sets the platform attr to the first platform of the module' do
|
||||
my_generator = payload_generator
|
||||
my_generator.choose_platform(payload_module)
|
||||
expect(my_generator.platform).to eq "Windows"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'when given an invalid platform' do
|
||||
let(:platform) { 'foobar' }
|
||||
|
||||
context '#platform_list' do
|
||||
it 'returns an empty PlatformList' do
|
||||
expect(payload_generator.platform_list.platforms).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context '#choose_platform' do
|
||||
it 'chooses the platform list for the module' do
|
||||
expect(payload_generator.choose_platform(payload_module).platforms).to eq [Msf::Module::Platform::Windows]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'when given a valid platform' do
|
||||
|
||||
context '#platform_list' do
|
||||
it 'returns a PlatformList containing the Platform class' do
|
||||
expect(payload_generator.platform_list.platforms.first).to eq Msf::Module::Platform::Windows
|
||||
end
|
||||
end
|
||||
|
||||
context '#choose_platform' do
|
||||
context 'when the chosen platform matches the module' do
|
||||
it 'returns the PlatformList for the selected platform' do
|
||||
expect(payload_generator.choose_platform(payload_module).platforms).to eq payload_generator.platform_list.platforms
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the chosen platform and module do not match' do
|
||||
let(:platform) { "linux" }
|
||||
it 'returns an empty PlatformList' do
|
||||
expect(payload_generator.choose_platform(payload_module).platforms).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context '#choose_arch' do
|
||||
context 'when no arch is selected' do
|
||||
let(:arch) { '' }
|
||||
|
||||
it 'returns the first arch of the module' do
|
||||
expect(payload_generator.choose_arch(payload_module)).to eq "x86"
|
||||
end
|
||||
|
||||
it 'sets the arch to match the module' do
|
||||
my_generator = payload_generator
|
||||
my_generator.choose_arch(payload_module)
|
||||
expect(my_generator.arch).to eq "x86"
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the arch matches the module' do
|
||||
it 'returns the selected arch' do
|
||||
expect(payload_generator.choose_arch(payload_module)).to eq arch
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the arch does not match the module' do
|
||||
let(:arch) { "mipsle" }
|
||||
|
||||
it "returns nil" do
|
||||
expect(payload_generator.choose_arch(payload_module)).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context '#generate_raw_payload' do
|
||||
|
||||
context 'when passing a payload through stdin' do
|
||||
let(:stdin) { "\x90\x90\x90"}
|
||||
let(:payload) { "stdin" }
|
||||
|
||||
context 'when no arch has been selected' do
|
||||
let(:arch) { '' }
|
||||
|
||||
it 'raises an IncompatibleArch error' do
|
||||
expect{payload_generator.generate_raw_payload}.to raise_error(Msf::IncompatibleArch, "You must select an arch for a custom payload")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no platform has been selected' do
|
||||
let(:platform) { '' }
|
||||
|
||||
it 'raises an IncompatiblePlatform error' do
|
||||
expect{payload_generator.generate_raw_payload}.to raise_error(Msf::IncompatiblePlatform, "You must select a platform for a custom payload")
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns the payload from stdin' do
|
||||
expect(payload_generator.generate_raw_payload).to eq stdin
|
||||
end
|
||||
end
|
||||
|
||||
context 'when selecting a metasploit payload' do
|
||||
context 'when the platform is incompatible with the payload' do
|
||||
let(:platform) { "linux" }
|
||||
|
||||
it 'raises an IncompatiblePlatform error' do
|
||||
expect{payload_generator.generate_raw_payload}.to raise_error(Msf::IncompatiblePlatform, "The selected platform is incompatible with the payload")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the arch is incompatible with the payload' do
|
||||
let(:arch) { "mipsle" }
|
||||
|
||||
it 'raises an IncompatibleArch error' do
|
||||
expect{payload_generator.generate_raw_payload}.to raise_error(Msf::IncompatibleArch, "The selected arch is incompatible with the payload")
|
||||
end
|
||||
end
|
||||
|
||||
context 'when one or more datastore options are missing' do
|
||||
let(:datastore) { {} }
|
||||
|
||||
it 'should raise an error' do
|
||||
expect{payload_generator.generate_raw_payload}.to raise_error(Msf::OptionValidateError)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns the raw bytes of the payload' do
|
||||
expect(payload_generator.generate_raw_payload).to be_present
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context '#add_shellcode' do
|
||||
|
||||
context 'when add_code is empty' do
|
||||
it 'returns the original shellcode' do
|
||||
expect(payload_generator.add_shellcode(shellcode)).to eq shellcode
|
||||
end
|
||||
end
|
||||
|
||||
context 'when add_code points to a valid file' do
|
||||
let(:add_code) { File.join(FILE_FIXTURES_PATH, "nop_shellcode.bin")}
|
||||
|
||||
context 'but platform is not Windows' do
|
||||
let(:platform) { "Linux" }
|
||||
|
||||
it 'returns the original shellcode' do
|
||||
expect(payload_generator.add_shellcode(shellcode)).to eq shellcode
|
||||
end
|
||||
end
|
||||
|
||||
context 'but arch is not x86' do
|
||||
let(:arch) { "x86_64" }
|
||||
|
||||
it 'returns the original shellcode' do
|
||||
expect(payload_generator.add_shellcode(shellcode)).to eq shellcode
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns modified shellcode' do
|
||||
# The exact length is variable due to random nops inserted into the routine
|
||||
# It looks like it should always be > 300
|
||||
# Can't do precise output matching due to this same issue
|
||||
expect(payload_generator.add_shellcode(shellcode).length).to be > 300
|
||||
end
|
||||
end
|
||||
|
||||
context 'when add_code points to an invalid file' do
|
||||
let(:add_code) { "gurfjhfdjhfdsjhfsdvfverf444" }
|
||||
it 'raises an error' do
|
||||
expect{payload_generator.add_shellcode(shellcode)}.to raise_error(Errno::ENOENT)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context '#prepend_nops' do
|
||||
context 'when nops are set to 0' do
|
||||
it 'returns the unmodified shellcode' do
|
||||
expect(payload_generator.prepend_nops(shellcode)).to eq shellcode
|
||||
end
|
||||
end
|
||||
|
||||
context 'when nops are set to more than 0' do
|
||||
let(:nops) { 20 }
|
||||
|
||||
it 'returns shellcode of the correct size' do
|
||||
expect(payload_generator.prepend_nops(shellcode).length).to eq 24
|
||||
end
|
||||
|
||||
it 'puts the nops in front of the original shellcode' do
|
||||
expect(payload_generator.prepend_nops(shellcode)[20,24]).to eq shellcode
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context '#get_encoders' do
|
||||
let(:encoder_names) { ["Polymorphic XOR Additive Feedback Encoder", "Alpha2 Alphanumeric Mixedcase Encoder" ] }
|
||||
|
||||
context 'when an encoder is selected' do
|
||||
it 'returns an array' do
|
||||
expect(payload_generator.get_encoders).to be_kind_of Array
|
||||
end
|
||||
|
||||
it 'returns an array with only one element' do
|
||||
expect(payload_generator.get_encoders.count).to eq 1
|
||||
end
|
||||
|
||||
it 'returns the correct encoder in the array' do
|
||||
expect(payload_generator.get_encoders.first.name).to eq encoder_names[0]
|
||||
end
|
||||
end
|
||||
|
||||
context 'when multiple encoders are selected' do
|
||||
let(:encoder) { "x86/shikata_ga_nai,x86/alpha_mixed"}
|
||||
|
||||
it 'returns an array of the right size' do
|
||||
expect(payload_generator.get_encoders.count).to eq 2
|
||||
end
|
||||
|
||||
it 'returns each of the selected encoders in the array' do
|
||||
payload_generator.get_encoders.each do |my_encoder|
|
||||
expect(encoder_names).to include my_encoder.name
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns the encoders in order of rank high to low' do
|
||||
expect(payload_generator.get_encoders[0].rank).to be > payload_generator.get_encoders[1].rank
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no encoder is selected but badchars are present' do
|
||||
let(:encoder) { '' }
|
||||
|
||||
it 'returns an array of all encoders with a compatible arch' do
|
||||
payload_generator.get_encoders.each do |my_encoder|
|
||||
expect(my_encoder.arch).to include arch
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when no encoder or badchars are selected' do
|
||||
let(:encoder) { '' }
|
||||
let(:badchars) { '' }
|
||||
|
||||
it 'returns an empty array' do
|
||||
expect(payload_generator.get_encoders).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context '#run_encoder' do
|
||||
|
||||
it 'should call the encoder a number of times equal to the iterations' do
|
||||
my_encoder = encoder_module
|
||||
my_encoder.should_receive(:encode).exactly(iterations).times.and_return(shellcode)
|
||||
payload_generator.run_encoder(my_encoder, shellcode)
|
||||
end
|
||||
|
||||
context 'when the encoder makes a buffer too large' do
|
||||
let(:space) { 4 }
|
||||
it 'should raise an error' do
|
||||
expect{payload_generator.run_encoder(encoder_module, shellcode)}.to raise_error(Msf::EncoderSpaceViolation, "encoder has made a buffer that is too big")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context '#format_payload' do
|
||||
context 'when format is js_be' do
|
||||
let(:format) { "js_be"}
|
||||
context 'and arch is x86' do
|
||||
it 'should raise an IncompatibleEndianess error' do
|
||||
expect{payload_generator.format_payload(shellcode)}.to raise_error(Msf::IncompatibleEndianess, "Big endian format selected for a non big endian payload")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when format is a transform format' do
|
||||
let(:format) { 'c' }
|
||||
|
||||
it 'applies the appropriate transform format' do
|
||||
::Msf::Simple::Buffer.should_receive(:transform).with(shellcode, format)
|
||||
payload_generator.format_payload(shellcode)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when format is an executable format' do
|
||||
let(:format) { 'exe' }
|
||||
|
||||
it 'applies the appropriate executable format' do
|
||||
::Msf::Util::EXE.should_receive(:to_executable_fmt).with(framework, arch, platform, shellcode, format, payload_generator.exe_options)
|
||||
payload_generator.format_payload(shellcode)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context '#generate_java_payload' do
|
||||
context 'when format is war' do
|
||||
let(:format) { 'war' }
|
||||
|
||||
context 'if the payload is a valid java payload' do
|
||||
let(:payload) { "java/meterpreter/reverse_tcp"}
|
||||
it 'calls the generate_war on the payload' do
|
||||
java_payload = framework.payloads.create("java/meterpreter/reverse_tcp")
|
||||
framework.stub_chain(:payloads, :keys).and_return ["java/meterpreter/reverse_tcp"]
|
||||
framework.stub_chain(:payloads, :create).and_return(java_payload)
|
||||
java_payload.should_receive(:generate_war).and_call_original
|
||||
payload_generator.generate_java_payload
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises an InvalidFormat exception' do
|
||||
expect{payload_generator.generate_java_payload}.to raise_error(Msf::InvalidFormat)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when format is raw' do
|
||||
let(:format) { 'raw' }
|
||||
|
||||
context 'if the payload is a valid java payload' do
|
||||
let(:payload) { "java/meterpreter/reverse_tcp"}
|
||||
it 'calls the generate_jar on the payload' do
|
||||
java_payload = framework.payloads.create("java/meterpreter/reverse_tcp")
|
||||
framework.stub_chain(:payloads, :keys).and_return ["java/meterpreter/reverse_tcp"]
|
||||
framework.stub_chain(:payloads, :create).and_return(java_payload)
|
||||
java_payload.should_receive(:generate_jar).and_call_original
|
||||
payload_generator.generate_java_payload
|
||||
end
|
||||
end
|
||||
|
||||
it 'raises an InvalidFormat exception' do
|
||||
expect{payload_generator.generate_java_payload}.to raise_error(Msf::InvalidFormat)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when format is a non-java format' do
|
||||
let(:format) { "exe" }
|
||||
|
||||
it 'raises an InvalidFormat exception' do
|
||||
expect{payload_generator.generate_java_payload}.to raise_error(Msf::InvalidFormat)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context '#generate_payload' do
|
||||
|
||||
it 'calls each step of the process' do
|
||||
my_generator = payload_generator
|
||||
my_generator.should_receive(:generate_raw_payload).and_call_original
|
||||
my_generator.should_receive(:add_shellcode).and_call_original
|
||||
my_generator.should_receive(:encode_payload).and_call_original
|
||||
my_generator.should_receive(:prepend_nops).and_call_original
|
||||
my_generator.should_receive(:format_payload).and_call_original
|
||||
my_generator.generate_payload
|
||||
end
|
||||
|
||||
context 'when the payload is java' do
|
||||
let(:payload) { "java/meterpreter/reverse_tcp" }
|
||||
|
||||
it 'calls generate_java_payload' do
|
||||
my_generator = payload_generator
|
||||
my_generator.should_receive(:generate_java_payload)
|
||||
my_generator.generate_payload
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -1,268 +0,0 @@
|
|||
|
||||
require 'spec_helper'
|
||||
require 'msf/core'
|
||||
# doesn't end in .rb or .so, so have to load instead of require
|
||||
load File.join(Msf::Config.install_root, 'msfvenom')
|
||||
|
||||
shared_examples_for "nop dumper" do
|
||||
it "should list known nops" do
|
||||
%w!
|
||||
x86/opty2
|
||||
armle/simple
|
||||
!.each do |name|
|
||||
dump.should include(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for "encoder dumper" do
|
||||
it "should list known encoders" do
|
||||
%w!
|
||||
generic/none
|
||||
x86/shikata_ga_nai
|
||||
x64/xor
|
||||
!.each do |name|
|
||||
dump.should include(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for "payload dumper" do
|
||||
it "should list known payloads" do
|
||||
# Just a representative sample of some of the important ones.
|
||||
%w!
|
||||
cmd/unix/reverse
|
||||
java/meterpreter/reverse_tcp
|
||||
java/meterpreter/reverse_https
|
||||
linux/x86/shell/reverse_tcp
|
||||
linux/x86/shell_reverse_tcp
|
||||
linux/x64/shell/reverse_tcp
|
||||
linux/x64/shell_reverse_tcp
|
||||
linux/armle/shell/reverse_tcp
|
||||
linux/armle/shell_reverse_tcp
|
||||
linux/mipsbe/shell_reverse_tcp
|
||||
php/meterpreter/reverse_tcp
|
||||
windows/meterpreter/reverse_tcp
|
||||
windows/meterpreter/reverse_https
|
||||
!.each do |name|
|
||||
dump.should include(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe MsfVenom do
|
||||
|
||||
let(:stdin) { StringIO.new("", "rb") }
|
||||
let(:stdout) { StringIO.new("", "wb") }
|
||||
let(:stderr) { StringIO.new("", "wb") }
|
||||
subject(:venom) { described_class.new(stdin, stdout, stderr, framework) }
|
||||
before(:each) do
|
||||
conf_dir = Metasploit::Framework.root.join('spec', 'dummy', 'framework','config')
|
||||
conf_dir.mkpath
|
||||
end
|
||||
after(:each) do
|
||||
dummy_dir = Metasploit::Framework.root.join('spec', 'dummy')
|
||||
dummy_dir.rmtree
|
||||
end
|
||||
|
||||
before(:all) do
|
||||
conf_dir = Metasploit::Framework.root.join('spec', 'dummy', 'framework','config')
|
||||
conf_dir.mkpath
|
||||
create_opts = {
|
||||
:module_types => [
|
||||
::Msf::MODULE_PAYLOAD, ::Msf::MODULE_ENCODER, ::Msf::MODULE_NOP
|
||||
],
|
||||
'ConfigDirectory' => conf_dir.to_s,
|
||||
'DisableDatabase' => true
|
||||
}
|
||||
@framework = ::Msf::Simple::Framework.create(create_opts)
|
||||
end
|
||||
|
||||
let(:framework) { @framework }
|
||||
describe "#dump_encoders" do
|
||||
it_behaves_like "encoder dumper" do
|
||||
let(:dump) { venom.dump_encoders }
|
||||
end
|
||||
end
|
||||
|
||||
describe "#dump_nops" do
|
||||
it_behaves_like "nop dumper" do
|
||||
let(:dump) { venom.dump_nops }
|
||||
end
|
||||
end
|
||||
|
||||
describe "#dump_payloads" do
|
||||
it_behaves_like "payload dumper" do
|
||||
let(:dump) { venom.dump_payloads }
|
||||
end
|
||||
end
|
||||
|
||||
describe "#parse_args" do
|
||||
|
||||
context "help" do
|
||||
it "should raise UsageError" do
|
||||
expect { venom.parse_args(%w! -h !) }.to raise_error(MsfVenom::UsageError)
|
||||
expect { venom.parse_args(%w! --help !) }.to raise_error(MsfVenom::UsageError)
|
||||
expect { venom.parse_args(%w! --help-formats !) }.to raise_error(MsfVenom::UsageError)
|
||||
end
|
||||
end
|
||||
|
||||
context "with bad arguments" do
|
||||
|
||||
it "should raise UsageError with empty arguments" do
|
||||
expect { venom.parse_args([]) }.to raise_error(MsfVenom::UsageError)
|
||||
end
|
||||
|
||||
it "should raise with unexpected options" do
|
||||
expect { venom.parse_args(%w! --non-existent-option !) }.to raise_error(MsfVenom::UsageError)
|
||||
end
|
||||
|
||||
%w! --platform -a -b -c -f -p -n -s -i -x !.each do |required_arg|
|
||||
it "should raise UsageError with no arg for option #{required_arg}" do
|
||||
expect { venom.parse_args([required_arg]) }.to raise_error(MsfVenom::UsageError)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "#generate_raw_payload" do
|
||||
|
||||
before do
|
||||
venom.parse_args(args)
|
||||
end
|
||||
|
||||
context "with --options" do
|
||||
|
||||
context "and a payload" do
|
||||
let(:args) { %w! -o -p windows/meterpreter/reverse_tcp ! }
|
||||
it "should print options" do
|
||||
expect { venom.generate_raw_payload }.to_not raise_error
|
||||
output = stderr.string
|
||||
output.should include("LHOST")
|
||||
output.should include("LPORT")
|
||||
end
|
||||
context "and some datastore options" do
|
||||
it "should print options" do
|
||||
venom.parse_args %w! -o -p windows/meterpreter/reverse_tcp LPORT=1234!
|
||||
expect { venom.generate_raw_payload }.to_not raise_error
|
||||
output = stderr.string
|
||||
output.should include("LHOST")
|
||||
output.should match(/LPORT\s+1234/)
|
||||
end
|
||||
|
||||
it "should print options case-insensitively" do
|
||||
venom.parse_args %w! -o -p windows/meterpreter/reverse_tcp lPoRt=1234!
|
||||
expect { venom.generate_raw_payload }.to_not raise_error
|
||||
output = stderr.string
|
||||
output.should include("LHOST")
|
||||
output.should match(/LPORT\s+1234/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "and an invalid payload" do
|
||||
let(:args) { %w! -o -p asdf! }
|
||||
it "should raise" do
|
||||
expect { venom.generate_raw_payload }.to raise_error(MsfVenom::UsageError)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
[
|
||||
{ :format => "elf", :arch => "x86" },
|
||||
{ :format => "raw", :arch => "x86" },
|
||||
{ :format => "elf", :arch => "armle" },
|
||||
{ :format => "raw", :arch => "armle" },
|
||||
{ :format => "elf", :arch => "ppc" },
|
||||
{ :format => "raw", :arch => "ppc" },
|
||||
{ :format => "elf", :arch => "mipsle" },
|
||||
{ :format => "raw", :arch => "mipsle" },
|
||||
].each do |format_hash|
|
||||
format = format_hash[:format]
|
||||
arch = format_hash[:arch]
|
||||
|
||||
context "building #{format} with linux/#{arch}/shell_bind_tcp" do
|
||||
let(:args) { %W! -f #{format} -p linux/#{arch}/shell_bind_tcp ! }
|
||||
# We're not encoding, so should be testable here
|
||||
it "should contain /bin/sh" do
|
||||
output = venom.generate_raw_payload
|
||||
# Usually push'd in two instructions, so the whole string
|
||||
# isn't all together. Check for the two pieces seperately.
|
||||
# Also should have into account payloads using imm16 moves.
|
||||
output.should include("sh")
|
||||
output.should include("bi")
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe "#generate" do
|
||||
include_context 'Msf::Util::Exe'
|
||||
|
||||
before { venom.parse_args(args) }
|
||||
|
||||
context "with --list" do
|
||||
|
||||
context "with invalid module type" do
|
||||
let(:args) { %w!--list asdf! }
|
||||
it "should raise UsageError" do
|
||||
expect { venom.generate }.to raise_error(MsfVenom::UsageError)
|
||||
end
|
||||
end
|
||||
|
||||
[ "nop", "encoder", "payload" ].each do |type|
|
||||
context "#{type}s" do
|
||||
let(:args) { %W!--list #{type}s! }
|
||||
it_behaves_like "#{type} dumper" do
|
||||
let(:dump) do
|
||||
venom.generate
|
||||
stderr.string
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context "with invalid datastore option" do
|
||||
let(:args) { %w!-f exe -p windows/shell_reverse_tcp LPORT=asdf! }
|
||||
it "should fail validation" do
|
||||
expect { venom.generate }.to raise_error(Msf::OptionValidateError)
|
||||
end
|
||||
end
|
||||
|
||||
context "without required datastore option" do
|
||||
# Requires LHOST
|
||||
let(:args) { %w!-f exe -p windows/shell_reverse_tcp! }
|
||||
it "should fail validation" do
|
||||
expect { venom.generate }.to raise_error(Msf::OptionValidateError)
|
||||
end
|
||||
end
|
||||
|
||||
@platform_format_map.each do |plat, formats|
|
||||
formats.each do |format_hash|
|
||||
format = format_hash[:format]
|
||||
arch = format_hash[:arch]
|
||||
# Need a new context for each so the let() will work correctly
|
||||
context "with format=#{format} platform=#{plat} arch=#{arch}" do
|
||||
# This will build executables with no payload. They won't work
|
||||
# of course, but at least we can see that it is producing the
|
||||
# correct file format for the given arch and platform.
|
||||
let(:args) { %W! -p - -f #{format} -a #{arch} --platform #{plat} ! }
|
||||
it "should print a #{format} to stdout" do
|
||||
venom.generate
|
||||
output = stdout.string
|
||||
verify_bin_fingerprint(format_hash, output)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
Loading…
Reference in New Issue