The techniques we've explored so far—evading detection during execution, bypassing security controls, surviving memory scans while sleeping—all address the challenge of running malicious code without being caught. But there's a fundamental limitation to these approaches: they only work while the system is running. The moment a user reboots their computer, all that careful work evaporates. Every process terminates, memory is cleared, and when Windows starts fresh, the implant is gone. Persistence mechanisms solve this problem by ensuring that malicious code automatically restarts after reboots, logons, or other system events.
Understanding persistence is essential for both offensive operators and defenders. Attackers need reliable methods to maintain access over extended periods. Defenders need to know where to look for these footholds and how to detect their installation. This chapter explores the full spectrum of persistence techniques, from simple registry modifications that any user can perform to sophisticated methods that require deep system understanding.
When an attacker first gains access to a system—whether through phishing, exploitation, or social engineering—that initial foothold is inherently fragile. The process hosting the implant might crash, the user might log off, or the system might restart for updates. Without persistence, the attacker would need to repeat the entire intrusion process to regain access. This is both operationally expensive and increases the risk of detection with each new compromise attempt.
Not all persistence mechanisms are created equal. The ideal persistence technique balances several competing requirements:
PERSISTENCE QUALITY FACTORS
┌─────────────────────────────────────────────────────────────────────┐
│ │
│ Reliability: │
│ ├── Survives reboots, updates, and user actions │
│ ├── Executes consistently in the right context │
│ └── Doesn't depend on fragile conditions │
│ │
│ Stealth: │
│ ├── Avoids well-known, heavily-monitored locations │
│ ├── Blends with legitimate system activity │
│ └── Resists discovery by automated tools │
│ │
│ Privilege Requirement: │
│ ├── User-level persistence doesn't require elevation │
│ ├── Admin-level provides more options but needs elevation │
│ └── SYSTEM-level offers maximum capability but hardest to achieve │
│ │
│ Trigger Mechanism: │
│ ├── Logon-based executes when user logs in │
│ ├── Boot-based executes when system starts │
│ └── Event-based executes on specific system events │
│ │
└─────────────────────────────────────────────────────────────────────┘
The best persistence technique for a given situation depends on the operational context. A noisy technique that runs reliably might be preferable during a time-sensitive engagement. A stealthy technique that occasionally fails might be acceptable for long-term access where re-establishing persistence is possible.
Windows offers numerous locations and mechanisms that can be abused for persistence. These roughly divide into three categories based on the privileges required to install them:
User-Level Persistence: These techniques require no administrative privileges. They execute in the context of a specific user account and typically trigger when that user logs in. While limited in scope, they're often sufficient for many operations and are the only option when admin access hasn't been achieved.
Admin-Level Persistence: These techniques require local administrator privileges to install but provide more reliable execution and often run with higher privileges. They can affect all users of the system and typically trigger at boot time or for any user logon.
SYSTEM-Level Persistence: The most powerful category, these techniques run as SYSTEM or integrate deeply with Windows internals. They require significant privileges to install and are often the hardest to detect but also provide the most robust and capable persistence.
The Windows registry is a hierarchical database that stores configuration settings for the operating system and applications. Many of these settings control which programs run automatically at startup or logon, making the registry the most common location for persistence mechanisms.
The Run registry keys are the most well-known persistence location. Programs listed in these keys automatically execute when a user logs in or the system starts, depending on whether the key is in HKCU (user-specific) or HKLM (machine-wide).
REGISTRY RUN KEY LOCATIONS
User-Level (HKCU) - No admin required:
┌─────────────────────────────────────────────────────────────────────┐
│ HKCU\Software\Microsoft\Windows\CurrentVersion\Run │
│ └── Values execute when THIS user logs in │
│ │
│ HKCU\Software\Microsoft\Windows\CurrentVersion\RunOnce │
│ └── Values execute once, then deleted automatically │
└─────────────────────────────────────────────────────────────────────┘
Machine-Level (HKLM) - Admin required:
┌─────────────────────────────────────────────────────────────────────┐
│ HKLM\Software\Microsoft\Windows\CurrentVersion\Run │
│ └── Values execute for ALL users at logon │
│ │
│ HKLM\Software\Microsoft\Windows\CurrentVersion\RunOnce │
│ └── Values execute once at next logon, then deleted │
└─────────────────────────────────────────────────────────────────────┘
32-bit on 64-bit (WoW64):
┌─────────────────────────────────────────────────────────────────────┐
│ HKLM\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Run │
│ HKCU\Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Run │
│ └── Separate keys for 32-bit applications on 64-bit Windows │
└─────────────────────────────────────────────────────────────────────┘
The simplicity of Run keys is both their strength and weakness. They're trivially easy to install—just create a string value with the path to your executable. But they're also the first place any security tool or investigator will look. Modern EDR solutions monitor these keys intensively, generating alerts for any modifications.
The Winlogon process handles interactive logon sessions and provides hooks for executing code during the logon sequence. Two particularly interesting values exist under the Winlogon key:
HKLM\Software\Microsoft\Windows NT\CurrentVersion\Winlogon
Shell = explorer.exe
└── The primary user shell. Default is explorer.exe.
└── Can be modified to "explorer.exe,payload.exe" to run additional programs.
Userinit = C:\Windows\system32\userinit.exe,
└── Programs that run during user initialization.
└── The trailing comma allows appending additional programs.
The Shell value specifies what runs as the user's desktop shell. By default, this is explorer.exe, which provides the familiar Windows desktop environment. Modifying this value to include additional programs causes them to run alongside explorer.exe during every logon. The comma-separated format allows adding programs without replacing the shell entirely.
The Userinit value points to programs that run during user initialization, before the shell loads. The default userinit.exe process sets up the user's environment and then launches the shell. Appending programs here causes them to run early in the logon process.
Both of these techniques require administrator privileges and are relatively well-known to security tools. However, they demonstrate an important principle: understanding the logon sequence reveals numerous insertion points for persistence.
Image File Execution Options (IFEO) is a debugging feature that allows specifying a debugger for any executable. When Windows prepares to launch an executable that has an IFEO entry, it instead launches the specified debugger with the original executable as an argument. This mechanism was designed for developers attaching debuggers to problematic applications, but attackers can abuse it for persistence or hijacking.
HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\
Legitimate use:
┌─────────────────────────────────────────────────────────────────────┐
│ .\notepad.exe\ │
│ Debugger = "C:\Debuggers\windbg.exe" │
│ │
│ When user runs notepad.exe: │
│ └── Windows launches: windbg.exe "notepad.exe" │
│ └── WinDbg starts with notepad.exe as the target │
└─────────────────────────────────────────────────────────────────────┘
Malicious abuse:
┌─────────────────────────────────────────────────────────────────────┐
│ .\sethc.exe\ (Sticky Keys) │
│ Debugger = "C:\Windows\System32\cmd.exe" │
│ │
│ When Shift is pressed 5 times (even at login screen): │
│ └── Windows launches: cmd.exe "sethc.exe" │
│ └── Command prompt appears instead of Sticky Keys dialog │
│ └── If done pre-logon, runs as SYSTEM │
└─────────────────────────────────────────────────────────────────────┘
The classic IFEO abuse targets accessibility features like Sticky Keys (sethc.exe), On-Screen Keyboard (osk.exe), or Narrator (narrator.exe). These programs can be launched from the Windows login screen by pressing specific key combinations. If their IFEO entry points to cmd.exe, an attacker gains a SYSTEM-level command prompt without logging in.
IFEO also supports a GlobalFlag value that enables various debugging features, and a SilentProcessExit subkey that can execute a monitoring program when a process terminates. Both of these provide additional persistence and monitoring capabilities.
Component Object Model (COM) is Windows' technology for software components and inter-process communication. Applications request COM objects by CLSID (Class Identifier), and Windows looks up the implementation in the registry. The lookup order creates a hijacking opportunity: HKCU entries take precedence over HKLM entries.
When an application instantiates a COM object, Windows searches the registry to find the DLL or executable that implements the object:
COM OBJECT RESOLUTION ORDER
Application: "Give me object {BCDE0395-E52F-467C-8E3D-C4579291692E}"
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Step 1: Check HKCU\Software\Classes\CLSID\{...} │
│ │
│ If found: │
│ └── Load InprocServer32 (DLL) or LocalServer32 (EXE) from here │
│ └── This is the ATTACKER-CONTROLLED location │
├─────────────────────────────────────────────────────────────────────┤
│ Step 2: If not in HKCU, check HKLM\Software\Classes\CLSID\{...} │
│ │
│ └── Load the legitimate implementation │
│ └── This is where Microsoft/vendors register COM objects │
└─────────────────────────────────────────────────────────────────────┘
This resolution order means that a standard user, without any administrator privileges, can create an HKCU entry for any COM object. When applications request that object, they'll load the attacker's DLL instead of the legitimate implementation.
Not all COM objects are good hijacking targets. The ideal target is:
Finding suitable targets involves enumerating HKLM COM registrations and checking which lack HKCU equivalents:
Discovery Process:
1. Enumerate all CLSIDs in HKLM\Software\Classes\CLSID
2. For each CLSID, check if HKCU\Software\Classes\CLSID\{same} exists
3. If not in HKCU, it's potentially hijackable
4. Monitor which CLSIDs are actually loaded during normal operation
5. Select targets based on frequency and loading context
Common High-Value Targets:
┌──────────────────────────────────────────┬─────────────────────────┐
│ CLSID │ Description │
├──────────────────────────────────────────┼─────────────────────────┤
│ {BCDE0395-E52F-467C-8E3D-C4579291692E} │ MMDeviceEnumerator │
│ └── Loaded by any app using audio │ (Audio applications) │
├──────────────────────────────────────────┼─────────────────────────┤
│ {0A29FF9E-7F9C-4437-8B11-F424491E3931} │ BITS (Background │
│ └── Loaded during background transfers │ Intelligent Transfer) │
├──────────────────────────────────────────┼─────────────────────────┤
│ {B5F8350B-0548-48B1-A6EE-88BD00B4A5E2} │ TaskFolder │
│ └── Loaded by Explorer │ (Task Scheduler) │
└──────────────────────────────────────────┴─────────────────────────┘
To hijack a COM object, create the HKCU registry structure pointing to your DLL:
Registry Structure:
HKCU\Software\Classes\CLSID\{BCDE0395-E52F-467C-8E3D-C4579291692E}
│
└── InprocServer32
│
├── (Default) = "C:\Users\victim\AppData\payload.dll"
│
└── ThreadingModel = "Both"
Components:
- The CLSID matches the target COM object
- InprocServer32 specifies a DLL to load in-process
- (Default) is the path to your DLL
- ThreadingModel must match or be compatible with the original
When any application loads the hijacked COM object, your DLL's DllMain executes. This provides a reliable trigger without modifying any system files or requiring administrator privileges.
The main limitation is scope: COM hijacking in HKCU only affects processes running as that specific user. It won't provide persistence across different user accounts or for system services.
Type Libraries (TypeLibs) describe COM interfaces for automation and scripting purposes. Like COM objects, TypeLib resolution follows a similar HKCU-before-HKLM pattern, creating another user-level persistence opportunity.
When an application loads a type library, Windows searches the registry for the TypeLib GUID:
TYPELIB RESOLUTION PATH
HKCU\Software\Classes\TypeLib\{GUID}\{Version}\0\win64
│
▼ (if not found)
HKLM\Software\Classes\TypeLib\{GUID}\{Version}\0\win64
Structure:
└── TypeLib
└── {F5078F18-C551-11D3-89B9-0000F81FE221} (TypeLib GUID)
└── 6.0 (Version)
└── 0 (LCID - Language Code ID)
├── win64 (64-bit path)
│ └── (Default) = "C:\path\to\typelib.dll"
└── win32 (32-bit path)
└── (Default) = "C:\path\to\typelib.dll"
TypeLib hijacking is less common than COM hijacking but potentially more stealthy. Fewer security tools specifically monitor TypeLib registrations, and the registry paths are less well-known.
Good TypeLib hijacking targets include libraries loaded by common applications:
| TypeLib GUID | Version | Description | Typical Trigger |
|---|---|---|---|
| {F5078F18-C551-11D3-89B9-0000F81FE221} | 6.0 | MSXML 6.0 | XML processing |
| {00000200-0000-0010-8000-00AA006D2EA4} | 2.8 | ADO 2.8 | Database apps |
| {420B2830-E718-11CF-893D-00A0C9054228} | 1.0 | Script Control | Scripting hosts |
When an application loads the hijacked TypeLib, your DLL's DllMain executes, providing code execution in the context of the loading application.
The .NET Common Language Runtime (CLR) supports profiling interfaces that allow external tools to monitor and instrument .NET applications. This profiling mechanism uses environment variables to specify which profiler DLL to load, creating a unique persistence opportunity that affects all .NET applications on the system.
When any .NET application starts, the CLR checks specific environment variables to determine whether a profiler should be loaded:
CLR PROFILER LOADING PROCESS
Environment Variables:
┌─────────────────────────────────────────────────────────────────────┐
│ COR_ENABLE_PROFILING = 1 │
│ └── Enables the profiling subsystem │
│ │
│ COR_PROFILER = {CLSID} │
│ └── CLSID of the profiler COM object │
│ │
│ COR_PROFILER_PATH = C:\path\to\profiler.dll │
│ └── Direct path to the profiler DLL (bypasses CLSID lookup) │
└─────────────────────────────────────────────────────────────────────┘
Loading Sequence:
┌─────────────────────────────────────────────────────────────────────┐
│ 1. Any .NET application starts │
│ 2. CLR initializes, checks COR_ENABLE_PROFILING │
│ 3. If enabled, loads DLL from COR_PROFILER_PATH │
│ 4. Calls ICorProfilerCallback::Initialize │
│ 5. Profiler now has access to the entire .NET runtime │
└─────────────────────────────────────────────────────────────────────┘
The clever aspect of this technique is that environment variables can be set persistently via the registry. Windows reads environment settings from registry keys and applies them to new processes.
Setting environment variables through the registry makes them persistent across reboots:
User-Level (no admin required):
HKCU\Environment
├── COR_ENABLE_PROFILING = "1"
├── COR_PROFILER = "{any-GUID}"
└── COR_PROFILER_PATH = "C:\Users\...\profiler.dll"
Machine-Level (admin required):
HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\Environment
├── COR_ENABLE_PROFILING = "1"
├── COR_PROFILER = "{any-GUID}"
└── COR_PROFILER_PATH = "C:\Windows\...\profiler.dll"
After setting these values, a WM_SETTINGCHANGE broadcast notifies running applications of the environment change. New processes will automatically inherit the new environment settings.
A CLR profiler DLL must implement the ICorProfilerCallback interface. However, for persistence purposes, the code often runs in DllMain or the Initialize callback and then returns E_FAIL to unload cleanly without actually performing profiling duties:
Minimal Profiler Behavior:
1. DllMain executes when the DLL loads
└── Execute payload here for immediate code execution
2. CLR calls ICorProfilerCallback::Initialize
└── Optionally execute code here
└── Return E_FAIL to indicate "profiler failed to initialize"
3. CLR unloads the profiler since it "failed"
└── Application continues normally
Result: Payload executed, application unaware
This technique is particularly powerful because:
The limitation is that it only affects .NET applications. Pure native code applications don't load the CLR and won't trigger the profiler. However, given the prevalence of .NET in enterprise environments, this still provides broad coverage.
Windows Task Scheduler provides a legitimate way to run programs at specified times or in response to events. Attackers can abuse this for persistence by creating tasks that run their payloads at logon, startup, or on a recurring schedule.
A scheduled task consists of several components that define what runs, when, and with what privileges:
SCHEDULED TASK ANATOMY
┌─────────────────────────────────────────────────────────────────────┐
│ TRIGGER: What causes the task to run │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ At logon (any user or specific user) │ │
│ │ At startup (before logon) │ │
│ │ On schedule (time-based) │ │
│ │ On event (specific Event Log entry) │ │
│ │ On idle (when system becomes idle) │ │
│ │ On workstation lock/unlock │ │
│ └────────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ ACTION: What the task does │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Start a program (most common) │ │
│ │ Send an email (deprecated) │ │
│ │ Display a message (deprecated) │ │
│ └────────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ PRINCIPAL: Who the task runs as │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Current user (limited privileges) │ │
│ │ Specific user account │ │
│ │ NT AUTHORITY\SYSTEM (highest privileges) │ │
│ └────────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ SETTINGS: Behavioral options │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Hidden: Don't show in Task Scheduler UI │ │
│ │ Run whether user is logged on or not │ │
│ │ Run with highest privileges │ │
│ └────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
Scheduled tasks can be created through PowerShell, the command line, or XML task definitions:
Command line approach using schtasks:
schtasks /create /tn "WindowsUpdate" /tr "C:\path\to\payload.exe" /sc onlogon /ru SYSTEM
This creates a task named "WindowsUpdate" that runs as SYSTEM at every user logon. The innocuous name helps avoid suspicion during casual examination.
More sophisticated tasks can be defined using XML, which provides access to all task properties including the hidden flag and complex trigger conditions.
Scheduled tasks are stored in two locations:
Task Definitions:
C:\Windows\System32\Tasks\
└── Each task is an XML file
Registry Metadata:
HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Schedule\TaskCache\
└── Tree (Folder structure)
└── Tasks (Task GUIDs and metadata)
└── Boot (Tasks triggered at boot)
└── Logon (Tasks triggered at logon)
Security tools commonly examine scheduled tasks for persistence. The Hidden attribute only hides tasks from the graphical interface; they're still visible through command-line enumeration and in the file system.
Windows services are programs that run in the background without user interaction, typically starting at boot time. Creating a malicious service provides highly reliable persistence that executes before any user logs in.
Services run under the control of the Service Control Manager (SCM), which starts, stops, and manages their lifecycle:
SERVICE EXECUTION MODEL
Boot Sequence:
┌─────────────────────────────────────────────────────────────────────┐
│ 1. Windows starts │
│ 2. Service Control Manager (services.exe) loads │
│ 3. SCM reads HKLM\SYSTEM\CurrentControlSet\Services │
│ 4. Services with start type "Auto" are started │
│ 5. Services execute before any user logs in │
└─────────────────────────────────────────────────────────────────────┘
Service Types:
┌─────────────────────────────────────────────────────────────────────┐
│ SERVICE_WIN32_OWN_PROCESS (0x10) │
│ └── Service runs in its own dedicated process │
│ │
│ SERVICE_WIN32_SHARE_PROCESS (0x20) │
│ └── Service shares a process with other services (svchost.exe) │
│ │
│ SERVICE_KERNEL_DRIVER (0x01) │
│ └── Service is a kernel driver │
└─────────────────────────────────────────────────────────────────────┘
Creating a service requires administrator privileges and establishes registry entries that define the service's properties:
HKLM\SYSTEM\CurrentControlSet\Services\{ServiceName}
Key Values:
├── Type = 16 (SERVICE_WIN32_OWN_PROCESS)
├── Start = 2 (SERVICE_AUTO_START)
├── ErrorControl = 0 (SERVICE_ERROR_IGNORE)
├── ImagePath = "C:\path\to\service.exe"
├── DisplayName = "Windows Update Service"
├── Description = "Provides support for..."
└── ObjectName = "LocalSystem"
The ImagePath points to the executable that implements the service. The Start value determines when the service runs: 0 (Boot), 1 (System), 2 (Automatic), 3 (Manual), or 4 (Disabled).
A service executable must implement the service control interface, registering a control handler and reporting status to the SCM:
Service Program Flow:
1. main() calls StartServiceCtrlDispatcher()
└── Connects to SCM and provides service entry point
2. SCM calls ServiceMain()
└── Service's main entry point
└── Register control handler with RegisterServiceCtrlHandler()
└── Report SERVICE_RUNNING status
3. Service executes its main logic
└── This is where the payload runs
└── Must handle control requests (stop, pause, etc.)
4. On stop request, report SERVICE_STOPPED
└── Clean up and exit
Services that don't properly implement this interface will be terminated by the SCM. However, the payload can execute in a separate thread while maintaining proper service status reporting in the main thread.
Windows Management Instrumentation (WMI) provides a powerful query-based interface for system management. WMI event subscriptions allow defining persistent triggers that execute actions in response to system events, creating an event-driven persistence mechanism.
A WMI event subscription consists of three linked objects:
WMI EVENT SUBSCRIPTION ARCHITECTURE
┌─────────────────────────────────────────────────────────────────────┐
│ EVENT FILTER │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Defines WHAT triggers the action │ │
│ │ Uses WQL (WMI Query Language) to specify conditions │ │
│ │ │ │
│ │ Example: "SELECT * FROM __InstanceCreationEvent │ │
│ │ WITHIN 60 │ │
│ │ WHERE TargetInstance ISA 'Win32_LogonSession'" │ │
│ │ │ │
│ │ This filter triggers when a new logon session is created │ │
│ └────────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ EVENT CONSUMER │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Defines WHAT action to take │ │
│ │ │ │
│ │ CommandLineEventConsumer: Execute a program │ │
│ │ ActiveScriptEventConsumer: Run VBScript/JScript │ │
│ │ LogFileEventConsumer: Write to a log file │ │
│ │ SMTPEventConsumer: Send an email │ │
│ └────────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────┤
│ FILTER-TO-CONSUMER BINDING │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Links the filter to the consumer │ │
│ │ When filter matches, consumer executes │ │
│ └────────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
WMI filters can respond to virtually any system event queryable through WMI:
| Filter Query | Trigger |
|---|---|
__InstanceCreationEvent ... 'Win32_LogonSession' |
User logon |
__InstanceCreationEvent ... 'Win32_Process' |
Process creation |
__TimerEvent |
Periodic timer |
RegistryKeyChangeEvent |
Registry modification |
Win32_VolumeChangeEvent |
USB device insertion |
The flexibility of WMI queries allows creating very specific trigger conditions that might not be possible with other persistence mechanisms.
WMI subscriptions are stored in the WMI repository, a database separate from the registry and file system:
WMI Repository Location:
C:\Windows\System32\wbem\Repository\
Namespace for subscriptions:
root\subscription
Objects stored:
├── __EventFilter instances
├── __EventConsumer instances (various types)
└── __FilterToConsumerBinding instances
WMI persistence is more difficult to detect than registry-based methods because the storage mechanism is less visible. However, modern security tools increasingly monitor WMI subscription creation through WMI's own eventing or by examining the repository directly.
When a Windows application loads a DLL without specifying a full path, the system searches multiple directories in a defined order. If an attacker can place a malicious DLL earlier in the search path than the legitimate DLL, the malicious version loads instead.
Windows searches for DLLs in this order (with SafeDllSearchMode enabled, the default):
DLL SEARCH ORDER
1. Directory containing the application executable
└── Highest priority, most reliable for hijacking
2. System directory (C:\Windows\System32)
└── Most system DLLs live here
3. 16-bit system directory (C:\Windows\System)
└── Legacy compatibility
4. Windows directory (C:\Windows)
└── Some DLLs here
5. Current working directory
└── Risky if current directory is attacker-controlled
6. Directories in PATH environment variable
└── Application-specific and user directories
Not every DLL is a good hijacking target. The ideal target is:
Process Monitor can identify hijackable DLLs by showing "NAME NOT FOUND" results for DLL load attempts:
Process Monitor Findings:
Process: application.exe
Operation: CreateFile
Path: C:\Program Files\App\version.dll
Result: NAME NOT FOUND
Process: application.exe
Operation: CreateFile
Path: C:\Windows\System32\version.dll
Result: SUCCESS
Analysis:
└── Application looked for version.dll in its own directory first
└── If application directory is writable, we can place our DLL there
└── Our DLL loads instead of the System32 version
Common hijackable DLLs include version.dll, userenv.dll, dbghelp.dll, and various application-specific dependencies.
A hijack DLL must export the same functions as the original to avoid crashing the application. Two approaches exist:
Proxy DLL: Export wrapper functions that forward to the original DLL while also executing payload code.
Minimal Functionality: If the application only uses a subset of the DLL's functions, export those specific functions.
The proxy approach is more reliable but requires more development effort. The minimal approach works but may cause issues if the application uses unexpected functions.
Understanding persistence detection helps both attackers (to avoid discovery) and defenders (to find installed persistence).
Each persistence mechanism has characteristic indicators:
PERSISTENCE DETECTION INDICATORS
Registry-Based:
┌─────────────────────────────────────────────────────────────────────┐
│ Run Keys: Sysmon Event ID 13 (Registry value set) │
│ COM Hijacking: HKCU\Software\Classes\CLSID modifications │
│ IFEO: New keys under Image File Execution Options │
│ CLR Profiler: COR_* environment variables in registry │
└─────────────────────────────────────────────────────────────────────┘
Task/Service-Based:
┌─────────────────────────────────────────────────────────────────────┐
│ Scheduled Tasks: New files in C:\Windows\System32\Tasks │
│ Services: New keys under HKLM\SYSTEM\...\Services │
│ Both visible through built-in enumeration tools │
└─────────────────────────────────────────────────────────────────────┘
WMI-Based:
┌─────────────────────────────────────────────────────────────────────┐
│ Event subscriptions in root\subscription namespace │
│ WMI repository changes │
│ Event ID 19-21 in Microsoft-Windows-Sysmon/Operational log │
└─────────────────────────────────────────────────────────────────────┘
File-Based:
┌─────────────────────────────────────────────────────────────────────┐
│ DLL Hijacking: New DLLs in application directories │
│ Startup Folder: New files/shortcuts │
│ File system monitoring and integrity checking │
└─────────────────────────────────────────────────────────────────────┘
Several tools specifically enumerate persistence mechanisms:
| Tool | Coverage | Access |
|---|---|---|
| Sysinternals Autoruns | Comprehensive | Free |
| Sysmon | Real-time monitoring | Free |
| KAPE | Forensic collection | Commercial |
| Velociraptor | Enterprise hunting | Open source |
Autoruns is particularly useful as it checks dozens of persistence locations and highlights entries not signed by Microsoft or verified publishers.
From an attacker's perspective, stealthier persistence involves:
Persistence mechanisms form the bridge between initial access and long-term presence. Without persistence, every system restart would terminate an attacker's access. With effective persistence, access survives reboots, updates, and even some remediation attempts.
| Technique | Privilege | Detection Risk | Reliability |
|---|---|---|---|
| Run Keys | User/Admin | High (well-monitored) | Very High |
| COM Hijacking | User | Medium | Medium |
| TypeLib Hijacking | User | Low | Medium |
| CLR Profiler | User | Low | Medium |
| Scheduled Tasks | Admin for SYSTEM | High | Very High |
| Services | Admin | High | Very High |
| WMI Subscriptions | Admin | Medium | High |
| DLL Hijacking | Varies | Low | Medium |
The key insight is that Windows provides dozens of legitimate mechanisms for running code automatically, and each of these can be abused for persistence. Defenders must monitor all of them; attackers only need to find one that isn't being watched.
The next chapter explores modern evasion techniques, building on the foundation of persistence to examine how contemporary threats avoid detection while maintaining long-term access.