Registry Parser
All articles

Investigating account compromise with the registry

8 min read

The ticket says "unauthorized access." A workstation that should have been idle over the weekend shows activity in the EDR console, and the on-call analyst wants to know whether someone logged in who should not have. That is an account compromise question, and the registry on the host answers a surprising amount of it before you ever touch the domain controller. The four things you need to establish in an unauthorized access investigation are: which local accounts exist on this box, which were actually used, whether any were created or escalated, and whether a persistence mechanism — autologon or a stashed service credential — was wired in. Local account forensics off the SAM, SECURITY and SOFTWARE hives gives you all four, per-host, which is exactly the scope of "this machine was accessed."

This post is the workflow. Each artifact gets a one-line summary and a link to its deep-dive; the value here is the order you work them in and how they correlate. It sits under the broader registry forensics investigations pillar, which maps the same hives to other case types.

The artifacts that matter

  • SAM — local account database. The roster: every local account, its RID, account-control flags, password-last-set, last login, and login count. This is "who can log into this box and what state are they in."
  • SAM group membership. Who sits in Administrators. The escalation answer lives here.
  • ProfileList — SID-to-user mapping. SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList: every SID that ever had a profile on this host, including domain SIDs the SAM cannot see, plus orphaned and .bak profiles.
  • LSA secrets — autologon and service creds. The SECURITY hive's Policy\Secrets: presence and timestamps of DefaultPassword (autologon), DPAPI, and _SC_* service-account secrets.
  • Last-logged-on user. SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon LastLoggedOnUser / LastLoggedOnSAMUser: the account name sitting in the logon UI at last interactive use.
  • UserAssist — per-user program launches. In each user's NTUSER.DAT: GUI program launches with run counts and last-run times. This is how you prove an account was not just present but active.

For the credential-theft angle on SAM specifically — what an attacker does after they have the hive — see T1003.002, SAM hive dumping. This post stays on the investigative side of that line.

A working order

The order matters because each step narrows the next. Do not start with the exciting artifact; start with the roster.

1. Pull the SAM account table. Load SAM and read every RID. Note the well-known set (500 Administrator, 501 Guest, the system-managed accounts in the 500s) and look hard at everything from 1000 up. For each account that matters, read three columns first: the account-control flags, password-last-set, and login count. The flags tell you whether the account is enabled, whether it allows a blank password (Password not required, 0x0004), and whether the password never expires (0x0200). Password-last-set tells you when the account was provisioned or reset — one of the more reliable FILETIMEs in the hive. Login count tells you whether it was ever used. An enabled account in the 1000s with a recent password-set time and a zero login count is a staged backdoor until proven otherwise. The mechanics of decoding the F and V blobs are in the samparse deep-dive; the point here is that this table is your suspect list.

2. Pull SAM group membership. Read the Administrators alias (Builtin\Aliases, RID 0x220 / 544). Cross-reference its members against the table from step 1. An account you flagged in step 1 — zero login count, blank-password-allowed, recently created — that also appears in Administrators is the join you came for: that is creation plus escalation in two artifacts. Be disciplined about what SAM can resolve: local members come back as usernames, but domain SIDs and well-known SIDs stay as raw SID strings, because the SAM hive does not carry domain data.

3. Map the SIDs through ProfileList. Switch to the SOFTWARE hive and read ProfileList. This covers the gap SAM cannot: domain accounts that logged into this host leave a profile here even though they never appear in the SAM Users keys, so ProfileList is where you discover that DOMAIN\contractor had a profile on a machine that should only ever have served one local user. It also surfaces the anomalies — an orphaned profile whose SID resolves to no account, a .bak-suffixed profile (a duplicated SID with a .bak sibling is a known sign of profile tampering), or a ProfileImagePath that does not match its username. Full breakdown in the ProfileList deep-dive.

4. Check the persistence wiring in LSA secrets. Load the SECURITY hive and look at Policy\Secrets. You are not extracting credentials — you are answering two yes/no questions. Is there a DefaultPassword secret? Its presence means autologon was configured, which on an unattended workstation is either a misconfiguration or a deliberate way to guarantee a session on boot; pair it with the DefaultUserName and AutoAdminLogon values under Winlogon. Are there _SC_<service> secrets, and what are their names and timestamps? A service-account secret whose name maps to a service that should not exist, or whose timestamp lines up with the intrusion window, is a lead. Record the secret names and modification timestamps and move on; the LSA secrets deep-dive covers what those secret types are and why the defensive read stops at metadata.

5. Read the last-logged-on user. Back in SOFTWARE, Winlogon\LastLoggedOnUser and LastLoggedOnSAMUser give you the account that was sitting in the logon UI at the last interactive logon. It is a single data point, but it is a fast sanity check against the SAM table and the ProfileList: if the last-logged-on user is an account you flagged in step 1, that is a strong, cheap corroboration that the suspect account was the one actually at the keyboard.

6. Confirm activity with UserAssist. Existence is not use. To prove the suspect account did something, open that user's NTUSER.DAT and read UserAssist. The ROT13-obfuscated entries decode to a list of GUI programs the account launched, with run counts and last-run timestamps. A flagged account whose UserAssist shows it launching a browser, a console, or an archiver inside the intrusion window is no longer a dormant roster entry — it was driven. The decoding and the GUID-folder layout are in the UserAssist deep-dive.

The correlation you are building, end to end: SAM says the account exists and is privileged, ProfileList confirms it had a profile and surfaces anything orphaned, LSA secrets says whether the access was wired to survive a reboot, last-logged-on says it was the account in the chair, and UserAssist says it ran code. Any one of those alone is weak; the chain is what holds up.

Gotchas

SAM is local, full stop. The single most common over-claim in these investigations is presenting a SAM account list as "the users of this environment." SAM knows only the accounts defined on this one host. Domain accounts live in NTDS.dit on a domain controller and never appear in the SAM Users keys. If a domain user touched this box, you find them in ProfileList, the profile keys and the event logs — not in SAM. Scope every SAM finding to the host.

Zero is "never," not "epoch." A FILETIME of zero in the SAM F structure renders empty and means the event has not happened — a never-used account, not one used in 1601. Do not timeline a zero.

Login count is cumulative, not a session log. A high count means routine use; a count of zero on an enabled account is the flag. It does not tell you when — pair it with password-last-set and UserAssist for timing.

.bak profiles cut both ways. A .bak profile can be benign (corrupted then rebuilt) or a sign of SID/profile manipulation. Treat it as a question, and resolve it against the SAM table and the file-system profile path.

LSA secrets need the SYSTEM hive to decrypt — but you do not need to. The secrets are encrypted with material from the SYSTEM bootkey. For an access investigation you almost never need the plaintext; the presence and timestamps of DefaultPassword and _SC_* answer the persistence question on their own. Keep it defensive and you sidestep decryption entirely.

Collect the hives together. SAM, SECURITY, SOFTWARE and the relevant NTUSER.DAT files are a set for this case type. Grabbing SAM alone strands you: no ProfileList, no LSA secrets, no per-user activity. If you are deciding what to acquire, plan for all four.

Work the case in your browser

You can run this entire workflow without a local toolchain. The parser on this site reads SAM, SECURITY, SOFTWARE and NTUSER.DAT client-side — the hives are parsed in the browser and never uploaded — and routes each one to the matching plugins: the SAM account and group tables, ProfileList, LSA secrets metadata, Winlogon, and UserAssist. Drop the four hives, work steps one through six in order, and export the findings with the file hashes for your case notes. Nothing leaves the page, which keeps chain of custody intact for evidence that carries credential material you have no business uploading anywhere.

Related