Esempio n. 1
0
def PrefixedBytes(name, length_field=construct.UBInt8("length")):  # noqa
    """
    Length-prefixed binary data.  This is like a
    :py:func:`construct.macros.PascalString` that raises a
    :py:class:`constrcut.AdaptationError` when encoding something
    other than :py:class:`bytes`.

    :param name: The attribute name under which this value will be
        accessible.
    :type name: :py:class:`str`

    :param length_field: (optional) The prefixed length field.
        Defaults to :py:func:`construct.macros.UBInt8`.
    :type length_field: a :py:class:`construct.core.FormatField`
    """
    return construct.LengthValueAdapter(
        construct.Sequence(
            name, length_field,
            BytesAdapter(
                construct.Field("data",
                                operator.attrgetter(length_field.name)))))
Esempio n. 2
0
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)
Esempio n. 3
0
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)
Esempio n. 4
0
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)
Esempio n. 5
0
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)
Esempio n. 6
0
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)
Esempio n. 7
0
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)
Esempio n. 8
0
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()
Esempio n. 9
0
class VolumeBootRecord(BootRecord):
    _NTFS_VBR_STRUCT = construct.Struct(
        'NTFS-VBR',
        construct.Field('JumpOverBPB', 3),
        construct.String("OemId", 8),
        construct.Struct(
            'BiosParameterBlock',
            construct.ULInt16('SectorSize'),
            construct.ULInt8('SectorsPerCluster'),
            construct.Field('Reserved1', 2),
            construct.Field('MustBeZero1', 3),
            construct.Field('MustBeZero2', 2),
            construct.ULInt8('MediaDescriptor'),
            construct.Field('MustBeZero3', 2),
            construct.ULInt16('SectorsPerTrack'),
            construct.ULInt16('NumberOfHeads'),
            construct.ULInt32('HiddenSectors'),
            construct.Field('NotUsed1', 4),
            construct.Const(construct.Field('DriveNumber', 1),
                            '80'.decode('hex')),
            construct.Field('Reserved2', 3),
            construct.ULInt64('TotalSectors'),
            construct.ULInt64('MFTCluster'),
            construct.ULInt64('MFTMirrCluster'),
            construct.SLInt8('ClustersPerMFTRecord'),
            construct.Field('NotUsed2', 3),
            construct.SLInt8('ClustersPerIdxBuffer'),
            construct.Field('NotUsed3', 3),
            construct.ULInt64('VolumneSN'),
            construct.Field('NotUsed4', 4),
        ),
        construct.HexDumpAdapter(construct.Bytes("Code", 426)),
        construct.Const(construct.Bytes("signature", 2), '55aa'.decode('hex')),
    )

    _BITLOCKER_VBR_STRUCT = construct.Struct(
        'FVE-VBR',
        construct.Field('JumpOverBPB', 3),
        construct.Const(construct.String("OemId", 8),
                        '-FVE-FS-'.encode('utf8')),
        construct.Struct(
            'BiosParameterBlock',
            construct.ULInt16('SectorSize'),
            construct.ULInt8('SectorsPerCluster'),
            construct.Field('Reserved1', 2),
            construct.Field('MustBeZero1', 3),
            construct.Field('MustBeZero2', 2),
            construct.ULInt8('MediaDescriptor'),
            construct.Field('MustBeZero3', 2),
            construct.ULInt16('SectorsPerTrack'),
            construct.ULInt16('NumberOfHeads'),
            construct.ULInt32('HiddenSectors'),
            construct.ULInt32('TotalSectors'),
            construct.ULInt32('SectorsPerFAT'),
            construct.ULInt16('FATFlags'),
            construct.ULInt16('Version'),
            construct.ULInt32('RootDirCluster'),
            construct.ULInt16('FSInfoSector'),
            construct.ULInt16('BackupSector'),
            construct.Field('Reserved2', 12),
            construct.Const(construct.Field('DriveNumber', 1),
                            '80'.decode('hex')),
            construct.Field('Reserved3', 1),
            construct.Field('ExtendedBootSignature', 1),
            construct.ULInt32('VolumneSN'),
            construct.Const(construct.String("VolumeLabel", 11),
                            'NO NAME    '.encode('utf8')),
            construct.Const(construct.String("SystemId", 8),
                            'FAT32   '.encode('utf8')),
        ),
        construct.HexDumpAdapter(construct.Bytes("Code1", 70)),
        construct.Field('BitlockerGUID', 16),
        construct.ULInt64('FVEMetadataBlockOffset1'),
        construct.ULInt64('FVEMetadataBlockOffset2'),
        construct.ULInt64('FVEMetadataBlockOffset3'),
        construct.HexDumpAdapter(construct.Bytes("Code2", 307)),
        construct.ULInt8('FirstStrOffset'),
        construct.ULInt8('SecondStrOffset'),
        construct.ULInt8('ThirdStrOffset'),
        construct.Const(construct.Bytes("signature", 2), '55aa'.decode('hex')),
    )

    def __init__(self, filePath, size, offset=None, whitelist=()):
        self._type = 'VBR'
        super(VolumeBootRecord, self).__init__(filePath, size, offset,
                                               whitelist)

    def _parse(self):
        """
            Main method in charge of parsing the VBR.
            It will try to parse the boot record according to known structures (NTFS and Bitlocker supported).
            It will then try to narrow down invariant code, hash it and match the hash against a whitelist.
            If no match was found, it will try some simple heuristics to detect malicious behaviours.
            Finally it will compare the HiddenSectors value in BPB to that of the record's dump offset.

        Returns: nothing

        """
        try:
            # This will parse both NTFS and Vista bitlocker volumes since they only differ by their OEM ID
            vbr = self._NTFS_VBR_STRUCT.parse(self._raw)
            expectedLoader, invariantCode = self._getInvariantCode('NTFS', vbr)
        except construct.core.ConstructError as e1:
            # Retry with Bitlocker (Win7+) volume header structure
            try:
                vbr = self._BITLOCKER_VBR_STRUCT.parse(self._raw)
                expectedLoader, invariantCode = self._getInvariantCode(
                    'bitlocker', vbr)
            except construct.core.ConstructError as e2:
                raise InvalidVBRError(
                    'Invalid VBR structure: e1={0}, e2={1}\n{2}'.format(
                        e1, e2, hexdump(self._raw)))

        self._oemId = vbr.OemId
        self._bpb = vbr.BiosParameterBlock
        codeHash = hashlib.sha256(invariantCode)
        self._matchHash(codeHash, expectedLoader)

        # If no whitelisted signature matched, try some simple heuristics to flag this VBR as malicious
        # Note that the self._checkCode method is only given the "invariant" code section to help with the
        # disassembling. This will obviously leads to broken offsets, but it doesn't matter since the heuristics don't
        # use them.
        if len(self._signature) == 0:
            self._checkCode(invariantCode)

        # At last, compare the offset at which this VBR was found with the value of the BPB HiddenSectors
        if self._offset is not None \
                and (vbr.BiosParameterBlock.HiddenSectors * vbr.BiosParameterBlock.SectorSize) != self._offset:
            self._suspiciousBehaviour.append(
                'Suspicious HiddenSectors value: {0} ({1} bytes)'.format(
                    vbr.BiosParameterBlock.HiddenSectors,
                    vbr.BiosParameterBlock.HiddenSectors *
                    vbr.BiosParameterBlock.SectorSize))

    def _getInvariantCode(self, vbrType, vbrStruct):
        """
            Helper method that finds all the sections of the boot code that can be hashed and compared to a whitelist.
            This means that localized strings and other variable parameters (BPB, etc...) are excluded.
            Currently, this method only supports NTFS and Bitlocker VBR.

        Args:
            vbrType: unicode string corresponding to the VBR type ('NTFS' or 'bitlocker')
            vbrStruct: construct.container of the VBR

        Returns: 2-tuple (unicode string of expected loader, concatenated strings of invariant sections of code)

        """
        codeStart = 0
        codeEnd = None
        invariantCode = str()
        expectedLoader = None

        if vbrType == 'NTFS':
            # The first three bytes are a jump over the NTFS BPB to where the code really starts (0x54) and a NOP
            invariantCode += vbrStruct.JumpOverBPB
            codeStart = 0x54
            # NTFS VBR contains localized strings which must be excluded from the hash computation.
            # Before Windows 8, these strings are located at 4 different offsets which can be calculated by adding
            # 0x100 to the values respectively stored in bytes 0x1f8, 0x1f9, 0x1fa and 0x1fb.
            # Starting from Windows 8, these strings are located at 3 different offsets which are directly stored in
            # little endian words respectively at 0x1f6, 0x1f8 and 0x1fa
            # Since there is no easy way to tell which version of Windows we are dealing with beforehand, we first
            # assume it is a Windows < 8 by testing 0x1f8 against all the known first offset. If all tests fail, assume
            # it is Windows >= 8 and check 0x1f6 against the only known first offset (to date)
            firstStrOffset = construct.UBInt8('FirstStringOffset').parse(
                self._raw[0x1f8])
            # Windows NT5
            if firstStrOffset == 0x83:
                expectedLoader = 'NT5.1/NT5.2 VBR'
                codeEnd = 0x100 + firstStrOffset
            # Windows NT6.0
            elif firstStrOffset == 0x80:
                expectedLoader = 'NT6.0 VBR'
                codeEnd = 0x100 + firstStrOffset
            # Windows NT6.1
            elif firstStrOffset == 0x8c:
                expectedLoader = 'NT6.1 VBR'
                codeEnd = 0x100 + firstStrOffset
            # Windows NT6.2+
            else:
                firstStrOffset = construct.ULInt16('FirstStringOffset').parse(
                    self._raw[0x1f6:0x1f8])
                if firstStrOffset == 0x18a:
                    expectedLoader = 'NT6.2+ VBR'
                    codeEnd = firstStrOffset

            if codeEnd is None:
                self._suspiciousBehaviour.append(
                    'Invalid string offset: {0:#x}'.format(firstStrOffset))
                self._logger.debug(
                    'First localized string offset is wrong for a NTFS VBR: {0:#x}. '
                    'It should be 0x83, 0x80, 0x8c or 0x18a.'.format(
                        firstStrOffset))
                codeEnd = 0

        elif vbrType == 'bitlocker':
            expectedLoader = 'NT6.1+ Bitlocker VBR'
            # The first three bytes are a jump over the NTFS BPB to where the code really starts (0x5A) and a NOP
            invariantCode += vbrStruct.JumpOverBPB
            # First section of code (_BITLOCKER_VBR_STRUCT.Code1)
            invariantCode += vbrStruct.Code1
            # In the second section of code, there are localized strings which must be excluded from hash computation.
            # Their offsets are stored in the last 3 bytes before the VBR signature (0x55aa).
            # For Windows 8, 8.1 and 10, the first string offset seems to always be 0x100 (ie. FirstStrOffset = 0x00)
            if vbrStruct.FirstStrOffset != 0:
                self._suspiciousBehaviour.append(
                    'Invalid string offset: {0:#x}'.format(
                        vbrStruct.FirstStrOffset))
                self._logger.debug(
                    'First localized string offset is wrong for a Bitlocker VBR. '
                    'It should be 0x00) : {0:#x}'.format(
                        vbrStruct.FirstStrOffset))
            codeStart = 0xc8  # Offset of Code2
            codeEnd = 0x100 + vbrStruct.FirstStrOffset
        else:
            raise NotImplementedError(
                'VBR type "{0}" is not implemented yet'.format(vbrType))

        self._logger.debug(
            'Expecting {0}. Code starts at {1:#x} and ends at {2:#x}'.format(
                expectedLoader, codeStart, codeEnd))

        invariantCode += self._raw[codeStart:codeEnd]
        return expectedLoader, invariantCode

    def _checkCode(self, code):
        md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_16)
        md.detail = True
        for i in md.disasm(code, 0):
            # Check for unknown interrupt
            if i.mnemonic == 'int' and i.bytes[1] not in (0x10, 0x13, 0x18,
                                                          0x1a):
                self._suspiciousBehaviour.append(
                    'Unknown Interrupt : {0:#x}'.format(i.bytes[1]))
