Chapter 09

Chapter 9: Persistence Mechanisms

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.


The Persistence Problem

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.

What Makes Good Persistence

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.

Persistence Categories

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.


Registry-Based 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.

Run Keys: The Classic Approach

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.

Winlogon: Shell and Userinit

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

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.


COM Hijacking

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.

Understanding COM Resolution

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.

Identifying Hijackable Objects

Not all COM objects are good hijacking targets. The ideal target is:

  1. Frequently loaded: Ensures the payload executes regularly
  2. Not already in HKCU: Avoids conflicts with existing entries
  3. Loaded by common applications: Provides more trigger opportunities

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)       │
    └──────────────────────────────────────────┴─────────────────────────┘

Installing a COM Hijack

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.


TypeLib Hijacking

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.

TypeLib Resolution

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.

Suitable TypeLib Targets

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.


CLR Profiler Hijacking

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.

The Profiler Mechanism

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.

Registry-Based Environment Variables

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.

Profiler DLL Requirements

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.


Scheduled Tasks

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.

Task Components

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                                   │ │
    │  └────────────────────────────────────────────────────────────────┘ │
    └─────────────────────────────────────────────────────────────────────┘

Creating Persistent Tasks

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.

Task Location and Detection

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

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.

Service Architecture

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                                    │
    └─────────────────────────────────────────────────────────────────────┘

Service Creation

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).

Service Executable Requirements

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.


WMI Event Subscriptions

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.

WMI Subscription Components

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 Persistence Triggers

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.

Storage and Detection

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.


DLL Search Order Hijacking

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.

The Search Order

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

Finding Hijackable DLLs

Not every DLL is a good hijacking target. The ideal target is:

  1. Loaded from a writable location: If the application directory is writable
  2. Missing from early search paths: DLL doesn't exist until later in the search
  3. Loaded consistently: Application always loads this DLL

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.

Hijack DLL Requirements

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.


Detection and Defense

Understanding persistence detection helps both attackers (to avoid discovery) and defenders (to find installed persistence).

Detection Methods

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                     │
    └─────────────────────────────────────────────────────────────────────┘

Detection Tools

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.

Evasion Considerations

From an attacker's perspective, stealthier persistence involves:

  1. Less-monitored locations: TypeLib over COM, CLR Profiler over Run keys
  2. Living off the land: Using legitimate tools (LOLBins) in persistence mechanisms
  3. Blending in: Names and locations that match legitimate software
  4. Temporal patterns: Persistence that only activates under specific conditions

Summary

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.


References

← Back to Wiki