Registry Parser
All articles

Detecting T1564.001: hidden registry keys and values

8 min read

MITRE ATT&CK catalogs registry concealment under T1564.001, Hide Artifacts: Hidden Files and Directories — and the registry version of it is one of the few defense-evasion tricks that beats the analyst with their own tools. The premise is simple and a little insulting: the attacker writes data the operating system can read perfectly well but the standard inspection tools refuse to display. Hidden registry keys, values with a NULL byte in the name, values that lie about their type — none of these are exotic. They exploit the gap between what the kernel's registry API allows and what RegEdit chooses to render. This post is about closing that gap, because the only reliable way to find these artifacts is to stop trusting the tool that ships with Windows and read the raw hive instead.

This is the registry slice of a broader subject. For the wider mapping of attacker behavior onto registry artifacts, see MITRE ATT&CK and the registry. Here we stay narrow: concealment, why it works, and how to detect it.

How registry artifacts get hidden

There is no single technique. T1564.001 in the registry is a family of tricks that all share one property — the data is fully functional but invisible to casual inspection.

The NULL byte in the name. This is the classic and the most reliable. The Win32 registry API splits into two layers. The high-level API (RegCreateKeyEx, RegSetValueEx) treats names as C strings — NULL-terminated. The native API (NtCreateKey, and the kernel beneath it) treats them as counted Unicode strings with an explicit length, where the NULL character 0x0000 is just another character. Run and Run\0 — three characters versus four — are distinct keys to the kernel. But a tool built on the high-level API reads the name up to the first NULL and stops. It sees Run. It cannot open Run\0, because the request gets truncated back to Run before it ever reaches the kernel. The malicious key is right there in the hive, fully operational and addressable by anything that loads it as a service or autostart entry, yet structurally unreachable by RegEdit. This is the territory tools like RegDelNull exist to address — the artifact cannot be read, edited, or deleted through the normal interface.

Values stored under unexpected types. Covered in depth in registry value types on disk: the on-disk type field is a hint the writer attached, not a contract the kernel enforces. A configuration blob stored as REG_BINARY is illegible to a triage tool that only renders strings. A UTF-16 string stored as REG_NONE shows up as opaque bytes. The data is in plain sight; the type tag is chosen to make the tool render it uselessly.

Very large values. Some tools cap how much of a value they display or export — a few kilobytes, a screenful — and silently truncate the rest. An attacker who stores a payload or staged config in a multi-megabyte value (the registry permits values far larger than anything legitimate code stores there) hides the meaningful bytes past the truncation point. The viewer shows a benign-looking prefix.

Keys placed where analysts do not look. Less a parsing trick than an attention trick. The 32-bit Wow6432Node views of autostart keys, deep service Parameters subkeys, UsrClass.dat rather than NTUSER.DAT, vendor-shaped key paths that blend into legitimate software. The data is plainly visible to any tool — the bet is that the analyst's eye slides past it.

Why native tools miss them

RegEdit is not a forensic tool, and the NULL-byte case shows why most precisely. RegEdit is a Win32 application built on the high-level registry API. When it enumerates subkeys it gets back a list of names, and the moment it tries to operate on a name containing an embedded NULL the string is truncated at that NULL before the request ever reaches the kernel. The result is one of two failure modes: the key either does not appear at all, or it appears but throws an error ("Cannot open: Error while opening key") when you click it, because RegEdit is asking the kernel for a name that no longer matches the real one. You can see the ghost; you cannot touch it. Deletion fails for the same reason — which is the entire point of the technique and the reason the dedicated RegDelNull utility had to exist.

The type and truncation problems are policy choices, not API limits. RegEdit renders REG_BINARY as a hex pane and REG_SZ as text; it has no notion that a REG_NONE value might be a string worth reading, or that a REG_SZ might hold non-text bytes after a deceptively clean prefix. reg.exe query inherits the same high-level-API blindness to NULL-embedded names. Even Sysinternals Autoruns runs against the live API and applies its own "this is benign, hide it" filtering — useful for noise reduction, exactly wrong when the adversary's goal is to look benign.

