Registry Parser
All articles

The Key Control Block: how the kernel caches the registry

9 min read

When you open a hive in a forensic tool, you are looking at a file. When Windows opens that same hive, it builds a second, parallel representation in kernel memory that the file knows nothing about. The bridge between the two is the Key Control Block, and understanding it is the difference between knowing what a hive is and knowing what the Configuration Manager actually does with it. This is the structure that makes the registry kernel cache work, and it is the reason a dead-box analyst and a live-response analyst are sometimes looking at genuinely different things.

This post is background, not exploitation. If you have not already, the regf hive format post covers what is on disk; here we cover what the kernel layers on top of it at runtime.

The Configuration Manager and what a hive cell is not

The Configuration Manager (Cm) is the kernel component that owns the registry. It is decades-old C code running in kernel space and reachable from user mode through the Nt*Key system calls. When you call RegOpenKeyEx from user mode, you eventually land in NtOpenKey, and the Configuration Manager does the work of resolving the path you asked for.

On disk, a key is a nk cell: a fixed-layout record inside an HBIN, addressed by a cell index, holding a name, a parent pointer, subkey and value list pointers, a security pointer, and a LastWrite timestamp. That is the persisted truth. But a nk cell is passive. It has no notion of "open", no handle count, no fast lookup path, no link to the process reading it. Resolving a path purely from cells would mean walking subkey lists and hashing names on every operation. The registry is one of the hottest data structures in the system; that would be unacceptably slow. So the Configuration Manager does not operate on cells directly during normal access. It operates on Key Control Blocks.

What a Key Control Block is

A Key Control Block (KCB), internally CM_KEY_CONTROL_BLOCK, is the kernel's in-memory descriptor for a single open key. It is created on demand when a key is opened and lives in kernel pool, not in the hive file. There is at most one KCB per key system-wide: before creating a new one, the Configuration Manager checks whether a KCB for that path already exists and reuses it if so. This uniqueness is what makes the KCB the natural place to hang shared, per-key runtime state.

The KCB caches the information the kernel touches most. Critically, it caches where the key lives on disk: a hive pointer (KeyHive, an _HHIVE) and a cell index (KeyCell) that together name the backing nk cell. This is the link that lets the kernel go from "the key the caller wants" back to "the bytes in the hive" without re-walking the tree. It also mirrors frequently read key-node fields so common queries can be answered from the KCB without dereferencing the cell at all.

Two structures sit around the KCB and are worth keeping straight:

  • The key body (CM_KEY_BODY) is the object backing a single open handle. Every successful open produces a key body. Many key bodies can point at one KCB; they are threaded together on a doubly linked list off the KCB. As an optimization, recent kernels keep the first handful of key body pointers in a small inline array on the KCB for fast, lock-light access.
  • The KCB is the shared, per-key state that all those handles converge on.

So the chain is: handle → key body → KCB → (hive + cell index) → nk cell on disk. The first three links exist only in memory. The fourth is the only part that survives a reboot.

The hash table and the fast path

The whole point of the cache is to answer "give me the KCB for \REGISTRY\MACHINE\SOFTWARE\...\Foo" quickly. The Configuration Manager maintains a global hash table for exactly this, organized under a registry root object and keyed by a hash of the key's name path. The hashing is a simple rolling scheme — successive characters folded together with multiplication by powers of 37 — chosen to be cheap and to spread names well across buckets.

When a path is opened, the Configuration Manager hashes it and probes the table. A hit means the key (or a prefix of it) is already resident, and resolution can short-circuit instead of walking subkey lists cell by cell. A miss means the kernel parses forward from the nearest cached ancestor, building KCBs as it goes. The effect is that frequently touched paths — the ones every process hammers at boot and login — collapse to hash lookups rather than tree walks. This is the registry kernel cache doing its job.

Researchers inspect this state with the !reg family of WinDbg extensions against a live kernel or a crash dump. !reg openkeys 0 dumps the entire hash table of open keys; !reg findkcb <path> resolves a path to its KCB if it is cached; !reg kcb <address> formats a specific block; !reg viewlist <hive> shows the mapped views. If a path is not in the table, !reg findkcb will not find it — itself information: nothing currently has it open.

Reference counting and why open handles pin keys