Esempio n. 10
0
class MasterBootRecord(BootRecord):
    _MBR_STRUCT = construct.Struct(
        "mbr",
        construct.HexDumpAdapter(construct.Bytes("bootloader_code", 440)),
        construct.Field('disk_signature', 4),
        construct.Padding(2),
        construct.Array(
            4,
            construct.Struct(
                "partitions",
                construct.SLInt8("state"),
                construct.BitStruct(
                    "beginning",
                    construct.Octet("head"),
                    construct.Bits("sect", 6),
                    construct.Bits("cyl", 10),
                ),
                construct.Enum(
                    construct.UBInt8("type"),
                    Nothing=0x00,
                    FAT12=0x01,
                    XENIX_ROOT=0x02,
                    XENIX_USR=0x03,
                    FAT16_old=0x04,
                    Extended_DOS=0x05,
                    FAT16=0x06,
                    FAT32=0x0b,
                    FAT32_LBA=0x0c,
                    NTFS=0x07,
                    LINUX_SWAP=0x82,
                    LINUX_NATIVE=0x83,
                    PROTECTIVE_MBR=0xee,
                    _default_=construct.Pass,
                ),
                construct.BitStruct(
                    "ending",
                    construct.Octet("head"),
                    construct.Bits("sect", 6),
                    construct.Bits("cyl", 10),
                ),
                construct.ULInt32(
                    "sector_offset"),  # offset from MBR in sectors
                construct.ULInt32("size"),  # in sectors
            )),
        construct.Const(construct.Bytes("signature", 2), '55aa'.decode('hex')),
    )

    def __init__(self, filePath, size, offset=None, whitelist=()):
        self._type = 'MBR'
        super(MasterBootRecord, self).__init__(filePath, size, offset,
                                               whitelist)

    def _parse(self):
        """
            Main method in charge of parsing the MBR.
            It will try to parse the boot record according to documented known structure and extract the partition table
            disk signature and code section.
            It will then try to narrow down invariant code, hash it and match the hash against a whitelist.
            If no match was found, it will try some simple heuristics to detect malicious behaviours.

        Returns: nothing

        """
        try:
            mbr = self._MBR_STRUCT.parse(self._raw)
        except construct.core.ConstructError as e:
            raise InvalidMBRError('Invalid MBR structure: {0}\n{1}'.format(
                e, hexdump(self._raw)))

        self._parsePartTable(mbr.partitions)

        # Windows stores the disk signature at 0x1B8, other MBRs seem to leave this area alone
        self._diskSignature = mbr.disk_signature

        # If code section is null, check for protective MBR signature (detected in partition table parsing). If found,
        # then the machine is likely using UEFI instead of BIOS to boot. If not, it could mean that the sample being
        # analyzed has been tampered by a bootkit
        if mbr.bootloader_code.encode('hex') == 440 * '00':
            if 'Protective MBR' in self._signature:
                self._signature.append('UEFI (no legacy boot code)')
            else:
                self._suspiciousBehaviour.append('Code section is null')
        else:
            expectedLoader, invariantCode = self._getInvariantCode(
                mbr.bootloader_code)
            codeHash = hashlib.sha256(invariantCode)
            self._matchHash(codeHash, expectedLoader)
            if len(self._signature) == 0:
                # No whitelisted signature matched, try some simple heuristics to flag this MBR as malicious
                # Note that the self._checkCode method is only given the "invariant" code section to help with the
                # disassembling. This will obviously leads to broken offsets, but it doesn't matter since the heuristics
                # don't use them.
                self._checkCode(invariantCode)

    def _parsePartTable(self, partitions):
        """
            Private method that parses the partition table of the MBR. Updates self._partTable list.

        Args:
            partitions: Construct.Container object of the partition table

        Returns: nothing
        """
        partNum = 0
        for part in partitions:
            partNum += 1
            # Assume a partition entry without size (in LBA) or type is invalid, and do not include it in the listing.
            if part.size != 0 and part.type != 'Nothing':
                self._partTable.append((partNum, part.state < 0, part.type,
                                        part.sector_offset, part.size))
            else:
                self._logger.debug('Ignoring invalid partition: %s', part)
            # Early detection of protective MBR so that we don't try to make sense of the MBR partition table
            if part.type == 'PROTECTIVE_MBR' and partNum == 1:
                self._logger.debug(
                    'Protective MBR detected, MBR partition table should not be taken into account. '
                    'GPT partition table parser not implemented yet')
                self._signature.append('Protective MBR')

    def _getInvariantCode(self, rawCode):
        """
            Helper method that tries to narrow down "invariant code" which can be hashed and compared to well known
            signatures. Most MBRs have localized error strings which must be excluded from the hash computation because
            they may vary from a country to another.
            First, this method tries to detect what kind of MBR it is dealing with. Most of the time, it is enough to
            to look for some known hardcoded strings that identify "well known" MBR (such as Truecrypt, GRUB2, etc...).
            Then, this method finds where the strings are and "removes" them (as in "does not include them").
            Finding these strings can be achieved by quickly studying the assembly code and looking for how these
            strings are echoed on screen at boot time (using interrupt 0x10).
            This research only needs to be done once for each type of MBR but requires an analyst to do it by static
            analysis. This script cannot take care of this. This method merely implements the results of such work.

            Currently supported MBR are:
             - Truecrypt
             - McAfee Endpoint Encryption (Safeboot)
             - GRUB2
             - Windows (XP to 10)

        Args:
            rawCode: str of the code section

        Returns: 2-tuple (unicode string of expected loader, concatenated strings of invariant sections of code)

        """
        # By default, assume all the MBR code section will be hashed. It is obviously wrong in most cases, but it allows
        # for a "default case" which will automatically matches no known hash in case something goes wrong with the
        # detection.
        codeStart = 0
        codeEnd = len(rawCode)
        expectedLoader = None
        invariantCode = str()

        # TrueCrypt (detected with the hardcoded string following the first jump: " TrueCrypt Boot Loader")
        if rawCode[0x5:0x1b].encode('hex').upper(
        ) == '2054727565437279707420426F6F74204C6F61646572':
            # TrueCrypt uses hardcoded and not-localized error strings. Therefore every TrueCrypt MBR should have the
            # same code from start to end
            expectedLoader = 'TrueCrypt MBR'

        # MacAfee SafeBoot (detected with the hardcoded string following the first jump: "Safeboot ")
        elif rawCode[0x3:0xc].encode('hex').upper() == '53616665426F6F7420':
            # Two versions have been seen but both start with a jump to the same offset (0x26).
            # There are some strings at the of the code section but localization is unlikely so it will be assumed
            # to be hardcoded (until a localized version is found...).
            # Therefore, Safeboot code can be hashed from 0x26 to the end of code section
            invariantCode += rawCode[:0x3]  # Hash the first JMP
            codeStart = 0x26
            expectedLoader = 'Safeboot MBR'

        # GRUB (detected with the hardcoded string "GRUB " located at 0x188)
        elif rawCode[0x188:0x18d].encode('hex').upper() == '4752554220':
            # GRUB has some error strings but they are hardcoded and not localized so they can be included in the hash
            # computation. However GRUB can be installed on a disk (MBR) as well as on a partition (in a kind of VBR).
            # But in both cases the code used is the same. Since a BPB is needed for the latter case it is also present
            # in the MBR (but not needed). It therefore has to be excluded from the hash computation.
            # GRUB is jumping over the BIOS Parameter Block located between 0x3 and 0x5a.
            # It should be followed by the kernel address (word), kernel sector (dword), kernel sector high (dword) and
            # boot drive (byte). Therefore the code really starts at 0x65.
            # These values are hardcoded in boot.img and have little chance to change anytime soon.
            codeStart = 0x65
            invariantCode += rawCode[:0x3]  # Hash the first JMP
            expectedLoader = 'GRUB2 MBR'

        # Windows MBR cannot be detected with hardcoded strings, so they fall in the default case and further checks
        # are then made based on the hypothesis that this is indeed a Windows MBR.
        else:
            # Starting with NT5.0, the MBR contains localized strings which must be excluded from the hash computation.
            # These strings are located after the code, at 3 different offsets which can be calculated by adding 0x100
            # to the values respectively stored in bytes 0x1b5, 0x1b6 and 0x1b7 (last bytes of the code section).
            # Eg: The first localized string is at : 0x100 + the value saved at offset 0x1B5
            # Even though localized strings can be of different lengths, the offset of the first one does not vary
            # given one Windows version. This can therefore be used to tell Windows versions apart.
            firstStrOffset = construct.UBInt8('FirstStringOffset').parse(
                rawCode[0x1b5])
            # Windows NT5
            if firstStrOffset == 0x2c:
                expectedLoader = 'NT5.1/NT5.2 MBR'
                codeEnd = 0x100 + firstStrOffset
            # Windows NT6.0
            elif firstStrOffset == 0x62:
                expectedLoader = 'NT6.0 MBR'
                codeEnd = 0x100 + firstStrOffset
            # Windows NT6.1+
            elif firstStrOffset == 0x63:
                expectedLoader = 'NT6.1+ MBR'
                codeEnd = 0x100 + firstStrOffset
            else:
                self._suspiciousBehaviour.append(
                    'Invalid string offset: {0:#x}'.format(firstStrOffset))
                self._logger.debug(
                    'First localized string offset is wrong for a windows MBR.'
                    'It should be 0x2c, 0x62 or 0x63) : {0:#x}'.format(
                        firstStrOffset))

        self._logger.debug(
            'Expecting {0}. Code starts at {1:#x} and ends at {2:#x}'.format(
                expectedLoader, codeStart, codeEnd))

        invariantCode += rawCode[codeStart:codeEnd]
        return expectedLoader, invariantCode

    def _checkCode(self, rawCode):
        md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_16)
        md.detail = True

        checkJmp = True
        for i in md.disasm(rawCode, 0):
            # Check for JUMPs and CALLs before the first PUSH/RET.
            if checkJmp and len(i.groups) > 0:
                # Group check if available
                if hasattr(capstone.x86, 'X86_GRP_CALL') and hasattr(
                        capstone.x86, 'X86_GRP_RET'):
                    if capstone.x86.X86_GRP_CALL in i.groups or capstone.x86.X86_GRP_JUMP in i.groups:
                        self._suspiciousBehaviour.append(
                            'JMP or CALL before relocation')
                        checkJmp = False
                    elif capstone.x86.X86_GRP_RET in i.groups:
                        # Stop search after the first PUSH/RET
                        checkJmp = False
                # Manual check in case capstone version doesn't support CALL and RET groups
                else:
                    if i.mnemonic[0] == 'j' or i.mnemonic == 'call':
                        self._suspiciousBehaviour.append(
                            'JMP or CALL before relocation')
                        checkJmp = False
                    elif i.mnemonic[:3] == 'ret':
                        # Stop search after the first PUSH/RET
                        checkJmp = False

            # Check for unknown interrupt
            if i.mnemonic == 'int' and i.bytes[1] not in (0x10, 0x13, 0x18,
                                                          0x1a):
                self._suspiciousBehaviour.append(
                    'Unknown Interrupt : {0:#x}'.format(i.bytes[1]))
