Skip to content
Please update to the latest release 0.77.3 to address Multiple CVEs.
Windows.Forensics.SAM

Windows.Forensics.SAM

Parses the Windows SAM registry hive to extract user account details, RIDs, and password hashes.

Based on Omer Yampel’s parser, as well as reverse-engineering efforts from the GitHub projects fortra/impacket and libyal/winreg-kb.

Although just a single source is provided, Parsed, an extensive SAM binary profile, as well as a number of helper functions, are exported. See the artifact Windows.Forensics.SAM.Enriched for an artifact that presents all available data from SAM.


name: Windows.Forensics.SAM
author: Mike Cohen & Andreas Misje – @misje
description: |
  Parses the Windows SAM registry hive to extract user account
  details, RIDs, and password hashes.

  Based on Omer Yampel's parser, as well as reverse-engineering efforts
  from the GitHub projects fortra/impacket and libyal/winreg-kb.

  Although just a single source is provided, Parsed, an extensive SAM
  binary profile, as well as a number of helper functions, are
  exported. See the artifact `Windows.Forensics.SAM.Enriched` for an
  artifact that presents all available data from SAM.

reference:
  - https://github.com/yampelo/samparser/blob/master/samparser.py
  - https://winreg-kb.readthedocs.io/en/latest/sources/security-accounts-manager-keys/Domains.html
  - https://github.com/fortra/impacket/blob/master/impacket/examples/secretsdump.py

parameters:
   - name: SAMPath
     description: Path to the SAM file to parse.
     default: C:/Windows/System32/Config/SAM

