Skip to content

Add Windows Boot Verification Program persistence#21550

Open
M4nu02 wants to merge 2 commits into
rapid7:masterfrom
M4nu02:windows-bootverificationprogram-persistence
Open

Add Windows Boot Verification Program persistence#21550
M4nu02 wants to merge 2 commits into
rapid7:masterfrom
M4nu02:windows-bootverificationprogram-persistence

Conversation

@M4nu02

@M4nu02 M4nu02 commented Jun 7, 2026

Copy link
Copy Markdown
Contributor

Add a Windows persistence module leveraging the registry key BootVerificationProgram. The module uploads an executable and sets the 'ImagePath' value, allowing execution via the Service Control Manager early in the boot cycle.

Verification

List the steps needed to make sure this thing works

  • Start msfconsole
  • Get an admin or SYSTEM shell
  • use exploit/windows/persistence/boot_verification_program
  • set SESSION [SESSION]
  • run
  • Reboot the target machine
  • You should get a SYSTEM shell

Add a Windows persistence module leveraging the registry key
BootVerificationProgram. The module uploads an executable and sets
the 'ImagePath' value, allowing execution via the Service Control
Manager early in the boot cycle.

@h00die h00die left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few minor things

[ 'Automatic', {} ]
],
'References' => [
['ATT&CK', Mitre::Attack::Technique::T1547_BOOT_OR_LOGON_AUTOSTART_EXECUTION],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
['ATT&CK', Mitre::Attack::Technique::T1547_BOOT_OR_LOGON_AUTOSTART_EXECUTION],
['ATT&CK', Mitre::Attack::Technique::T1547_BOOT_OR_LOGON_AUTOSTART_EXECUTION],
['ATT&CK', Mitre::Attack::Technique::T1546_EVENT_TRIGGERED_EXECUTION],

end

def check
return CheckCode::Unknown('Admin or SYSTEM privileges are required') unless is_system? || is_admin?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return CheckCode::Unknown('Admin or SYSTEM privileges are required') unless is_system? || is_admin?
return CheckCode::Safe('Admin or SYSTEM privileges are required') unless is_system? || is_admin?


def check
return CheckCode::Unknown('Admin or SYSTEM privileges are required') unless is_system? || is_admin?
return CheckCode::Unknown("Target directory #{writable_dir} does not exist") unless directory?(writable_dir)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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)
print_warning('Payloads in %TEMP% will only last until reboot, you want to choose elsewhere.') if datastore['WritableDir'].start_with?('%TEMP%') # check the original value
return CheckCode::Safe("#{writable_dir} doesnt exist") unless exists?(writable_dir)

this has been my boilerplate code block for checking

end

def install_persistence
fail_with(Failure::NoAccess, 'Admin or SYSTEM privileges are required') unless is_system? || is_admin?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fail_with(Failure::NoAccess, 'Admin or SYSTEM privileges are required') unless is_system? || is_admin?

Autocheck is enabled, so no need to recheck this

def install_persistence
fail_with(Failure::NoAccess, 'Admin or SYSTEM privileges are required') unless is_system? || is_admin?

payload_name = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha(8)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
payload_name = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha(8)
payload_name = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha((rand(6..13)))

boilerplate, i like to add a little more randomness

Comment on lines +85 to +96
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fail_with will kill the module off, so i think this can be simplified down to:

Suggested change
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
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
end
vprint_good('The BootVerificationProgram key already exists. Skipping creation step.')
had_preexisting_boot_key = true
old_image_path = registry_getvaldata(boot_reg_key, 'ImagePath')

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The 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 had_preexisting_boot_key = true. During cleanup, the module will then treat the key as if it was always there and leave it behind, rather than deleting the structure it created.


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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
value under HKLM\SYSTEM\CurrentControlSet\Control\BootVerificationProgram.
value under `HKLM\SYSTEM\CurrentControlSet\Control\BootVerificationProgram`.

## Vulnerable Application

This module establishes persistence by configuring the BootVerificationProgram
registry key. It uploads a payload executable and modifies the 'ImagePath'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
registry key. It uploads a payload executable and modifies the 'ImagePath'
registry key. It uploads a payload executable and modifies the `ImagePath`

@github-project-automation github-project-automation Bot moved this from Todo to Waiting on Contributor in Metasploit Kanban Jun 8, 2026
@dwelch-r7 dwelch-r7 added module rn-modules release notes for new or majorly enhanced modules labels Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

module rn-modules release notes for new or majorly enhanced modules

Projects

Status: Waiting on Contributor

Development

Successfully merging this pull request may close these issues.

4 participants