A KCB carries a reference count. It is incremented through the CmpReferenceKeyControlBlock family and decremented through CmpDereferenceKeyControlBlock. Every key body that points at the KCB holds a reference; so do parent/child relationships and various transient operations. While the count is non-zero, the KCB stays resident.

This is the mechanism behind a behavior every Windows administrator has hit: a key (or its hive) that "cannot be deleted" or "is in use" because something has a handle open. The handle keeps a key body alive, the key body keeps a reference on the KCB, and the KCB keeps the key resident and, transitively, the hive's views mapped. The count is wide (historically 16-bit, 64-bit on current systems) and guarded, so practical overflow is not a concern. The kernel also defers some dereferences rather than tearing a KCB down the instant the count hits zero, betting a hot key will be reopened shortly — another caching decision, not a correctness one.

For a forensic reader the takeaway is: residency is driven by handles, not by the data. A key that no one has open may have no KCB at all, even though its nk cell sits perfectly intact in the hive.

Views: 4 KB windows into the hive

The hive file itself does not get loaded wholesale into a flat buffer on modern Windows. Instead the Configuration Manager maps portions of it into kernel address space in views, conventionally page-sized (4 KB) windows, faulted in on demand as cells in those regions are touched. A cell index is resolved to a virtual address by locating the right view and offsetting into it. Cold parts of a large hive may never be mapped during a given boot; hot parts stay pinned. (The mechanics here are version-dependent and have shifted across Windows releases, so treat the 4 KB granularity as the general model rather than a guarantee for every build.) !reg viewlist enumerates the pinned and mapped views for a hive when you want to see which regions are actually live.

The consequence is the same as with KCBs: the in-memory footprint of a hive at runtime is a subset and a transformation of the file, shaped by what has been accessed, not a mirror of it.

Why none of this is in the hive file

Here is the payoff for the analyst. When you acquire a hive — reg save, a VSS copy, carving it from a memory image's mapped pages — you get the persisted regf structure: base block, HBINs, cells, security descriptors, the tree. You do not get:

  • The KCBs. They are kernel pool, not file content.
  • The hash table of open keys. There is no on-disk "what was open" list.
  • Reference counts, key bodies, or the handle-to-key mapping. The fact that a process had a key open at acquisition time leaves no trace in the regf file.
  • The view mapping state. Which 4 KB windows were resident is a runtime property of that boot.

A dead-box examination shows you the persisted state: every key that exists, whatever the last flushed write left behind (replay the transaction logs first — see the hive layout and regf notes). It does not, and cannot, show you the runtime cache: which keys were open, by whom, and how many handles were outstanding. If you need that, you need a memory image and the !reg extensions, not a hive parser. This is a hard boundary, and conflating the two is a real source of overstated conclusions in reports.

It cuts the other way too. Because the cache is just an optimization over the persisted tree, anything you recover from a hive offline is faithful to what the kernel would have served — the KCB never holds key content the cell does not. The cache changes performance and lifetime, not truth. That is exactly why a browser-based offline parser is a legitimate way to read a hive: you can parse a hive in your browser and trust that the tree you see is the tree the Configuration Manager would have reconstructed, minus the runtime bookkeeping that was never yours to recover.

In short

The Configuration Manager builds a Key Control Block for each open key, indexes them in a hash table for fast path resolution, pins them with reference counts driven by open handles, and maps the hive into memory as on-demand 4 KB views. This registry kernel cache is the live bridge between handles and on-disk cells — and it exists only in kernel memory. A hive file preserves the destination of that bridge, never the bridge itself. Know which one you are holding.

Further reading

  • Google Project Zero, The Windows Registry Adventure #4: Hives and the registry layout, and #1: Introduction. The deepest current public treatment of the kernel-side structures; start here if you want the offsets and the code paths.
  • Mark Russinovich, David Solomon, Alex Ionescu, Windows Internals (Microsoft Press): the registry / Configuration Manager sections for the canonical description of KCBs, key bodies, and hive views.
  • Microsoft, !reg debugger extension reference: the openkeys, findkcb, kcb, and viewlist subcommands for inspecting this state in WinDbg.
  • For the broader picture, the Windows registry internals overview ties the on-disk format, the kernel cache, and the forensic artifacts together.