Land #2950 - New Payload Generator for MsfVenom

bug/bundler_fix
sinn3r 2014-02-13 15:13:10 -06:00
commit 4dd60631cb
No known key found for this signature in database
GPG Key ID: 2384DB4EF06F730B
6 changed files with 990 additions and 548 deletions

View File

@ -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

View File

@ -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
View File

@ -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

View File

@ -0,0 +1 @@
瑞瑞

View File

@ -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

View File

@ -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