>_
The Fuzz.
← /blog
SOC

NTUSER.MAN persistence: HKCU writes without a registry API call

A persistence technique that loads keys into HKCU without ever calling a registry API. How the mechanism works, why it's a blind spot for callback-based EDR, and what to actually hunt for.

$ author 0xFuzz·Apr 15, 2026·8 min read
persistenceregistrywindowsedrdfir

Most user-mode persistence on Windows shows up as a registry write. EDR vendors hook CmRegisterCallbackEx and watch the predictable spots: Run keys, AppInit_DLLs, IFEO. The assumption is that getting persistence into HKCU requires touching a registry API, and that touch is observable.

NTUSER.MAN persistence skips the API entirely. The keys still land in HKCU, but they arrive via a file drop, not a RegSetValueEx. Credit for the original writeup goes to Rad Kawar at DeceptIQ (Registry Writes Without Registry Callbacks). This post focuses on the defender side: what's actually in scope to hunt, plus a starter Sigma rule.

How Windows loads HKCU

At logon, the profile loader looks in the target user's profile directory (C:\Users\<name>\) for the hive that becomes HKEY_CURRENT_USER. It checks two filenames in order:

  1. NTUSER.MAN
  2. NTUSER.DAT

If NTUSER.MAN is present, it wins. The hive is mapped as the user's HKCU for the session. This is the legitimate "mandatory profile" feature, originally meant for kiosks and shared workstations. Changes made during the session live in memory only and are discarded at logout.

The relevant detail for this post: the hive contents are loaded by mounting the file. No call to the registry API surface, so EDR callbacks attached to those APIs see exactly nothing for whatever keys are inside.

The blind spot

Registry callbacks are the standard mechanism for HKCU telemetry. If your EDR's persistence detections rely on observing a RegSetValue for HKCU\Software\Microsoft\Windows\CurrentVersion\Run, those detections never fire when the value enters the world via NTUSER.MAN. The hive load itself can be logged (Sysmon Event 11 for the file create, plus kernel hive-load events), but the contents of the hive don't reach the registry-callback channel.

In practice: an attacker with regular user privileges on a machine can drop a crafted hive into their own profile directory, log off, and on next logon their persistence is live with no registry-write trail.

Walkthrough

Worth reproducing in a lab once. The shape:

  1. Export the target user's HKCU as a .reg file. No elevation needed; a user can export their own hive.

    export-hive.cmd
    reg export HKCU C:\Users\Public\hkcu.reg /y
  2. Edit the .reg file to add Run keys, or whatever persistence flavor you're testing.

  3. Convert the .reg back into a binary hive. The deceptiq post points at HiveSwarming. python-registry and libregf can do similar work programmatically.

  4. Write the resulting binary hive to C:\Users\<target>\NTUSER.MAN. The user themselves has write permission on their own profile directory; an attacker who already controls the user account does too.

  5. Log the user off. The active hive is locked while the session is open, so the swap only takes effect on next logon.

  6. On next logon, NTUSER.MAN wins the priority check and becomes HKCU. Anything under Run executes.

This is post-exploitation. It assumes the attacker is already a regular-user-on-the-host. The point isn't initial access, it's that the persistence write step is silent on the registry-callback channel.

What defenders should hunt

The technique relies on a file that has no business existing on a modern machine. Mandatory profiles are a legacy feature. Outside of specifically deployed environments (some VDI, some kiosk setups, niche AD configurations), NTUSER.MAN should not appear in user profile directories.

Concrete hunt items:

  • File creation of NTUSER.MAN in any subdirectory of C:\Users\ outside legitimate mandatory-profile deployments.
  • AD profilePath attribute audits. Legitimate mandatory profiles are configured at the domain level. Inventory once, alert on drift.
  • Roaming profile shares (e.g. \\server\share\profile.v6\) sprouting unexpected .MAN files.
  • Hive load events (Microsoft-Windows-Kernel-General Event ID 16) for hive paths ending in .MAN, correlated with the file-create.
  • Logon events correlated with new NTUSER.MAN files in the logging-on user's profile within a short window before the logon.

If your environment has zero legitimate mandatory profiles (most don't), file-create is a single-stage detection with near-zero false positives. If you do have legitimate ones, allowlist by path once and treat anything outside the list as high-priority.

A starter Sigma rule

file-create-ntuser-man.yml
title: NTUSER.MAN created in user profile directory
id: 7e9a7c2b-3d1e-4b8a-9d2e-1f4b8c0d2a91
status: experimental
description: |
  Detects creation of NTUSER.MAN inside a user profile directory.
  Mandatory profile hives are rare in modern environments; their
  appearance can indicate HKCU persistence that bypasses registry
  callback monitoring.
references:
  - https://deceptiq.com/blog/ntuser-man-registry-persistence
author: 0xFuzz
date: 2026/04/25
logsource:
  product: windows
  category: file_event
detection:
  selection:
    TargetFilename|endswith: '\NTUSER.MAN'
    TargetFilename|contains: '\Users\'
  filter_system_image:
    Image|contains:
      - '\System32\'
      - '\SysWOW64\'
  condition: selection and not filter_system_image
falsepositives:
  - Legitimate mandatory profile deployment (rare; allowlist by path)
  - Admin tooling copying profiles during migration
level: high

Drop that into your detection-as-code pipeline, allowlist legitimate mandatory-profile paths, and it's a single-event high-fidelity catch.

What to test in the lab

Two questions worth answering once with your real EDR in a real test environment:

  1. Does the EDR fire on the file create of NTUSER.MAN?
  2. After logon, when the Run-key payload executes, does the EDR fire on the child process spawned from userinit.exe or the user's shell?

If both answers are yes, you're in good shape. If only (2), you have detection but it's lagged by a logon cycle. If neither, the Sigma rule above (or its equivalent in your stack) is the patch.

References

$ echo "thanks for reading" | tee /dev/null
$ next post

More in SOC