Esempio n. 11
0
class LsaDecryptor():
  """TODO: add description."""
  SIGNATURE = None
  IV_LENGTH = 16
  PTR_IV_OFFSET = None
  PTR_AES_KEY_OFFSET = None
  PTR_DES_KEY_OFFSET = None
  UUUR_TAG = 0x55555552
  MSSK_TAG = 0x4d53534b

  HARD_KEY = construct.Struct('KIWI_HARD_KEY',
      construct.ULInt32('cbSecret'),
      construct.Field('data', lambda ctx: ctx.cbSecret))

  # Modified to include HARD_KEY size.
  BCRYPT_KEY = construct.Struct('KIWI_BCRYPT_KEY',
      construct.ULInt32('size'),
      construct.ULInt32('tag'), # 'MSSK'.
      construct.ULInt32('type'),
      construct.ULInt32('unk0'),
      construct.ULInt32('unk1'),
      construct.ULInt32('unk2'),
      construct.ULInt32('cbSecret'))

  def __init__(self):
    self.iv = ''
    self.aes_key = ''
    self.des_key = ''
    
  def find_signature(self):
    for mod in self.task.get_load_modules():
      if str(mod.BaseDllName).lower() == 'lsasrv.dll':
        scanner = MemoryScanner(self.task)
        return scanner.find_first(mod.DllBase.v(), self.SIGNATURE)
    debug.warning('[LsaDecryptor:find_signature()] signature not found!')

  def get_IV(self, pos):
    ptr_iv = self.get_ptr_with_offset(pos + self.PTR_IV_OFFSET)
    if ptr_iv:
      return self.get_data(ptr_iv, self.IV_LENGTH)

  def get_key(self, pos, key_offset):
    ptr_key = self.get_ptr_with_offset(pos + key_offset)
    if ptr_key:
      ptr_key = self.get_ptr(ptr_key)
      if ptr_key:
        size = self.BCRYPT_HANDLE_KEY.sizeof()
        data = self.get_data(ptr_key, size)
        if data:
          kbhk = self.BCRYPT_HANDLE_KEY.parse(data)
          if kbhk.tag == self.UUUR_TAG:
            ptr_key = kbhk.ptr_kiwi_bcrypt_key
            size = self.BCRYPT_KEY.sizeof()
            data = self.get_data(ptr_key, size)
            if data:
              kbk = self.BCRYPT_KEY.parse(data)
              if kbk.tag == self.MSSK_TAG:
                adjust = construct.ULInt32('').sizeof()
                size = kbk.cbSecret + adjust
                ptr_key = ptr_key + self.BCRYPT_KEY.sizeof() - adjust
                data = self.get_data(ptr_key, size)
                if data:
                  khk = self.HARD_KEY.parse(data)
                  return khk.data
                else:
                  debug.warning('get_key() unable to get HARD_KEY.')
              else:
                debug.warning('get_key() BCRYPT_KEY invalid tag')
            else:
              debug.warning('get_key() unable to read BCRYPT_KEY data.')
          else:
            debug.warning('get_key() BCRYPT_HANDLE_KEY invalid tag')
            debug.warning(kbhk)
        else:
          debug.warning('get_key() unable to read BCRYPT_HANDLE_KEY data.')
      else:
        debug.warning('get_key() unable to get BCRYPT_HANDLE_KEY pointer.')
    else:
      debug.warning('get_key()unable to get first pointer.')

  def get_des_key(self, pos):
    return self.get_key(pos, self.PTR_DES_KEY_OFFSET)
    
  def get_aes_key(self, pos):
    return self.get_key(pos, self.PTR_AES_KEY_OFFSET)

  def acquire_crypto_material(self):
    sigpos = self.find_signature()
    if not sigpos:
      debug.warning('[LsaDecryptor] unable to find signature!')
      return
    self.iv = self.get_IV(sigpos)
    self.des_key = self.get_des_key(sigpos)
    self.aes_key = self.get_aes_key(sigpos)

  def decrypt(self, encrypted):
    # TODO: NT version specific, move from here in subclasses.
    cleartext = ''
    size = len(encrypted)
    if size:
      if size % 8:
        if not self.aes_key or not self.iv:
          return cleartext
        cipher = AES.new(self.aes_key, AES.MODE_CBC, self.iv)
      else:
        if not self.des_key or not self.iv:
          return cleartext
        cipher = DES3.new(self.des_key, DES3.MODE_CBC, self.iv[:8])
      cleartext = cipher.decrypt(encrypted)
    return cleartext

  def dump(self):
    print 'Dumping LSA Decryptor'
    print '     IV ({}): {}'.format(len(self.iv), self.iv.encode('hex'))
    print 'DES_KEY ({}): {}'.format(
        len(self.des_key), self.des_key.encode('hex'))
    print 'AES_KEY ({}): {}'.format(
        len(self.aes_key), self.aes_key.encode('hex'))
Esempio n. 12
0
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
Esempio n. 13
0
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)
Esempio n. 14
0
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)
Esempio n. 15
0
                            lambda ctx: ctx.count,
                            construct.Struct(
                                'record',
                                construct.ULInt16('type'),
                                construct.ULInt16('flag'),
                                construct.ULInt64(
                                    'offset'),  # offset to data record
                                _POINTER(
                                    lambda ctx: ctx._._.abs_offset + ctx.
                                    offset,
                                    construct.Struct(
                                        'data_record',
                                        construct.ULInt32('length'),
                                        construct.ULInt32('type'),
                                        construct.Field(
                                            'data',
                                            lambda ctx: ctx.length)))))))))))
_DATA = construct.Struct('std_data', construct.ULInt32('length'),
                         construct.ULInt32('type'),
                         construct.Field('data', lambda ctx: ctx.length))


def parse():
    result = {}
    bookmark = _BOOKMARK_DATA.parse(raw_data)

    for table in bookmark.ftoc_offset.toc:
        for record in table.record:
            if record.type == 4100:  # pathComponents
                result['path'] = ''
                offsets = interpret(record.data_record,
Esempio n. 16
0
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)
Esempio n. 17
0
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)