More msfvenom refactoring
* Make @framework into a caching method instead * Allow instantiating with streams for where payloads and comments should go. This allows us to capture std{out,err} when running specs * Specs are still woefully under-representative * Get rid of all the calls to exitbug/bundler_fix
parent
4b2ae4ef6a
commit
95451862d6
123
msfvenom
123
msfvenom
|
@ -20,20 +20,35 @@ 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
|
||||
|
||||
def init_framework(create_opts={})
|
||||
return @framework if @framework
|
||||
create_opts[:module_types] ||= [
|
||||
::Msf::MODULE_PAYLOAD, ::Msf::MODULE_ENCODER, ::Msf::MODULE_NOP
|
||||
]
|
||||
@framework = ::Msf::Simple::Framework.create(create_opts.merge('DisableDatabase' => true))
|
||||
end
|
||||
|
||||
def framework
|
||||
return @framework if @framework
|
||||
|
||||
init_framework
|
||||
|
||||
@framework
|
||||
end
|
||||
|
||||
def parse_args(args)
|
||||
@opts = {}
|
||||
@datastore = {}
|
||||
|
@ -42,7 +57,7 @@ class MsfVenom
|
|||
opt.separator('')
|
||||
opt.separator('Options:')
|
||||
|
||||
opt.on('-p', '--payload [payload]', String, 'Payload to use. Specify a \'-\' or stdin to use custom payloads') do |p|
|
||||
opt.on('-p', '--payload <payload>', String, 'Payload to use. Specify a \'-\' or stdin to use custom payloads') do |p|
|
||||
if p == '-'
|
||||
@opts[:payload] = 'stdin'
|
||||
else
|
||||
|
@ -57,11 +72,11 @@ class MsfVenom
|
|||
@opts[:list] = l
|
||||
end
|
||||
|
||||
opt.on('-n', '--nopsled [length]', Integer, 'Prepend a nopsled of [length] size on to the payload') do |n|
|
||||
opt.on('-n', '--nopsled <length>', Integer, 'Prepend a nopsled of [length] size on to the payload') do |n|
|
||||
@opts[:nopsled] = n.to_i
|
||||
end
|
||||
|
||||
opt.on('-f', '--format [format]', String, "Output format (use --help-formats for a list)") do |f|
|
||||
opt.on('-f', '--format <format>', String, "Output format (use --help-formats for a list)") do |f|
|
||||
@opts[:format] = f
|
||||
end
|
||||
|
||||
|
@ -70,31 +85,31 @@ class MsfVenom
|
|||
@opts[:encoder] = e
|
||||
end
|
||||
|
||||
opt.on('-a', '--arch [architecture]', String, 'The architecture to use') do |a|
|
||||
opt.on('-a', '--arch <architecture>', String, 'The architecture to use') do |a|
|
||||
@opts[:arch] = a
|
||||
end
|
||||
|
||||
opt.on('--platform [platform]', String, 'The platform of the payload') do |l|
|
||||
opt.on('--platform <platform>', String, 'The platform of the payload') do |l|
|
||||
@opts[:platform] = l
|
||||
end
|
||||
|
||||
opt.on('-s', '--space [length]', Integer, 'The maximum size of the resulting payload') do |s|
|
||||
opt.on('-s', '--space <length>', Integer, 'The maximum size of the resulting payload') do |s|
|
||||
@opts[:space] = s
|
||||
end
|
||||
|
||||
opt.on('-b', '--bad-chars [list] ', String, 'The list of characters to avoid example: \'\x00\xff\'') do |b|
|
||||
opt.on('-b', '--bad-chars <list>', String, 'The list of characters to avoid example: \'\x00\xff\'') do |b|
|
||||
@opts[:badchars] = b
|
||||
end
|
||||
|
||||
opt.on('-i', '--iterations [count] ', Integer, 'The number of times to encode the payload') do |i|
|
||||
opt.on('-i', '--iterations <count>', Integer, 'The number of times to encode the payload') do |i|
|
||||
@opts[:iterations] = i
|
||||
end
|
||||
|
||||
opt.on('-c', '--add-code [path] ', String, 'Specify an additional win32 shellcode file to include') do |x|
|
||||
opt.on('-c', '--add-code <path>', String, 'Specify an additional win32 shellcode file to include') do |x|
|
||||
@opts[:addshellcode] = x
|
||||
end
|
||||
|
||||
opt.on('-x', '--template [path] ', String, 'Specify a custom executable file to use as a template') do |x|
|
||||
opt.on('-x', '--template <path>', String, 'Specify a custom executable file to use as a template') do |x|
|
||||
@opts[:template] = x
|
||||
end
|
||||
|
||||
|
@ -120,12 +135,11 @@ class MsfVenom
|
|||
end
|
||||
|
||||
begin
|
||||
opt.parse!
|
||||
opt.parse!(args)
|
||||
rescue OptionParser::InvalidOption => e
|
||||
p e
|
||||
raise UsageError, "Invalid option\n#{opt}"
|
||||
rescue OptionParser::MissingArgument => e
|
||||
p e
|
||||
raise UsageError, "Missing required argument for option\n#{opt}"
|
||||
end
|
||||
|
||||
if @opts.empty?
|
||||
|
@ -145,20 +159,20 @@ class MsfVenom
|
|||
end
|
||||
|
||||
def print_status(msg)
|
||||
$stderr.puts(Status + msg)
|
||||
@err.puts(Status + msg)
|
||||
end
|
||||
|
||||
def print_error(msg)
|
||||
$stderr.puts(Error + msg)
|
||||
@err.puts(Error + msg)
|
||||
end
|
||||
|
||||
def get_encoders(arch, encoder)
|
||||
encoders = []
|
||||
|
||||
if (encoder)
|
||||
encoders << @framework.encoders.create(encoder)
|
||||
encoders << framework.encoders.create(encoder)
|
||||
else
|
||||
@framework.encoders.each_module_ranked(
|
||||
framework.encoders.each_module_ranked(
|
||||
'Arch' => arch ? arch.split(',') : nil) { |name, mod|
|
||||
encoders << mod.new
|
||||
}
|
||||
|
@ -168,8 +182,8 @@ class MsfVenom
|
|||
end
|
||||
|
||||
def payload_stdin
|
||||
$stdin.binmode
|
||||
payload = $stdin.read
|
||||
@in.binmode
|
||||
payload = @in.read
|
||||
payload
|
||||
end
|
||||
|
||||
|
@ -178,14 +192,14 @@ class MsfVenom
|
|||
nop_jpts['SaveRegisters'] ||= [ 'esp', 'ebp', 'esi', 'edi' ]
|
||||
|
||||
if nop_mod
|
||||
nop = @framework.nops.create(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|
|
||||
framework.nops.each_module_ranked('Arch' => arch) do |name, mod|
|
||||
begin
|
||||
nop = @framework.nops.create(name)
|
||||
nop = framework.nops.create(name)
|
||||
raw = nop.generate_sled(len, nop_opts)
|
||||
return raw if raw
|
||||
rescue
|
||||
|
@ -198,14 +212,14 @@ class MsfVenom
|
|||
init_framework(:module_types => [ ::Msf::MODULE_PAYLOAD ])
|
||||
tbl = Rex::Ui::Text::Table.new(
|
||||
'Indent' => 4,
|
||||
'Header' => "Framework Payloads (#{@framework.stats.num_payloads} total)",
|
||||
'Header' => "Framework Payloads (#{framework.stats.num_payloads} total)",
|
||||
'Columns' =>
|
||||
[
|
||||
"Name",
|
||||
"Description"
|
||||
])
|
||||
|
||||
@framework.payloads.each_module { |name, mod|
|
||||
framework.payloads.each_module { |name, mod|
|
||||
tbl << [ name, mod.new.description ]
|
||||
}
|
||||
|
||||
|
@ -225,7 +239,7 @@ class MsfVenom
|
|||
])
|
||||
cnt = 0
|
||||
|
||||
@framework.encoders.each_module(
|
||||
framework.encoders.each_module(
|
||||
'Arch' => arch ? arch.split(',') : nil) { |name, mod|
|
||||
tbl << [ name, mod.rank_to_s, mod.new.name ]
|
||||
|
||||
|
@ -239,14 +253,14 @@ class MsfVenom
|
|||
init_framework(:module_types => [ ::Msf::MODULE_NOP ])
|
||||
tbl = Rex::Ui::Text::Table.new(
|
||||
'Indent' => 4,
|
||||
'Header' => "Framework NOPs (#{@framework.stats.num_nops} total)",
|
||||
'Header' => "Framework NOPs (#{framework.stats.num_nops} total)",
|
||||
'Columns' =>
|
||||
[
|
||||
"Name",
|
||||
"Description"
|
||||
])
|
||||
|
||||
@framework.nops.each_module { |name, mod|
|
||||
framework.nops.each_module { |name, mod|
|
||||
tbl << [ name, mod.new.description ]
|
||||
}
|
||||
|
||||
|
@ -270,15 +284,14 @@ class MsfVenom
|
|||
@opts[:platform] = ::Msf::Module::PlatformList.transform("Windows")
|
||||
end
|
||||
else
|
||||
payload = @framework.payloads.create(@opts[:payload])
|
||||
payload = framework.payloads.create(@opts[:payload])
|
||||
if payload.nil?
|
||||
print_error("Invalid payload: #{@opts[:payload]}")
|
||||
exit
|
||||
raise UsageError, "Invalid payload: #{@opts[:payload]}"
|
||||
end
|
||||
if @opts[:list_options]
|
||||
print_status("Options for #{payload.fullname}\n\n" +
|
||||
::Msf::Serializer::ReadableText.dump_module(payload,' '))
|
||||
exit
|
||||
return
|
||||
end
|
||||
@opts[:arch] ||= payload.arch[0]
|
||||
# If it's not stdin, we'll already have a PlatformList
|
||||
|
@ -299,23 +312,23 @@ class MsfVenom
|
|||
@opts[:list].each do |mod|
|
||||
case mod.downcase
|
||||
when "payloads"
|
||||
$stderr.puts dump_payloads
|
||||
@err.puts dump_payloads
|
||||
when "encoders"
|
||||
$stderr.puts dump_encoders(@opts[:arch])
|
||||
@err.puts dump_encoders(@opts[:arch])
|
||||
when "nops"
|
||||
$stderr.puts dump_nops
|
||||
@err.puts dump_nops
|
||||
when "all"
|
||||
# Init here so #dump_payloads doesn't create a framework with
|
||||
# only payloads, etc.
|
||||
init_framework
|
||||
$stderr.puts dump_payloads
|
||||
$stderr.puts dump_encoders
|
||||
$stderr.puts dump_nops
|
||||
@err.puts dump_payloads
|
||||
@err.puts dump_encoders
|
||||
@err.puts dump_nops
|
||||
else
|
||||
print_error("Invalid module type")
|
||||
end
|
||||
end
|
||||
exit
|
||||
return
|
||||
end
|
||||
|
||||
# Normalize the options
|
||||
|
@ -326,7 +339,6 @@ class MsfVenom
|
|||
@opts[:encode] ||= !(@opts[:badchars].nil? or @opts[:badchars].empty?)
|
||||
|
||||
|
||||
init_framework
|
||||
payload_raw = generate_raw_payload
|
||||
|
||||
if @opts[:template]
|
||||
|
@ -391,69 +403,66 @@ class MsfVenom
|
|||
rescue => e
|
||||
print_error("#{enc.refname} failed: #{e.class} #{e}")
|
||||
e.backtrace.each { |el|
|
||||
$stderr.puts(el.to_s)
|
||||
@err.puts(el.to_s)
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if @opts[:nopsled]
|
||||
#puts @opts[:arch].class
|
||||
nopts = { 'BadChars' => @opts[:badchars] }
|
||||
nops = generate_nops([@opts[:arch]], @opts[:nopsled], nil, nopts)
|
||||
payload_raw = nops + payload_raw
|
||||
end
|
||||
|
||||
$stdout.binmode
|
||||
@out.binmode
|
||||
|
||||
case @opts[:format].downcase
|
||||
# Special-case this to check endianness
|
||||
when "js_be"
|
||||
|
||||
if Rex::Arch.endian(payload.arch) != ENDIAN_BIG
|
||||
print_error("Big endian format selected for a non big endian payload")
|
||||
exit
|
||||
raise IncompatibleError, "Big endian format selected for a non big endian payload"
|
||||
end
|
||||
$stdout.write ::Msf::Simple::Buffer.transform(payload_raw, @opts[:format])
|
||||
@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)
|
||||
exe = ::Msf::Util::EXE.to_executable_fmt(framework, @opts[:arch], @opts[:platform], payload_raw, @opts[:format], exeopts)
|
||||
if (!exe and payload.platform.platforms.index(::Msf::Module::Platform::Java))
|
||||
exe = payload.generate_war.pack
|
||||
else
|
||||
exe = ::Msf::Util::EXE.to_jsp_war(exe)
|
||||
end
|
||||
$stdout.write exe
|
||||
@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)
|
||||
exe = ::Msf::Util::EXE.to_executable_fmt(framework, @opts[:arch], @opts[:platform], payload_raw, @opts[:format], exeopts)
|
||||
if(!exe and payload.platform.platforms.index(::Msf::Module::Platform::Java))
|
||||
exe = payload.generate_jar.pack
|
||||
end
|
||||
if exe
|
||||
$stdout.write exe
|
||||
@out.write exe
|
||||
else
|
||||
print_error("Could not generate payload format")
|
||||
end
|
||||
|
||||
when *::Msf::Simple::Buffer.transform_formats
|
||||
$stdout.write ::Msf::Simple::Buffer.transform(payload_raw, @opts[:format])
|
||||
@out.write ::Msf::Simple::Buffer.transform(payload_raw, @opts[:format])
|
||||
|
||||
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)
|
||||
exe = ::Msf::Util::EXE.to_executable_fmt(framework, @opts[:arch], @opts[:platform], payload_raw, @opts[:format], exeopts)
|
||||
if exe.nil?
|
||||
print_error("This format does not support that platform/architecture")
|
||||
exit
|
||||
raise IncompatibleError, "This format does not support that platform/architecture"
|
||||
end
|
||||
$stdout.write exe
|
||||
@out.write exe
|
||||
|
||||
else
|
||||
print_error("Unsupported format")
|
||||
exit
|
||||
raise IncompatibleError, "Unsupported format"
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,31 +5,58 @@ require 'msf/core'
|
|||
load File.join(Msf::Config.install_root, 'msfvenom')
|
||||
|
||||
describe MsfVenom do
|
||||
subject(:venom) { described_class.new }
|
||||
|
||||
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 "should list known encoders" do
|
||||
dump = venom.dump_encoders
|
||||
|
||||
%w|
|
||||
%w!
|
||||
generic/none
|
||||
x86/shikata_ga_nai
|
||||
x64/xor
|
||||
php/base64
|
||||
|.each do |name|
|
||||
!.each do |name|
|
||||
dump.should include(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe "#dump_payloads" do
|
||||
it "should list known payloads" do
|
||||
dump = venom.dump_payloads
|
||||
|
||||
%w|
|
||||
windows/meterpreter/reverse_tcp
|
||||
windows/meterpreter/reverse_https
|
||||
%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
|
||||
|
@ -37,13 +64,95 @@ describe MsfVenom do
|
|||
linux/armle/shell/reverse_tcp
|
||||
linux/armle/shell_reverse_tcp
|
||||
linux/mipsbe/shell_reverse_tcp
|
||||
java/meterpreter/reverse_tcp
|
||||
java/meterpreter/reverse_https
|
||||
php/meterpreter/reverse_tcp
|
||||
|.each do |name|
|
||||
windows/meterpreter/reverse_tcp
|
||||
windows/meterpreter/reverse_https
|
||||
!.each do |name|
|
||||
dump.should include(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#parse_args" do
|
||||
|
||||
context "with unexpected options" do
|
||||
it "should raise" do
|
||||
expect {
|
||||
venom.parse_args(%w! --non-existent-option !)
|
||||
}.to raise_error(MsfVenom::UsageError)
|
||||
end
|
||||
end
|
||||
|
||||
context "with missing required arg" do
|
||||
%w! --platform -a -b -c -f -p -n -s -i -x !.each do |required_arg|
|
||||
it "#{required_arg} should raise" 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
|
||||
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
|
||||
|
||||
end
|
||||
|
||||
describe "#generate" do
|
||||
before { venom.parse_args(args) }
|
||||
|
||||
context "with 'exe' format" do
|
||||
let(:args) { %w!-f exe -p windows/shell_reverse_tcp LHOST=192.168.0.1! }
|
||||
it "should print an exe to stdout" do
|
||||
expect { venom.generate }.to_not raise_error
|
||||
output = stdout.string
|
||||
output[0,2].should == "MZ"
|
||||
end
|
||||
end
|
||||
|
||||
context "with incorrect datastore option format" 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
|
||||
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
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue