def _get_data_subblocks(name): """Return Adapter to parse GIF data sub-blocks.""" return construct.ExprAdapter( construct.Struct( name, construct.RepeatUntil( lambda obj, ctx: obj.block_size == 0x00, construct.Struct( 'blocks', construct.ULInt8('block_size'), construct.Bytes('data_values', lambda ctx: ctx.block_size), ), ), ), # from comment string, build Containers encoder=lambda obj, ctx: construct.Container(blocks=[ construct.Container( block_size=len(chunk), data_values=chunk, ) for chunk in [obj[i:i + 255] for i in xrange(0, len(obj), 255)] ] + [construct.Container(block_size=0, data_values='')], ), # from Containers, build comment string decoder=lambda obj, ctx: ''.join(dsb.data_values for dsb in obj.blocks), )
class MRUListExStringAndShellItemListPlugin( interface.KeyPlugin, MRUListExPluginMixin): """Windows Registry plugin to parse a string and shell item list MRUListEx.""" NAME = 'winreg_mrulistex_string_and_shell_item_list' DESCRIPTION = u'Parser for Most Recently Used (MRU) Registry data.' REG_TYPE = 'any' REG_KEYS = frozenset([ (u'\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ComDlg32\\' u'LastVisitedPidlMRU')]) _STRING_AND_SHELL_ITEM_LIST_STRUCT = construct.Struct( 'string_and_shell_item', construct.RepeatUntil( lambda obj, ctx: obj == '\x00\x00', construct.Field('string', 2)), construct.Anchor('shell_item_list')) def _ParseMRUListExEntryValue( self, parser_context, key, entry_index, entry_number, text_dict, codepage='cp1252', **unused_kwargs): """Parses the MRUListEx entry value. Args: parser_context: A parser context object (instance of ParserContext). key: the Registry key (instance of winreg.WinRegKey) that contains the MRUListEx value. entry_index: integer value representing the MRUListEx entry index. entry_number: integer value representing the entry number. text_dict: text dictionary object to append textual strings. codepage: Optional extended ASCII string codepage. The default is cp1252. """ value_string = u'' value = key.GetValue(u'{0:d}'.format(entry_number)) if value is None: logging.debug( u'[{0:s}] Missing MRUListEx entry value: {1:d} in key: {2:s}.'.format( self.NAME, entry_number, key.path)) elif not value.DataIsBinaryData(): logging.debug(( u'[{0:s}] Non-binary MRUListEx entry value: {1:d} in key: ' u'{2:s}.').format(self.NAME, entry_number, key.path)) elif value.data: value_struct = self._STRING_AND_SHELL_ITEM_LIST_STRUCT.parse(value.data) try: # The struct includes the end-of-string character that we need # to strip off. path = ''.join(value_struct.string).decode('utf16')[:-1] except UnicodeDecodeError as exception: logging.warning(( u'[{0:s}] Unable to decode string MRUListEx entry value: {1:d} ' u'in key: {2:s} with error: {3:s}').format( self.NAME, entry_number, key.path, exception)) path = u'' if path: shell_item_list_data = value.data[value_struct.shell_item_list:] if not shell_item_list_data: logging.debug(( u'[{0:s}] Missing shell item in MRUListEx entry value: {1:d}' u'in key: {2:s}').format(self.NAME, entry_number, key.path)) value_string = u'Path: {0:s}'.format(path) else: shell_items_parser = shell_items.ShellItemsParser(key.path) shell_items_parser.Parse( parser_context, shell_item_list_data, codepage=codepage) value_string = u'Path: {0:s}, Shell item list: [{1:s}]'.format( path, shell_items_parser.CopyToPath()) value_text = u'Index: {0:d} [MRU Value {1:d}]'.format( entry_index + 1, entry_number) text_dict[value_text] = value_string def GetEntries( self, parser_context, key=None, registry_type=None, codepage='cp1252', **unused_kwargs): """Extract event objects from a Registry key containing a MRUListEx value. Args: parser_context: A parser context object (instance of ParserContext). key: Optional Registry key (instance of winreg.WinRegKey). The default is None. registry_type: Optional Registry type string. The default is None. codepage: Optional extended ASCII string codepage. The default is cp1252. """ self._ParseMRUListExKey( parser_context, key, registry_type=registry_type, codepage=codepage)
class MRUListExStringPlugin(interface.ValuePlugin, MRUListExPluginMixin): """Windows Registry plugin to parse a string MRUListEx.""" NAME = 'winreg_mrulistex_string' DESCRIPTION = u'Parser for Most Recently Used (MRU) Registry data.' REG_TYPE = 'any' REG_VALUES = frozenset(['MRUListEx', '0']) URLS = [ u'http://forensicartifacts.com/2011/02/recentdocs/', u'https://code.google.com/p/winreg-kb/wiki/MRUKeys'] _STRING_STRUCT = construct.Struct( 'string_and_shell_item', construct.RepeatUntil( lambda obj, ctx: obj == '\x00\x00', construct.Field('string', 2))) def _ParseMRUListExEntryValue( self, parser_context, key, entry_index, entry_number, text_dict, **unused_kwargs): """Parses the MRUListEx entry value. Args: parser_context: A parser context object (instance of ParserContext). key: the Registry key (instance of winreg.WinRegKey) that contains the MRUListEx value. entry_index: integer value representing the MRUListEx entry index. entry_number: integer value representing the entry number. text_dict: text dictionary object to append textual strings. """ value_string = u'' value = key.GetValue(u'{0:d}'.format(entry_number)) if value is None: logging.debug( u'[{0:s}] Missing MRUListEx entry value: {1:d} in key: {2:s}.'.format( self.NAME, entry_number, key.path)) elif value.DataIsString(): value_string = value.data elif value.DataIsBinaryData(): logging.debug(( u'[{0:s}] Non-string MRUListEx entry value: {1:d} parsed as string ' u'in key: {2:s}.').format(self.NAME, entry_number, key.path)) utf16_stream = binary.ByteStreamCopyToUtf16Stream(value.data) try: value_string = utf16_stream.decode('utf-16-le') except UnicodeDecodeError as exception: value_string = binary.HexifyBuffer(utf16_stream) logging.warning(( u'[{0:s}] Unable to decode UTF-16 stream: {1:s} in MRUListEx entry ' u'value: {2:d} in key: {3:s} with error: {4:s}').format( self.NAME, value_string, entry_number, key.path, exception)) value_text = u'Index: {0:d} [MRU Value {1:d}]'.format( entry_index + 1, entry_number) text_dict[value_text] = value_string def GetEntries( self, parser_context, key=None, registry_type=None, codepage='cp1252', **unused_kwargs): """Extract event objects from a Registry key containing a MRUListEx value. Args: parser_context: A parser context object (instance of ParserContext). key: Optional Registry key (instance of winreg.WinRegKey). The default is None. registry_type: Optional Registry type string. The default is None. codepage: Optional extended ASCII string codepage. The default is cp1252. """ self._ParseMRUListExKey( parser_context, key, registry_type=registry_type, codepage=codepage) def Process(self, parser_context, key=None, codepage='cp1252', **kwargs): """Determine if we can process this Registry key or not. Args: parser_context: A parser context object (instance of ParserContext). key: A Windows Registry key (instance of WinRegKey). codepage: Optional extended ASCII string codepage. The default is cp1252. """ # Prevent this plugin triggering on sub paths of non-string MRUListEx # values. if (u'BagMRU' in key.path or u'Explorer\\StreamMRU' in key.path or u'\\Explorer\\ComDlg32\\OpenSavePidlMRU' in key.path): return super(MRUListExStringPlugin, self).Process( parser_context, key=key, codepage=codepage, **kwargs)
class MRUListExStringAndShellItemListPlugin(BaseMRUListExPlugin): """Windows Registry plugin to parse a string and shell item list MRUListEx.""" NAME = u'mrulistex_string_and_shell_item_list' DESCRIPTION = u'Parser for Most Recently Used (MRU) Registry data.' FILTERS = frozenset([ interface.WindowsRegistryKeyPathFilter( u'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\' u'Explorer\\ComDlg32\\LastVisitedPidlMRU') ]) _STRING_AND_SHELL_ITEM_LIST_STRUCT = construct.Struct( u'string_and_shell_item', construct.RepeatUntil(lambda obj, ctx: obj == b'\x00\x00', construct.Field(u'string', 2)), construct.Anchor(u'shell_item_list')) def _ParseMRUListExEntryValue(self, parser_mediator, key, entry_index, entry_number, codepage=u'cp1252', **unused_kwargs): """Parses the MRUListEx entry value. Args: parser_mediator: A parser mediator object (instance of ParserMediator). key: the Registry key (instance of dfwinreg.WinRegistryKey) that contains the MRUListEx value. entry_index: integer value representing the MRUListEx entry index. entry_number: integer value representing the entry number. codepage: Optional extended ASCII string codepage. Returns: A string containing the value. """ value_string = u'' value = key.GetValueByName(u'{0:d}'.format(entry_number)) if value is None: logging.debug( u'[{0:s}] Missing MRUListEx entry value: {1:d} in key: {2:s}.'. format(self.NAME, entry_number, key.path)) elif not value.DataIsBinaryData(): logging.debug( (u'[{0:s}] Non-binary MRUListEx entry value: {1:d} in key: ' u'{2:s}.').format(self.NAME, entry_number, key.path)) elif value.data: value_struct = self._STRING_AND_SHELL_ITEM_LIST_STRUCT.parse( value.data) try: # The struct includes the end-of-string character that we need # to strip off. path = b''.join(value_struct.string).decode(u'utf16')[:-1] except UnicodeDecodeError as exception: logging.warning(( u'[{0:s}] Unable to decode string MRUListEx entry value: {1:d} ' u'in key: {2:s} with error: {3:s}').format( self.NAME, entry_number, key.path, exception)) path = u'' if path: shell_item_list_data = value.data[value_struct. shell_item_list:] if not shell_item_list_data: logging.debug(( u'[{0:s}] Missing shell item in MRUListEx entry value: {1:d}' u'in key: {2:s}').format(self.NAME, entry_number, key.path)) value_string = u'Path: {0:s}'.format(path) else: shell_items_parser = shell_items.ShellItemsParser(key.path) shell_items_parser.ParseByteStream(parser_mediator, shell_item_list_data, codepage=codepage) value_string = u'Path: {0:s}, Shell item path: {1:s}'.format( path, shell_items_parser.CopyToPath()) return value_string def GetEntries(self, parser_mediator, registry_key, codepage=u'cp1252', **kwargs): """Extract event objects from a Registry key containing a MRUListEx value. Args: parser_mediator: A parser mediator object (instance of ParserMediator). registry_key: A Windows Registry key (instance of dfwinreg.WinRegistryKey). codepage: Optional extended ASCII string codepage. """ self._ParseMRUListExKey(parser_mediator, registry_key, codepage=codepage)
class MRUListExStringPlugin(BaseMRUListExPlugin): """Windows Registry plugin to parse a string MRUListEx.""" NAME = u'mrulistex_string' DESCRIPTION = u'Parser for Most Recently Used (MRU) Registry data.' FILTERS = frozenset([MRUListExStringRegistryKeyFilter()]) URLS = [ u'http://forensicartifacts.com/2011/02/recentdocs/', u'https://github.com/libyal/winreg-kb/wiki/MRU-keys' ] _STRING_STRUCT = construct.Struct( u'string_and_shell_item', construct.RepeatUntil(lambda obj, ctx: obj == b'\x00\x00', construct.Field(u'string', 2))) def _ParseMRUListExEntryValue(self, parser_mediator, key, entry_index, entry_number, **unused_kwargs): """Parses the MRUListEx entry value. Args: parser_mediator: A parser mediator object (instance of ParserMediator). key: the Registry key (instance of dfwinreg.WinRegistryKey) that contains the MRUListEx value. entry_index: integer value representing the MRUListEx entry index. entry_number: integer value representing the entry number. Returns: A string containing the value. """ value_string = u'' value = key.GetValueByName(u'{0:d}'.format(entry_number)) if value is None: logging.debug( u'[{0:s}] Missing MRUListEx entry value: {1:d} in key: {2:s}.'. format(self.NAME, entry_number, key.path)) elif value.DataIsString(): value_string = value.GetDataAsObject() elif value.DataIsBinaryData(): logging.debug(( u'[{0:s}] Non-string MRUListEx entry value: {1:d} parsed as string ' u'in key: {2:s}.').format(self.NAME, entry_number, key.path)) utf16_stream = binary.ByteStreamCopyToUTF16Stream(value.data) try: value_string = utf16_stream.decode(u'utf-16-le') except UnicodeDecodeError as exception: value_string = binary.HexifyBuffer(utf16_stream) logging.warning(( u'[{0:s}] Unable to decode UTF-16 stream: {1:s} in MRUListEx entry ' u'value: {2:d} in key: {3:s} with error: {4:s}').format( self.NAME, value_string, entry_number, key.path, exception)) return value_string def GetEntries(self, parser_mediator, registry_key, codepage=u'cp1252', **kwargs): """Extract event objects from a Registry key containing a MRUListEx value. Args: parser_mediator: A parser mediator object (instance of ParserMediator). registry_key: A Windows Registry key (instance of dfwinreg.WinRegistryKey). codepage: Optional extended ASCII string codepage. """ self._ParseMRUListExKey(parser_mediator, registry_key, codepage=codepage)
class RestorePointLogParser(interface.BaseParser): """A parser for Windows Restore Point (rp.log) files.""" NAME = u'rplog' DESCRIPTION = u'Parser for Windows Restore Point (rp.log) files.' _FILE_HEADER_STRUCT = construct.Struct( u'file_header', construct.ULInt32(u'event_type'), construct.ULInt32(u'restore_point_type'), construct.ULInt64(u'sequence_number'), construct.RepeatUntil(lambda obj, ctx: obj == b'\x00\x00', construct.Field(u'description', 2))) _FILE_FOOTER_STRUCT = construct.Struct(u'file_footer', construct.ULInt64(u'creation_time')) def _ParseFileHeader(self, file_object): """Parses the file header. Args: file_object: A file-like object to read data from. Returns: The file header construct object. """ try: file_header = self._FILE_HEADER_STRUCT.parse_stream(file_object) except (IOError, construct.FieldError) as exception: raise errors.UnableToParseFile( u'Unable to parse file header with error: {0:s}'.format( exception)) if not file_header: raise errors.UnableToParseFile(u'Unable to read file header') return file_header def _ParseFileFooter(self, file_object): """Parses the file footer. Args: file_object: A file-like object to read data from. Returns: The file footer construct object. """ try: file_footer = self._FILE_FOOTER_STRUCT.parse_stream(file_object) except (IOError, construct.FieldError) as exception: raise errors.UnableToParseFile( u'Unable to parse file footer with error: {0:s}'.format( exception)) if not file_footer: raise errors.UnableToParseFile(u'Unable to read file footer') return file_footer def Parse(self, parser_mediator, **kwargs): """Parses a single file. Args: parser_mediator: A parser mediator object (instance of ParserMediator). """ # TODO: for now cheat with detecting this file type. file_entry = parser_mediator.GetFileEntry() if file_entry.name.lower() != u'rp.log': raise errors.UnableToParseFile(u'File not named: rp.log') file_object = parser_mediator.GetFileObject(offset=None) try: self.ParseFileObject(parser_mediator, file_object, **kwargs) finally: file_object.close() def ParseFileObject(self, parser_mediator, file_object, **unused_kwargs): """Parses a Windows Prefetch file-like object. Args: parser_mediator: A parser mediator object (instance of ParserMediator). file_object: A file-like object. Raises: UnableToParseFile: when the file cannot be parsed. """ file_object.seek(0, os.SEEK_SET) file_header = self._ParseFileHeader(file_object) try: # The struct includes the end-of-string character that we need # to strip off. description = b''.join( file_header.description).decode(u'utf16')[:-1] except UnicodeDecodeError as exception: description = u'' logging.warning( (u'[{0:s}] Unable to decode description UTF-16 stream with ' u'error: {1:s}').format(self.NAME, exception)) file_object.seek(-8, os.SEEK_END) file_footer = self._ParseFileFooter(file_object) timestamp = file_footer.get(u'creation_time', 0) if not timestamp: parser_mediator.ProduceParseError(u'Timestamp not set.') else: event_object = RestorePointInfoEvent( timestamp, file_header.event_type, file_header.restore_point_type, file_header.sequence_number, description) parser_mediator.ProduceEvent(event_object)
class MRUListExStringAndShellItemListPlugin(BaseMRUListExPlugin): """Windows Registry plugin to parse a string and shell item list MRUListEx.""" NAME = 'mrulistex_string_and_shell_item_list' DESCRIPTION = 'Parser for Most Recently Used (MRU) Registry data.' FILTERS = frozenset([ interface.WindowsRegistryKeyPathFilter( 'HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\' 'Explorer\\ComDlg32\\LastVisitedPidlMRU')]) _STRING_AND_SHELL_ITEM_LIST_STRUCT = construct.Struct( 'string_and_shell_item', construct.RepeatUntil( lambda obj, ctx: obj == b'\x00\x00', construct.Field('string', 2)), construct.Anchor('shell_item_list')) # pylint: disable=arguments-differ def _ParseMRUListExEntryValue( self, parser_mediator, registry_key, entry_index, entry_number, codepage='cp1252', **kwargs): """Parses the MRUListEx entry value. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. registry_key (dfwinreg.WinRegistryKey): Windows Registry key that contains the MRUListEx value. entry_index (int): MRUListEx entry index. entry_number (int): entry number. codepage (Optional[str]): extended ASCII string codepage. Returns: str: MRUList entry value. """ value_string = '' value = registry_key.GetValueByName('{0:d}'.format(entry_number)) if value is None: logger.debug( '[{0:s}] Missing MRUListEx entry value: {1:d} in key: {2:s}.'.format( self.NAME, entry_number, registry_key.path)) elif not value.DataIsBinaryData(): logger.debug(( '[{0:s}] Non-binary MRUListEx entry value: {1:d} in key: ' '{2:s}.').format(self.NAME, entry_number, registry_key.path)) elif value.data: value_struct = self._STRING_AND_SHELL_ITEM_LIST_STRUCT.parse(value.data) try: # The struct includes the end-of-string character that we need # to strip off. path = b''.join(value_struct.string).decode('utf16')[:-1] except UnicodeDecodeError as exception: logger.warning(( '[{0:s}] Unable to decode string MRUListEx entry value: {1:d} ' 'in key: {2:s} with error: {3!s}').format( self.NAME, entry_number, registry_key.path, exception)) path = '' if path: shell_item_list_data = value.data[value_struct.shell_item_list:] if not shell_item_list_data: logger.debug(( '[{0:s}] Missing shell item in MRUListEx entry value: {1:d}' 'in key: {2:s}').format( self.NAME, entry_number, registry_key.path)) value_string = 'Path: {0:s}'.format(path) else: shell_items_parser = shell_items.ShellItemsParser(registry_key.path) shell_items_parser.ParseByteStream( parser_mediator, shell_item_list_data, codepage=codepage) value_string = 'Path: {0:s}, Shell item path: {1:s}'.format( path, shell_items_parser.CopyToPath()) return value_string # pylint: disable=arguments-differ def ExtractEvents( self, parser_mediator, registry_key, codepage='cp1252', **kwargs): """Extracts events from a Windows Registry key. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. registry_key (dfwinreg.WinRegistryKey): Windows Registry key. codepage (Optional[str]): extended ASCII string codepage. """ self._ParseMRUListExKey(parser_mediator, registry_key, codepage=codepage)
class RestorePointChangeLogFile(object): """Class that contains a Windows Restore Point change.log file.""" SIGNATURE = 0xabcdef12 _CHANGE_LOG_ENTRY = construct.Struct( u'restore_point_change_log_entry', construct.ULInt32(u'record_size'), construct.ULInt32(u'record_type'), construct.ULInt32(u'signature'), construct.ULInt32(u'entry_type'), construct.ULInt32(u'entry_flags'), construct.ULInt32(u'file_attribute_flags'), construct.ULInt64(u'sequence_number'), construct.Padding(32), construct.ULInt32(u'process_name_data_size'), construct.ULInt32(u'unknown1'), construct.RepeatUntil(lambda obj, ctx: obj == b'\x00\x00', construct.Field(u'process_name', 2)), construct.Anchor(u'sub_record_data')) _FILE_HEADER = construct.Struct(u'restore_point_change_log_file_header', construct.ULInt32(u'record_size'), construct.ULInt32(u'record_type'), construct.ULInt32(u'signature'), construct.ULInt32(u'format_version')) _RECORD_HEADER = construct.Struct( u'restore_point_change_log_record_header', construct.ULInt32(u'record_size'), construct.ULInt32(u'record_type')) _UTF16_STREAM = construct.RepeatUntil(lambda obj, ctx: obj == b'\x00\x00', construct.Field(u'stream', 2)) _VOLUME_PATH = construct.Struct( u'restore_point_change_log_volume_path', construct.ULInt32(u'record_size'), construct.ULInt32(u'record_type'), construct.RepeatUntil(lambda obj, ctx: obj == b'\x00\x00', construct.Field(u'volume_path', 2))) LOG_ENTRY_FLAGS = { 0x00000001: u'CHANGE_LOG_ENTRYFLAGS_TEMPPATH', 0x00000002: u'CHANGE_LOG_ENTRYFLAGS_SECONDPATH', 0x00000004: u'CHANGE_LOG_ENTRYFLAGS_ACLINFO', 0x00000008: u'CHANGE_LOG_ENTRYFLAGS_DEBUGINFO', 0x00000010: u'CHANGE_LOG_ENTRYFLAGS_SHORTNAME', } LOG_ENTRY_TYPES = { 0x00000001: u'CHANGE_LOG_ENTRYTYPES_STREAMCHANGE', 0x00000002: u'CHANGE_LOG_ENTRYTYPES_ACLCHANGE', 0x00000004: u'CHANGE_LOG_ENTRYTYPES_ATTRCHANGE', 0x00000008: u'CHANGE_LOG_ENTRYTYPES_STREAMOVERWRITE', 0x00000010: u'CHANGE_LOG_ENTRYTYPES_FILEDELETE', 0x00000020: u'CHANGE_LOG_ENTRYTYPES_FILECREATE', 0x00000040: u'CHANGE_LOG_ENTRYTYPES_FILERENAME', 0x00000080: u'CHANGE_LOG_ENTRYTYPES_DIRCREATE', 0x00000100: u'CHANGE_LOG_ENTRYTYPES_DIRRENAME', 0x00000200: u'CHANGE_LOG_ENTRYTYPES_DIRDELETE', 0x00000400: u'CHANGE_LOG_ENTRYTYPES_MOUNTCREATE', 0x00000800: u'CHANGE_LOG_ENTRYTYPES_MOUNTDELETE', 0x00001000: u'CHANGE_LOG_ENTRYTYPES_VOLUMEERROR', 0x00002000: u'CHANGE_LOG_ENTRYTYPES_STREAMCREATE', 0x00010000: u'CHANGE_LOG_ENTRYTYPES_NOOPTIMIZE', 0x00020000: u'CHANGE_LOG_ENTRYTYPES_ISDIR', 0x00040000: u'CHANGE_LOG_ENTRYTYPES_ISNOTDIR', 0x00080000: u'CHANGE_LOG_ENTRYTYPES_SIMULATEDELETE', 0x00100000: u'CHANGE_LOG_ENTRYTYPES_INPRECREATE', 0x00200000: u'CHANGE_LOG_ENTRYTYPES_OPENBYID', } _RECORD_TYPES = { 0: u'RecordTypeLogHeader', 1: u'RecordTypeLogEntry', 2: u'RecordTypeVolumePath', 3: u'RecordTypeFirstPath', 4: u'RecordTypeSecondPath', 5: u'RecordTypeTempPath', 6: u'RecordTypeAclInline', 7: u'RecordTypeAclFile', 8: u'RecordTypeDebugInfo', 9: u'RecordTypeShortName', } def __init__(self, debug=False): """Initializes the change.log file object. Args: debug: optional boolean value to indicate if debug information should be printed. """ super(RestorePointChangeLogFile, self).__init__() self._debug = debug self._file_object = None self._file_object_opened_in_object = False self._file_size = 0 self.entries = [] self.volume_path = None def _ReadChangeLogEntries(self): """Reads the change log entries. Raises: IOError: if the change log entries cannot be read. """ while self._file_object.tell() < self._file_size: change_log_entry = self._ReadChangeLogEntry() self.entries.append(change_log_entry) def _ReadChangeLogEntry(self): """Reads a change log entry. Returns: A change log entry (instance of ChangeLogEntry). Raises: IOError: if the change log entry cannot be read. """ file_offset = self._file_object.tell() try: change_log_entry_struct = self._CHANGE_LOG_ENTRY.parse_stream( self._file_object) except construct.FieldError as exception: raise IOError( u'Unable to parse change log entry with error: {0:s}'.format( exception)) record_size = change_log_entry_struct.get(u'record_size') record_type = change_log_entry_struct.get(u'record_type') if record_type != 1: raise IOError( u'Unsupported record type: {0:d}'.format(record_type)) signature = change_log_entry_struct.get(u'signature') if signature != self.SIGNATURE: raise IOError(u'Unsupported change.log file signature') change_log_entry = ChangeLogEntry() change_log_entry.entry_type = change_log_entry_struct.get( u'entry_type') change_log_entry.entry_flags = change_log_entry_struct.get( u'entry_flags') change_log_entry.file_attribute_flags = change_log_entry_struct.get( u'file_attribute_flags') change_log_entry.sequence_number = change_log_entry_struct.get( u'sequence_number') try: # The struct includes the end-of-string character that we need # to strip off. process_name = change_log_entry_struct.get(u'process_name') process_name = b''.join(process_name).decode(u'utf16')[:-1] except UnicodeDecodeError as exception: process_name = u'' change_log_entry.process_name = process_name self._file_object.seek(file_offset, os.SEEK_SET) change_log_entry_record_data = self._file_object.read(record_size) if self._debug: print(u'Change log entry record data:') print(hexdump.Hexdump(change_log_entry_record_data)) if self._debug: print(u'Record size\t\t\t\t\t\t\t\t: {0:d}'.format(record_size)) print(u'Record type\t\t\t\t\t\t\t\t: {0:d} ({1:s})'.format( record_type, self._RECORD_TYPES.get(record_type, u'Unknown'))) print(u'Signature\t\t\t\t\t\t\t\t: 0x{0:08x}'.format(signature)) print(u'Entry type\t\t\t\t\t\t\t\t: 0x{0:08x}'.format( change_log_entry.entry_type)) for flag, description in self.LOG_ENTRY_TYPES.items(): if change_log_entry.entry_type & flag: print(u'\t{0:s}'.format(description)) print(u'') print(u'Entry flags\t\t\t\t\t\t\t\t: 0x{0:08x}'.format( change_log_entry.entry_flags)) for flag, description in self.LOG_ENTRY_FLAGS.items(): if change_log_entry.entry_flags & flag: print(u'\t{0:s}'.format(description)) print(u'') print(u'File attribute flags\t\t\t\t\t\t\t: 0x{0:08x}'.format( change_log_entry.file_attribute_flags)) # TODO: print flags. print(u'Sequence number\t\t\t\t\t\t\t\t: {0:d}'.format( change_log_entry.sequence_number)) print(u'Process name data size\t\t\t\t\t\t\t: 0x{0:08x}'.format( change_log_entry_struct.get(u'process_name_data_size'))) print(u'Unknown1\t\t\t\t\t\t\t\t: 0x{0:08x}'.format( change_log_entry_struct.get(u'unknown1'))) print(u'Process name\t\t\t\t\t\t\t\t: {0:s}'.format( change_log_entry.process_name)) sub_record_data_offset = (change_log_entry_struct.sub_record_data - file_offset) sub_record_data_size = record_size - 4 if self._debug: print(u'Sub record data offset\t\t\t\t\t\t\t: {0:d}'.format( sub_record_data_offset)) print(u'Sub record data size\t\t\t\t\t\t\t: {0:d}'.format( sub_record_data_size - sub_record_data_offset)) if sub_record_data_offset < sub_record_data_size: print(u'') while sub_record_data_offset < sub_record_data_size: read_size = self._ReadRecord(change_log_entry_record_data, sub_record_data_offset) if read_size == 0: break sub_record_data_offset += read_size copy_of_record_size = construct.ULInt32(u'record_size').parse( change_log_entry_record_data[-4:]) if record_size != copy_of_record_size: raise IOError(u'Record size mismatch ({0:d} != {1:d})'.format( record_size, copy_of_record_size)) if self._debug: print(u'Copy of record size\t\t\t\t\t\t\t: {0:d}'.format( copy_of_record_size)) print(u'') return change_log_entry def _ReadFileHeader(self): """Reads the file header. Raises: IOError: if the file header cannot be read. """ if self._debug: print(u'Seeking file header offset: 0x{0:08x}'.format(0)) self._file_object.seek(0, os.SEEK_SET) try: file_header_struct = self._FILE_HEADER.parse_stream( self._file_object) except construct.FieldError as exception: raise IOError( u'Unable to parse file header with error: {0:s}'.format( exception)) signature = file_header_struct.get(u'signature') if signature != self.SIGNATURE: raise IOError(u'Unsupported change.log file signature') record_size = file_header_struct.get(u'record_size') record_type = file_header_struct.get(u'record_type') if record_type != 0: raise IOError( u'Unsupported record type: {0:d}'.format(record_type)) format_version = file_header_struct.get(u'format_version') if format_version != 2: raise IOError( u'Unsupported change.log format version: {0:d}'.format( format_version)) self._file_object.seek(0, os.SEEK_SET) file_header_data = self._file_object.read(record_size) if self._debug: print(u'File header data:') print(hexdump.Hexdump(file_header_data)) if self._debug: print(u'Record size\t\t\t\t\t\t\t\t: {0:d}'.format(record_size)) print(u'Record type\t\t\t\t\t\t\t\t: {0:d} ({1:s})'.format( record_type, self._RECORD_TYPES.get(record_type, u'Unknown'))) print(u'Signature\t\t\t\t\t\t\t\t: 0x{0:08x}'.format(signature)) print(u'Format version\t\t\t\t\t\t\t\t: {0:d}'.format( format_version)) self._ReadVolumePath(file_header_data[16:-4]) copy_of_record_size = construct.ULInt32(u'record_size').parse( file_header_data[-4:]) if record_size != copy_of_record_size: raise IOError(u'Record size mismatch ({0:d} != {1:d})'.format( record_size, copy_of_record_size)) if self._debug: print(u'Copy of record size\t\t\t\t\t\t\t: {0:d}'.format( copy_of_record_size)) print(u'') def _ReadRecord(self, record_data, record_data_offset): """Reads a record. Args: record_data: the record data. record_data_offset: the record data offset. Returns: The record size. Raises: IOError: if the record cannot be read. """ try: record_header_struct = self._RECORD_HEADER.parse( record_data[record_data_offset:]) except construct.FieldError as exception: raise IOError( u'Unable to parse record header with error: {0:s}'.format( exception)) record_size = record_header_struct.get(u'record_size') record_type = record_header_struct.get(u'record_type') if self._debug: print(u'Record data:') print( hexdump.Hexdump( record_data[record_data_offset:record_data_offset + record_size])) if self._debug: print(u'Record size\t\t\t\t\t\t\t\t: {0:d}'.format(record_size)) print(u'Record type\t\t\t\t\t\t\t\t: {0:d} ({1:s})'.format( record_type, self._RECORD_TYPES.get(record_type, u'Unknown'))) record_data_offset += self._RECORD_HEADER.sizeof() if record_type in [4, 5, 9]: try: utf16_stream = self._UTF16_STREAM.parse( record_data[record_data_offset:]) except construct.FieldError as exception: raise IOError( u'Unable to parse UTF-16 stream with error: {0:s}'.format( exception)) try: # The UTF-16 stream includes the end-of-string character that we need # to strip off. value_string = b''.join(utf16_stream).decode(u'utf16')[:-1] except UnicodeDecodeError as exception: value_string = u'' # TODO: add support for other record types. # TODO: store record values in runtime objects. if self._debug: if record_type == 4: print(u'Secondary path\t\t\t\t\t\t\t\t: {0:s}'.format( value_string)) elif record_type == 5: print(u'Backup filename\t\t\t\t\t\t\t\t: {0:s}'.format( value_string)) elif record_type == 9: print(u'Short filename\t\t\t\t\t\t\t\t: {0:s}'.format( value_string)) print(u'') return record_size def _ReadVolumePath(self, volume_path_record_data): """Reads the volume path. Args: volume_path_record_data: the volume path record data. Raises: IOError: if the volume path cannot be read. """ try: volume_path_struct = self._VOLUME_PATH.parse( volume_path_record_data) except construct.FieldError as exception: raise IOError( u'Unable to parse volume path with error: {0:s}'.format( exception)) record_size = volume_path_struct.get(u'record_size') record_type = volume_path_struct.get(u'record_type') if record_type != 2: raise IOError( u'Unsupported record type: {0:d}'.format(record_type)) if self._debug: print(u'Volume path record data:') print(hexdump.Hexdump(volume_path_record_data)) try: # The struct includes the end-of-string character that we need # to strip off. self.volume_path = volume_path_struct.get(u'volume_path') self.volume_path = b''.join(self.volume_path).decode(u'utf16')[:-1] except UnicodeDecodeError as exception: self.volume_path = u'' if self._debug: print(u'Record size\t\t\t\t\t\t\t\t: {0:d}'.format(record_size)) print(u'Record type\t\t\t\t\t\t\t\t: {0:d} ({1:s})'.format( record_type, self._RECORD_TYPES.get(record_type, u'Unknown'))) print(u'Volume path\t\t\t\t\t\t\t\t: {0:s}'.format( self.volume_path)) print(u'') def Close(self): """Closes the change.log file.""" if self._file_object_opened_in_object: self._file_object.close() self._file_object = None def Open(self, filename): """Opens the change.log file. Args: filename: the filename. """ stat_object = os.stat(filename) self._file_size = stat_object.st_size self._file_object = open(filename, 'rb') self._file_object_opened_in_object = True self._ReadFileHeader() self._ReadChangeLogEntries()
construct.RepeatUntil( lambda obj, ctx: obj.block_start == 0x3B, construct.Struct( 'body', construct.ULInt8('block_start'), construct.Embedded( construct.Switch( 'block', lambda ctx: ctx.block_start, { 0x3B: construct.Struct( # workaround for Pass not working 'terminator', construct.Value('terminator', lambda ctx: 'terminator'), ), 0x2C: _image_block, 0x21: construct.Struct( 'ext', construct.ULInt8('ext_label'), construct.Embedded( construct.Switch( 'extension', lambda ctx: ctx.ext_label, { 0xFF: _application_extension, 0xFE: _comment_extension, 0xF9: _gce_extension, }, default=_unknown_extension, ), ), ), }, ), ), ), ),
c.ULInt16("busses"), c.ULInt16("planes"), c.ULInt16("ships"), ), c.Struct("stations", c.ULInt16("trains"), c.ULInt16("trucks"), c.ULInt16("busses"), c.ULInt16("planes"), c.ULInt16("ships"), ), c.ULInt8("has_clients"), c.If(lambda ctx: ctx["has_clients"] > 0, c.RepeatUntil(lambda obj, ctx: obj.more == 0, c.Struct("clients", c.CString("name"), c.CString("unique_id"), OpenTTD_NewDate("joined"), c.ULInt8("more"), )), ), )), c.ULInt8("has_spectators"), c.If(lambda ctx: ctx["has_spectators"] > 0, c.RepeatUntil(lambda obj, ctx: obj.more == 0, c.Struct("spectators", c.CString("name"), c.CString("unique_id"), OpenTTD_NewDate("joined"), c.ULInt8("more"), )), ), )
class BsmParser(interface.FileObjectParser): """Parser for BSM files.""" _INITIAL_FILE_OFFSET = None NAME = u'bsm_log' DESCRIPTION = u'Parser for BSM log files.' # BSM supported version (0x0b = 11). AUDIT_HEADER_VERSION = 11 # Magic Trail Header. BSM_TOKEN_TRAILER_MAGIC = b'b105' # IP Version constants. AU_IPv4 = 4 AU_IPv6 = 16 IPV4_STRUCT = construct.UBInt32(u'ipv4') IPV6_STRUCT = construct.Struct(u'ipv6', construct.UBInt64(u'high'), construct.UBInt64(u'low')) # Tested structures. # INFO: I have ommited the ID in the structures declaration. # I used the BSM_TYPE first to read the ID, and then, the structure. # Tokens always start with an ID value that identifies their token # type and subsequent structure. BSM_TYPE = construct.UBInt8(u'token_id') # Data type structures. BSM_TOKEN_DATA_CHAR = construct.String(u'value', 1) BSM_TOKEN_DATA_SHORT = construct.UBInt16(u'value') BSM_TOKEN_DATA_INTEGER = construct.UBInt32(u'value') # Common structure used by other structures. # audit_uid: integer, uid that generates the entry. # effective_uid: integer, the permission user used. # effective_gid: integer, the permission group used. # real_uid: integer, user id of the user that execute the process. # real_gid: integer, group id of the group that execute the process. # pid: integer, identification number of the process. # session_id: unknown, need research. BSM_TOKEN_SUBJECT_SHORT = construct.Struct( u'subject_data', construct.UBInt32(u'audit_uid'), construct.UBInt32(u'effective_uid'), construct.UBInt32(u'effective_gid'), construct.UBInt32(u'real_uid'), construct.UBInt32(u'real_gid'), construct.UBInt32(u'pid'), construct.UBInt32(u'session_id')) # Common structure used by other structures. # Identify the kind of inet (IPv4 or IPv6) # TODO: instead of 16, AU_IPv6 must be used. BSM_IP_TYPE_SHORT = construct.Struct( u'bsm_ip_type_short', construct.UBInt32(u'net_type'), construct.Switch(u'ip_addr', _BsmTokenGetNetType, {16: IPV6_STRUCT}, default=IPV4_STRUCT)) # Initial fields structure used by header structures. # length: integer, the length of the entry, equal to trailer (doc: length). # version: integer, version of BSM (AUDIT_HEADER_VERSION). # event_type: integer, the type of event (/etc/security/audit_event). # modifier: integer, unknown, need research (It is always 0). BSM_HEADER = construct.Struct(u'bsm_header', construct.UBInt32(u'length'), construct.UBInt8(u'version'), construct.UBInt16(u'event_type'), construct.UBInt16(u'modifier')) # First token of one entry. # timestamp: unsigned integer, number of seconds since # January 1, 1970 00:00:00 UTC. # microsecond: unsigned integer, number of micro seconds. BSM_HEADER32 = construct.Struct(u'bsm_header32', BSM_HEADER, construct.UBInt32(u'timestamp'), construct.UBInt32(u'microsecond')) BSM_HEADER64 = construct.Struct(u'bsm_header64', BSM_HEADER, construct.UBInt64(u'timestamp'), construct.UBInt64(u'microsecond')) BSM_HEADER32_EX = construct.Struct(u'bsm_header32_ex', BSM_HEADER, BSM_IP_TYPE_SHORT, construct.UBInt32(u'timestamp'), construct.UBInt32(u'microsecond')) # Token TEXT, provides extra information. BSM_TOKEN_TEXT = construct.Struct( u'bsm_token_text', construct.UBInt16(u'length'), construct.Array(_BsmTokenGetLength, construct.UBInt8(u'text'))) # Path of the executable. BSM_TOKEN_PATH = BSM_TOKEN_TEXT # Identified the end of the record (follow by TRAILER). # status: integer that identifies the status of the exit (BSM_ERRORS). # return: returned value from the operation. BSM_TOKEN_RETURN32 = construct.Struct(u'bsm_token_return32', construct.UBInt8(u'status'), construct.UBInt32(u'return_value')) BSM_TOKEN_RETURN64 = construct.Struct(u'bsm_token_return64', construct.UBInt8(u'status'), construct.UBInt64(u'return_value')) # Identified the number of bytes that was written. # magic: 2 bytes that identifies the TRAILER (BSM_TOKEN_TRAILER_MAGIC). # length: integer that has the number of bytes from the entry size. BSM_TOKEN_TRAILER = construct.Struct(u'bsm_token_trailer', construct.UBInt16(u'magic'), construct.UBInt32(u'record_length')) # A 32-bits argument. # num_arg: the number of the argument. # name_arg: the argument's name. # text: the string value of the argument. BSM_TOKEN_ARGUMENT32 = construct.Struct( u'bsm_token_argument32', construct.UBInt8(u'num_arg'), construct.UBInt32(u'name_arg'), construct.UBInt16(u'length'), construct.Array(_BsmTokenGetLength, construct.UBInt8(u'text'))) # A 64-bits argument. # num_arg: integer, the number of the argument. # name_arg: text, the argument's name. # text: the string value of the argument. BSM_TOKEN_ARGUMENT64 = construct.Struct( u'bsm_token_argument64', construct.UBInt8(u'num_arg'), construct.UBInt64(u'name_arg'), construct.UBInt16(u'length'), construct.Array(_BsmTokenGetLength, construct.UBInt8(u'text'))) # Identify an user. # terminal_id: unknown, research needed. # terminal_addr: unknown, research needed. BSM_TOKEN_SUBJECT32 = construct.Struct(u'bsm_token_subject32', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt32(u'terminal_port'), IPV4_STRUCT) # Identify an user using a extended Token. # terminal_port: unknown, need research. # net_type: unknown, need research. BSM_TOKEN_SUBJECT32_EX = construct.Struct( u'bsm_token_subject32_ex', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt32(u'terminal_port'), BSM_IP_TYPE_SHORT) # au_to_opaque // AUT_OPAQUE BSM_TOKEN_OPAQUE = BSM_TOKEN_TEXT # au_to_seq // AUT_SEQ BSM_TOKEN_SEQUENCE = BSM_TOKEN_DATA_INTEGER # Program execution with options. # For each argument we are going to have a string+ "\x00". # Example: [00 00 00 02][41 42 43 00 42 42 00] # 2 Arguments, Arg1: [414243] Arg2: [4242]. BSM_TOKEN_EXEC_ARGUMENTS = construct.UBInt32(u'number_arguments') BSM_TOKEN_EXEC_ARGUMENT = construct.Struct( u'bsm_token_exec_argument', construct.RepeatUntil(_BsmTokenIsEndOfString, construct.StaticField("text", 1))) # au_to_in_addr // AUT_IN_ADDR: BSM_TOKEN_ADDR = IPV4_STRUCT # au_to_in_addr_ext // AUT_IN_ADDR_EX: BSM_TOKEN_ADDR_EXT = construct.Struct(u'bsm_token_addr_ext', construct.UBInt32(u'net_type'), IPV6_STRUCT) # au_to_ip // AUT_IP: # TODO: parse this header in the correct way. BSM_TOKEN_IP = construct.String(u'binary_ipv4_add', 20) # au_to_ipc // AUT_IPC: BSM_TOKEN_IPC = construct.Struct(u'bsm_token_ipc', construct.UBInt8(u'object_type'), construct.UBInt32(u'object_id')) # au_to_ipc_perm // au_to_ipc_perm BSM_TOKEN_IPC_PERM = construct.Struct( u'bsm_token_ipc_perm', construct.UBInt32(u'user_id'), construct.UBInt32(u'group_id'), construct.UBInt32(u'creator_user_id'), construct.UBInt32(u'creator_group_id'), construct.UBInt32(u'access_mode'), construct.UBInt32(u'slot_seq'), construct.UBInt32(u'key')) # au_to_iport // AUT_IPORT: BSM_TOKEN_PORT = construct.UBInt16(u'port_number') # au_to_file // AUT_OTHER_FILE32: BSM_TOKEN_FILE = construct.Struct( u'bsm_token_file', construct.UBInt32(u'timestamp'), construct.UBInt32(u'microsecond'), construct.UBInt16(u'length'), construct.Array(_BsmTokenGetLength, construct.UBInt8(u'text'))) # au_to_subject64 // AUT_SUBJECT64: BSM_TOKEN_SUBJECT64 = construct.Struct(u'bsm_token_subject64', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt64(u'terminal_port'), IPV4_STRUCT) # au_to_subject64_ex // AU_IPv4: BSM_TOKEN_SUBJECT64_EX = construct.Struct( u'bsm_token_subject64_ex', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt32(u'terminal_port'), construct.UBInt32(u'terminal_type'), BSM_IP_TYPE_SHORT) # au_to_process32 // AUT_PROCESS32: BSM_TOKEN_PROCESS32 = construct.Struct(u'bsm_token_process32', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt32(u'terminal_port'), IPV4_STRUCT) # au_to_process64 // AUT_PROCESS32: BSM_TOKEN_PROCESS64 = construct.Struct(u'bsm_token_process64', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt64(u'terminal_port'), IPV4_STRUCT) # au_to_process32_ex // AUT_PROCESS32_EX: BSM_TOKEN_PROCESS32_EX = construct.Struct( u'bsm_token_process32_ex', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt32(u'terminal_port'), BSM_IP_TYPE_SHORT) # au_to_process64_ex // AUT_PROCESS64_EX: BSM_TOKEN_PROCESS64_EX = construct.Struct( u'bsm_token_process64_ex', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt64(u'terminal_port'), BSM_IP_TYPE_SHORT) # au_to_sock_inet32 // AUT_SOCKINET32: BSM_TOKEN_AUT_SOCKINET32 = construct.Struct( u'bsm_token_aut_sockinet32', construct.UBInt16(u'net_type'), construct.UBInt16(u'port_number'), IPV4_STRUCT) # Info: checked against the source code of XNU, but not against # real BSM file. BSM_TOKEN_AUT_SOCKINET128 = construct.Struct( u'bsm_token_aut_sockinet128', construct.UBInt16(u'net_type'), construct.UBInt16(u'port_number'), IPV6_STRUCT) INET6_ADDR_TYPE = construct.Struct(u'addr_type', construct.UBInt16(u'ip_type'), construct.UBInt16(u'source_port'), construct.UBInt64(u'saddr_high'), construct.UBInt64(u'saddr_low'), construct.UBInt16(u'destination_port'), construct.UBInt64(u'daddr_high'), construct.UBInt64(u'daddr_low')) INET4_ADDR_TYPE = construct.Struct( u'addr_type', construct.UBInt16(u'ip_type'), construct.UBInt16(u'source_port'), construct.UBInt32(u'source_address'), construct.UBInt16(u'destination_port'), construct.UBInt32(u'destination_address')) # au_to_socket_ex // AUT_SOCKET_EX # TODO: Change the 26 for unixbsm.BSM_PROTOCOLS.INET6. BSM_TOKEN_AUT_SOCKINET32_EX = construct.Struct( u'bsm_token_aut_sockinet32_ex', construct.UBInt16(u'socket_domain'), construct.UBInt16(u'socket_type'), construct.Switch(u'structure_addr_port', _BsmTokenGetSocketDomain, {26: INET6_ADDR_TYPE}, default=INET4_ADDR_TYPE)) # au_to_sock_unix // AUT_SOCKUNIX BSM_TOKEN_SOCKET_UNIX = construct.Struct( u'bsm_token_au_to_sock_unix', construct.UBInt16(u'family'), construct.RepeatUntil(_BsmTokenIsEndOfString, construct.StaticField("path", 1))) # au_to_data // au_to_data # how to print: bsmtoken.BSM_TOKEN_DATA_PRINT. # type: bsmtoken.BSM_TOKEN_DATA_TYPE. # unit_count: number of type values. # BSM_TOKEN_DATA has a end field = type * unit_count BSM_TOKEN_DATA = construct.Struct(u'bsm_token_data', construct.UBInt8(u'how_to_print'), construct.UBInt8(u'data_type'), construct.UBInt8(u'unit_count')) # au_to_attr32 // AUT_ATTR32 BSM_TOKEN_ATTR32 = construct.Struct( u'bsm_token_attr32', construct.UBInt32(u'file_mode'), construct.UBInt32(u'uid'), construct.UBInt32(u'gid'), construct.UBInt32(u'file_system_id'), construct.UBInt64(u'file_system_node_id'), construct.UBInt32(u'device')) # au_to_attr64 // AUT_ATTR64 BSM_TOKEN_ATTR64 = construct.Struct( u'bsm_token_attr64', construct.UBInt32(u'file_mode'), construct.UBInt32(u'uid'), construct.UBInt32(u'gid'), construct.UBInt32(u'file_system_id'), construct.UBInt64(u'file_system_node_id'), construct.UBInt64(u'device')) # au_to_exit // AUT_EXIT BSM_TOKEN_EXIT = construct.Struct(u'bsm_token_exit', construct.UBInt32(u'status'), construct.UBInt32(u'return_value')) # au_to_newgroups // AUT_NEWGROUPS # INFO: we must read BSM_TOKEN_DATA_INTEGER for each group. BSM_TOKEN_GROUPS = construct.UBInt16(u'group_number') # au_to_exec_env == au_to_exec_args BSM_TOKEN_EXEC_ENV = BSM_TOKEN_EXEC_ARGUMENTS # au_to_zonename //AUT_ZONENAME BSM_TOKEN_ZONENAME = BSM_TOKEN_TEXT # Token ID. # List of valid Token_ID. # Token_ID -> [NAME_STRUCTURE, STRUCTURE] # Only the checked structures are been added to the valid structures lists. BSM_TYPE_LIST = { 17: [u'BSM_TOKEN_FILE', BSM_TOKEN_FILE], 19: [u'BSM_TOKEN_TRAILER', BSM_TOKEN_TRAILER], 20: [u'BSM_HEADER32', BSM_HEADER32], 21: [u'BSM_HEADER64', BSM_HEADER64], 33: [u'BSM_TOKEN_DATA', BSM_TOKEN_DATA], 34: [u'BSM_TOKEN_IPC', BSM_TOKEN_IPC], 35: [u'BSM_TOKEN_PATH', BSM_TOKEN_PATH], 36: [u'BSM_TOKEN_SUBJECT32', BSM_TOKEN_SUBJECT32], 38: [u'BSM_TOKEN_PROCESS32', BSM_TOKEN_PROCESS32], 39: [u'BSM_TOKEN_RETURN32', BSM_TOKEN_RETURN32], 40: [u'BSM_TOKEN_TEXT', BSM_TOKEN_TEXT], 41: [u'BSM_TOKEN_OPAQUE', BSM_TOKEN_OPAQUE], 42: [u'BSM_TOKEN_ADDR', BSM_TOKEN_ADDR], 43: [u'BSM_TOKEN_IP', BSM_TOKEN_IP], 44: [u'BSM_TOKEN_PORT', BSM_TOKEN_PORT], 45: [u'BSM_TOKEN_ARGUMENT32', BSM_TOKEN_ARGUMENT32], 47: [u'BSM_TOKEN_SEQUENCE', BSM_TOKEN_SEQUENCE], 96: [u'BSM_TOKEN_ZONENAME', BSM_TOKEN_ZONENAME], 113: [u'BSM_TOKEN_ARGUMENT64', BSM_TOKEN_ARGUMENT64], 114: [u'BSM_TOKEN_RETURN64', BSM_TOKEN_RETURN64], 116: [u'BSM_HEADER32_EX', BSM_HEADER32_EX], 119: [u'BSM_TOKEN_PROCESS64', BSM_TOKEN_PROCESS64], 122: [u'BSM_TOKEN_SUBJECT32_EX', BSM_TOKEN_SUBJECT32_EX], 127: [u'BSM_TOKEN_AUT_SOCKINET32_EX', BSM_TOKEN_AUT_SOCKINET32_EX], 128: [u'BSM_TOKEN_AUT_SOCKINET32', BSM_TOKEN_AUT_SOCKINET32] } # Untested structures. # When not tested structure is found, we try to parse using also # these structures. BSM_TYPE_LIST_NOT_TESTED = { 49: [u'BSM_TOKEN_ATTR32', BSM_TOKEN_ATTR32], 50: [u'BSM_TOKEN_IPC_PERM', BSM_TOKEN_IPC_PERM], 52: [u'BSM_TOKEN_GROUPS', BSM_TOKEN_GROUPS], 59: [u'BSM_TOKEN_GROUPS', BSM_TOKEN_GROUPS], 60: [u'BSM_TOKEN_EXEC_ARGUMENTS', BSM_TOKEN_EXEC_ARGUMENTS], 61: [u'BSM_TOKEN_EXEC_ENV', BSM_TOKEN_EXEC_ENV], 62: [u'BSM_TOKEN_ATTR32', BSM_TOKEN_ATTR32], 82: [u'BSM_TOKEN_EXIT', BSM_TOKEN_EXIT], 115: [u'BSM_TOKEN_ATTR64', BSM_TOKEN_ATTR64], 117: [u'BSM_TOKEN_SUBJECT64', BSM_TOKEN_SUBJECT64], 123: [u'BSM_TOKEN_PROCESS32_EX', BSM_TOKEN_PROCESS32_EX], 124: [u'BSM_TOKEN_PROCESS64_EX', BSM_TOKEN_PROCESS64_EX], 125: [u'BSM_TOKEN_SUBJECT64_EX', BSM_TOKEN_SUBJECT64_EX], 126: [u'BSM_TOKEN_ADDR_EXT', BSM_TOKEN_ADDR_EXT], 129: [u'BSM_TOKEN_AUT_SOCKINET128', BSM_TOKEN_AUT_SOCKINET128], 130: [u'BSM_TOKEN_SOCKET_UNIX', BSM_TOKEN_SOCKET_UNIX] } def __init__(self): """Initializes a parser object.""" super(BsmParser, self).__init__() # Create the dictionary with all token IDs: tested and untested. self.bsm_type_list_all = self.BSM_TYPE_LIST.copy() self.bsm_type_list_all.update(self.BSM_TYPE_LIST_NOT_TESTED) def _CopyByteArrayToBase16String(self, byte_array): """Copies a byte array into a base-16 encoded Unicode string. Args: byte_array: A byte array. Returns: A base-16 encoded Unicode string. """ return u''.join([u'{0:02x}'.format(byte) for byte in byte_array]) def _CopyUtf8ByteArrayToString(self, byte_array): """Copies a UTF-8 encoded byte array into a Unicode string. Args: byte_array: A byte array containing an UTF-8 encoded string. Returns: A Unicode string. """ byte_stream = b''.join(map(chr, byte_array)) try: string = byte_stream.decode(u'utf-8') except UnicodeDecodeError: logging.warning(u'Unable to decode UTF-8 formatted byte array.') string = byte_stream.decode(u'utf-8', errors=u'ignore') string, _, _ = string.partition(b'\x00') return string def _IPv4Format(self, address): """Change an integer IPv4 address value for its 4 octets representation. Args: address: integer with the IPv4 address. Returns: IPv4 address in 4 octet representation (class A, B, C, D). """ ipv4_string = self.IPV4_STRUCT.build(address) return socket.inet_ntoa(ipv4_string) def _IPv6Format(self, high, low): """Provide a readable IPv6 IP having the high and low part in 2 integers. Args: high: 64 bits integers number with the high part of the IPv6. low: 64 bits integers number with the low part of the IPv6. Returns: String with a well represented IPv6. """ ipv6_string = self.IPV6_STRUCT.build( construct.Container(high=high, low=low)) # socket.inet_ntop not supported in Windows. if hasattr(socket, u'inet_ntop'): return socket.inet_ntop(socket.AF_INET6, ipv6_string) # TODO: this approach returns double "::", illegal IPv6 addr. str_address = binascii.hexlify(ipv6_string) address = [] blank = False for pos in range(0, len(str_address), 4): if str_address[pos:pos + 4] == u'0000': if not blank: address.append(u'') blank = True else: blank = False address.append(str_address[pos:pos + 4].lstrip(u'0')) return u':'.join(address) def _RawToUTF8(self, byte_stream): """Copies a UTF-8 byte stream into a Unicode string. Args: byte_stream: A byte stream containing an UTF-8 encoded string. Returns: A Unicode string. """ try: string = byte_stream.decode(u'utf-8') except UnicodeDecodeError: logging.warning( u'Decode UTF8 failed, the message string may be cut short.') string = byte_stream.decode(u'utf-8', errors=u'ignore') return string.partition(b'\x00')[0] def ParseFileObject(self, parser_mediator, file_object, **kwargs): """Parses a BSM file-like object. Args: parser_mediator: A parser mediator object (instance of ParserMediator). file_object: A file-like object. Raises: UnableToParseFile: when the file cannot be parsed. """ file_object.seek(0, os.SEEK_SET) try: is_bsm = self.VerifyFile(parser_mediator, file_object) except (IOError, construct.FieldError) as exception: raise errors.UnableToParseFile( u'Unable to parse BSM file with error: {0:s}'.format( exception)) if not is_bsm: raise errors.UnableToParseFile(u'Not a BSM File, unable to parse.') event_object = self.ReadBSMEvent(parser_mediator, file_object) while event_object: parser_mediator.ProduceEvent(event_object) event_object = self.ReadBSMEvent(parser_mediator, file_object) def ReadBSMEvent(self, parser_mediator, file_object): """Returns a BsmEvent from a single BSM entry. Args: parser_mediator: A parser mediator object (instance of ParserMediator). file_object: A file-like object. Returns: An event object. """ # A list of tokens that has the entry. extra_tokens = [] offset = file_object.tell() # Token header, first token for each entry. try: token_id = self.BSM_TYPE.parse_stream(file_object) except (IOError, construct.FieldError): return bsm_type, structure = self.BSM_TYPE_LIST.get(token_id, [u'', u'']) if bsm_type == u'BSM_HEADER32': token = structure.parse_stream(file_object) elif bsm_type == u'BSM_HEADER64': token = structure.parse_stream(file_object) elif bsm_type == u'BSM_HEADER32_EX': token = structure.parse_stream(file_object) else: logging.warning( u'Token ID Header {0} not expected at position 0x{1:X}.' u'The parsing of the file cannot be continued'.format( token_id, file_object.tell())) # TODO: if it is a Mac OS X, search for the trailer magic value # as a end of the entry can be a possibility to continue. return length = token.bsm_header.length event_type = u'{0} ({1})'.format( bsmtoken.BSM_AUDIT_EVENT.get(token.bsm_header.event_type, u'UNKNOWN'), token.bsm_header.event_type) timestamp = timelib.Timestamp.FromPosixTimeWithMicrosecond( token.timestamp, token.microsecond) # Read until we reach the end of the record. while file_object.tell() < (offset + length): # Check if it is a known token. try: token_id = self.BSM_TYPE.parse_stream(file_object) except (IOError, construct.FieldError): logging.warning( u'Unable to parse the Token ID at position: {0:d}'.format( file_object.tell())) return if not token_id in self.BSM_TYPE_LIST: pending = (offset + length) - file_object.tell() extra_tokens.extend( self.TryWithUntestedStructures(file_object, token_id, pending)) else: token = self.BSM_TYPE_LIST[token_id][1].parse_stream( file_object) extra_tokens.append( self.FormatToken(token_id, token, file_object)) if file_object.tell() > (offset + length): logging.warning(u'Token ID {0} not expected at position 0x{1:X}.' u'Jumping for the next entry.'.format( token_id, file_object.tell())) try: file_object.seek((offset + length) - file_object.tell(), os.SEEK_CUR) except (IOError, construct.FieldError) as exception: logging.warning( u'Unable to jump to next entry with error: {0:s}'.format( exception)) return # BSM can be in more than one OS: BSD, Solaris and Mac OS X. if parser_mediator.platform == u'MacOSX': # In Mac OS X the last two tokens are the return status and the trailer. if len(extra_tokens) >= 2: return_value = extra_tokens[-2:-1][0] if (return_value.startswith(u'[BSM_TOKEN_RETURN32') or return_value.startswith(u'[BSM_TOKEN_RETURN64')): _ = extra_tokens.pop(len(extra_tokens) - 2) else: return_value = u'Return unknown' else: return_value = u'Return unknown' if extra_tokens: trailer = extra_tokens[-1] if trailer.startswith(u'[BSM_TOKEN_TRAILER'): _ = extra_tokens.pop(len(extra_tokens) - 1) else: trailer = u'Trailer unknown' else: trailer = u'Trailer unknown' return MacBsmEvent(event_type, timestamp, u'. '.join(extra_tokens), return_value, trailer, offset) else: # Generic BSM format. if extra_tokens: trailer = extra_tokens[-1] if trailer.startswith(u'[BSM_TOKEN_TRAILER'): _ = extra_tokens.pop(len(extra_tokens) - 1) else: trailer = u'Trailer unknown' else: trailer = u'Trailer unknown' return BsmEvent(event_type, timestamp, u'. '.join(extra_tokens), trailer, offset) def VerifyFile(self, parser_mediator, file_object): """Check if the file is a BSM file. Args: parser_mediator: A parser mediator object (instance of ParserMediator). file_event: file that we want to check. Returns: True if this is a valid BSM file, otherwise False. """ if file_object.tell() != 0: file_object.seek(0) # First part of the entry is always a Header. try: token_id = self.BSM_TYPE.parse_stream(file_object) except (IOError, construct.FieldError): return False if token_id not in self.BSM_TYPE_LIST: return False bsm_type, structure = self.BSM_TYPE_LIST.get(token_id, [u'', u'']) try: if bsm_type == u'BSM_HEADER32': header = structure.parse_stream(file_object) elif bsm_type == u'BSM_HEADER64': header = structure.parse_stream(file_object) elif bsm_type == u'BSM_HEADER32_EX': header = structure.parse_stream(file_object) else: return False except (IOError, construct.FieldError): return False if header.bsm_header.version != self.AUDIT_HEADER_VERSION: return False try: token_id = self.BSM_TYPE.parse_stream(file_object) except (IOError, construct.FieldError): return False # If is Mac OS X BSM file, next entry is a text token indicating # if it is a normal start or it is a recovery track. if parser_mediator.platform == u'MacOSX': bsm_type_list = self.BSM_TYPE_LIST.get(token_id) if not bsm_type_list: return False if bsm_type_list[0] != u'BSM_TOKEN_TEXT': logging.warning( u'It is not a valid first entry for Mac OS X BSM.') return False try: token = self.BSM_TOKEN_TEXT.parse_stream(file_object) except (IOError, construct.FieldError): return text = self._CopyUtf8ByteArrayToString(token.text) if (text != u'launchctl::Audit startup' and text != u'launchctl::Audit recovery'): logging.warning( u'It is not a valid first entry for Mac OS X BSM.') return False file_object.seek(0) return True def TryWithUntestedStructures(self, file_object, token_id, pending): """Try to parse the pending part of the entry using untested structures. Args: file_object: BSM file. token_id: integer with the id that comes from the unknown token. pending: pending length of the entry. Returns: A list of extra tokens data that can be parsed using non-tested structures. A message indicating that a structure cannot be parsed is added for unparsed structures. """ # Data from the unknown structure. start_position = file_object.tell() start_token_id = token_id extra_tokens = [] # Read all the "pending" bytes. try: if token_id in self.bsm_type_list_all: token = self.bsm_type_list_all[token_id][1].parse_stream( file_object) extra_tokens.append( self.FormatToken(token_id, token, file_object)) while file_object.tell() < (start_position + pending): # Check if it is a known token. try: token_id = self.BSM_TYPE.parse_stream(file_object) except (IOError, construct.FieldError): logging.warning( u'Unable to parse the Token ID at position: {0:d}'. format(file_object.tell())) return if token_id not in self.bsm_type_list_all: break token = self.bsm_type_list_all[token_id][1].parse_stream( file_object) extra_tokens.append( self.FormatToken(token_id, token, file_object)) except (IOError, construct.FieldError): token_id = 255 next_entry = (start_position + pending) if file_object.tell() != next_entry: # Unknown Structure. logging.warning( u'Unknown Token at "0x{0:X}", ID: {1} (0x{2:X})'.format( start_position - 1, token_id, token_id)) # TODO: another way to save this information must be found. extra_tokens.append(u'Plaso: some tokens from this entry can ' u'not be saved. Entry at 0x{0:X} with unknown ' u'token id "0x{1:X}".'.format( start_position - 1, start_token_id)) # Move to next entry. file_object.seek(next_entry - file_object.tell(), os.SEEK_CUR) # It returns null list because it doesn't know witch structure was # the incorrect structure that makes that it can arrive to the spected # end of the entry. return [] return extra_tokens # TODO: instead of compare the text to know what structure was parsed # is better to compare directly the numeric number (token_id), # less readable, but better performance. def FormatToken(self, token_id, token, file_object): """Parse the Token depending of the type of the structure. Args: token_id: Identification integer of the token_type. token: Token struct to parse. file_object: BSM file. Returns: String with the parsed Token values. """ if token_id not in self.bsm_type_list_all: return u'Type Unknown: {0:d} (0x{0:X})'.format(token_id) bsm_type, _ = self.bsm_type_list_all.get(token_id, [u'', u'']) if bsm_type in [ u'BSM_TOKEN_TEXT', u'BSM_TOKEN_PATH', u'BSM_TOKEN_ZONENAME' ]: try: string = self._CopyUtf8ByteArrayToString(token.text) except TypeError: string = u'Unknown' return u'[{0}: {1:s}]'.format(bsm_type, string) elif bsm_type in [ u'BSM_TOKEN_RETURN32', u'BSM_TOKEN_RETURN64', u'BSM_TOKEN_EXIT' ]: return u'[{0}: {1} ({2}), System call status: {3}]'.format( bsm_type, bsmtoken.BSM_ERRORS.get(token.status, u'Unknown'), token.status, token.return_value) elif bsm_type in [u'BSM_TOKEN_SUBJECT32', u'BSM_TOKEN_SUBJECT64']: return ( u'[{0}: aid({1}), euid({2}), egid({3}), uid({4}), gid({5}), ' u'pid({6}), session_id({7}), terminal_port({8}), ' u'terminal_ip({9})]').format( bsm_type, token.subject_data.audit_uid, token.subject_data.effective_uid, token.subject_data.effective_gid, token.subject_data.real_uid, token.subject_data.real_gid, token.subject_data.pid, token.subject_data.session_id, token.terminal_port, self._IPv4Format(token.ipv4)) elif bsm_type in [ u'BSM_TOKEN_SUBJECT32_EX', u'BSM_TOKEN_SUBJECT64_EX' ]: if token.bsm_ip_type_short.net_type == self.AU_IPv6: ip = self._IPv6Format(token.bsm_ip_type_short.ip_addr.high, token.bsm_ip_type_short.ip_addr.low) elif token.bsm_ip_type_short.net_type == self.AU_IPv4: ip = self._IPv4Format(token.bsm_ip_type_short.ip_addr) else: ip = u'unknown' return ( u'[{0}: aid({1}), euid({2}), egid({3}), uid({4}), gid({5}), ' u'pid({6}), session_id({7}), terminal_port({8}), ' u'terminal_ip({9})]').format( bsm_type, token.subject_data.audit_uid, token.subject_data.effective_uid, token.subject_data.effective_gid, token.subject_data.real_uid, token.subject_data.real_gid, token.subject_data.pid, token.subject_data.session_id, token.terminal_port, ip) elif bsm_type in [u'BSM_TOKEN_ARGUMENT32', u'BSM_TOKEN_ARGUMENT64']: string = self._CopyUtf8ByteArrayToString(token.text) return u'[{0}: {1:s}({2}) is 0x{3:X}]'.format( bsm_type, string, token.num_arg, token.name_arg) elif bsm_type in [u'BSM_TOKEN_EXEC_ARGUMENTS', u'BSM_TOKEN_EXEC_ENV']: arguments = [] for _ in range(0, token): sub_token = self.BSM_TOKEN_EXEC_ARGUMENT.parse_stream( file_object) string = self._CopyUtf8ByteArrayToString(sub_token.text) arguments.append(string) return u'[{0}: {1:s}]'.format(bsm_type, u' '.join(arguments)) elif bsm_type == u'BSM_TOKEN_AUT_SOCKINET32': return (u'[{0}: {1} ({2}) open in port {3}. Address {4}]'.format( bsm_type, bsmtoken.BSM_PROTOCOLS.get(token.net_type, u'UNKNOWN'), token.net_type, token.port_number, self._IPv4Format(token.ipv4))) elif bsm_type == u'BSM_TOKEN_AUT_SOCKINET128': return u'[{0}: {1} ({2}) open in port {3}. Address {4}]'.format( bsm_type, bsmtoken.BSM_PROTOCOLS.get(token.net_type, u'UNKNOWN'), token.net_type, token.port_number, self._IPv6Format(token.ipv6.high, token.ipv6.low)) elif bsm_type == u'BSM_TOKEN_ADDR': return u'[{0}: {1}]'.format(bsm_type, self._IPv4Format(token)) elif bsm_type == u'BSM_TOKEN_IP': return u'[IPv4_Header: 0x{0:s}]'.format(token.encode(u'hex')) elif bsm_type == u'BSM_TOKEN_ADDR_EXT': return u'[{0}: {1} ({2}). Address {3}]'.format( bsm_type, bsmtoken.BSM_PROTOCOLS.get(token.net_type, u'UNKNOWN'), token.net_type, self._IPv6Format(token.ipv6.high, token.ipv6.low)) elif bsm_type == u'BSM_TOKEN_PORT': return u'[{0}: {1}]'.format(bsm_type, token) elif bsm_type == u'BSM_TOKEN_TRAILER': return u'[{0}: {1}]'.format(bsm_type, token.record_length) elif bsm_type == u'BSM_TOKEN_FILE': # TODO: if this timestamp is usefull, it must be extracted as a separate # event object. timestamp = timelib.Timestamp.FromPosixTimeWithMicrosecond( token.timestamp, token.microsecond) date_time = timelib.Timestamp.CopyToDatetime(timestamp, pytz.UTC) date_time_string = date_time.strftime(u'%Y-%m-%d %H:%M:%S') string = self._CopyUtf8ByteArrayToString(token.text) return u'[{0}: {1:s}, timestamp: {2:s}]'.format( bsm_type, string, date_time_string) elif bsm_type == u'BSM_TOKEN_IPC': return u'[{0}: object type {1}, object id {2}]'.format( bsm_type, token.object_type, token.object_id) elif bsm_type in [u'BSM_TOKEN_PROCESS32', u'BSM_TOKEN_PROCESS64']: return ( u'[{0}: aid({1}), euid({2}), egid({3}), uid({4}), gid({5}), ' u'pid({6}), session_id({7}), terminal_port({8}), ' u'terminal_ip({9})]').format( bsm_type, token.subject_data.audit_uid, token.subject_data.effective_uid, token.subject_data.effective_gid, token.subject_data.real_uid, token.subject_data.real_gid, token.subject_data.pid, token.subject_data.session_id, token.terminal_port, self._IPv4Format(token.ipv4)) elif bsm_type in [ u'BSM_TOKEN_PROCESS32_EX', u'BSM_TOKEN_PROCESS64_EX' ]: if token.bsm_ip_type_short.net_type == self.AU_IPv6: ip = self._IPv6Format(token.bsm_ip_type_short.ip_addr.high, token.bsm_ip_type_short.ip_addr.low) elif token.bsm_ip_type_short.net_type == self.AU_IPv4: ip = self._IPv4Format(token.bsm_ip_type_short.ip_addr) else: ip = u'unknown' return ( u'[{0}: aid({1}), euid({2}), egid({3}), uid({4}), gid({5}), ' u'pid({6}), session_id({7}), terminal_port({8}), ' u'terminal_ip({9})]').format( bsm_type, token.subject_data.audit_uid, token.subject_data.effective_uid, token.subject_data.effective_gid, token.subject_data.real_uid, token.subject_data.real_gid, token.subject_data.pid, token.subject_data.session_id, token.terminal_port, ip) elif bsm_type == u'BSM_TOKEN_DATA': data = [] data_type = bsmtoken.BSM_TOKEN_DATA_TYPE.get(token.data_type, u'') if data_type == u'AUR_CHAR': for _ in range(token.unit_count): data.append( self.BSM_TOKEN_DATA_CHAR.parse_stream(file_object)) elif data_type == u'AUR_SHORT': for _ in range(token.unit_count): data.append( self.BSM_TOKEN_DAT_SHORT.parse_stream(file_object)) elif data_type == u'AUR_INT32': for _ in range(token.unit_count): data.append( self.BSM_TOKEN_DATA_INTEGER.parse_stream(file_object)) else: data.append(u'Unknown type data') # TODO: the data when it is string ends with ".", HW a space is return # after uses the UTF-8 conversion. return u'[{0}: Format data: {1}, Data: {2}]'.format( bsm_type, bsmtoken.BSM_TOKEN_DATA_PRINT[token.how_to_print], self._RawToUTF8(u''.join(data))) elif bsm_type in [u'BSM_TOKEN_ATTR32', u'BSM_TOKEN_ATTR64']: return (u'[{0}: Mode: {1}, UID: {2}, GID: {3}, ' u'File system ID: {4}, Node ID: {5}, Device: {6}]').format( bsm_type, token.file_mode, token.uid, token.gid, token.file_system_id, token.file_system_node_id, token.device) elif bsm_type == u'BSM_TOKEN_GROUPS': arguments = [] for _ in range(token): arguments.append( self._RawToUTF8( self.BSM_TOKEN_DATA_INTEGER.parse_stream(file_object))) return u'[{0}: {1:s}]'.format(bsm_type, u','.join(arguments)) elif bsm_type == u'BSM_TOKEN_AUT_SOCKINET32_EX': if bsmtoken.BSM_PROTOCOLS.get(token.socket_domain, u'') == u'INET6': saddr = self._IPv6Format(token.structure_addr_port.saddr_high, token.structure_addr_port.saddr_low) daddr = self._IPv6Format(token.structure_addr_port.daddr_high, token.structure_addr_port.daddr_low) else: saddr = self._IPv4Format( token.structure_addr_port.source_address) daddr = self._IPv4Format( token.structure_addr_port.destination_address) return u'[{0}: from {1} port {2} to {3} port {4}]'.format( bsm_type, saddr, token.structure_addr_port.source_port, daddr, token.structure_addr_port.destination_port) elif bsm_type == u'BSM_TOKEN_IPC_PERM': return (u'[{0}: user id {1}, group id {2}, create user id {3}, ' u'create group id {4}, access {5}]').format( bsm_type, token.user_id, token.group_id, token.creator_user_id, token.creator_group_id, token.access_mode) elif bsm_type == u'BSM_TOKEN_SOCKET_UNIX': string = self._CopyUtf8ByteArrayToString(token.path) return u'[{0}: Family {1}, Path {2:s}]'.format( bsm_type, token.family, string) elif bsm_type == u'BSM_TOKEN_OPAQUE': string = self._CopyByteArrayToBase16String(token.text) return u'[{0}: {1:s}]'.format(bsm_type, string) elif bsm_type == u'BSM_TOKEN_SEQUENCE': return u'[{0}: {1}]'.format(bsm_type, token)
class FlacReader: FRAME_HEADER = construct.Struct( 'frame_header', construct.Bits('sync', 14), construct.Bits('reserved', 2), construct.Bits('block_size', 4), construct.Bits('sample_rate', 4), construct.Bits('channel_assignment', 4), construct.Bits('bits_per_sample', 3), construct.Padding(1), construct.IfThenElse( 'total_channels', lambda ctx1: ctx1['channel_assignment'] <= 7, construct.Value('c', lambda ctx2: ctx2['channel_assignment'] + 1), construct.Value('c', lambda ctx3: 2)), UTF8('frame_number'), construct.IfThenElse( 'extended_block_size', lambda ctx1: ctx1['block_size'] == 6, construct.Bits('b', 8), construct.If(lambda ctx2: ctx2['block_size'] == 7, construct.Bits('b', 16))), construct.IfThenElse( 'extended_sample_rate', lambda ctx1: ctx1['sample_rate'] == 12, construct.Bits('s', 8), construct.If(lambda ctx2: ctx2['sample_rate'] in (13, 14), construct.Bits('s', 16))), construct.Bits('crc8', 8)) UNARY = construct.Struct( 'unary', construct.RepeatUntil(lambda obj, ctx: obj == '\x01', construct.Field('bytes', 1)), construct.Value('value', lambda ctx: len(ctx['bytes']) - 1)) SUBFRAME_HEADER = construct.Struct( 'subframe_header', construct.Padding(1), construct.Bits('subframe_type', 6), construct.Flag('has_wasted_bits_per_sample'), construct.IfThenElse('wasted_bits_per_sample', lambda ctx: ctx['has_wasted_bits_per_sample'], PlusOne(Unary('value')), construct.Value('value', lambda ctx2: 0))) GET_BLOCKSIZE_FROM_STREAMINFO = -1 GET_8BIT_BLOCKSIZE_FROM_END_OF_HEADER = -2 GET_16BIT_BLOCKSIZE_FROM_END_OF_HEADER = -3 BLOCK_SIZE = (GET_BLOCKSIZE_FROM_STREAMINFO, 192, 576, 1152, 2304, 4608, GET_8BIT_BLOCKSIZE_FROM_END_OF_HEADER, GET_16BIT_BLOCKSIZE_FROM_END_OF_HEADER, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768) GET_SAMPLE_SIZE_FROM_STREAMINFO = -1 SAMPLE_SIZE = (GET_SAMPLE_SIZE_FROM_STREAMINFO, 8, 12, None, 16, 20, 24, None) def FIXED0(subframe, residual, i): subframe.insert(i, residual[i]) def FIXED1(subframe, residual, i): subframe.insert(i, subframe[i - 1] + residual[i]) def FIXED2(subframe, residual, i): subframe.insert(i, ((2 * subframe[i - 1]) - subframe[i - 2] + \ residual[i])) def FIXED3(subframe, residual, i): subframe.insert(i, ((3 * subframe[i - 1]) - (3 * subframe[i - 2]) + \ subframe[i - 3] + residual[i])) def FIXED4(subframe, residual, i): subframe.insert(i, ((4 * subframe[i - 1]) - (6 * subframe[i - 2]) + \ (4 * subframe[i - 3]) - subframe[i - 4] + residual[i])) #iterates over all of the channels, in order def MERGE_INDEPENDENT(channel_list): channel_data = [iter(c) for c in channel_list] while (True): for channel in channel_data: yield channel.next() def MERGE_LEFT(channel_list): channel_left = iter(channel_list[0]) channel_side = iter(channel_list[1]) while (True): left = channel_left.next() side = channel_side.next() yield left yield left - side def MERGE_RIGHT(channel_list): channel_side = iter(channel_list[0]) channel_right = iter(channel_list[1]) while (True): side = channel_side.next() right = channel_right.next() yield side + right yield right def MERGE_MID(channel_list): channel_mid = iter(channel_list[0]) channel_side = iter(channel_list[1]) while (True): mid = channel_mid.next() side = channel_side.next() mid = mid << 1 mid |= (side & 0x1) yield (mid + side) >> 1 yield (mid - side) >> 1 CHANNEL_FUNCTIONS = (MERGE_INDEPENDENT, MERGE_INDEPENDENT, MERGE_INDEPENDENT, MERGE_INDEPENDENT, MERGE_INDEPENDENT, MERGE_INDEPENDENT, MERGE_INDEPENDENT, MERGE_INDEPENDENT, MERGE_LEFT, MERGE_RIGHT, MERGE_MID) FIXED_FUNCTIONS = (FIXED0, FIXED1, FIXED2, FIXED3, FIXED4) def __init__(self, flac_stream): self.stream = BufferedStream(flac_stream) self.streaminfo = None self.bitstream = None #ensure the file starts with 'fLaC' self.read_stream_marker() #initialize self.bitstream self.begin_bitstream() #find self.streaminfo in case we need it self.read_metadata_blocks() def close(self): if (self.bitstream != None): self.bitstream.close() else: self.stream.close() def read_stream_marker(self): if (self.stream.read(4) != 'fLaC'): raise FlacStreamException('invalid stream marker') def read_metadata_blocks(self): block = audiotools.FlacAudio.METADATA_BLOCK_HEADER.parse_stream( self.stream) while (block.last_block == 0): if (block.block_type == 0): self.streaminfo = audiotools.FlacAudio.STREAMINFO.parse_stream( self.stream) else: self.stream.seek(block.block_length, 1) block = audiotools.FlacAudio.METADATA_BLOCK_HEADER.parse_stream( self.stream) self.stream.seek(block.block_length, 1) def begin_bitstream(self): import bitstream #self.bitstream = construct.BitStreamReader(self.stream) self.bitstream = bitstream.BitStreamReader(self.stream) def read_frame(self): self.stream.reset_buffer() try: header = FlacReader.FRAME_HEADER.parse_stream(self.bitstream) except construct.core.FieldError: return "" if (header.sync != 0x3FFE): raise FlacStreamException('invalid sync') if (crc8(self.stream.getvalue()[0:-1]) != header.crc8): raise FlacStreamException('crc8 checksum failed') #block_size tells us how many samples we need from each subframe block_size = FlacReader.BLOCK_SIZE[header.block_size] if (block_size == self.GET_BLOCKSIZE_FROM_STREAMINFO): block_size = self.streaminfo.maximum_blocksize elif ((block_size == self.GET_8BIT_BLOCKSIZE_FROM_END_OF_HEADER) or (block_size == self.GET_16BIT_BLOCKSIZE_FROM_END_OF_HEADER)): block_size = header.extended_block_size + 1 #grab subframe data as 32-bit array objects subframe_data = [] for channel_number in xrange(header.total_channels): subframe_data.append( self.read_subframe(header, block_size, channel_number)) crc16sum = crc16(self.stream.getvalue()) #try to byte-align the stream if (len(self.bitstream.buffer) > 0): self.bitstream.read(len(self.bitstream.buffer)) if (crc16sum != construct.Bits('crc16', 16).parse_stream( self.bitstream)): raise FlacStreamException('crc16 checksum failed') #convert our list of subframe data arrays into #a string of sample data if (FlacReader.SAMPLE_SIZE[header.bits_per_sample] == 16): merged_frames = array.array( 'h', FlacReader.CHANNEL_FUNCTIONS[header.channel_assignment]( subframe_data)) if (audiotools.BIG_ENDIAN): merged_frames.byteswap() return merged_frames.tostring() elif (FlacReader.SAMPLE_SIZE[header.bits_per_sample] == 8): merged_frames = array.array( 'b', FlacReader.CHANNEL_FUNCTIONS[header.channel_assignment]( subframe_data)) return merged_frames.tostring() else: if (FlacReader.SAMPLE_SIZE[header.bits_per_sample] == \ self.GET_SAMPLE_SIZE_FROM_STREAMINFO): bits_per_sample = self.streaminfo.bits_per_sample + 1 elif (FlacReader.SAMPLE_SIZE[header.bits_per_sample] == None): raise FlacStreamException('invalid bits per sample') else: bits_per_sample = FlacReader.SAMPLE_SIZE[ header.bits_per_sample] stream = construct.GreedyRepeater( construct.BitStruct( 'bits', construct.Bits('value', bits_per_sample, swapped=True, signed=True))) return stream.build([ construct.Container(value=v) for v in FlacReader.CHANNEL_FUNCTIONS[ header.channel_assignment](subframe_data) ]) def read_subframe(self, frame_header, block_size, channel_number): subframe_header = \ FlacReader.SUBFRAME_HEADER.parse_stream(self.bitstream) #figure out the bits-per-sample of this subframe if ((frame_header.channel_assignment == 8) and (channel_number == 1)): #if channel is stored as left+difference #and this is the difference, add 1 bit bits_per_sample = FlacReader.SAMPLE_SIZE[ frame_header.bits_per_sample] + 1 elif ((frame_header.channel_assignment == 9) and (channel_number == 0)): #if channel is stored as difference+right #and this is the difference, add 1 bit bits_per_sample = FlacReader.SAMPLE_SIZE[ frame_header.bits_per_sample] + 1 elif ((frame_header.channel_assignment == 10) and (channel_number == 1)): #if channel is stored as average+difference #and this is the difference, add 1 bit bits_per_sample = FlacReader.SAMPLE_SIZE[ frame_header.bits_per_sample] + 1 else: #otherwise, use the number from the frame header bits_per_sample = FlacReader.SAMPLE_SIZE[ frame_header.bits_per_sample] if (subframe_header.has_wasted_bits_per_sample): bits_per_sample -= subframe_header.wasted_bits_per_sample if (subframe_header.subframe_type == 0): subframe = self.read_subframe_constant(block_size, bits_per_sample) elif (subframe_header.subframe_type == 1): subframe = self.read_subframe_verbatim(block_size, bits_per_sample) elif ((subframe_header.subframe_type & 0x38) == 0x08): subframe = self.read_subframe_fixed( subframe_header.subframe_type & 0x07, block_size, bits_per_sample) elif ((subframe_header.subframe_type & 0x20) == 0x20): subframe = self.read_subframe_lpc( (subframe_header.subframe_type & 0x1F) + 1, block_size, bits_per_sample) else: raise FlacStreamException('invalid subframe type') if (subframe_header.has_wasted_bits_per_sample): return array.array('i', [ i << subframe_header.wasted_bits_per_sample for i in subframe ]) else: return subframe def read_subframe_constant(self, block_size, bits_per_sample): sample = construct.Bits('b', bits_per_sample).parse_stream(self.bitstream) subframe = array.array('i', [sample] * block_size) return subframe def read_subframe_verbatim(self, block_size, bits_per_sample): return array.array( 'i', construct.StrictRepeater( block_size, construct.Bits("samples", bits_per_sample, signed=True)).parse_stream(self.bitstream)) def read_subframe_fixed(self, order, block_size, bits_per_sample): samples = construct.StrictRepeater( order, construct.Bits("warm_up_samples", bits_per_sample, signed=True)) subframe = array.array('i', samples.parse_stream(self.bitstream)) residual = self.read_residual(block_size, order) fixed_func = self.FIXED_FUNCTIONS[order] for i in xrange(len(subframe), block_size): fixed_func(subframe, residual, i) return subframe def read_subframe_lpc(self, order, block_size, bits_per_sample): samples = construct.StrictRepeater( order, construct.Bits("warm_up_samples", bits_per_sample, signed=True)) subframe = array.array('i', samples.parse_stream(self.bitstream)) lpc_precision = construct.Bits('lpc_precision', 4).parse_stream( self.bitstream) + 1 lpc_shift = construct.Bits('lpc_shift', 5).parse_stream(self.bitstream) coefficients = array.array( 'i', construct.StrictRepeater( order, construct.Bits('coefficients', lpc_precision, signed=True)).parse_stream(self.bitstream)) residual = self.read_residual(block_size, order) for i in xrange(len(subframe), block_size): subframe.insert(i, (sum( [coefficients[j] * subframe[i - j - 1] for j in xrange(0,len(coefficients))]) >> lpc_shift) + \ residual[i]) return subframe def read_residual(self, block_size, predictor_order): rice = array.array('i') #add some dummy rice so that the Rice index matches #that of the rest of the subframe for i in xrange(predictor_order): rice.append(0) coding_method = self.bitstream.read(2) if (coding_method == '\x00\x00'): rice2 = False elif (coding_method == '\x00\x01'): rice2 = True else: raise FlacStreamException('invalid residual coding method') partition_order = construct.Bits('partition_order', 4).parse_stream(self.bitstream) if (partition_order > 0): total_samples = ((block_size / 2**partition_order) - predictor_order) rice.extend(self.read_encoded_rice(total_samples, rice2)) for i in xrange(1, 2**partition_order): total_samples = (block_size / 2**partition_order) rice.extend(self.read_encoded_rice(total_samples, rice2)) else: rice.extend( self.read_encoded_rice(block_size - predictor_order, rice2)) return rice def read_encoded_rice(self, total_samples, rice2=False): bin_to_int = construct.lib.binary.bin_to_int samples = array.array('i') if (not rice2): rice_parameter = construct.Bits('rice_parameter', 4).parse_stream(self.bitstream) else: rice_parameter = construct.Bits('rice_parameter', 5).parse_stream(self.bitstream) if (rice_parameter != 0xF): #a Rice encoded residual for x in xrange(total_samples): #count the number of 0 bits before the next 1 bit #(unary encoding) #to find our most significant bits msb = 0 s = self.bitstream.read(1) while (s != '\x01'): msb += 1 s = self.bitstream.read(1) #grab the proper number of least significant bits lsb = bin_to_int(self.bitstream.read(rice_parameter)) #combine msb and lsb to get the Rice-encoded value value = (msb << rice_parameter) | lsb if ((value & 0x1) == 0x1): #negative samples.append(-(value >> 1) - 1) else: #positive samples.append(value >> 1) else: #unencoded residual bits_per_sample = construct.Bits('escape_code', 5).parse_stream(self.bitstream) sample = construct.Bits("sample", bits_per_sample, signed=True) for x in xrange(total_samples): samples.append(sample.parse_stream(self.bitstream)) return samples
def __init__(self, name): construct.Adapter.__init__( self, construct.RepeatUntil(lambda obj, ctx: obj == 1, construct.Byte(name)))
class MRUListExStringAndShellItemPlugin(interface.KeyPlugin, MRUListExPluginMixin): """Windows Registry plugin to parse a string and shell item MRUListEx.""" NAME = 'winreg_mrulistex_string_and_shell_item' DESCRIPTION = u'Parser for Most Recently Used (MRU) Registry data.' REG_TYPE = 'any' REG_KEYS = frozenset([ u'\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\RecentDocs' ]) _STRING_AND_SHELL_ITEM_STRUCT = construct.Struct( 'string_and_shell_item', construct.RepeatUntil(lambda obj, ctx: obj == '\x00\x00', construct.Field('string', 2)), construct.Anchor('shell_item')) def _ParseMRUListExEntryValue(self, parser_mediator, key, entry_index, entry_number, codepage='cp1252', **unused_kwargs): """Parses the MRUListEx entry value. Args: parser_mediator: A parser mediator object (instance of ParserMediator). key: the Registry key (instance of winreg.WinRegKey) that contains the MRUListEx value. entry_index: integer value representing the MRUListEx entry index. entry_number: integer value representing the entry number. codepage: Optional extended ASCII string codepage. The default is cp1252. Returns: A string containing the value. """ value_string = u'' value = key.GetValue(u'{0:d}'.format(entry_number)) if value is None: logging.debug( u'[{0:s}] Missing MRUListEx entry value: {1:d} in key: {2:s}.'. format(self.NAME, entry_number, key.path)) elif not value.DataIsBinaryData(): logging.debug( (u'[{0:s}] Non-binary MRUListEx entry value: {1:d} in key: ' u'{2:s}.').format(self.NAME, entry_number, key.path)) elif value.data: value_struct = self._STRING_AND_SHELL_ITEM_STRUCT.parse(value.data) try: # The struct includes the end-of-string character that we need # to strip off. path = ''.join(value_struct.string).decode('utf16')[:-1] except UnicodeDecodeError as exception: logging.warning(( u'[{0:s}] Unable to decode string MRUListEx entry value: {1:d} ' u'in key: {2:s} with error: {3:s}').format( self.NAME, entry_number, key.path, exception)) path = u'' if path: shell_item_list_data = value.data[value_struct.shell_item:] if not shell_item_list_data: logging.debug(( u'[{0:s}] Missing shell item in MRUListEx entry value: {1:d}' u'in key: {2:s}').format(self.NAME, entry_number, key.path)) value_string = u'Path: {0:s}'.format(path) else: shell_items_parser = shell_items.ShellItemsParser(key.path) shell_items_parser.UpdateChainAndParse( parser_mediator, shell_item_list_data, None, codepage=codepage) value_string = u'Path: {0:s}, Shell item: [{1:s}]'.format( path, shell_items_parser.CopyToPath()) return value_string def GetEntries(self, parser_mediator, key=None, registry_type=None, codepage='cp1252', **kwargs): """Extract event objects from a Registry key containing a MRUListEx value. Args: parser_mediator: A parser mediator object (instance of ParserMediator). key: Optional Registry key (instance of winreg.WinRegKey). The default is None. registry_type: Optional Registry type string. The default is None. codepage: Optional extended ASCII string codepage. The default is cp1252. """ self._ParseMRUListExKey(parser_mediator, key, registry_type=registry_type, codepage=codepage) if key.name == u'RecentDocs': # For the RecentDocs MRUListEx we also need to parse its subkeys # since the Registry key path does not support wildcards yet. for subkey in key.GetSubkeys(): self._ParseMRUListExKey(parser_mediator, subkey, registry_type=registry_type, codepage=codepage)
class BSMParser(interface.FileObjectParser): """Parser for BSM files.""" NAME = 'bsm_log' DESCRIPTION = 'Parser for BSM log files.' # BSM supported version (0x0b = 11). AUDIT_HEADER_VERSION = 11 # Magic Trail Header. BSM_TOKEN_TRAILER_MAGIC = b'b105' # IP Version constants. AU_IPv4 = 4 AU_IPv6 = 16 IPV4_STRUCT = construct.UBInt32('ipv4') IPV6_STRUCT = construct.Struct( 'ipv6', construct.UBInt64('high'), construct.UBInt64('low')) # Tested structures. # INFO: I have ommited the ID in the structures declaration. # I used the BSM_TYPE first to read the ID, and then, the structure. # Tokens always start with an ID value that identifies their token # type and subsequent structure. _BSM_TOKEN = construct.UBInt8('token_id') # Data type structures. BSM_TOKEN_DATA_CHAR = construct.String('value', 1) BSM_TOKEN_DATA_SHORT = construct.UBInt16('value') BSM_TOKEN_DATA_INTEGER = construct.UBInt32('value') # Common structure used by other structures. # audit_uid: integer, uid that generates the entry. # effective_uid: integer, the permission user used. # effective_gid: integer, the permission group used. # real_uid: integer, user id of the user that execute the process. # real_gid: integer, group id of the group that execute the process. # pid: integer, identification number of the process. # session_id: unknown, need research. BSM_TOKEN_SUBJECT_SHORT = construct.Struct( 'subject_data', construct.UBInt32('audit_uid'), construct.UBInt32('effective_uid'), construct.UBInt32('effective_gid'), construct.UBInt32('real_uid'), construct.UBInt32('real_gid'), construct.UBInt32('pid'), construct.UBInt32('session_id')) # Common structure used by other structures. # Identify the kind of inet (IPv4 or IPv6) # TODO: instead of 16, AU_IPv6 must be used. BSM_IP_TYPE_SHORT = construct.Struct( 'bsm_ip_type_short', construct.UBInt32('net_type'), construct.Switch( 'ip_addr', _BSMTokenGetNetType, {16: IPV6_STRUCT}, default=IPV4_STRUCT)) # Initial fields structure used by header structures. # length: integer, the length of the entry, equal to trailer (doc: length). # version: integer, version of BSM (AUDIT_HEADER_VERSION). # event_type: integer, the type of event (/etc/security/audit_event). # modifier: integer, unknown, need research (It is always 0). BSM_HEADER = construct.Struct( 'bsm_header', construct.UBInt32('length'), construct.UBInt8('version'), construct.UBInt16('event_type'), construct.UBInt16('modifier')) # First token of one entry. # timestamp: unsigned integer, number of seconds since # January 1, 1970 00:00:00 UTC. # microseconds: unsigned integer, number of micro seconds. BSM_HEADER32 = construct.Struct( 'bsm_header32', BSM_HEADER, construct.UBInt32('timestamp'), construct.UBInt32('microseconds')) BSM_HEADER64 = construct.Struct( 'bsm_header64', BSM_HEADER, construct.UBInt64('timestamp'), construct.UBInt64('microseconds')) BSM_HEADER32_EX = construct.Struct( 'bsm_header32_ex', BSM_HEADER, BSM_IP_TYPE_SHORT, construct.UBInt32('timestamp'), construct.UBInt32('microseconds')) # Token TEXT, provides extra information. BSM_TOKEN_TEXT = construct.Struct( 'bsm_token_text', construct.UBInt16('length'), construct.Array(_BSMTokenGetLength, construct.UBInt8('text'))) # Path of the executable. BSM_TOKEN_PATH = BSM_TOKEN_TEXT # Identified the end of the record (follow by TRAILER). # status: integer that identifies the status of the exit (BSM_ERRORS). # return: returned value from the operation. BSM_TOKEN_RETURN32 = construct.Struct( 'bsm_token_return32', construct.UBInt8('status'), construct.UBInt32('return_value')) BSM_TOKEN_RETURN64 = construct.Struct( 'bsm_token_return64', construct.UBInt8('status'), construct.UBInt64('return_value')) # Identified the number of bytes that was written. # magic: 2 bytes that identifies the TRAILER (BSM_TOKEN_TRAILER_MAGIC). # length: integer that has the number of bytes from the entry size. BSM_TOKEN_TRAILER = construct.Struct( 'bsm_token_trailer', construct.UBInt16('magic'), construct.UBInt32('record_length')) # A 32-bits argument. # num_arg: the number of the argument. # name_arg: the argument's name. # text: the string value of the argument. BSM_TOKEN_ARGUMENT32 = construct.Struct( 'bsm_token_argument32', construct.UBInt8('num_arg'), construct.UBInt32('name_arg'), construct.UBInt16('length'), construct.Array(_BSMTokenGetLength, construct.UBInt8('text'))) # A 64-bits argument. # num_arg: integer, the number of the argument. # name_arg: text, the argument's name. # text: the string value of the argument. BSM_TOKEN_ARGUMENT64 = construct.Struct( 'bsm_token_argument64', construct.UBInt8('num_arg'), construct.UBInt64('name_arg'), construct.UBInt16('length'), construct.Array(_BSMTokenGetLength, construct.UBInt8('text'))) # Identify an user. # terminal_id: unknown, research needed. # terminal_addr: unknown, research needed. BSM_TOKEN_SUBJECT32 = construct.Struct( 'bsm_token_subject32', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt32('terminal_port'), IPV4_STRUCT) # Identify an user using a extended Token. # terminal_port: unknown, need research. # net_type: unknown, need research. BSM_TOKEN_SUBJECT32_EX = construct.Struct( 'bsm_token_subject32_ex', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt32('terminal_port'), BSM_IP_TYPE_SHORT) # au_to_opaque // AUT_OPAQUE BSM_TOKEN_OPAQUE = BSM_TOKEN_TEXT # au_to_seq // AUT_SEQ BSM_TOKEN_SEQUENCE = BSM_TOKEN_DATA_INTEGER # Program execution with options. # For each argument we are going to have a string+ "\x00". # Example: [00 00 00 02][41 42 43 00 42 42 00] # 2 Arguments, Arg1: [414243] Arg2: [4242]. BSM_TOKEN_EXEC_ARGUMENTS = construct.UBInt32('number_arguments') BSM_TOKEN_EXEC_ARGUMENT = construct.Struct( 'bsm_token_exec_argument', construct.RepeatUntil( _BSMTokenIsEndOfString, construct.StaticField("text", 1))) # au_to_in_addr // AUT_IN_ADDR: BSM_TOKEN_ADDR = IPV4_STRUCT # au_to_in_addr_ext // AUT_IN_ADDR_EX: BSM_TOKEN_ADDR_EXT = construct.Struct( 'bsm_token_addr_ext', construct.UBInt32('net_type'), IPV6_STRUCT) # au_to_ip // AUT_IP: # TODO: parse this header in the correct way. BSM_TOKEN_IP = construct.String('binary_ipv4_add', 20) # au_to_ipc // AUT_IPC: BSM_TOKEN_IPC = construct.Struct( 'bsm_token_ipc', construct.UBInt8('object_type'), construct.UBInt32('object_id')) # au_to_ipc_perm // au_to_ipc_perm BSM_TOKEN_IPC_PERM = construct.Struct( 'bsm_token_ipc_perm', construct.UBInt32('user_id'), construct.UBInt32('group_id'), construct.UBInt32('creator_user_id'), construct.UBInt32('creator_group_id'), construct.UBInt32('access_mode'), construct.UBInt32('slot_seq'), construct.UBInt32('key')) # au_to_iport // AUT_IPORT: BSM_TOKEN_PORT = construct.UBInt16('port_number') # au_to_file // AUT_OTHER_FILE32: BSM_TOKEN_FILE = construct.Struct( 'bsm_token_file', construct.UBInt32('timestamp'), construct.UBInt32('microseconds'), construct.UBInt16('length'), construct.Array(_BSMTokenGetLength, construct.UBInt8('text'))) # au_to_subject64 // AUT_SUBJECT64: BSM_TOKEN_SUBJECT64 = construct.Struct( 'bsm_token_subject64', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt64('terminal_port'), IPV4_STRUCT) # au_to_subject64_ex // AU_IPv4: BSM_TOKEN_SUBJECT64_EX = construct.Struct( 'bsm_token_subject64_ex', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt32('terminal_port'), construct.UBInt32('terminal_type'), BSM_IP_TYPE_SHORT) # au_to_process32 // AUT_PROCESS32: BSM_TOKEN_PROCESS32 = construct.Struct( 'bsm_token_process32', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt32('terminal_port'), IPV4_STRUCT) # au_to_process64 // AUT_PROCESS32: BSM_TOKEN_PROCESS64 = construct.Struct( 'bsm_token_process64', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt64('terminal_port'), IPV4_STRUCT) # au_to_process32_ex // AUT_PROCESS32_EX: BSM_TOKEN_PROCESS32_EX = construct.Struct( 'bsm_token_process32_ex', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt32('terminal_port'), BSM_IP_TYPE_SHORT) # au_to_process64_ex // AUT_PROCESS64_EX: BSM_TOKEN_PROCESS64_EX = construct.Struct( 'bsm_token_process64_ex', BSM_TOKEN_SUBJECT_SHORT, construct.UBInt64('terminal_port'), BSM_IP_TYPE_SHORT) # au_to_sock_inet32 // AUT_SOCKINET32: BSM_TOKEN_AUT_SOCKINET32 = construct.Struct( 'bsm_token_aut_sockinet32', construct.UBInt16('net_type'), construct.UBInt16('port_number'), IPV4_STRUCT) # Info: checked against the source code of XNU, but not against # real BSM file. BSM_TOKEN_AUT_SOCKINET128 = construct.Struct( 'bsm_token_aut_sockinet128', construct.UBInt16('net_type'), construct.UBInt16('port_number'), IPV6_STRUCT) INET6_ADDR_TYPE = construct.Struct( 'addr_type', construct.UBInt16('ip_type'), construct.UBInt16('source_port'), construct.UBInt64('saddr_high'), construct.UBInt64('saddr_low'), construct.UBInt16('destination_port'), construct.UBInt64('daddr_high'), construct.UBInt64('daddr_low')) INET4_ADDR_TYPE = construct.Struct( 'addr_type', construct.UBInt16('ip_type'), construct.UBInt16('source_port'), construct.UBInt32('source_address'), construct.UBInt16('destination_port'), construct.UBInt32('destination_address')) # au_to_socket_ex // AUT_SOCKET_EX # TODO: Change the 26 for unixbsm.BSM_PROTOCOLS.INET6. BSM_TOKEN_AUT_SOCKINET32_EX = construct.Struct( 'bsm_token_aut_sockinet32_ex', construct.UBInt16('socket_domain'), construct.UBInt16('socket_type'), construct.Switch( 'structure_addr_port', _BSMTokenGetSocketDomain, {26: INET6_ADDR_TYPE}, default=INET4_ADDR_TYPE)) # au_to_sock_unix // AUT_SOCKUNIX BSM_TOKEN_SOCKET_UNIX = construct.Struct( 'bsm_token_au_to_sock_unix', construct.UBInt16('family'), construct.RepeatUntil( _BSMTokenIsEndOfString, construct.StaticField("path", 1))) # au_to_data // au_to_data # how to print: bsmtoken.BSM_TOKEN_DATA_PRINT. # type: bsmtoken.BSM_TOKEN_DATA_TYPE. # unit_count: number of type values. # BSM_TOKEN_DATA has a end field = type * unit_count BSM_TOKEN_DATA = construct.Struct( 'bsm_token_data', construct.UBInt8('how_to_print'), construct.UBInt8('data_type'), construct.UBInt8('unit_count')) # au_to_attr32 // AUT_ATTR32 BSM_TOKEN_ATTR32 = construct.Struct( 'bsm_token_attr32', construct.UBInt32('file_mode'), construct.UBInt32('uid'), construct.UBInt32('gid'), construct.UBInt32('file_system_id'), construct.UBInt64('file_system_node_id'), construct.UBInt32('device')) # au_to_attr64 // AUT_ATTR64 BSM_TOKEN_ATTR64 = construct.Struct( 'bsm_token_attr64', construct.UBInt32('file_mode'), construct.UBInt32('uid'), construct.UBInt32('gid'), construct.UBInt32('file_system_id'), construct.UBInt64('file_system_node_id'), construct.UBInt64('device')) # au_to_exit // AUT_EXIT BSM_TOKEN_EXIT = construct.Struct( 'bsm_token_exit', construct.UBInt32('status'), construct.UBInt32('return_value')) # au_to_newgroups // AUT_NEWGROUPS # INFO: we must read BSM_TOKEN_DATA_INTEGER for each group. BSM_TOKEN_GROUPS = construct.UBInt16('group_number') # au_to_exec_env == au_to_exec_args BSM_TOKEN_EXEC_ENV = BSM_TOKEN_EXEC_ARGUMENTS # au_to_zonename //AUT_ZONENAME BSM_TOKEN_ZONENAME = BSM_TOKEN_TEXT # Token ID. # List of valid Token_ID. # Token_ID -> (NAME_STRUCTURE, STRUCTURE) # Only the checked structures are been added to the valid structures lists. _BSM_TOKEN_TYPES = { 17: ('BSM_TOKEN_FILE', BSM_TOKEN_FILE), 19: ('BSM_TOKEN_TRAILER', BSM_TOKEN_TRAILER), 20: ('BSM_HEADER32', BSM_HEADER32), 21: ('BSM_HEADER64', BSM_HEADER64), 33: ('BSM_TOKEN_DATA', BSM_TOKEN_DATA), 34: ('BSM_TOKEN_IPC', BSM_TOKEN_IPC), 35: ('BSM_TOKEN_PATH', BSM_TOKEN_PATH), 36: ('BSM_TOKEN_SUBJECT32', BSM_TOKEN_SUBJECT32), 38: ('BSM_TOKEN_PROCESS32', BSM_TOKEN_PROCESS32), 39: ('BSM_TOKEN_RETURN32', BSM_TOKEN_RETURN32), 40: ('BSM_TOKEN_TEXT', BSM_TOKEN_TEXT), 41: ('BSM_TOKEN_OPAQUE', BSM_TOKEN_OPAQUE), 42: ('BSM_TOKEN_ADDR', BSM_TOKEN_ADDR), 43: ('BSM_TOKEN_IP', BSM_TOKEN_IP), 44: ('BSM_TOKEN_PORT', BSM_TOKEN_PORT), 45: ('BSM_TOKEN_ARGUMENT32', BSM_TOKEN_ARGUMENT32), 47: ('BSM_TOKEN_SEQUENCE', BSM_TOKEN_SEQUENCE), 96: ('BSM_TOKEN_ZONENAME', BSM_TOKEN_ZONENAME), 113: ('BSM_TOKEN_ARGUMENT64', BSM_TOKEN_ARGUMENT64), 114: ('BSM_TOKEN_RETURN64', BSM_TOKEN_RETURN64), 116: ('BSM_HEADER32_EX', BSM_HEADER32_EX), 119: ('BSM_TOKEN_PROCESS64', BSM_TOKEN_PROCESS64), 122: ('BSM_TOKEN_SUBJECT32_EX', BSM_TOKEN_SUBJECT32_EX), 127: ('BSM_TOKEN_AUT_SOCKINET32_EX', BSM_TOKEN_AUT_SOCKINET32_EX), 128: ('BSM_TOKEN_AUT_SOCKINET32', BSM_TOKEN_AUT_SOCKINET32)} # Untested structures. # When not tested structure is found, we try to parse using also # these structures. BSM_TYPE_LIST_NOT_TESTED = { 49: ('BSM_TOKEN_ATTR', BSM_TOKEN_ATTR32), 50: ('BSM_TOKEN_IPC_PERM', BSM_TOKEN_IPC_PERM), 52: ('BSM_TOKEN_GROUPS', BSM_TOKEN_GROUPS), 59: ('BSM_TOKEN_GROUPS', BSM_TOKEN_GROUPS), 60: ('BSM_TOKEN_EXEC_ARGUMENTS', BSM_TOKEN_EXEC_ARGUMENTS), 61: ('BSM_TOKEN_EXEC_ENV', BSM_TOKEN_EXEC_ENV), 62: ('BSM_TOKEN_ATTR32', BSM_TOKEN_ATTR32), 82: ('BSM_TOKEN_EXIT', BSM_TOKEN_EXIT), 115: ('BSM_TOKEN_ATTR64', BSM_TOKEN_ATTR64), 117: ('BSM_TOKEN_SUBJECT64', BSM_TOKEN_SUBJECT64), 123: ('BSM_TOKEN_PROCESS32_EX', BSM_TOKEN_PROCESS32_EX), 124: ('BSM_TOKEN_PROCESS64_EX', BSM_TOKEN_PROCESS64_EX), 125: ('BSM_TOKEN_SUBJECT64_EX', BSM_TOKEN_SUBJECT64_EX), 126: ('BSM_TOKEN_ADDR_EXT', BSM_TOKEN_ADDR_EXT), 129: ('BSM_TOKEN_AUT_SOCKINET128', BSM_TOKEN_AUT_SOCKINET128), 130: ('BSM_TOKEN_SOCKET_UNIX', BSM_TOKEN_SOCKET_UNIX)} MESSAGE_CAN_NOT_SAVE = ( 'Plaso: some tokens from this entry can not be saved. Entry at 0x{0:X} ' 'with unknown token id "0x{1:X}".') # BSM token types: # https://github.com/openbsm/openbsm/blob/master/sys/bsm/audit_record.h _BSM_TOKEN_TYPE_ARGUMENT32 = 45 _BSM_TOKEN_TYPE_ARGUMENT64 = 113 _BSM_TOKEN_TYPE_ATTR = 49 _BSM_TOKEN_TYPE_ATTR32 = 62 _BSM_TOKEN_TYPE_ATTR64 = 115 _BSM_TOKEN_TYPE_EXEC_ARGUMENTS = 60 _BSM_TOKEN_TYPE_EXEC_ENV = 61 _BSM_TOKEN_TYPE_EXIT = 82 _BSM_TOKEN_TYPE_HEADER32 = 20 _BSM_TOKEN_TYPE_HEADER32_EX = 116 _BSM_TOKEN_TYPE_HEADER64 = 21 _BSM_TOKEN_TYPE_PATH = 35 _BSM_TOKEN_TYPE_PROCESS32 = 38 _BSM_TOKEN_TYPE_PROCESS32_EX = 123 _BSM_TOKEN_TYPE_PROCESS64 = 119 _BSM_TOKEN_TYPE_PROCESS64_EX = 124 _BSM_TOKEN_TYPE_RETURN32 = 39 _BSM_TOKEN_TYPE_RETURN64 = 114 _BSM_TOKEN_TYPE_SUBJECT32 = 36 _BSM_TOKEN_TYPE_SUBJECT32_EX = 122 _BSM_TOKEN_TYPE_SUBJECT64 = 117 _BSM_TOKEN_TYPE_SUBJECT64_EX = 125 _BSM_TOKEN_TYPE_TEXT = 40 _BSM_TOKEN_TYPE_ZONENAME = 96 _BSM_ARGUMENT_TOKEN_TYPES = ( _BSM_TOKEN_TYPE_ARGUMENT32, _BSM_TOKEN_TYPE_ARGUMENT64) _BSM_ATTR_TOKEN_TYPES = ( _BSM_TOKEN_TYPE_ATTR, _BSM_TOKEN_TYPE_ATTR32, _BSM_TOKEN_TYPE_ATTR64) _BSM_EXEV_TOKEN_TYPES = ( _BSM_TOKEN_TYPE_EXEC_ARGUMENTS, _BSM_TOKEN_TYPE_EXEC_ENV) _BSM_HEADER_TOKEN_TYPES = ( _BSM_TOKEN_TYPE_HEADER32, _BSM_TOKEN_TYPE_HEADER32_EX, _BSM_TOKEN_TYPE_HEADER64) _BSM_PROCESS_TOKEN_TYPES = ( _BSM_TOKEN_TYPE_PROCESS32, _BSM_TOKEN_TYPE_PROCESS64) _BSM_PROCESS_EX_TOKEN_TYPES = ( _BSM_TOKEN_TYPE_PROCESS32_EX, _BSM_TOKEN_TYPE_PROCESS64_EX) _BSM_RETURN_TOKEN_TYPES = ( _BSM_TOKEN_TYPE_EXIT, _BSM_TOKEN_TYPE_RETURN32, _BSM_TOKEN_TYPE_RETURN64) _BSM_SUBJECT_TOKEN_TYPES = ( _BSM_TOKEN_TYPE_SUBJECT32, _BSM_TOKEN_TYPE_SUBJECT64) _BSM_SUBJECT_EX_TOKEN_TYPES = ( _BSM_TOKEN_TYPE_SUBJECT32_EX, _BSM_TOKEN_TYPE_SUBJECT64_EX) _BSM_UTF8_BYTE_ARRAY_TOKEN_TYPES = ( _BSM_TOKEN_TYPE_PATH, _BSM_TOKEN_TYPE_TEXT, _BSM_TOKEN_TYPE_ZONENAME) def __init__(self): """Initializes a parser object.""" super(BSMParser, self).__init__() # Create the dictionary with all token IDs: tested and untested. self._bsm_type_list_all = self._BSM_TOKEN_TYPES.copy() self._bsm_type_list_all.update(self.BSM_TYPE_LIST_NOT_TESTED) def _CopyByteArrayToBase16String(self, byte_array): """Copies a byte array into a base-16 encoded Unicode string. Args: byte_array (bytes): A byte array. Returns: str: a base-16 encoded Unicode string. """ return ''.join(['{0:02x}'.format(byte) for byte in byte_array]) def _CopyUtf8ByteArrayToString(self, byte_array): """Copies a UTF-8 encoded byte array into a Unicode string. Args: byte_array (bytes): A byte array containing an UTF-8 encoded string. Returns: str: A Unicode string. """ byte_stream = b''.join(map(chr, byte_array)) try: string = byte_stream.decode('utf-8') except UnicodeDecodeError: logging.warning('Unable to decode UTF-8 formatted byte array.') string = byte_stream.decode('utf-8', errors='ignore') string, _, _ = string.partition(b'\x00') return string def _IPv4Format(self, address): """Formats an IPv4 address as a human readable string. Args: address (int): IPv4 address. Returns: str: human readable string of IPv4 address in 4 octet representation: "1.2.3.4". """ ipv4_string = self.IPV4_STRUCT.build(address) return socket.inet_ntoa(ipv4_string) def _IPv6Format(self, high, low): """Formats an IPv6 address as a human readable string. Args: high (int): upper 64-bit part of the IPv6 address. low (int): lower 64-bit part of the IPv6 address. Returns: str: human readable string of IPv6 address. """ ipv6_string = self.IPV6_STRUCT.build( construct.Container(high=high, low=low)) # socket.inet_ntop not supported in Windows. if hasattr(socket, 'inet_ntop'): return socket.inet_ntop(socket.AF_INET6, ipv6_string) # TODO: this approach returns double "::", illegal IPv6 addr. str_address = binascii.hexlify(ipv6_string) address = [] blank = False for pos in range(0, len(str_address), 4): if str_address[pos:pos + 4] == '0000': if not blank: address.append('') blank = True else: blank = False address.append(str_address[pos:pos + 4].lstrip('0')) return ':'.join(address) def _ParseBSMEvent(self, parser_mediator, file_object): """Parses a BSM entry (BSMEvent) from the file-like object. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. file_object (dfvfs.FileIO): a file-like object. Returns: bool: True if the BSM entry was parsed. """ record_start_offset = file_object.tell() try: token_type = self._BSM_TOKEN.parse_stream(file_object) except (IOError, construct.FieldError) as exception: parser_mediator.ProduceExtractionError(( 'unable to parse BSM token type at offset: 0x{0:08x} with error: ' '{1:s}.').format(record_start_offset, exception)) return False if token_type not in self._BSM_HEADER_TOKEN_TYPES: parser_mediator.ProduceExtractionError( 'unsupported token type: {0:d} at offset: 0x{1:08x}.'.format( token_type, record_start_offset)) # TODO: if it is a Mac OS X, search for the trailer magic value # as a end of the entry can be a possibility to continue. return False _, record_structure = self._BSM_TOKEN_TYPES.get(token_type, ('', None)) try: token = record_structure.parse_stream(file_object) except (IOError, construct.FieldError) as exception: parser_mediator.ProduceExtractionError(( 'unable to parse BSM record at offset: 0x{0:08x} with error: ' '{1:s}.').format(record_start_offset, exception)) return False event_type = bsmtoken.BSM_AUDIT_EVENT.get( token.bsm_header.event_type, 'UNKNOWN') event_type = '{0:s} ({1:d})'.format( event_type, token.bsm_header.event_type) timestamp = (token.timestamp * 1000000) + token.microseconds date_time = dfdatetime_posix_time.PosixTimeInMicroseconds( timestamp=timestamp) record_length = token.bsm_header.length record_end_offset = record_start_offset + record_length # A dict of tokens that has the entry. extra_tokens = {} # Read until we reach the end of the record. while file_object.tell() < record_end_offset: # Check if it is a known token. try: token_type = self._BSM_TOKEN.parse_stream(file_object) except (IOError, construct.FieldError): logging.warning( 'Unable to parse the Token ID at position: {0:d}'.format( file_object.tell())) return False _, record_structure = self._BSM_TOKEN_TYPES.get(token_type, ('', None)) if not record_structure: pending = record_end_offset - file_object.tell() new_extra_tokens = self.TryWithUntestedStructures( file_object, token_type, pending) extra_tokens.update(new_extra_tokens) else: token = record_structure.parse_stream(file_object) new_extra_tokens = self.FormatToken(token_type, token, file_object) extra_tokens.update(new_extra_tokens) if file_object.tell() > record_end_offset: logging.warning( 'Token ID {0:d} not expected at position 0x{1:08x}.' 'Jumping for the next entry.'.format( token_type, file_object.tell())) try: file_object.seek( record_end_offset - file_object.tell(), os.SEEK_CUR) except (IOError, construct.FieldError) as exception: logging.warning( 'Unable to jump to next entry with error: {0:s}'.format(exception)) return False # BSM can be in more than one OS: BSD, Solaris and Mac OS X. if parser_mediator.platform != 'MacOSX': event_data = BSMEventData() else: event_data = MacBSMEventData() # In Mac OS X the last two tokens are the return status and the trailer. return_value = extra_tokens.get('BSM_TOKEN_RETURN32') if not return_value: return_value = extra_tokens.get('BSM_TOKEN_RETURN64') if not return_value: return_value = 'UNKNOWN' event_data.return_value = return_value event_data.event_type = event_type event_data.extra_tokens = extra_tokens event_data.offset = record_start_offset event_data.record_length = record_length # TODO: check why trailer was passed to event in original while # event was expecting record length. # if extra_tokens: # trailer = extra_tokens.get('BSM_TOKEN_TRAILER', 'unknown') event = time_events.DateTimeValuesEvent( date_time, definitions.TIME_DESCRIPTION_CREATION) parser_mediator.ProduceEventWithEventData(event, event_data) return True def _RawToUTF8(self, byte_stream): """Copies a UTF-8 byte stream into a Unicode string. Args: byte_stream (bytes): byte stream containing an UTF-8 encoded string. Returns: str: A Unicode string. """ try: string = byte_stream.decode('utf-8') except UnicodeDecodeError: logging.warning( 'Decode UTF8 failed, the message string may be cut short.') string = byte_stream.decode('utf-8', errors='ignore') return string.partition(b'\x00')[0] def ParseFileObject(self, parser_mediator, file_object, **kwargs): """Parses a BSM file-like object. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. file_object (dfvfs.FileIO): a file-like object. Raises: UnableToParseFile: when the file cannot be parsed. """ try: is_bsm = self.VerifyFile(parser_mediator, file_object) except (IOError, construct.FieldError) as exception: raise errors.UnableToParseFile( 'Unable to parse BSM file with error: {0:s}'.format(exception)) if not is_bsm: raise errors.UnableToParseFile('Not a BSM File, unable to parse.') file_object.seek(0, os.SEEK_SET) while self._ParseBSMEvent(parser_mediator, file_object): pass def VerifyFile(self, parser_mediator, file_object): """Check if the file is a BSM file. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. file_object (dfvfs.FileIO): a file-like object. Returns: bool: True if this is a valid BSM file, False otherwise. """ # First part of the entry is always a Header. try: token_type = self._BSM_TOKEN.parse_stream(file_object) except (IOError, construct.FieldError): return False if token_type not in self._BSM_HEADER_TOKEN_TYPES: return False _, record_structure = self._BSM_TOKEN_TYPES.get(token_type, ('', None)) try: header = record_structure.parse_stream(file_object) except (IOError, construct.FieldError): return False if header.bsm_header.version != self.AUDIT_HEADER_VERSION: return False try: token_identifier = self._BSM_TOKEN.parse_stream(file_object) except (IOError, construct.FieldError): return False # If is Mac OS X BSM file, next entry is a text token indicating # if it is a normal start or it is a recovery track. if parser_mediator.platform == 'MacOSX': token_type, record_structure = self._BSM_TOKEN_TYPES.get( token_identifier, ('', None)) if not record_structure: return False if token_type != 'BSM_TOKEN_TEXT': logging.warning('It is not a valid first entry for Mac OS X BSM.') return False try: token = record_structure.parse_stream(file_object) except (IOError, construct.FieldError): return text = self._CopyUtf8ByteArrayToString(token.text) if (text != 'launchctl::Audit startup' and text != 'launchctl::Audit recovery'): logging.warning('It is not a valid first entry for Mac OS X BSM.') return False return True def TryWithUntestedStructures(self, file_object, token_id, pending): """Try to parse the pending part of the entry using untested structures. Args: file_object: BSM file. token_id: integer with the id that comes from the unknown token. pending: pending length of the entry. Returns: A list of extra tokens data that can be parsed using non-tested structures. A message indicating that a structure cannot be parsed is added for unparsed structures. """ # Data from the unknown structure. start_position = file_object.tell() start_token_id = token_id extra_tokens = {} # Read all the "pending" bytes. try: if token_id in self._bsm_type_list_all: token = self._bsm_type_list_all[token_id][1].parse_stream(file_object) new_extra_tokens = self.FormatToken(token_id, token, file_object) extra_tokens.update(new_extra_tokens) while file_object.tell() < (start_position + pending): # Check if it is a known token. try: token_id = self._BSM_TOKEN.parse_stream(file_object) except (IOError, construct.FieldError): logging.warning( 'Unable to parse the Token ID at position: {0:d}'.format( file_object.tell())) return if token_id not in self._bsm_type_list_all: break token = self._bsm_type_list_all[token_id][1].parse_stream(file_object) new_extra_tokens = self.FormatToken(token_id, token, file_object) extra_tokens.update(new_extra_tokens) except (IOError, construct.FieldError): token_id = 255 next_entry = (start_position + pending) if file_object.tell() != next_entry: # Unknown Structure. logging.warning('Unknown Token at "0x{0:X}", ID: {1} (0x{2:X})'.format( start_position - 1, token_id, token_id)) # TODO: another way to save this information must be found. extra_tokens.update( {'message': self.MESSAGE_CAN_NOT_SAVE.format( start_position - 1, start_token_id)}) # Move to next entry. file_object.seek(next_entry - file_object.tell(), os.SEEK_CUR) # It returns null list because it doesn't know witch structure was # the incorrect structure that makes that it can arrive to the spected # end of the entry. return {} return extra_tokens def FormatToken(self, token_id, token, file_object): """Parse the Token depending of the type of the structure. Args: token_id (int): identification of the token_type. token (structure): token struct to parse. file_object: BSM file. Returns: (dict): parsed Token values. Keys for returned dictionary are token name like BSM_TOKEN_SUBJECT32. Values of this dictionary are key-value pairs like terminal_ip:127.0.0.1. """ if token_id not in self._bsm_type_list_all: return {} bsm_type, _ = self._bsm_type_list_all.get(token_id, ['', '']) if token_id in self._BSM_UTF8_BYTE_ARRAY_TOKEN_TYPES: try: string = self._CopyUtf8ByteArrayToString(token.text) except TypeError: string = 'Unknown' return {bsm_type: string} elif token_id in self._BSM_RETURN_TOKEN_TYPES: return {bsm_type: { 'error': bsmtoken.BSM_ERRORS.get(token.status, 'Unknown'), 'token_status': token.status, 'call_status': token.return_value }} elif token_id in self._BSM_SUBJECT_TOKEN_TYPES: return {bsm_type: { 'aid': token.subject_data.audit_uid, 'euid': token.subject_data.effective_uid, 'egid': token.subject_data.effective_gid, 'uid': token.subject_data.real_uid, 'gid': token.subject_data.real_gid, 'pid': token.subject_data.pid, 'session_id': token.subject_data.session_id, 'terminal_port': token.terminal_port, 'terminal_ip': self._IPv4Format(token.ipv4) }} elif token_id in self._BSM_SUBJECT_EX_TOKEN_TYPES: if token.bsm_ip_type_short.net_type == self.AU_IPv6: ip = self._IPv6Format( token.bsm_ip_type_short.ip_addr.high, token.bsm_ip_type_short.ip_addr.low) elif token.bsm_ip_type_short.net_type == self.AU_IPv4: ip = self._IPv4Format(token.bsm_ip_type_short.ip_addr) else: ip = 'unknown' return {bsm_type: { 'aid': token.subject_data.audit_uid, 'euid': token.subject_data.effective_uid, 'egid': token.subject_data.effective_gid, 'uid': token.subject_data.real_uid, 'gid': token.subject_data.real_gid, 'pid': token.subject_data.pid, 'session_id': token.subject_data.session_id, 'terminal_port': token.terminal_port, 'terminal_ip': ip }} elif token_id in self._BSM_ARGUMENT_TOKEN_TYPES: string = self._CopyUtf8ByteArrayToString(token.text) return {bsm_type: { 'string': string, 'num_arg': token.num_arg, 'is': token.name_arg}} elif token_id in self._BSM_EXEV_TOKEN_TYPES: arguments = [] for _ in range(0, token): sub_token = self.BSM_TOKEN_EXEC_ARGUMENT.parse_stream(file_object) string = self._CopyUtf8ByteArrayToString(sub_token.text) arguments.append(string) return {bsm_type: ' '.join(arguments)} elif bsm_type == 'BSM_TOKEN_AUT_SOCKINET32': return {bsm_type: { 'protocols': bsmtoken.BSM_PROTOCOLS.get(token.net_type, 'UNKNOWN'), 'net_type': token.net_type, 'port': token.port_number, 'address': self._IPv4Format(token.ipv4) }} elif bsm_type == 'BSM_TOKEN_AUT_SOCKINET128': return {bsm_type: { 'protocols': bsmtoken.BSM_PROTOCOLS.get(token.net_type, 'UNKNOWN'), 'net_type': token.net_type, 'port': token.port_number, 'address': self._IPv6Format(token.ipv6.high, token.ipv6.low) }} elif bsm_type == 'BSM_TOKEN_ADDR': return {bsm_type: self._IPv4Format(token)} elif bsm_type == 'BSM_TOKEN_IP': return {'IPv4_Header': '0x{0:s}]'.format(token.encode('hex'))} elif bsm_type == 'BSM_TOKEN_ADDR_EXT': return {bsm_type: { 'protocols': bsmtoken.BSM_PROTOCOLS.get(token.net_type, 'UNKNOWN'), 'net_type': token.net_type, 'address': self._IPv6Format(token.ipv6.high, token.ipv6.low) }} elif bsm_type == 'BSM_TOKEN_PORT': return {bsm_type: token} elif bsm_type == 'BSM_TOKEN_TRAILER': return {bsm_type: token.record_length} elif bsm_type == 'BSM_TOKEN_FILE': # TODO: if this timestamp is usefull, it must be extracted as a separate # event object. timestamp = timelib.Timestamp.FromPosixTimeWithMicrosecond( token.timestamp, token.microseconds) date_time = timelib.Timestamp.CopyToDatetime(timestamp, pytz.UTC) date_time_string = date_time.strftime('%Y-%m-%d %H:%M:%S') string = self._CopyUtf8ByteArrayToString(token.text) return {bsm_type: {'string': string, 'timestamp': date_time_string}} elif bsm_type == 'BSM_TOKEN_IPC': return {bsm_type: { 'object_type': token.object_type, 'object_id': token.object_id }} elif token_id in self._BSM_PROCESS_TOKEN_TYPES: return {bsm_type: { 'aid': token.subject_data.audit_uid, 'euid': token.subject_data.effective_uid, 'egid': token.subject_data.effective_gid, 'uid': token.subject_data.real_uid, 'gid': token.subject_data.real_gid, 'pid': token.subject_data.pid, 'session_id': token.subject_data.session_id, 'terminal_port': token.terminal_port, 'terminal_ip': self._IPv4Format(token.ipv4) }} elif token_id in self._BSM_PROCESS_EX_TOKEN_TYPES: if token.bsm_ip_type_short.net_type == self.AU_IPv6: ip = self._IPv6Format( token.bsm_ip_type_short.ip_addr.high, token.bsm_ip_type_short.ip_addr.low) elif token.bsm_ip_type_short.net_type == self.AU_IPv4: ip = self._IPv4Format(token.bsm_ip_type_short.ip_addr) else: ip = 'unknown' return {bsm_type: { 'aid': token.subject_data.audit_uid, 'euid': token.subject_data.effective_uid, 'egid': token.subject_data.effective_gid, 'uid': token.subject_data.real_uid, 'gid': token.subject_data.real_gid, 'pid': token.subject_data.pid, 'session_id': token.subject_data.session_id, 'terminal_port': token.terminal_port, 'terminal_ip': ip }} elif bsm_type == 'BSM_TOKEN_DATA': data = [] data_type = bsmtoken.BSM_TOKEN_DATA_TYPE.get(token.data_type, '') if data_type == 'AUR_CHAR': for _ in range(token.unit_count): data.append(self.BSM_TOKEN_DATA_CHAR.parse_stream(file_object)) elif data_type == 'AUR_SHORT': for _ in range(token.unit_count): data.append(self.BSM_TOKEN_DATA_SHORT.parse_stream(file_object)) elif data_type == 'AUR_INT32': for _ in range(token.unit_count): data.append(self.BSM_TOKEN_DATA_INTEGER.parse_stream(file_object)) else: data.append('Unknown type data') # TODO: the data when it is string ends with ".", HW a space is return # after uses the UTF-8 conversion. return {bsm_type: { 'format': bsmtoken.BSM_TOKEN_DATA_PRINT[token.how_to_print], 'data': '{0}'.format(self._RawToUTF8(''.join(map(str, data)))) }} elif token_id in self._BSM_ATTR_TOKEN_TYPES: return {bsm_type: { 'mode': token.file_mode, 'uid': token.uid, 'gid': token.gid, 'system_id': token.file_system_id, 'node_id': token.file_system_node_id, 'device': token.device}} elif bsm_type == 'BSM_TOKEN_GROUPS': arguments = [] for _ in range(token): arguments.append( self._RawToUTF8( self.BSM_TOKEN_DATA_INTEGER.parse_stream(file_object))) return {bsm_type: ','.join(arguments)} elif bsm_type == 'BSM_TOKEN_AUT_SOCKINET32_EX': if bsmtoken.BSM_PROTOCOLS.get(token.socket_domain, '') == 'INET6': saddr = self._IPv6Format( token.structure_addr_port.saddr_high, token.structure_addr_port.saddr_low) daddr = self._IPv6Format( token.structure_addr_port.daddr_high, token.structure_addr_port.daddr_low) else: saddr = self._IPv4Format(token.structure_addr_port.source_address) daddr = self._IPv4Format(token.structure_addr_port.destination_address) return {bsm_type:{ 'from': saddr, 'from_port': token.structure_addr_port.source_port, 'to': daddr, 'to_port': token.structure_addr_port.destination_port}} elif bsm_type == 'BSM_TOKEN_IPC_PERM': return {bsm_type: { 'user_id': token.user_id, 'group_id': token.group_id, 'creator_user_id': token.creator_user_id, 'creator_group_id': token.creator_group_id, 'access': token.access_mode}} elif bsm_type == 'BSM_TOKEN_SOCKET_UNIX': string = self._CopyUtf8ByteArrayToString(token.path) return {bsm_type: {'family': token.family, 'path': string}} elif bsm_type == 'BSM_TOKEN_OPAQUE': string = self._CopyByteArrayToBase16String(token.text) return {bsm_type: string} elif bsm_type == 'BSM_TOKEN_SEQUENCE': return {bsm_type: token}
class MRUListExStringPlugin(BaseMRUListExPlugin): """Windows Registry plugin to parse a string MRUListEx.""" NAME = 'mrulistex_string' DESCRIPTION = 'Parser for Most Recently Used (MRU) Registry data.' FILTERS = frozenset([MRUListExStringRegistryKeyFilter()]) URLS = [ 'http://forensicartifacts.com/2011/02/recentdocs/', 'https://github.com/libyal/winreg-kb/wiki/MRU-keys'] _STRING_STRUCT = construct.Struct( 'string_and_shell_item', construct.RepeatUntil( lambda obj, ctx: obj == b'\x00\x00', construct.Field('string', 2))) def _ParseMRUListExEntryValue( self, parser_mediator, registry_key, entry_index, entry_number, **kwargs): """Parses the MRUListEx entry value. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. registry_key (dfwinreg.WinRegistryKey): Windows Registry key that contains the MRUListEx value. entry_index (int): MRUListEx entry index. entry_number (int): entry number. Returns: str: MRUList entry value. """ value_string = '' value = registry_key.GetValueByName('{0:d}'.format(entry_number)) if value is None: logger.debug( '[{0:s}] Missing MRUListEx entry value: {1:d} in key: {2:s}.'.format( self.NAME, entry_number, registry_key.path)) elif value.DataIsString(): value_string = value.GetDataAsObject() elif value.DataIsBinaryData(): logger.debug(( '[{0:s}] Non-string MRUListEx entry value: {1:d} parsed as string ' 'in key: {2:s}.').format(self.NAME, entry_number, registry_key.path)) utf16_stream = binary.ByteStreamCopyToUTF16Stream(value.data) try: value_string = utf16_stream.decode('utf-16-le') except UnicodeDecodeError as exception: value_string = binary.HexifyBuffer(utf16_stream) logger.warning(( '[{0:s}] Unable to decode UTF-16 stream: {1:s} in MRUListEx entry ' 'value: {2:d} in key: {3:s} with error: {4!s}').format( self.NAME, value_string, entry_number, registry_key.path, exception)) return value_string # pylint: disable=arguments-differ def ExtractEvents( self, parser_mediator, registry_key, codepage='cp1252', **kwargs): """Extracts events from a Windows Registry key. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. registry_key (dfwinreg.WinRegistryKey): Windows Registry key. codepage (Optional[str]): extended ASCII string codepage. """ self._ParseMRUListExKey(parser_mediator, registry_key, codepage=codepage)
class RestorePointLogParser(interface.FileObjectParser): """A parser for Windows Restore Point (rp.log) files.""" NAME = u'rplog' DESCRIPTION = u'Parser for Windows Restore Point (rp.log) files.' FILTERS = frozenset([ interface.FileNameFileEntryFilter(u'rp.log')]) _FILE_HEADER_STRUCT = construct.Struct( u'file_header', construct.ULInt32(u'event_type'), construct.ULInt32(u'restore_point_type'), construct.ULInt64(u'sequence_number'), construct.RepeatUntil( lambda character, ctx: character == b'\x00\x00', construct.Field(u'description', 2))) _FILE_FOOTER_STRUCT = construct.Struct( u'file_footer', construct.ULInt64(u'creation_time')) def _ParseFileHeader(self, file_object): """Parses the file header. Args: file_object: A file-like object to read data from. Returns: The file header construct object. Raises: UnableToParseFile: when the header cannot be parsed. """ try: file_header = self._FILE_HEADER_STRUCT.parse_stream(file_object) except (IOError, construct.FieldError) as exception: raise errors.UnableToParseFile( u'Unable to parse file header with error: {0:s}'.format(exception)) if not file_header: raise errors.UnableToParseFile(u'Unable to read file header') return file_header def _ParseFileFooter(self, file_object): """Parses the file footer. Args: file_object: A file-like object to read data from. Returns: The file footer construct object. Raises: UnableToParseFile: when the footer cannot be parsed. """ try: file_footer = self._FILE_FOOTER_STRUCT.parse_stream(file_object) except (IOError, construct.FieldError) as exception: raise errors.UnableToParseFile( u'Unable to parse file footer with error: {0:s}'.format(exception)) if not file_footer: raise errors.UnableToParseFile(u'Unable to read file footer') return file_footer def ParseFileObject(self, parser_mediator, file_object, **unused_kwargs): """Parses a Windows Restore Point (rp.log) log file-like object. Args: parser_mediator: A parser mediator object (instance of ParserMediator). file_object: A file-like object. Raises: UnableToParseFile: when the file cannot be parsed. """ file_header = self._ParseFileHeader(file_object) try: # The struct includes the end-of-string character that we need # to strip off. description = b''.join(file_header.description).decode(u'utf16')[:-1] except UnicodeDecodeError as exception: description = u'' parser_mediator.ProduceParseError(( u'unable to decode description UTF-16 stream with error: ' u'{0:s}').format(exception)) file_object.seek(-8, os.SEEK_END) file_footer = self._ParseFileFooter(file_object) timestamp = file_footer.get(u'creation_time', None) if timestamp is None: parser_mediator.ProduceParseError(u'Timestamp not set.') else: event_object = RestorePointInfoEvent( timestamp, file_header.event_type, file_header.restore_point_type, file_header.sequence_number, description) parser_mediator.ProduceEvent(event_object)
class RestorePointLogParser(interface.FileObjectParser): """A parser for Windows Restore Point (rp.log) files.""" NAME = u'rplog' DESCRIPTION = u'Parser for Windows Restore Point (rp.log) files.' FILTERS = frozenset([ interface.FileNameFileEntryFilter(u'rp.log')]) _FILE_HEADER_STRUCT = construct.Struct( u'file_header', construct.ULInt32(u'event_type'), construct.ULInt32(u'restore_point_type'), construct.ULInt64(u'sequence_number'), construct.RepeatUntil( lambda character, ctx: character == b'\x00\x00', construct.Field(u'description', 2))) _FILE_FOOTER_STRUCT = construct.Struct( u'file_footer', construct.ULInt64(u'creation_time')) def ParseFileObject(self, parser_mediator, file_object, **unused_kwargs): """Parses a Windows Restore Point (rp.log) log file-like object. Args: parser_mediator (ParserMediator): mediates interactions between parsers and other components, such as storage and dfvfs. file_object (dfvfs.FileIO): file-like object. """ try: file_header_struct = self._FILE_HEADER_STRUCT.parse_stream(file_object) except (IOError, construct.FieldError) as exception: parser_mediator.ProduceExtractionError( u'unable to parse file header with error: {0:s}'.format(exception)) return file_object.seek(-8, os.SEEK_END) try: file_footer_struct = self._FILE_FOOTER_STRUCT.parse_stream(file_object) except (IOError, construct.FieldError) as exception: parser_mediator.ProduceExtractionError( u'unable to parse file footer with error: {0:s}'.format(exception)) return try: description = b''.join(file_header_struct.description) # The struct includes the end-of-string character that we need # to strip off. description = description.decode(u'utf16')[:-1] except UnicodeDecodeError as exception: description = u'' parser_mediator.ProduceExtractionError(( u'unable to decode description UTF-16 stream with error: ' u'{0:s}').format(exception)) if file_footer_struct.creation_time == 0: date_time = dfdatetime_semantic_time.SemanticTime(u'Not set') else: date_time = dfdatetime_filetime.Filetime( timestamp=file_footer_struct.creation_time) event_data = RestorePointEventData() event_data.description = description event_data.restore_point_event_type = file_header_struct.event_type event_data.restore_point_type = file_header_struct.restore_point_type event_data.sequence_number = file_header_struct.sequence_number event = time_events.DateTimeValuesEvent( date_time, eventdata.EventTimestamp.CREATION_TIME) parser_mediator.ProduceEventWithEventData(event, event_data)