Source code for winregrc.sam

# -*- coding: utf-8 -*-
"""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(object): """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(UserAccount, self).__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(SecurityAccountManagerCollector, self).__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