The sk record: how the registry stores key security descriptors
9 min read
Most people who open a hive never think about who is allowed to read or write a key. They look at the values and move on. But every registry key carries an access control list, and that ACL lives in a dedicated cell type — the sk record, short for "security key". If you want to know who could touch a given key, whether a sensitive key has had its permissions weakened, or how an attacker hid a service from a non-administrator account, the sk record is where the registry security descriptor is stored. RegEdit will show you key permissions through a dialog if you go looking, but it hides the underlying structure entirely, and it certainly will not tell you that one descriptor is shared across ten thousand keys. The on-disk format does.
This post sits in the same series as the regf hive format overview and the nk key node record writeup. If you have not read those, the short version is: a hive is a sequence of HBIN blocks containing typed cells, each cell tagged with a two-byte ASCII signature. nk is the key node, vk is a value, and sk — the subject here — is the security descriptor.
The signature and the shape of the cell
The sk cell starts with the two-byte signature sk (0x73 0x6B). After a two-byte field that is effectively unused, the layout on modern hives (format 1.2 and later) is straightforward and stable enough to rely on:
- Offset 0x00 (2 bytes): the
sksignature. - Offset 0x02 (2 bytes): unused.
- Offset 0x04 (4 bytes): forward link (
flink) — the cell offset of the nextskin the list. - Offset 0x08 (4 bytes): backward link (
blink) — the cell offset of the previoussk. - Offset 0x0C (4 bytes): reference count.
- Offset 0x10 (4 bytes): size of the embedded security descriptor in bytes.
- Offset 0x14: the security descriptor itself, in self-relative form.
All offsets, as everywhere in regf, are relative to the start of the hive bin data area, not the file, and everything is little-endian. There is an older 1.1 variant with a slightly different prefix, but if you are looking at a hive from any Windows in the last fifteen years you will see the layout above. The flink and blink fields are the interesting part, and so is that reference count.
A doubly-linked list, shared by reference
Here is the design decision that makes sk different from every other cell type. A typical Windows hive has hundreds of thousands of keys. The overwhelming majority of them have identical permissions — the same handful of ACLs repeated over and over (Administrators full control, SYSTEM full control, Users read). If each nk stored its own copy of a several-hundred-byte security descriptor, the hive would balloon and the kernel would spend its life copying ACLs around.
So the registry deduplicates. Each unique security descriptor is stored exactly once in an sk cell, and every key that wants those permissions simply points at that one cell. The sk cells are threaded together into a doubly-linked list via the flink/blink offsets, beginning at the descriptor attached to the root key. When the kernel needs to set permissions on a key, it walks this list looking for a descriptor that already matches; if one exists, it reuses it and bumps the reference count, and if none does, it allocates a new sk and splices it into the list.
The reference count at offset 0x0C is exactly that: the number of nk records currently pointing at this descriptor. It is the mechanism that lets the kernel know when an sk can be freed — when the count drops to zero, nothing references it and the cell can be reclaimed. For an analyst, the count is a quick signal. An sk with a reference count of one is a descriptor that applies to a single key. In a hive where nearly everything shares a few common descriptors, a one-off ACL is worth a second look — somebody deliberately set custom permissions on exactly one key.
How an nk finds its sk
Every key node carries a pointer to its security descriptor. In the nk record this is the security cell offset (in the _CM_KEY_NODE layout it sits at offset 0x2C). It holds the cell index of the sk that governs that key. Following it is a single dereference: read the offset, jump to that cell, confirm the sk signature, read the descriptor.
Because the relationship is many-to-one, you cannot reason from the sk back to a single key by looking at the descriptor alone — the cell does not store a list of the keys that reference it, only the count of how many do. To map a descriptor to the keys it governs, you walk the tree, read each nk's security offset, and group keys by the sk they land on. A good parser does this for you and lets you ask "show me every key using this ACL", which is the question that actually matters in an investigation. Every key except the system-managed exit nodes that link hives together has a valid security reference; a key with a missing or dangling sk pointer is a sign of corruption or tampering.
The embedded security descriptor
What sits at offset 0x14 is not a registry-specific structure. It is a standard Windows SECURITY_DESCRIPTOR in self-relative form — the same structure used for files, processes, and every other securable object. Self-relative means every internal pointer is an offset from the start of the descriptor rather than an absolute memory address, which is precisely what you need for something that gets serialized to disk and read back at a different address.
A self-relative descriptor contains, in order: a small fixed header (revision byte and control flags), then four offsets pointing at the four components that follow:
- Owner SID — the security identifier of the key's owner. Owner matters because the owner can always rewrite the DACL regardless of what the DACL currently says.
- Group SID — the primary group; largely vestigial on Windows, present for POSIX-compatibility heritage.
- DACL — the discretionary access control list. This is the one that matters: an ordered list of access control entries (ACEs), each granting or denying a specific set of rights to a specific SID. Read this to answer "who can do what to this key".
- SACL — the system access control list, which specifies what accesses get audited rather than what is allowed. Usually empty unless auditing is configured.
The control flags in the header tell you whether each list is present at all. A DACL that is present but empty denies everyone — a deliberate lockdown. A DACL that is absent (the present flag is clear) means no discretionary restrictions: everyone gets full access. Those two states look superficially similar and mean opposite things; conflating them is the classic mistake when reading descriptors by hand. Parsing ACEs accurately — the type, the flags, the access mask, and the trustee SID — is fiddly enough that you should lean on a library that already handles the edge cases rather than hand-rolling it.
Forensic value
The reason to care about any of this is that ACLs are evidence, and the sk cells are the only place that evidence lives in an offline hive.
Recovering who could access a key. When you need to establish whether a particular account could have read or modified a key — credentials cached under a service key, an autostart entry, a stored configuration blob — the DACL in the referenced sk is the authoritative answer for the state of the hive at acquisition time. No live system required.
Spotting weakened ACLs on sensitive keys. Attackers who want persistence sometimes loosen permissions so a low-privilege account can later modify a key that would normally require administrator rights — or tighten them to hide a key from everyone but SYSTEM. As noted in the regf overview, a service key under HKLM\SYSTEM\CurrentControlSet\Services\ whose descriptor restricts read access to SYSTEM only is a tell: legitimate Microsoft services do not hide themselves that way. The deduplication property sharpens this. Because common keys share common descriptors, a sensitive key whose sk has a reference count of one — a bespoke ACL no other key uses — stands out immediately. That is a key somebody touched on purpose.
Reconstructing intent. Owner SID plus DACL together tell a story. A key owned by a normal user SID under a system hive path, or a DACL granting write access to a SID that has no business being there, is the kind of anomaly that a permissions audit catches and a casual tree walk does not.
RegEdit's permissions dialog will show you a friendly view of one key's ACL at a time on a live system, with SIDs resolved to names by a domain controller that may no longer exist. It will not show you the reference count, the sharing, the raw SIDs, or the descriptor on a dead hive pulled from an image. For offline work you parse the sk cells directly.
Tools
- libregf by Joachim Metz documents the
skrecord layout precisely and parses the embedded descriptor; it is the conservative reference most forensic tooling builds on. - hivex exposes per-node security descriptors through its API, which is convenient when scripting an audit across many keys.
- The parser on this site lets you load a hive in your browser and inspect security descriptors per key with nothing uploaded — useful for a quick check on whether a sensitive key has an ACL that nothing else in the hive shares. For the broader picture of how these cells fit together, start with Windows registry internals.
Further reading
- Google Project Zero, The Windows Registry Adventure #5: The regf format: the deep dive that inspired this series, with the
_CM_KEY_SECURITYstructure and the kernel's view of the security descriptor list. - Joachim Metz, libregf: the format documentation and a battle-tested parser.
- Microsoft's
SECURITY_DESCRIPTORand ACL documentation for the self-relative descriptor and ACE layouts theskcell embeds.
The sk record is easy to skip past — it is not where the values are, and it does not show up in the tree. But it is the one cell that answers a question nothing else in the hive can: not what the key holds, but who was allowed to touch it. On a host where an attacker may have hidden behind permissions, that is the question worth asking.