Registry Run / RunOnce persistence

← persistence index · docs/index

TL;DR

Write the implant's path to one of the four canonical Run / RunOnce registry keys (HKCU + HKLM, persistent + one-shot). Windows launches every value at user logon. HKCU does not need admin; HKLM does. Implements persistence.Mechanism for redundant composition.

Primer

Windows reads four registry keys at logon and launches every value as a process command-line. This is one of the oldest and most documented persistence techniques — and one of the most monitored. Its appeal is the trivial install (single RegSetValueEx) and the no-admin HKCU path: even a limited-token implant can self-restart after every reboot.

Run keys persist across reboots; RunOnce keys self-delete after firing once — useful for first-boot bootstrappers that hand off to a more durable mechanism and then vanish.

How It Works

sequenceDiagram
    participant Impl as "Implant"
    participant Reg as "HKCU\…\Run"
    participant Logon as "User logon"
    participant Bin as "Implant binary"

    Impl->>Reg: RegSetValueEx("IntelGraphicsUpdate",<br>"C:\…\winupdate.exe")
    Note over Logon: Reboot / log off + log on
    Logon->>Reg: RegEnumValue
    Reg-->>Logon: each Run value
    Logon->>Bin: CreateProcess(value as cmdline)

Registry paths:

HiveKeyBehaviourAdmin?
HKCUSoftware\Microsoft\Windows\CurrentVersion\Runpersistent, per-userno
HKCUSoftware\Microsoft\Windows\CurrentVersion\RunOnceone-shot, per-userno
HKLMSoftware\Microsoft\Windows\CurrentVersion\Runpersistent, machine-wideyes
HKLMSoftware\Microsoft\Windows\CurrentVersion\RunOnceone-shot, machine-wideyes

RunOnce self-cleanup happens after launch succeeds — values where the binary is missing or fails to launch stay in the registry, which is itself a forensic tell.

API Reference

type Hive int / type KeyType int

godoc

ConstantMaps to
HiveCurrentUserHKEY_CURRENT_USER
HiveLocalMachineHKEY_LOCAL_MACHINE
KeyRun…\CurrentVersion\Run
KeyRunOnce…\CurrentVersion\RunOnce

Functions

SymbolDescription
Set(hive, keyType, name, value)Write the value; create the key if missing
Get(hive, keyType, name)Read a single value
Delete(hive, keyType, name)Remove the value (idempotent)
Exists(hive, keyType, name)Cheap presence probe
RunKey(hive, keyType, name, value) *RunKeyMechanismMechanism adapter for persistence.InstallAll

Sentinel errors

ErrorTrigger
ErrNotFoundGet / Exists on a value that doesn't exist

Examples

Simple — HKCU install + remove

import "github.com/oioio-space/maldev/persistence/registry"

_ = registry.Set(registry.HiveCurrentUser, registry.KeyRun,
    "IntelGraphicsUpdate", `C:\Users\Public\winupdate.exe`)
defer registry.Delete(registry.HiveCurrentUser, registry.KeyRun,
    "IntelGraphicsUpdate")

Composed — Mechanism + idempotent install

m := registry.RunKey(registry.HiveCurrentUser, registry.KeyRun,
    "IntelGraphicsUpdate", `C:\Users\Public\winupdate.exe`)

if exists, _ := registry.Exists(registry.HiveCurrentUser,
    registry.KeyRun, "IntelGraphicsUpdate"); !exists {
    _ = m.Install()
}

Advanced — hive selection + RunOnce bootstrap

Pick HKLM when the implant has admin, otherwise fall back to HKCU; pair with a RunOnce bootstrap that hands off to a service.

import (
    "github.com/oioio-space/maldev/persistence/registry"
    "github.com/oioio-space/maldev/win/privilege"
)

const (
    name    = "IntelGraphicsCompat"
    payload = `C:\Users\Public\Intel\stage1.exe`
)

hive := registry.HiveCurrentUser
if admin, elevated, _ := privilege.IsAdmin(); admin && elevated {
    hive = registry.HiveLocalMachine
}

if exists, _ := registry.Exists(hive, registry.KeyRun, name); exists {
    return
}
_ = registry.Set(hive, registry.KeyRun, name, payload)
_ = registry.Set(hive, registry.KeyRunOnce, name+"_bootstrap",
    payload+" --bootstrap")

See ExampleSet

OPSEC & Detection

ArtefactWhere defenders look
Sysmon Event 13 (registry value set) under …\RunHigh-fidelity rule on every mature EDR; HKCU\…\Run draws less default coverage than HKLM\…\Run
autoruns.exe Run-key listingSysinternals Autoruns is universal IR triage
Defender ASR rule "Block credential stealing" doesn't apply, but ASR "Block persistence through WMI event subscription" detects siblingsEDR rule library
Value name keyed against known IOC list (payload, update, svchost)Naive YARA-style rules on registry value contents
Binary path under user-writable directories (%TEMP%, %APPDATA%\Local\Temp)Defender heuristic — legitimate Run values target installed-software paths
RegEnumValue / RegOpenKeyEx from non-explorer.exeEDR API telemetry; rare unless tooling explicitly polls Run keys

D3FEND counters:

  • D3-SICA — registry change auditing.
  • D3-SEA — Run-key value content inspection.

Hardening for the operator:

  • Prefer HKCU when current-user scope is sufficient — lower default coverage and no admin prompt.
  • Pick value names that mimic real Run-key values (Adobe Updater, Intel Graphics, Microsoft OneDrive) — pair the binary path with a name + path that match.
  • Drop the binary in %PROGRAMDATA%\Microsoft\…\ rather than %TEMP%.
  • Pair with another mechanism via persistence.InstallAll so loss of the Run key (autoruns.exe -e -accepteula -c cleanup) does not lose persistence.
  • For one-shot bootstrappers, use RunOnce so the registry evidence vanishes on first boot.

MITRE ATT&CK

T-IDNameSub-coverageD3FEND counter
T1547.001Boot or Logon Autostart Execution: Registry Run Keys / Startup Folderfull — Run / RunOnce both supportedD3-SICA, D3-SEA

Limitations

  • Logon trigger only. Run keys fire at user logon, not at boot. For pre-logon execution use persistence/service or persistence/scheduler with a Boot / Startup trigger.
  • HKLM admin requirement. Without admin the operator is HKCU-only.
  • No CWD control. Windows launches Run-key values via CreateProcess with the user's profile as CWD; binaries that depend on a specific CWD must encode it via cd /d in the value or read it from a config.
  • Value-name collision. Two implants writing to the same value name cause silent overwrite — pick distinctive names.
  • Visible to standard tooling. regedit, reg query, PowerShell Get-ItemProperty, and autoruns.exe all surface Run-key values. No way to hide a Run-key entry from a thorough triage.

See also