#!/usr/bin/env ruby # -*- coding: binary -*- class MsfVenomError < StandardError; end class HelpError < StandardError; end class UsageError < MsfVenomError; end require 'optparse' require 'timeout' def require_deps msfbase = __FILE__ while File.symlink?(msfbase) msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase)) end $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) require 'msfenv' $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] require 'rex' require 'msf/ui' require 'msf/base' require 'msf/core/payload_generator' @framework_loaded = true end # Creates a new framework object. # # @note Ignores any previously cached value. # @param (see ::Msf::Simple::Framework.create) # @return [Msf::Framework] def init_framework(create_opts={}) require_deps unless @framework_loaded create_opts[:module_types] ||= [ ::Msf::MODULE_PAYLOAD, ::Msf::MODULE_ENCODER, ::Msf::MODULE_NOP ] create_opts[:module_types].map! do |type| type = Msf.const_get("MODULE_#{type.upcase}") end @framework = ::Msf::Simple::Framework.create(create_opts.merge('DisableDatabase' => true)) end # Cached framework object # # @return [Msf::Framework] def framework return @framework if @framework init_framework @framework end def parse_args(args) opts = {} datastore = {} opt = OptionParser.new banner = "MsfVenom - a Metasploit standalone payload generator.\n" banner << "Also a replacement for msfpayload and msfencode.\n" banner << "Usage: #{$0} [options] \n" banner << "Example: #{$0} -p windows/meterpreter/reverse_tcp LHOST= -f exe -o payload.exe" opt.banner = banner opt.separator('') opt.separator('Options:') opt.on('-l', '--list ', Array, 'List all modules for [type]. Types are: payloads, encoders, nops, platforms, archs, formats, all') do |l| if l.to_s.empty? l = ["all"] end opts[:list] = l end opt.on('-p', '--payload ', String, "Payload to use (--list payloads to list, --list-options for arguments). Specify '-' or STDIN for custom") do |p| if p == '-' opts[:payload] = 'stdin' else opts[:payload] = p end end opt.on('--list-options', "List --payload 's standard, advanced and evasion options") do opts[:list_options] = true end opt.on('-f', '--format ', String, "Output format (use --list formats to list)") do |f| opts[:format] = f.downcase end opt.on('-e', '--encoder ', String, 'The encoder to use (use --list encoders to list)') do |e| opts[:encoder] = e end opt.on('--smallest', 'Generate the smallest possible payload using all available encoders') do opts[:smallest] = true end opt.on('-a', '--arch ', String, 'The architecture to use for --payload and --encoders (use --list archs to list)') do |a| opts[:arch] = a end opt.on('--platform ', String, 'The platform for --payload (use --list platforms to list)') do |l| opts[:platform] = l end opt.on('-o', '--out ', 'Save the payload to a file') do |x| opts[:out] = x end opt.on('-b', '--bad-chars ', String, 'Characters to avoid example: \'\x00\xff\'') do |b| init_framework() opts[:badchars] = Rex::Text.hex_to_raw(b) end opt.on('-n', '--nopsled ', Integer, 'Prepend a nopsled of [length] size on to the payload') do |n| opts[:nops] = n.to_i end opt.on('--pad-nops', 'Use nopsled size specified by -n as the total payload size, thus performing a subtraction to prepend a nopsled of quantity (nops minus payload length)') do opts[:padnops] = true end opt.on('-s', '--space ', Integer, 'The maximum size of the resulting payload') do |s| opts[:space] = s end opt.on('--encoder-space ', Integer, 'The maximum size of the encoded payload (defaults to the -s value)') do |s| opts[:encoder_space] = s end opt.on('-i', '--iterations ', Integer, 'The number of times to encode the payload') do |i| opts[:iterations] = i end opt.on('-c', '--add-code ', String, 'Specify an additional win32 shellcode file to include') do |x| opts[:add_code] = x end opt.on('-x', '--template ', String, 'Specify a custom executable file to use as a template') do |x| opts[:template] = x end opt.on('-k', '--keep', 'Preserve the --template behaviour and inject the payload as a new thread') do opts[:keep] = true end opt.on('-v', '--var-name ', String, 'Specify a custom variable name to use for certain output formats') do |x| opts[:var_name] = x end opt.on('-t', '--timeout ', Integer, "The number of seconds to wait when reading the payload from STDIN (default 30, 0 to disable)") do |x| opts[:timeout] = x end opt.on_tail('-h', '--help', 'Show this message') do raise HelpError, "#{opt}" end begin opt.parse!(args) rescue OptionParser::InvalidOption => e raise UsageError, "Invalid option\n#{opt}" rescue OptionParser::MissingArgument => e raise UsageError, "Missing required argument for option\n#{opt}" end 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 end if opts[:payload].to_s =~ /[\_\/]reverse/ && datastore['LHOST'].nil? init_framework() datastore['LHOST'] = Rex::Socket.source_address end end if opts[:payload].nil? # if no payload option is selected assume we are reading it from stdin opts[:payload] = "stdin" end if opts[:payload].downcase == 'stdin' && !opts[:list] $stderr.puts "Attempting to read payload from STDIN..." begin opts[:timeout] ||= 30 ::Timeout.timeout(opts[:timeout]) 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 dump_platforms init_framework(:module_types => []) supported_platforms = [] Msf::Module::Platform.subclasses.each {|c| supported_platforms << c.realname.downcase} tbl = Rex::Text::Table.new( 'Indent' => 4, 'Header' => "Framework Platforms [--platform ]", 'Columns' => [ "Name", ]) supported_platforms.sort.each do |name| tbl << [name] end "\n" + tbl.to_s + "\n" end def dump_archs init_framework(:module_types => []) supported_archs = ARCH_ALL.dup tbl = Rex::Text::Table.new( 'Indent' => 4, 'Header' => "Framework Architectures [--arch ]", 'Columns' => [ "Name", ]) supported_archs.sort.each do |name| tbl << [name] end "\n" + tbl.to_s + "\n" end def dump_archs init_framework(:module_types => []) supported_archs = ARCH_ALL.dup tbl = Rex::Text::Table.new( 'Indent' => 4, 'Header' => "Framework Architectures [--arch ]", 'Columns' => [ "Name", ]) supported_archs.sort.each do |name| tbl << [name] end "\n" + tbl.to_s + "\n" end def dump_formats init_framework(:module_types => []) tbl1 = Rex::Text::Table.new( 'Indent' => 4, 'Header' => "Framework Executable Formats [--format ]", 'Columns' => [ "Name" ]) ::Msf::Util::EXE.to_executable_fmt_formats.each do |name| tbl1 << [ name ] end tbl2 = Rex::Text::Table.new( 'Indent' => 4, 'Header' => "Framework Transform Formats [--format ]", 'Columns' => [ "Name" ]) ::Msf::Simple::Buffer.transform_formats.each do |name| tbl2 << [ name ] end "\n" + tbl1.to_s + "\n" + tbl2.to_s + "\n" end def dump_payloads init_framework(:module_types => [ :payload ]) tbl = Rex::Text::Table.new( 'Indent' => 4, 'Header' => "Framework Payloads (#{framework.stats.num_payloads} total) [--payload ]", 'Columns' => [ "Name", "Description" ]) framework.payloads.each_module { |name, mod| tbl << [ name, mod.new.description.split.join(' ') ] } "\n" + tbl.to_s + "\n" end def dump_encoders(arch = nil) init_framework(:module_types => [ :encoder ]) tbl = Rex::Text::Table.new( 'Indent' => 4, 'Header' => "Framework Encoders" + ((arch) ? " (architectures: #{arch})" : "") + " [--encoder ]", 'Columns' => [ "Name", "Rank", "Description" ]) cnt = 0 framework.encoders.each_module( 'Arch' => arch ? arch.split(',') : nil) { |name, mod| tbl << [ name, mod.rank_to_s, mod.new.name ] cnt += 1 } (cnt > 0) ? "\n" + tbl.to_s + "\n" : "\nNo compatible encoders found.\n\n" end def dump_nops init_framework(:module_types => [ :nop ]) tbl = Rex::Text::Table.new( 'Indent' => 4, 'Header' => "Framework NOPs (#{framework.stats.num_nops} total)", 'Columns' => [ "Name", "Description" ]) framework.nops.each_module { |name, mod| tbl << [ name, mod.new.description.split.join(' ') ] } "\n" + tbl.to_s + "\n" end begin generator_opts = parse_args(ARGV) rescue HelpError => e $stderr.puts e.message exit(1) rescue MsfVenomError => e $stderr.puts "Error: #{e.message}" exit(1) end if generator_opts[:list] generator_opts[:list].each do |mod| case mod.downcase when "payloads", "payload", "p" $stdout.puts dump_payloads when "encoders", "encoder", "e" $stdout.puts dump_encoders(generator_opts[:arch]) when "nops", "nop", "n" $stdout.puts dump_nops when "platforms", "dump_platform" $stdout.puts dump_platforms when "archs", "dump_arch" $stdout.puts dump_archs when "formats", "format", "f" $stdout.puts dump_formats when "all", "a" # Init here so #dump_payloads doesn't create a framework with # only payloads, etc. init_framework $stdout.puts dump_payloads $stdout.puts dump_encoders $stdout.puts dump_nops $stdout.puts dump_platforms $stdout.puts dump_archs $stdout.puts dump_formats else $stderr.puts "Invalid type (#{mod}). These are valid: payloads, encoders, nops, platforms, archs, formats, all" end end exit(0) end if generator_opts[:list_options] payload_mod = framework.payloads.create(generator_opts[:payload]) if payload_mod.nil? $stderr.puts "Invalid payload: #{generator_opts[:payload]}" exit(1) end $stderr.puts "Options for #{payload_mod.fullname}:\n" + "="*25 + "\n\n" $stdout.puts ::Msf::Serializer::ReadableText.dump_module(payload_mod, ' ') $stderr.puts "\nAdvanced options for #{payload_mod.fullname}:\n" + "="*25 + "\n\n" $stdout.puts ::Msf::Serializer::ReadableText.dump_advanced_options(payload_mod, ' ') $stderr.puts "\nEvasion options for #{payload_mod.fullname}:\n" + "="*25 + "\n\n" $stdout.puts ::Msf::Serializer::ReadableText.dump_evasion_options(payload_mod, ' ') exit(0) end generator_opts[:framework] = framework generator_opts[:cli] = true begin venom_generator = Msf::PayloadGenerator.new(generator_opts) payload = venom_generator.generate_payload rescue ::Msf::Simple::Buffer::BufferFormatError => e $stderr.puts "Error: #{e.message}" $stderr.puts dump_formats rescue ::Exception => e elog("#{e.class} : #{e.message}\n#{e.backtrace * "\n"}") $stderr.puts "Error: #{e.message}" end # No payload generated, no point to go on exit(2) unless payload if generator_opts[:out] begin ::File.open(generator_opts[:out], 'wb') do |f| f.write(payload) end $stderr.puts "Saved as: #{generator_opts[:out]}" rescue ::Exception => e # If I can't save it, then I can't save it. I don't think it matters what error. elog("#{e.class} : #{e.message}\n#{e.backtrace * "\n"}") $stderr.puts "Error: #{e.message}" end else output_stream = $stdout output_stream.binmode output_stream.write payload # trailing newline for pretty output $stderr.puts unless payload =~ /\n$/ end