export: |
  LET Profile = '''
  [
    # Reference: https://github.com/yampelo/samparser/blob/master/samparser.py:
    ["F", 0, [
      ["LastLoginDate", 8, "WinFileTime"],
      ["__last_login_raw", 8, "uint64"],
      ["PasswordResetDate", 24, "WinFileTime"],
      ["__password_reset_raw", 24, "uint64"],
      ["PasswordFailDate", 40, "WinFileTime"],
      ["__password_fail_raw", 40, "uint64"],
      ["RID", 48, "uint32"],
      ["PrimaryGroupID", 52, "uint32"],
      ["Flags", 56, "Flags", {
          "type": "uint16",
          "bitmap": {
           "Account Disabled": 0,
           "Home directory required": 1,
           "Password not required": 2,
           "Temporary duplicate account": 3,
           "Normal user account": 4,
           "MNS logon user account": 5,
           "Interdomain trust account": 6,
           "Workstation trust account": 7,
           "Server trust account": 8,
           "Password does not expire": 9,
           "Account auto locked": 10
          }
      }],
      ["FailedLoginCount", 64, "uint16"],
      ["LoginCount", 66, "uint16"],
    ]],
    ["V", 0, [
     ["AccountType", 4, "Enumeration", {
         "type": "uint32",
         "choices": {
            "188": "Default Admin User",
            "244": "Default Admin User",
            "176": "Default Guest Acct",
            "232": "Default Guest Acct",
            "212": "Custom Limited Acct",
            "268": "Custom Limited Acct"
         }
     }],
     ["__username_offset", 12, "uint32"],
     ["__username_length", 16, "uint32"],
     ["username", "x=>x.__username_offset + 0xcc", "String", {
         "length": "x=>x.__username_length",
         "encoding": "utf16",
     }],
     ["__fullname_offset", 24, "uint32"],
     ["__fullname_length", 28, "uint32"],
     ["fullname", "x=>x.__fullname_offset + 0xcc", "String", {
         "length": "x=>x.__fullname_length",
         "encoding": "utf16",
     }],
     ["__comment_offset", 36, "uint32"],
     ["__comment_length", 40, "uint32"],
     ["comment", "x=>x.__comment_offset + 0xcc", "String", {
         encoding: "utf16",
         length: "x=>x.__comment_length",
     }],

     ["__driveletter_offset", 84, "uint32"],
     ["__driveletter_length", 88, "uint32"],
     ["driveletter", "x=>x.__driveletter_offset + 0xcc", "String", {
         encoding: "utf16",
         length: "x=>x.__driveletter_length",
     }],

     ["__logon_script_offset", 96, "uint32"],
     ["__logon_script_length", 100, "uint32"],
     ["logon_script", "x=>x.__logon_script_offset + 0xcc", "String", {
         encoding: "utf16",
         length: "x=>x.__logon_script_length",
     }],

     ["__profile_path_offset", 108, "uint32"],
     ["__profile_path_length", 112, "uint32"],
     ["profile_path", "x=>x.__profile_path_offset + 0xcc", "String", {
         encoding: "utf16",
         length: "x=>x.__profile_path_length",
     }],

     ["__workstation_offset", 120, "uint32"],
     ["__workstation_length", 124, "uint32"],
     ["workstation", "x=>x.__workstation_offset + 0xcc", "String", {
         encoding: "utf16",
         length: "x=>x.__workstation_length",
     }],

     ["__lmpwd_hash_offset", 156, "uint32"],
     ["__lmpwd_hash_length", 160, "uint32"],
     ["lmpwd_hash", "x=>x.__lmpwd_hash_offset + 0xcc", "String", {
         "length": "x=>x.__lmpwd_hash_length",
         "term": "",
     }],

     ["__ntpwd_hash_offset", 168, "uint32"],
     ["__ntpwd_hash_length", 172, "uint32"],
     ["ntpwd_hash", "x=>x.__ntpwd_hash_offset + 0xcc", "String", {
         "length": "x=>x.__ntpwd_hash_length",
         "term": "",
     }]
    ]],

    ["SID", "x=>8 + 4 * x.SubAuthorityCount", [
      ["Revision", 0, "uint8"],
      ["SubAuthorityCount", 1, "uint8"],
      ["IdentifierAuthorityHigh", 2, "uint16be"],
      ["IdentifierAuthorityLow", 4, "uint32be"],
      ["RID", "x=>8 + 4 * (x.SubAuthorityCount - 1)", "uint32"],
      ["SubAuthority", 8, "Array", {
          "type": "uint32",
          "count": "x=>x.SubAuthorityCount",
      }],
    ]],

    ["AliasC", 0, [
      ["RID", 0, "uint32"],
      ["NameOffset", 16, "uint32"],
      ["NameLength", 20, "uint32"],
      ["DescriptionOffset", 28, "uint32"],
      ["DescriptionLength", 32, "uint32"],
      ["MembersOffset", 40, "uint32"],
      ["MembersCount", 48, "uint32"],
      ["Name", "x=>x.NameOffset + 52", "String", {
          "length": "x=>x.NameLength",
          "encoding": "utf16",
      }],
      ["Description", "x=>x.DescriptionOffset + 52", "String", {
          "length": "x=>x.DescriptionLength",
          "encoding": "utf16",
      }],
      ["Members", "x=>x.MembersOffset + 52", "Array", {
          "type": "SID",
          "count": "x=>x.MembersCount",
      }],
    ]],

    # Reference: https://winreg-kb.readthedocs.io/en/latest/sources/security-accounts-manager-keys/Domains.html#groups-sub-key:
    ["GroupC", 0, [
      ["RID", 4, "uint32"],
      ["NameOffset", 32, "uint32"],
      ["NameLength", 36, "uint32"],
      ["DescriptionOffset", 44, "uint32"],
      ["DescriptionLength", 48, "uint32"],
      ["MembersOffset", 56, "uint32"],
      ["MembersCount", 64, "uint32"],
      ["Name", "x=>x.NameOffset + 68", "String", {
          "length": "x=>x.NameLength",
          "encoding": "utf16",
      }],
      ["Description", "x=>x.DescriptionOffset + 68", "String", {
          "length": "x=>x.DescriptionLength",
          "encoding": "utf16",
      }],
      ["Members", "x=>x.MembersOffset + 68", "Array", {
          "type": "uint32",
          "count": "x=>x.MembersCount",
      }],
    ]],

    # Reference: https://github.com/fortra/impacket/blob/master/impacket/examples/secretsdump.py
    # Password properties flags: https://learn.microsoft.com/en-us/windows/win32/api/ntsecapi/ns-ntsecapi-domain_password_information:
    ["DomainF", 0, [
      ["CreationTime", 8, "WinFileTime"],
      ["MaxPasswordAge", 24, "int64"],
      ["MinPasswordAge", 32, "int64"],
      ["LockoutDuration", 48, "int64"],
      ["LockoutObservationWindow", 56, "int64"],
      ["NextRID", 72, "uint32"],
      ["PasswordProperties", 76, "Flags", {
          "type": "uint32",
          "bitmap": {
             "Complex passwords required": 0,
             "No anonymous password change": 1,
             "No clear-text password change": 2,
             "Built-in Administrator can be locked out": 3,
             "Passwords stored in reversible encryption": 4,
             "Machine account password change not required": 5
          }
      }],
      ["MinPasswordLength", 80, "uint16"],
      ["PasswordHistoryLength", 82, "uint16"],
      ["LockoutThreshold", 84, "uint16"],
    ]],

    # Reference: https://winreg-kb.readthedocs.io/en/latest/sources/security-accounts-manager-keys/Domains.html:
    # The last 12 bytes of SAM\Domains\Account's "V" Value (Windows does
    # not store the common prefix "S-1-5-21"):
    ["Uint32Triplet", 0, [
      ["D1", 0, "uint32"],
      ["D2", 4, "uint32"],
      ["D3", 8, "uint32"],
    ]],
  ]
  '''

  // Render a parsed SID object from the profile as the usual "S-1-5-…" form:
  LET FormatSID(SID) = format(
      format='S-%d-%d-%v',
      args=[SID.Revision, SID.IdentifierAuthorityHigh * 4294967296 +
        SID.IdentifierAuthorityLow, join(array=SID.SubAuthority.Value, sep='-')])

  // Universal well-known SIDs and single-RID NT AUTHORITY SIDs, plus the
  // S-1-5-32-* BUILTIN local-group SIDs (source:
  // https://learn.microsoft.com/en-us/windows/win32/secauthz/well-known-sids and
  // https://learn.microsoft.com/en-us/windows-server/identity/ad-ds/manage/understand-security-identifiers):
  LET WellKnownSIDs <= dict(
      // Universal well-known SIDs (SECURITY_NULL/WORLD/LOCAL/CREATOR_SID_AUTHORITY):
      `S-1-0-0`='NULL SID',
      `S-1-1-0`='Everyone',
      `S-1-2-0`='LOCAL',
      `S-1-2-1`='CONSOLE LOGON',
      `S-1-3-0`='CREATOR OWNER',
      `S-1-3-1`='CREATOR GROUP',
      `S-1-3-2`='CREATOR OWNER SERVER',
      `S-1-3-3`='CREATOR GROUP SERVER',
      `S-1-3-4`='OWNER RIGHTS',
      // Single-RID SECURITY_NT_AUTHORITY (S-1-5-x) SIDs:
      `S-1-5-1`='DIALUP',
      `S-1-5-2`='NETWORK',
      `S-1-5-3`='BATCH',
      `S-1-5-4`='INTERACTIVE',
      `S-1-5-6`='SERVICE',
      `S-1-5-7`='ANONYMOUS LOGON',
      `S-1-5-8`='PROXY',
      `S-1-5-9`='ENTERPRISE DOMAIN CONTROLLERS',
      `S-1-5-10`='SELF',
      `S-1-5-11`='Authenticated Users',
      `S-1-5-12`='RESTRICTED CODE',
      `S-1-5-13`='TERMINAL SERVER USER',
      `S-1-5-14`='REMOTE INTERACTIVE LOGON',
      `S-1-5-15`='THIS ORGANIZATION',
      `S-1-5-17`='IUSR',
      `S-1-5-18`='SYSTEM',
      `S-1-5-19`='LOCAL SERVICE',
      `S-1-5-20`='NETWORK SERVICE',
      `S-1-5-33`='WRITE RESTRICTED CODE',
      `S-1-5-99`='RESTRICTED SERVICES',
      `S-1-5-113`='Local account',
      `S-1-5-114`='Local account and member of Administrators group',
      // Two-part fixed SIDs under other NT_AUTHORITY sub-ranges:
      `S-1-5-64-10`='NTLM Authentication',
      `S-1-5-64-14`='SChannel Authentication',
      `S-1-5-64-21`='Digest Authentication',
      `S-1-5-80-0`='NT SERVICE\\ALL SERVICES',
      `S-1-5-83-0`='NT VIRTUAL MACHINE\\Virtual Machines',
      // BUILTIN local-group SIDs (S-1-5-32-<RID>):
      `S-1-5-32-544`='Administrators',
      `S-1-5-32-545`='Users',
      `S-1-5-32-546`='Guests',
      `S-1-5-32-547`='Power Users',
      `S-1-5-32-548`='Account Operators',
      `S-1-5-32-549`='Server Operators',
      `S-1-5-32-550`='Print Operators',
      `S-1-5-32-551`='Backup Operators',
      `S-1-5-32-552`='Replicator',
      `S-1-5-32-554`='Pre-Windows 2000 Compatible Access',
      `S-1-5-32-555`='Remote Desktop Users',
      `S-1-5-32-556`='Network Configuration Operators',
      `S-1-5-32-557`='Incoming Forest Trust Builders',
      `S-1-5-32-558`='Performance Monitor Users',
      `S-1-5-32-559`='Performance Log Users',
      `S-1-5-32-560`='Windows Authorization Access Group',
      `S-1-5-32-561`='Terminal Server License Servers',
      `S-1-5-32-562`='Distributed COM Users',
      `S-1-5-32-568`='IIS_IUSRS',
      `S-1-5-32-569`='Cryptographic Operators',
      `S-1-5-32-571`='Cacheable Principals Group',
      `S-1-5-32-572`='Non-cacheable Principals Group',
      `S-1-5-32-573`='Event Log Readers',
      `S-1-5-32-574`='Certificate Service DCOM Access',
      `S-1-5-32-575`='RDS Remote Access Servers',
      `S-1-5-32-576`='RDS Endpoint Servers',
      `S-1-5-32-577`='RDS Management Servers',
      `S-1-5-32-578`='Hyper-V Administrators',
      `S-1-5-32-579`='Access Control Assistance Operators',
      `S-1-5-32-580`='Remote Management Users',
      `S-1-5-32-581`='System Managed Accounts Group',
      `S-1-5-32-582`='Storage Replica Administrators',
      `S-1-5-32-583`='Device Owners',
      `S-1-5-32-585`='OpenSSH Users')

  // SAM\Domains\Account's own F/V values (the domain object, distinct
  // from any SAM\Domains\Account\Users\<RID>\F or V):
  LET DomainAccount(SAMPath) = SELECT get(field="F") AS DomainF,
                                      get(field="V") AS DomainV
    FROM read_reg_key(globs='SAM\\Domains\\Account',
                      root=pathspec(DelegatePath=SAMPath),
                      accessor="raw_reg")

  // This machine's own account-domain SID, read as the last three
  // sub-authorities of SAM\Domains\Account's "V" value (i.e. excluding
  // the "S-1-5-21" prefix):
  LET MachineSIDSuffixStruct(SAMPath) = SELECT
      parse_binary(accessor="data",
                   filename=DomainV,
                   profile=Profile,
                   struct="Uint32Triplet",
                   offset=stat(filename=DomainV, accessor="data").Size - 12) AS Suffix
    FROM DomainAccount(
      SAMPath=SAMPath)

  LET MachineSIDSuffix(SAMPath) = SELECT Suffix.D1 AS D1,
                                         Suffix.D2 AS D2,
                                         Suffix.D3 AS D3
    FROM MachineSIDSuffixStruct(SAMPath=SAMPath)

  LET MachineSID(SAMPath) = SELECT format(
                                     format='S-1-5-21-%d-%d-%d',
                                     args=[D1, D2, D3]) AS SID
    FROM MachineSIDSuffix(SAMPath=SAMPath)

  // Local usernames, keyed by RID, from SAM\Domains\Account\Users:
  LET LocalUsersByRID(SAMPath) = to_dict(item={
      SELECT str(str=parse_binary(accessor="data",
                                  filename=get(field="F"),
                                  profile=Profile,
                                  struct="F").RID) AS _key,
             parse_binary(accessor="data",
                          filename=get(field="V"),
                          profile=Profile,
                          struct="V").username AS _value
      FROM read_reg_key(globs='SAM\\Domains\\Account\\Users\\0*',
                        root=pathspec(DelegatePath=SAMPath),
                        accessor="raw_reg")
      WHERE get(field="F")
       AND get(field="V")
    })

  // Local group names, keyed by RID, from SAM\Domains\Account\Groups:
  LET LocalGroupsByRID(SAMPath) = to_dict(item={
      SELECT str(str=parse_binary(accessor="data",
                                  filename=get(field="C"),
                                  profile=Profile,
                                  struct="GroupC").RID) AS _key,
             parse_binary(accessor="data",
                          filename=get(field="C"),
                          profile=Profile,
                          struct="GroupC").Name AS _value
      FROM read_reg_key(globs='SAM\\Domains\\Account\\Groups\\0*',
                        root=pathspec(DelegatePath=SAMPath),
                        accessor="raw_reg")
      WHERE get(field="C")
    })

  // Classify a member SID to this machine (the Suffix), returing either
  // - "Local account"
  // - "Domain account" (not found in local accounts)
  // - "Well-known SID": (not as "S-1-5-21" SID):
  LET ClassifyMember(SID, Suffix) = if(
      condition=SID.SubAuthorityCount = 5
       AND SID.SubAuthority[0] = 21,
      then=if(condition=SID.SubAuthority[1] = Suffix.D1
               AND SID.SubAuthority[2] = Suffix.D2
                    AND SID.SubAuthority[3] = Suffix.D3,
              then='Local account',
              else='Domain account'),
      else='Well-known SID')

  // Resolve a member's name, either by user RID, group RID or from well-known SIDs:
  LET ResolveMemberName(SID, Class, UsersByRID, GroupsByRID, KnownSIDs) =
      if(
        condition=Class = 'Local account',
        then=if(
          condition=get(
            item=UsersByRID,
            field=str(
              str=SID.RID)),
          then=get(
            item=UsersByRID,
            field=str(
              str=SID.RID)),
          else=get(
            item=GroupsByRID,
            field=str(
              str=SID.RID))),
        else=if(
          condition=Class = 'Well-known SID',
          then=get(
            item=KnownSIDs,
            field=FormatSID(
              SID=SID))))

  // Every local group ("SAM aliases"):
  LET Aliases(SAMPath) = SELECT
      Key.OSPath.Path AS RegistryKey,
      Key.OSPath.Components[-3] AS GroupDomain,
      parse_binary(accessor="data",
                   filename=get(field="C"),
                   profile=Profile,
                   struct="AliasC") AS Group
    FROM read_reg_key(globs='SAM\\Domains\\*\\Aliases\\0*',
                      root=pathspec(DelegatePath=SAMPath),
                      accessor="raw_reg")
    WHERE get(field="C")

  // Aliases (local groups) joined against their own member SIDs, flattened:
  LET AliasMembers(SAMPath) = SELECT *
    FROM foreach(row=Aliases(SAMPath=SAMPath),
                 query={
      SELECT RegistryKey,
             GroupDomain,
             Group,
             dict(Revision=Revision,
                  SubAuthorityCount=SubAuthorityCount,
                  IdentifierAuthorityHigh=IdentifierAuthorityHigh,
                  IdentifierAuthorityLow=IdentifierAuthorityLow,
                  RID=RID,
                  SubAuthority=SubAuthority) AS Member
      FROM foreach(row=Group.Members)
    })

  LET GroupMembersContext(SAMPath) = SELECT
      D1,
      D2,
      D3,
      LocalUsersByRID(SAMPath=SAMPath) AS UsersByRID,
      LocalGroupsByRID(SAMPath=SAMPath) AS GroupsByRID
    FROM foreach(row=MachineSIDSuffix(SAMPath=SAMPath))

  // Local group membership with enriched information, like usernames:
  LET GroupMembers(SAMPath) = SELECT *
    FROM foreach(
      row=GroupMembersContext(SAMPath=SAMPath),
      query={
      SELECT
      RegistryKey,
      GroupDomain,
      Group.RID AS GroupRID,
      Group.Name AS GroupName,
      Group.Description AS GroupDescription,
      FormatSID(SID=Member) AS MemberSID,
      Member.RID AS MemberRID,
      ClassifyMember(SID=Member, Suffix=dict(D1=D1, D2=D2, D3=D3)) AS MemberClass,
      ResolveMemberName(
        SID=Member,
        Class=ClassifyMember(
          SID=Member,
          Suffix=dict(
            D1=D1,
            D2=D2,
            D3=D3)),
        UsersByRID=UsersByRID,
        GroupsByRID=GroupsByRID,
        KnownSIDs=WellKnownSIDs) AS MemberName
      FROM AliasMembers(
        SAMPath=SAMPath)
    })

  // Global groups:
  LET Groups(SAMPath) = SELECT Key.OSPath.Path AS RegistryKey,
                               ParsedGroup.RID AS GroupRID,
                               ParsedGroup.Name AS GroupName,
                               ParsedGroup.Description AS GroupDescription,
                               ParsedGroup.MembersCount AS MemberCount,
                               ParsedGroup.Members.Value AS MemberRIDs
    FROM foreach(row={
      SELECT Key,
             parse_binary(accessor="data",
                          filename=get(field="C"),
                          profile=Profile,
                          struct="GroupC") AS ParsedGroup
      FROM read_reg_key(globs='SAM\\Domains\\Account\\Groups\\0*',
                        root=pathspec(DelegatePath=SAMPath),
                        accessor="raw_reg")
      WHERE get(field="C")
    })

  // Account/lockout policy and the domain's own creation time from
  // SAM\Domains\Account's "F" value:
  LET AccountPolicy(SAMPath) = SELECT
      parse_binary(accessor="data",
                   filename=DomainF,
                   profile=Profile,
                   struct="DomainF") AS Policy
    FROM DomainAccount(SAMPath=SAMPath)
    WHERE DomainF