The common root: every live tool reads the registry through the same kernel interface the attacker is exploiting, and most of them through the high-level layer that the NULL trick specifically defeats. To see what the kernel sees, you have to stop using the kernel as an intermediary.

Detection with a raw-hive parser

A raw-hive parser does not call the registry API at all. It opens the hive file — SYSTEM, SOFTWARE, NTUSER.DAT, UsrClass.dat — as bytes and walks the on-disk structures directly: the regf base block, the hive bins, the cells, the nk key records and vk value records. (The on-disk layout is covered in the regf hive format and hive bins and cells.) Because it reads names as the counted Unicode strings the format actually stores — with an explicit length field — there is no first-NULL truncation. A name is whatever bytes the length says it is, NULLs included.

That changes the detection posture for every variant of T1564.001:

  • NULL byte in the name. Decode each nk/vk name to its full declared length and check for 0x0000 anywhere before the end. Any embedded NULL in a key or value name is, in practice, malicious or corrupt — there is no legitimate reason for it. Flag and render it visibly (as Run\x00 or with the NULL position marked) rather than truncating. This is the single highest-confidence registry-concealment signal there is.
  • Type/data mismatch. Carry both the declared type and the raw bytes, and compare. A REG_DWORD whose data length is not 4. A REG_SZ whose bytes do not decode as valid UTF-16, or that decode cleanly but to a path that ends well before the declared length. A REG_BINARY whose bytes are clean UTF-16. Each divergence is a finding in itself.
  • Oversized values. Surface the declared data length up front. A value in an autostart or configuration key that runs to tens of kilobytes or megabytes is anomalous regardless of type — sort by size and the outliers announce themselves.
  • Unexpected locations. Parse every hive including the 32-bit views and UsrClass.dat, and diff against known-good baselines or VSS snapshots rather than against where you expect to look.

One more reason the raw approach wins: a parser that reads cells directly can also walk unallocated cells — value and key records that were deleted but whose bytes the hive has not yet reused. Concealment and deletion often go together; the raw read recovers both.

Investigate it in your browser

You can analyze the hive in your browser with this tool. It parses the raw regf structures client-side — nothing is uploaded — and surfaces exactly the three layers that concealment exploits: the full key and value names (embedded NULLs rendered, not truncated), the declared type alongside the raw bytes so type/data mismatches are visible side by side, and the declared data length so oversized values sort to the top. Names with embedded NULLs are flagged as findings rather than quietly dropped, which is the behavior RegEdit cannot offer because the API it sits on will not let it. Drop in a SOFTWARE, SYSTEM, or NTUSER.DAT and the artifacts a live tool hides are simply rows in the tree.

Related techniques

T1564.001 rarely travels alone. The hidden key is hiding something, and that something is usually another technique:

  • Persistence. A NULL-named or oddly-typed autostart entry is concealment wrapped around Boot or Logon Autostart Execution — Run keys, IFEO debuggers, service hijacks. Walk the persistence locations with a parser that shows full names and you catch both at once.
  • Deletion / log tampering (T1070). Concealment and post-hoc deletion are two ends of the same goal. The unallocated-cell recovery above is the counter to both.
  • Type abuse as obfuscation. Storing config or staged payloads in misleadingly-typed values overlaps T1564.001 with Obfuscated Files or Information (T1027); the value-types deep dive is the reference for that case.

The through-line for detection is the same one this site keeps returning to: the registry API is the attacker's medium, so do not investigate through the API. Read the bytes. An embedded NULL, a type that contradicts its data, a value too large to be benign — these are invisible to the kernel-mediated tools by design and trivially visible to a parser that treats the hive as what it is, a file on disk. Reference: MITRE ATT&CK T1564.001.