Source code for winregrc.sam

"""Security Accounts Manager (SAM) collector."""

import pyfwnt

from dfdatetime import filetime as dfdatetime_filetime
from dfdatetime import semantic_time as dfdatetime_semantic_time

from winregrc import data_format
from winregrc import errors
from winregrc import interface


[docs] class UserAccount: """User account. Attributes: account_expiration_time (dfdatetime.DateTimeValues): account expiration date and time. codepage (str): code page. comment (str): comment. full_name (str): full name. last_login_time (dfdatetime.DateTimeValues): last log-in date and time. last_password_failure_time (dfdatetime.DateTimeValues): last password failure date and time. last_password_set_time (dfdatetime.DateTimeValues): last password set date and time. name (str): name number_of_logons (int): number of log-ons. number_of_password_failures (int): number of password failures. primary_gid (int): primary group identifier (GID). rid (str): relative identifier (RID). user_account_control_flags (int): user account control flags. user_comment (str): user comment. username (str): username. """
[docs] def __init__(self): """Initializes an user account.""" super().__init__() self.account_expiration_time = None self.codepage = None self.comment = None self.full_name = None self.last_login_time = None self.last_password_failure_time = None self.last_password_set_time = None self.name = None self.number_of_logons = None self.number_of_password_failures = None self.primary_gid = None self.rid = None self.user_account_control_flags = None self.user_comment = None self.username = None
[docs] class SecurityAccountManagerDataParser(data_format.BinaryDataFormat): """Security Accounts Manager (SAM) data parser.""" _DEFINITION_FILE = "sam.yaml" _USER_INFORMATION_DESCRIPTORS = [ "security descriptor", "username", "full name", "comment", "user comment", "unknown1", "home directory", "home directory connect", "script path", "profile path", "workstations", "hours allowed", "unknown2", "LM hash", "NTLM hash", "unknown3", "unknown4", ] _USER_ACCOUNT_CONTROL_FLAGS = { 0x00000001: "USER_ACCOUNT_DISABLED", 0x00000002: "USER_HOME_DIRECTORY_REQUIRED", 0x00000004: "USER_PASSWORD_NOT_REQUIRED", 0x00000008: "USER_TEMP_DUPLICATE_ACCOUNT", 0x00000010: "USER_NORMAL_ACCOUNT", 0x00000020: "USER_MNS_LOGON_ACCOUNT", 0x00000040: "USER_INTERDOMAIN_TRUST_ACCOUNT", 0x00000080: "USER_WORKSTATION_TRUST_ACCOUNT", 0x00000100: "USER_SERVER_TRUST_ACCOUNT", 0x00000200: "USER_DONT_EXPIRE_PASSWORD", 0x00000400: "USER_ACCOUNT_AUTO_LOCKED", 0x00000800: "USER_ENCRYPTED_TEXT_PASSWORD_ALLOWED", 0x00001000: "USER_SMARTCARD_REQUIRED", 0x00002000: "USER_TRUSTED_FOR_DELEGATION", 0x00004000: "USER_NOT_DELEGATED", 0x00008000: "USER_USE_DES_KEY_ONLY", 0x00010000: "USER_DONT_REQUIRE_PREAUTH", 0x00020000: "USER_PASSWORD_EXPIRED", 0x00040000: "USER_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION", 0x00080000: "USER_NO_AUTH_DATA_REQUIRED", 0x00100000: "USER_PARTIAL_SECRETS_ACCOUNT", 0x00200000: "USER_USE_AES_KEYS", } _DEBUG_INFO_C_VALUE = [ ("format_version", "Format version", "_FormatIntegerAsDecimal"), ("unknown1", "Unknown1", "_FormatIntegerAsHexadecimal2"), ("unknown2", "Unknown1", "_FormatIntegerAsHexadecimal4"), ( "security_descriptor_size", "Security descriptor size", "_FormatIntegerAsDecimal", ), ("unknown3", "Unknown1", "_FormatIntegerAsHexadecimal2"), ("unknown4", "Unknown1", "_FormatIntegerAsHexadecimal2"), ("security_descriptor", "Security descriptor", "_FormatSecurityDescriptor"), ] _DEBUG_INFO_F_VALUE = [ ("major_version", "Major version", "_FormatIntegerAsDecimal"), ("minor_version", "Minor version", "_FormatIntegerAsDecimal"), ("unknown1", "Unknown1", "_FormatIntegerAsHexadecimal8"), ("last_login_time", "Last login time", "_FormatIntegerAsFiletime"), ("unknown2", "Unknown2", "_FormatIntegerAsHexadecimal8"), ( "last_password_set_time", "Last password set time", "_FormatIntegerAsFiletime", ), ( "account_expiration_time", "Account expiration time", "_FormatIntegerAsFiletime", ), ( "last_password_failure_time", "Last password failure time", "_FormatIntegerAsFiletime", ), ("rid", "Relative identifier (RID)", "_FormatIntegerAsDecimal"), ("primary_gid", "Primary group identifier (GID)", "_FormatIntegerAsDecimal"), ( "user_account_control_flags", "User account control flags", "_FormatIntegerAsHexadecimal8", ), ("user_account_control_flags", None, "_FormatUserAccountControlFlags"), ("country_code", "Country code", "_FormatIntegerAsHexadecimal4"), ("codepage", "Codepage", "_FormatIntegerAsDecimal"), ( "number_of_password_failures", "Number of password failures", "_FormatIntegerAsDecimal", ), ("number_of_logons", "Number of logons", "_FormatIntegerAsDecimal"), ("unknown6", "Unknown6", "_FormatIntegerAsHexadecimal8"), ("unknown7", "Unknown7", "_FormatIntegerAsHexadecimal8"), ("unknown8", "Unknown8", "_FormatIntegerAsHexadecimal8"), ] def _DebugPrintUserInformationDescriptor( self, index, descriptor, descriptor_data_offset, descriptor_data ): """Prints an user information descriptor. Args: index (int): index of the user information descriptor. descriptor (user_information_descriptor): user information descriptor. descriptor_data_offset (int): offset of the descriptor data relative from the start of the V value data. descriptor_data (bytes): descriptor data. """ descriptor_index = index + 1 self._DebugPrintText(f"User information descriptor: {descriptor_index:d}:\n") value_string = self._USER_INFORMATION_DESCRIPTORS[index] self._DebugPrintValue("Description", value_string) self._DebugPrintValue( "Offset", (f"0x{descriptor.offset:08x} (0x{descriptor_data_offset:08x})") ) self._DebugPrintDecimalValue("Size", descriptor.size) self._DebugPrintValue("Unknown1", f"0x{descriptor.unknown1:08x}") self._DebugPrintData("Data", descriptor_data) # pylint: disable=no-member,using-constant-test def _FormatSecurityDescriptor(self, security_descriptor_data): """Formats security descriptor. Args: security_descriptor_data (bytes): security descriptor data. Returns: str: formatted security descriptor. """ fwnt_descriptor = pyfwnt.security_descriptor() fwnt_descriptor.copy_from_byte_stream(security_descriptor_data) lines = [] if fwnt_descriptor.owner: identifier_string = fwnt_descriptor.owner.get_string() lines.append(f"\tOwner: {identifier_string:s}") if fwnt_descriptor.group: identifier_string = fwnt_descriptor.group.get_string() lines.append(f"\tGroup: {identifier_string:s}") # TODO: format SACL # TODO: format DACL lines.append("") return "\n".join(lines) # pylint: enable=no-member,using-constant-test def _FormatUserAccountControlFlags(self, user_account_control_flags): """Formats user account control flags. Args: user_account_control_flags (int): user account control flags. Returns: str: formatted user account control flags. """ lines = [] if user_account_control_flags: for flag, identifier in sorted(self._USER_ACCOUNT_CONTROL_FLAGS.items()): if flag & user_account_control_flags: lines.append(f"\t{identifier:s} (0x{flag:08x})") lines.append("") lines.append("") return "\n".join(lines)
[docs] def ParseCValue(self, value_data): """Parses the C value data. Args: value_data (bytes): F value data. Raises: ParseError: if the value data could not be parsed. """ data_type_map = self._GetDataTypeMap("c_value") try: c_value = self._ReadStructureFromByteStream( value_data, 0, data_type_map, "C value" ) except (ValueError, errors.ParseError) as exception: raise errors.ParseError( f"Unable to parse C value with error: {exception!s}" ) if self._debug: self._DebugPrintStructureObject(c_value, self._DEBUG_INFO_C_VALUE)
def _ParseFiletime(self, filetime): """Parses a FILETIME timestamp value. Args: filetime (int): a FILETIME timestamp value. Returns: dfdatetime.DateTimeValues: date and time values. """ if filetime == 0: return dfdatetime_semantic_time.SemanticTime(string="Not set") if filetime == 0x7FFFFFFFFFFFFFFF: return dfdatetime_semantic_time.SemanticTime(string="Never") return dfdatetime_filetime.Filetime(timestamp=filetime)
[docs] def ParseFValue(self, value_data, user_account): """Parses the F value data. Args: value_data (bytes): F value data. user_account (UserAccount): user account. Raises: ParseError: if the value data could not be parsed. """ data_type_map = self._GetDataTypeMap("f_value") try: f_value = self._ReadStructureFromByteStream( value_data, 0, data_type_map, "F value" ) except (ValueError, errors.ParseError) as exception: raise errors.ParseError( f"Unable to parse F value with error: {exception!s}" ) # TODO: change FILETIME timestamps into date time values. # date_time = self._ParseFiletime(f_value.last_login_time) user_account.last_login_time = f_value.last_login_time user_account.last_password_set_time = f_value.last_password_set_time user_account.account_expiration_time = f_value.account_expiration_time user_account.last_password_failure_time = f_value.last_password_failure_time user_account.rid = f_value.rid user_account.primary_gid = f_value.primary_gid user_account.user_account_control_flags = f_value.user_account_control_flags user_account.codepage = f_value.codepage user_account.number_of_password_failures = f_value.number_of_password_failures user_account.number_of_logons = f_value.number_of_logons if self._debug: self._DebugPrintStructureObject(f_value, self._DEBUG_INFO_F_VALUE)
[docs] def ParseVValue(self, value_data, user_account): """Parses the V value data. Args: value_data (bytes): V value data. user_account (UserAccount): user account. Raises: ParseError: if the value data could not be parsed. """ data_type_map = self._GetDataTypeMap("v_value") try: v_value = self._ReadStructureFromByteStream( value_data, 0, data_type_map, "V value" ) except (ValueError, errors.ParseError) as exception: raise errors.ParseError( f"Unable to parse V value with error: {exception!s}" ) for index in range(0, 17): user_information_descriptor = v_value[index] data_start_offset = user_information_descriptor.offset + 0xCC data_end_offset = data_start_offset + user_information_descriptor.size descriptor_data = value_data[data_start_offset:data_end_offset] if self._debug: self._DebugPrintUserInformationDescriptor( index, user_information_descriptor, data_start_offset, descriptor_data, ) if index == 0: if self._debug: value_string = self._FormatSecurityDescriptor(descriptor_data) self._DebugPrintText("Security descriptor:\n") self._DebugPrintText(value_string) self._DebugPrintText("\n") elif index == 1: user_account.username = descriptor_data.decode("utf-16-le").rstrip( "\x00" ) if self._debug: self._DebugPrintValue("Username", user_account.username) self._DebugPrintText("\n") elif index == 2: user_account.full_name = descriptor_data.decode("utf-16-le").rstrip( "\x00" ) if self._debug: self._DebugPrintValue("Full name", user_account.full_name) self._DebugPrintText("\n") elif index == 3: user_account.comment = descriptor_data.decode("utf-16-le").rstrip( "\x00" ) if self._debug: self._DebugPrintValue("Comment", user_account.comment) self._DebugPrintText("\n") elif index == 4: user_account.user_comment = descriptor_data.decode("utf-16-le").rstrip( "\x00" ) if self._debug: self._DebugPrintValue("User comment", user_account.user_comment) self._DebugPrintText("\n") if self._debug: self._DebugPrintText("\n")
[docs] class SecurityAccountManagerCollector(interface.WindowsRegistryKeyCollector): """Security Accounts Manager (SAM) collector. Attributes: user_accounts (list[UserAccount]): user accounts. """ _USERS_KEY_PATH = "HKEY_LOCAL_MACHINE\\SAM\\SAM\\Domains\\Account\\Users"
[docs] def __init__(self, debug=False, output_writer=None): """Initializes a Security Accounts Manager (SAM) collector. Args: debug (Optional[bool]): True if debug information should be printed. output_writer (Optional[OutputWriter]): output writer. """ super().__init__(debug=debug) self._parser = SecurityAccountManagerDataParser( debug=debug, output_writer=output_writer ) self.user_accounts = []
[docs] def Collect(self, registry): # pylint: disable=arguments-differ """Collects the Security Accounts Manager (SAM) information. Args: registry (dfwinreg.WinRegistry): Windows Registry. Returns: bool: True if the Security Accounts Manager (SAM) information key was found, False if not. """ main_key = registry.GetKeyByPath("HKEY_LOCAL_MACHINE\\SAM\\SAM") if not main_key: return False c_value = main_key.GetValueByName("C") if c_value: self._parser.ParseCValue(c_value.data) users_key = registry.GetKeyByPath(self._USERS_KEY_PATH) if not users_key: return False for subkey in users_key.GetSubkeys(): if subkey.name == "Names": continue user_account = UserAccount() f_value = subkey.GetValueByName("F") if f_value: self._parser.ParseFValue(f_value.data, user_account) v_value = subkey.GetValueByName("V") if v_value: self._parser.ParseVValue(v_value.data, user_account) self.user_accounts.append(user_account) return True