precondition:
  SELECT OS From info() where OS = 'windows'

sources:
  - name: Parsed
    query: |
      SELECT
          Key.OSPath.Path AS Key,
          Key.OSPath.DelegatePath AS Hive,
          get(field="F") AS _F,
          get(field="V") AS _V,
          get(field="SupplementalCredentials") AS _SupplementalCredentials,
          parse_binary(accessor="data", filename=F, profile=Profile, struct="F") AS ParsedF,
          parse_binary(
            accessor="data",
            filename=V,
            profile=Profile,
            struct="V") AS ParsedV
      FROM read_reg_key(
        globs='SAM\\Domains\\Account\\Users\\0*',
        root=pathspec(
          DelegatePath=SAMPath),
        accessor="raw_reg")
      WHERE _F
       AND _V

  - name: CreateTimes
    description: "Show the modified times of the \\SAM\\Domains\\Account\\Users\\Names keys"
    query: |
      SELECT Name AS Username,
             Mtime AS CreatedTime
      FROM glob(globs='SAM\\Domains\\Account\\Users\\Names\\*',
                root=pathspec(DelegatePath=SAMPath),
                accessor="raw_reg")
      WHERE Data.type =~ "Key"

column_types:
  - name: _F
    type: hex
  - name: _V
    type: hex
  - name: lmpwd_hash
    type: hex
  - name: ntpwd_hash
    type: hex````