Mapping SIDs to users: the RegRipper profilelist plugin
8 min read
Almost every interesting artifact on a Windows system is keyed by SID, not by username. BAM records execution under a SID. UserAssist lives in a per-user hive that you have to attribute to a SID. SAM stores accounts by RID, which is the tail end of a SID. The string S-1-5-21-3623811015-3361044348-30300820-1013 means nothing in a report. ProfileList is where that string becomes "jdoe", and the RegRipper profilelist plugin is the fastest way to do the SID-to-username translation. If you only run one plugin against a SOFTWARE hive at the start of a case, run this one.
Where it lives
The data sits under one key in the SOFTWARE hive:
SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList
Each subkey under ProfileList is named for a SID. That subkey is created when a profile is first loaded on the box, which in practice means the first interactive (or sometimes service) logon for that account. The presence of a SID subkey here is itself evidence: it says "this account had a profile materialized on this host," which is a stronger statement than "this account exists in SAM."
The values that matter inside each SID subkey are few:
ProfileImagePath: aREG_EXPAND_SZpointing at the profile directory, e.g.C:\Users\jdoe. This is the load-bearing value. The profile folder name is, by overwhelming convention, the username (or a decorated variant of it).Flags: aREG_DWORDof profile attribute bits.Sid: aREG_BINARYcopy of the SID in raw form. Redundant with the key name, but occasionally useful when the key name is mangled.State,RefCount,ProfileLoadTimeLow/High,RunLogonScriptSync, and a scatter of others. Mostly operational; rarely probative.
What the plugin extracts
The profilelist plugin's job is deliberately narrow: enumerate the SID subkeys, pull ProfileImagePath from each, and turn that into a SID-to-name table. The parser on this site implements exactly that. It opens Microsoft\Windows NT\CurrentVersion\ProfileList, walks the subkeys, reads the ProfileImagePath value (case-insensitively), and derives the username by taking the last path segment of the profile path. The output columns are:
SID Username Profile path Key last written (UTC)
S-1-5-21-3623811015-...-1013 jdoe C:\Users\jdoe 2026-06-11 08:14:02
S-1-5-18 systemprofile C:\Windows\System32\config\systemprofile ...
That username column is the deliverable. Note what it is and is not: it is the profile folder name, not an authoritative account name. The two usually match, but not always (see the collision and .bak cases below). When the distinction matters in a report, say "profile path C:\Users\jdoe" rather than asserting the account was literally named jdoe. The folder name is a strong indicator, not proof of the SAM account name. The SAM account name comes from samparse; ProfileList just gives you the human-readable handle for the SID so everything else stops being a wall of digits.
The "key last written" timestamp is the SID subkey's LastWrite. It is not the last logon time and should not be cited as one, but it is a coarse signal that something touched the profile metadata around then.
Reading a SID
The SID structure is worth internalizing because it tells you things the username column does not.
A typical user SID:
S-1-5-21-3623811015-3361044348-30300820-1013
└┬┘ │ └┬┘ └──────────────┬──────────────┘ └─┬┘
│ │ │ │ └─ RID (relative identifier)
│ │ │ └─ machine/domain identifier
│ │ └─ "21" = SECURITY_NT_NON_UNIQUE (a local or domain account authority)
│ └─ "5" = NT Authority
└─ revision
- The block after
S-1-5-21-is the machine identifier for a local account, or the domain identifier for a domain account. Every local account on a given host shares this identifier; every domain account shares the domain's identifier. - The final number is the RID. This is the same RID you see in SAM. RID
500is the built-in Administrator.501is Guest.503is DefaultAccount. Locally created accounts start at1000and climb. Seeing RID500map to a profile folder that is not "Administrator" is a renamed-admin tell worth a note.
Local vs. domain is read directly off the structure: if multiple SID subkeys share the same S-1-5-21-A-B-C middle block, they are local accounts on this machine. If you see several distinct middle blocks, you are looking at accounts from more than one authority — local accounts plus one or more domains. A domain account profile on a workstation is a logon you care about: it means someone with domain credentials sat at, or RDP'd into, this box.
The well-known SIDs
Not every subkey under ProfileList is a person. Three you will see on essentially every host:
S-1-5-18— the LocalSystem account (SYSTEM). ItsProfileImagePathisC:\Windows\System32\config\systemprofile.S-1-5-19— LocalService. Profile underC:\Windows\ServiceProfiles\LocalService.S-1-5-20— NetworkService. Profile underC:\Windows\ServiceProfiles\NetworkService.
These are short, fixed SIDs with no -21- block and no per-machine identifier — that is how you recognize a service identity at a glance. They are not interesting as "users" but they are very interesting as context: an artifact keyed by S-1-5-18 ran as SYSTEM, which is exactly what you would expect from a service-installed payload that, by design, will not show up in UserAssist. Knowing that S-1-5-18 is SYSTEM is what lets you read a BAM/DAM entry filed under that SID and immediately understand the privilege level it executed at.
Orphaned profiles and the .bak tell
This is where ProfileList earns its keep beyond simple translation.
ProfileList entry, no folder. A SID subkey with a ProfileImagePath pointing at a directory that no longer exists on disk indicates a profile that was loaded at some point and later deleted (manually, by a cleanup tool, or by an actor covering tracks). The registry metadata outlives the folder. If your image still has the SOFTWARE hive but the corresponding C:\Users\<name> is gone, ProfileList may be your only record that the account ever had a presence here. Cross-check against SAM: if the SID's RID has no matching SAM entry either, the account itself was deleted, not just its profile.
The .bak subkey. When Windows tries to load a profile and the existing one is in a bad state — or when a SID gets reused or recreated — it can rename the existing SID subkey by appending .bak, e.g. S-1-5-21-...-1013.bak, and create a fresh subkey for the same SID. So a .bak key beside a live key for the same SID is the signature of a profile that was re-created. That is a meaningful event: it can mean a corrupted profile was rebuilt, a roaming-profile sync went wrong, or — in the cases that matter to us — that an account was deleted and a new account was created and assigned the same RID, inheriting the same SID. The two profiles may point at different folders (C:\Users\jdoe vs C:\Users\jdoe.DOMAIN or jdoe.000). When the username column disagrees between the live key and its .bak, treat it as two distinct human identities that happen to collide on one SID.
A caution on tooling: the value-extraction this site's parser does focuses on the ProfileImagePath mapping. It enumerates whatever subkeys are present — so a .bak key shows up as its own row — but it does not, as written, decode the Flags bitmask or diff a SID against its .bak twin for you. Treat the .bak correlation as analyst work: scan the SID column for any name ending in .bak, then line it up against the base SID by eye. RegRipper's Perl profilelist.pl behaves similarly — it reports the path per SID; the interpretation is yours.
Folder-name collisions
Because the username column is just the profile folder name, two different SIDs can present the same-looking username with different folders: jdoe and jdoe.CORP. Windows decorates the folder when a name would collide — a local jdoe and a domain CORP\jdoe cannot both own C:\Users\jdoe, so the second one gets a suffix. If you see a base name and a suffixed variant, you are looking at two accounts (one local, one domain, per the SID middle block), not one. Reporting them as the same user is a real attribution error.
Tools
- RegRipper's
profilelistplugin (source). Run it against the SOFTWARE hive. It prints, per SID, the path and (depending on version) the LastWrite and Flags. It is the canonical SID-to-username reference and the first thing to run when you crack open a SOFTWARE hive. - Eric Zimmerman's RECmd with a ProfileList batch file produces the same mapping as CSV, convenient for joining against BAM and SAM output in a spreadsheet.
- You can analyze a SOFTWARE hive in your browser here — the
profilelistview gives you the SID, derived username, profile path, and key LastWrite directly, no upload, no install. - See the full RegRipper plugins reference for the other SOFTWARE- and SYSTEM-hive plugins you will want alongside this one.
Why it is the Rosetta Stone
Build your ProfileList table first, then everything else decodes against it. BAM hands you execution under a SID — join to ProfileList and you have "jdoe ran this." SAM hands you a RID and logon counts — strip the RID off the SID and you reconcile the two. UserAssist hands you a per-user hive that you have to attribute — match the NTUSER.DAT's owning SID to a ProfileList row. Without ProfileList you are reading a case in machine code. With it, every other artifact gets a name attached. It does almost nothing on its own, and it makes almost everything else legible.