def testInitialize(self): """Tests the __init__ function.""" definitions_file = self._GetTestFilePath(['integer.yaml']) with open(definitions_file, 'rb') as file_object: yaml_definition = file_object.read() factory = fabric.DataTypeFabric(yaml_definition=yaml_definition) self.assertIsNotNone(factory)
def _ReadDefinitionFile(self, path): """Reads a dtFabric definition file. Args: path (str): path of the dtFabric definition file. Returns: dtfabric.DataTypeFabric: data type fabric which contains the data format data type maps of the data type definition, such as a structure, that can be mapped onto binary data or None if no path is provided. """ if not path: return None with open(path, 'rb') as file_object: definition = file_object.read() return dtfabric_fabric.DataTypeFabric(yaml_definition=definition)
def _ReadDefinitionFile(self, filename): """Reads a dtFabric definition file. Args: filename (str): name of the dtFabric definition file. Returns: dtfabric.DataTypeFabric: data type fabric which contains the data format data type maps of the data type definition, such as a structure, that can be mapped onto binary data or None if no filename is provided. """ if not filename: return None path = os.path.join(self._DEFINITION_FILES_PATH, filename) with open(path, 'rb') as file_object: definition = file_object.read() return dtfabric_fabric.DataTypeFabric(yaml_definition=definition)
class TestBinaryDataFormat(data_format.BinaryDataFormat): """Binary data format for testing.""" _DEFINITION = b"""\ name: uint32 type: integer attributes: format: unsigned size: 4 units: bytes --- name: point3d type: structure attributes: byte_order: little-endian members: - name: x data_type: uint32 - name: y data_type: uint32 - name: z data_type: uint32 --- name: shape3d type: structure attributes: byte_order: little-endian members: - name: number_of_points data_type: uint32 - name: points type: sequence element_data_type: point3d number_of_elements: shape3d.number_of_points """ _FABRIC = dtfabric_fabric.DataTypeFabric(yaml_definition=_DEFINITION) POINT3D = _FABRIC.CreateDataTypeMap('point3d') POINT3D_SIZE = POINT3D.GetByteSize() SHAPE3D = _FABRIC.CreateDataTypeMap('shape3d')
class DtFabricBaseParserTest(test_lib.BaseTestCase): """Shared functionality for dtFabric-based data format parsers tests.""" # pylint: disable=protected-access _DATA_TYPE_FABRIC_DEFINITION = b"""\ name: uint32 type: integer attributes: format: unsigned size: 4 units: bytes --- name: point3d type: structure attributes: byte_order: little-endian members: - name: x data_type: uint32 - name: y data_type: uint32 - name: z data_type: uint32 --- name: shape3d type: structure attributes: byte_order: little-endian members: - name: number_of_points data_type: uint32 - name: points type: sequence element_data_type: point3d number_of_elements: shape3d.number_of_points """ _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric( yaml_definition=_DATA_TYPE_FABRIC_DEFINITION) _POINT3D = _DATA_TYPE_FABRIC.CreateDataTypeMap('point3d') _POINT3D_SIZE = _POINT3D.GetByteSize() _SHAPE3D = _DATA_TYPE_FABRIC.CreateDataTypeMap('shape3d') # TODO: add tests for _GetDataTypeMap def testReadData(self): """Tests the _ReadData function.""" parser = dtfabric_parser.DtFabricBaseParser() file_object = io.BytesIO( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00') parser._ReadData(file_object, 0, self._POINT3D_SIZE) # Test with missing file-like object. with self.assertRaises(ValueError): parser._ReadData(None, 0, self._POINT3D_SIZE) # Test with file-like object with insufficient data. file_object = io.BytesIO( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00') with self.assertRaises(errors.ParseError): parser._ReadData(file_object, 0, self._POINT3D_SIZE) # Test with file-like object that raises an IOError. file_object = ErrorBytesIO( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00') with self.assertRaises(errors.ParseError): parser._ReadData(file_object, 0, self._POINT3D_SIZE) # TODO: add tests for _ReadDefinitionFile def testReadStructureFromByteStream(self): """Tests the _ReadStructureFromByteStream function.""" parser = dtfabric_parser.DtFabricBaseParser() parser._ReadStructureFromByteStream( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0, self._POINT3D) # Test with missing byte stream. with self.assertRaises(ValueError): parser._ReadStructureFromByteStream(None, 0, self._POINT3D) # Test with missing data map type. with self.assertRaises(ValueError): parser._ReadStructureFromByteStream( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0, None) # Test with data type map that raises an dtfabric.MappingError. data_type_map = ErrorDataTypeMap(None) with self.assertRaises(errors.ParseError): parser._ReadStructureFromByteStream( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0, data_type_map) def testReadStructureFromFileObject(self): """Tests the _ReadStructureFromFileObject function.""" parser = dtfabric_parser.DtFabricBaseParser() file_object = io.BytesIO( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00') parser._ReadStructureFromFileObject(file_object, 0, self._POINT3D) file_object = io.BytesIO( b'\x03\x00\x00\x00' b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00' b'\x04\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00' b'\x06\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00') parser._ReadStructureFromFileObject(file_object, 0, self._SHAPE3D)
class CPIOArchiveFile(data_format.DataFormat): """CPIO archive file. Attributes: file_format (str): CPIO file format. """ _DATA_TYPE_FABRIC_DEFINITION_FILE = os.path.join(os.path.dirname(__file__), 'cpio.yaml') with open(_DATA_TYPE_FABRIC_DEFINITION_FILE, 'rb') as file_object: _DATA_TYPE_FABRIC_DEFINITION = file_object.read() _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric( yaml_definition=_DATA_TYPE_FABRIC_DEFINITION) _CPIO_BINARY_BIG_ENDIAN_FILE_ENTRY = _DATA_TYPE_FABRIC.CreateDataTypeMap( 'cpio_binary_big_endian_file_entry') _CPIO_BINARY_BIG_ENDIAN_FILE_ENTRY_SIZE = ( _CPIO_BINARY_BIG_ENDIAN_FILE_ENTRY.GetByteSize()) _CPIO_BINARY_LITTLE_ENDIAN_FILE_ENTRY = _DATA_TYPE_FABRIC.CreateDataTypeMap( 'cpio_binary_little_endian_file_entry') _CPIO_BINARY_LITTLE_ENDIAN_FILE_ENTRY_SIZE = ( _CPIO_BINARY_LITTLE_ENDIAN_FILE_ENTRY.GetByteSize()) _CPIO_PORTABLE_ASCII_FILE_ENTRY = _DATA_TYPE_FABRIC.CreateDataTypeMap( 'cpio_portable_ascii_file_entry') _CPIO_PORTABLE_ASCII_FILE_ENTRY_SIZE = ( _CPIO_PORTABLE_ASCII_FILE_ENTRY.GetByteSize()) _CPIO_NEW_ASCII_FILE_ENTRY = _DATA_TYPE_FABRIC.CreateDataTypeMap( 'cpio_new_ascii_file_entry') _CPIO_NEW_ASCII_FILE_ENTRY_SIZE = _CPIO_NEW_ASCII_FILE_ENTRY.GetByteSize() _CPIO_SIGNATURE_BINARY_BIG_ENDIAN = b'\x71\xc7' _CPIO_SIGNATURE_BINARY_LITTLE_ENDIAN = b'\xc7\x71' _CPIO_SIGNATURE_PORTABLE_ASCII = b'070707' _CPIO_SIGNATURE_NEW_ASCII = b'070701' _CPIO_SIGNATURE_NEW_ASCII_WITH_CHECKSUM = b'070702' _CPIO_ATTRIBUTE_NAMES_ODC = ('device_number', 'inode_number', 'mode', 'user_identifier', 'group_identifier', 'number_of_links', 'special_device_number', 'modification_time', 'path_size', 'file_size') _CPIO_ATTRIBUTE_NAMES_CRC = ('inode_number', 'mode', 'user_identifier', 'group_identifier', 'number_of_links', 'modification_time', 'path_size', 'file_size', 'device_major_number', 'device_minor_number', 'special_device_major_number', 'special_device_minor_number', 'checksum') def __init__(self, encoding='utf-8'): """Initializes a CPIO archive file. Args: encoding (str): encoding of paths within the archive file. """ super(CPIOArchiveFile, self).__init__() self._encoding = encoding self._file_entries = None self._file_object = None self._file_size = 0 self.file_format = None @property def encoding(self): """str: encoding of paths within the archive file.""" return self._encoding def _ReadFileEntry(self, file_object, file_offset): """Reads a file entry. Args: file_object (FileIO): file-like object. file_offset (int): offset of the data relative from the start of the file-like object. Returns: CPIOArchiveFileEntry: a file entry. Raises: FileFormatError: if the file entry cannot be read. """ if self.file_format == 'bin-big-endian': data_type_map = self._CPIO_BINARY_BIG_ENDIAN_FILE_ENTRY file_entry_data_size = self._CPIO_BINARY_BIG_ENDIAN_FILE_ENTRY_SIZE elif self.file_format == 'bin-little-endian': data_type_map = self._CPIO_BINARY_LITTLE_ENDIAN_FILE_ENTRY file_entry_data_size = self._CPIO_BINARY_LITTLE_ENDIAN_FILE_ENTRY_SIZE elif self.file_format == 'odc': data_type_map = self._CPIO_PORTABLE_ASCII_FILE_ENTRY file_entry_data_size = self._CPIO_PORTABLE_ASCII_FILE_ENTRY_SIZE elif self.file_format in ('crc', 'newc'): data_type_map = self._CPIO_NEW_ASCII_FILE_ENTRY file_entry_data_size = self._CPIO_NEW_ASCII_FILE_ENTRY_SIZE file_entry = self._ReadStructure(file_object, file_offset, file_entry_data_size, data_type_map, 'file entry') file_offset += file_entry_data_size if self.file_format in ('bin-big-endian', 'bin-little-endian'): file_entry.modification_time = ( (file_entry.modification_time.upper << 16) | file_entry.modification_time.lower) file_entry.file_size = ((file_entry.file_size.upper << 16) | file_entry.file_size.lower) if self.file_format == 'odc': for attribute_name in self._CPIO_ATTRIBUTE_NAMES_ODC: value = getattr(file_entry, attribute_name, None) try: value = int(value, 8) except ValueError: raise errors.FileFormatError( 'Unable to convert attribute: {0:s} into an integer'. format(attribute_name)) value = setattr(file_entry, attribute_name, value) elif self.file_format in ('crc', 'newc'): for attribute_name in self._CPIO_ATTRIBUTE_NAMES_CRC: value = getattr(file_entry, attribute_name, None) try: value = int(value, 16) except ValueError: raise errors.FileFormatError( 'Unable to convert attribute: {0:s} into an integer'. format(attribute_name)) value = setattr(file_entry, attribute_name, value) path_data = file_object.read(file_entry.path_size) file_offset += file_entry.path_size path = path_data.decode(self._encoding) path, _, _ = path.partition('\x00') if self.file_format in ('bin-big-endian', 'bin-little-endian'): padding_size = file_offset % 2 if padding_size > 0: padding_size = 2 - padding_size elif self.file_format == 'odc': padding_size = 0 elif self.file_format in ('crc', 'newc'): padding_size = file_offset % 4 if padding_size > 0: padding_size = 4 - padding_size file_offset += padding_size archive_file_entry = CPIOArchiveFileEntry() archive_file_entry.data_offset = file_offset archive_file_entry.data_size = file_entry.file_size archive_file_entry.group_identifier = file_entry.group_identifier archive_file_entry.inode_number = file_entry.inode_number archive_file_entry.modification_time = file_entry.modification_time archive_file_entry.path = path archive_file_entry.mode = file_entry.mode archive_file_entry.size = (file_entry_data_size + file_entry.path_size + padding_size + file_entry.file_size) archive_file_entry.user_identifier = file_entry.user_identifier file_offset += file_entry.file_size if self.file_format in ('bin-big-endian', 'bin-little-endian'): padding_size = file_offset % 2 if padding_size > 0: padding_size = 2 - padding_size elif self.file_format == 'odc': padding_size = 0 elif self.file_format in ('crc', 'newc'): padding_size = file_offset % 4 if padding_size > 0: padding_size = 4 - padding_size if padding_size > 0: archive_file_entry.size += padding_size return archive_file_entry def _ReadFileEntries(self, file_object): """Reads the file entries from the cpio archive. Args: file_object (FileIO): file-like object. """ self._file_entries = {} file_offset = 0 while file_offset < self._file_size or self._file_size == 0: file_entry = self._ReadFileEntry(file_object, file_offset) file_offset += file_entry.size if file_entry.path == 'TRAILER!!!': break if file_entry.path in self._file_entries: # TODO: alert on file entries with duplicate paths? continue self._file_entries[file_entry.path] = file_entry def Close(self): """Closes the CPIO archive file.""" self._file_entries = None self._file_object = None self._file_size = None def FileEntryExistsByPath(self, path): """Determines if file entry for a specific path exists. Returns: bool: True if the file entry exists. """ if self._file_entries is None: return False return path in self._file_entries def GetFileEntries(self, path_prefix=''): """Retrieves the file entries. Args: path_prefix (str): path prefix. Yields: CPIOArchiveFileEntry: a CPIO archive file entry. """ if self._file_entries: for path, file_entry in self._file_entries.items(): if path.startswith(path_prefix): yield file_entry def GetFileEntryByPath(self, path): """Retrieves a file entry for a specific path. Returns: CPIOArchiveFileEntry: a CPIO archive file entry or None if not available. """ if not self._file_entries: return None return self._file_entries.get(path, None) def Open(self, file_object): """Opens the CPIO archive file. Args: file_object (FileIO): a file-like object. Raises: IOError: if the file format signature is not supported. OSError: if the file format signature is not supported. """ file_object.seek(0, os.SEEK_SET) signature_data = file_object.read(6) self.file_format = None if len(signature_data) > 2: if signature_data[:2] == self._CPIO_SIGNATURE_BINARY_BIG_ENDIAN: self.file_format = 'bin-big-endian' elif signature_data[: 2] == self._CPIO_SIGNATURE_BINARY_LITTLE_ENDIAN: self.file_format = 'bin-little-endian' elif signature_data == self._CPIO_SIGNATURE_PORTABLE_ASCII: self.file_format = 'odc' elif signature_data == self._CPIO_SIGNATURE_NEW_ASCII: self.file_format = 'newc' elif signature_data == self._CPIO_SIGNATURE_NEW_ASCII_WITH_CHECKSUM: self.file_format = 'crc' if self.file_format is None: raise IOError('Unsupported CPIO format.') self._file_object = file_object self._file_size = file_object.get_size() self._ReadFileEntries(self._file_object) def ReadDataAtOffset(self, file_offset, size): """Reads a byte string from the file-like object at a specific offset. Args: file_offset (int): file offset. size (int): number of bytes to read. Returns: bytes: data read. Raises: IOError: if the read failed. OSError: if the read failed. """ self._file_object.seek(file_offset, os.SEEK_SET) return self._file_object.read(size)
class EMFFile(data_format.BinaryDataFile): """Enhanced Metafile Format (EMF) file.""" FILE_TYPE = 'Windows Enhanced Metafile' _DATA_TYPE_FABRIC_DEFINITION_FILE = os.path.join(os.path.dirname(__file__), 'emf.yaml') with open(_DATA_TYPE_FABRIC_DEFINITION_FILE, 'rb') as file_object: _DATA_TYPE_FABRIC_DEFINITION = file_object.read() _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric( yaml_definition=_DATA_TYPE_FABRIC_DEFINITION) _RECORD_TYPE = _DATA_TYPE_FABRIC.CreateDataTypeMap('emf_record_type') _FILE_HEADER = _DATA_TYPE_FABRIC.CreateDataTypeMap('emf_file_header') _FILE_HEADER_SIZE = _FILE_HEADER.GetByteSize() _RECORD_HEADER = _DATA_TYPE_FABRIC.CreateDataTypeMap('emf_record_header') _RECORD_HEADER_SIZE = _RECORD_HEADER.GetByteSize() _EMF_SIGNATURE = b'FME\x20' # Here None represents that the record has no additional data. _EMF_RECORD_DATA_STRUCT_TYPES = { 0x0018: _DATA_TYPE_FABRIC.CreateDataTypeMap('emf_settextcolor'), 0x0025: _DATA_TYPE_FABRIC.CreateDataTypeMap('emf_selectobject') } _EMF_STOCK_OBJECT = _DATA_TYPE_FABRIC.CreateDataTypeMap('emf_stock_object') def _DebugPrintFileHeader(self, file_header): """Prints file header debug information. Args: file_header (emf_file_header): file header. """ record_type_string = self._RECORD_TYPE.GetName(file_header.record_type) value_string = '0x{0:04x} ({1:s})'.format( file_header.record_type, record_type_string or 'UNKNOWN') self._DebugPrintValue('Record type', value_string) value_string = '{0:d}'.format(file_header.record_size) self._DebugPrintValue('Record size', value_string) value_string = '0x{0:04x}'.format(file_header.signature) self._DebugPrintValue('Signature', value_string) value_string = '0x{0:04x}'.format(file_header.format_version) self._DebugPrintValue('Format version', value_string) value_string = '{0:d}'.format(file_header.file_size) self._DebugPrintValue('File size', value_string) value_string = '{0:d}'.format(file_header.number_of_records) self._DebugPrintValue('Number of records', value_string) value_string = '{0:d}'.format(file_header.number_of_handles) self._DebugPrintValue('Number of handles', value_string) value_string = '0x{0:04x}'.format(file_header.unknown1) self._DebugPrintValue('Unknown (reserved)', value_string) value_string = '{0:d}'.format(file_header.description_string_size) self._DebugPrintValue('Description string size', value_string) value_string = '0x{0:04x}'.format( file_header.description_string_offset) self._DebugPrintValue('Description string offset', value_string) self._DebugPrintText('\n') def _DebugPrintRecordHeader(self, record_header): """Prints record header debug information. Args: record_header (emf_record_header): record header. """ record_type_string = self._RECORD_TYPE.GetName( record_header.record_type) value_string = '0x{0:04x} ({1:s})'.format( record_header.record_type, record_type_string or 'UNKNOWN') self._DebugPrintValue('Record type', value_string) value_string = '{0:d}'.format(record_header.record_size) self._DebugPrintValue('Record size', value_string) self._DebugPrintText('\n') def _ReadFileHeader(self, file_object): """Reads a file header. Args: file_object (file): file-like object. Raises: ParseError: if the file header cannot be read. """ file_offset = file_object.tell() file_header = self._ReadStructure(file_object, file_offset, self._FILE_HEADER_SIZE, self._FILE_HEADER, 'file header') if self._debug: self._DebugPrintFileHeader(file_header) # TODO: check record type # TODO: check record size # TODO: check signature def _ReadRecord(self, file_object, file_offset): """Reads a record. Args: file_object (file): file-like object. file_offset (int): offset of the data relative to the start of the file-like object. Raises: ParseError: if the record cannot be read. """ record_header = self._ReadStructure(file_object, file_offset, self._RECORD_HEADER_SIZE, self._RECORD_HEADER, 'record header') if self._debug: self._DebugPrintRecordHeader(record_header) data_offset = file_offset + self._RECORD_HEADER_SIZE data_size = record_header.record_size - self._RECORD_HEADER_SIZE if self._debug: self._ReadRecordData(file_object, record_header.record_type, data_size) return Record(record_header.record_type, record_header.record_size, data_offset, data_size) def _ReadRecordData(self, file_object, record_type, data_size): """Reads a record. Args: file_object (file): file-like object. record_type (int): record type. data_size (int): size of the record data. Raises: ParseError: if the record data cannot be read. """ record_data = file_object.read(data_size) if self._debug and data_size > 0: self._DebugPrintData('Record data', record_data) # TODO: use lookup dict with callback. data_type_map = self._EMF_RECORD_DATA_STRUCT_TYPES.get( record_type, None) if not data_type_map: return try: record = data_type_map.MapByteStream(record_data) except dtfabric_errors.MappingError as exception: raise errors.ParseError( ('Unable to parse record data with error: {0:s}' ).format(exception)) if self._debug: if record_type == 0x0018: value_string = '0x{0:04x}'.format(record.color) self._DebugPrintValue('Color', value_string) elif record_type == 0x0025: stock_object_string = self._EMF_STOCK_OBJECT.GetName( record.object_identifier) if stock_object_string: value_string = '0x{0:08x} ({1:s})'.format( record.object_identifier, stock_object_string) else: value_string = '0x{0:08x}'.format(record.object_identifier) self._DebugPrintValue('Object identifier', value_string) self._DebugPrintText('\n') def ReadFileObject(self, file_object): """Reads a Enhanced Metafile Format (EMF) file-like object. Args: file_object (file): file-like object. """ self._ReadFileHeader(file_object) file_offset = file_object.tell() while file_offset < self._file_size: record = self._ReadRecord(file_object, file_offset) file_offset += record.size
class WMFFile(data_format.BinaryDataFile): """Windows Metafile Format (WMF) file.""" FILE_TYPE = 'Windows Metafile' _DATA_TYPE_FABRIC_DEFINITION_FILE = os.path.join(os.path.dirname(__file__), 'wmf.yaml') with open(_DATA_TYPE_FABRIC_DEFINITION_FILE, 'rb') as file_object: _DATA_TYPE_FABRIC_DEFINITION = file_object.read() # https://msdn.microsoft.com/en-us/library/cc250370.aspx # TODO: merge with YAML file _TODO = b'\n'.join([ b'---', b'name: wmf_record', b'type: structure', b'description: WMF record', b'urls: ["https://msdn.microsoft.com/en-us/library/cc250387.aspx"]', b'attributes:', b' byte_order: little-endian', b'members:', b'- name: record_size', b' aliases: [RecordSize]', b' description: Size of the record as number of 16-bit values', b' data_type: uint32', # TODO: link to wmf_record_type b'- name: record_type', b' aliases: [RecordFunction]', b' data_type: uint16', b'- name: record_data', b' aliases: [rdParam]', b' type: stream', b' element_data_type: byte', b' element_data_size: (wmf_record.record_size * 2) - 6', b'---', b'name: wmf_restoredc_record', b'type: structure', b'description: Restores the playback device context', b'urls: ["https://msdn.microsoft.com/en-us/library/cc250469.aspx"]', b'attributes:', b' byte_order: little-endian', b'members:', b'- name: record_size', b' data_type: uint32', b'- name: record_type', b' data_type: uint16', # TODO: or support wmf_record_type.META_RESTOREDC b' value: 0x0127', b'- name: number_of_saved_device_context', b' data_type: uint16', b'---', b'name: wmf_setmapmode_record', b'type: structure', b'description: Defines the mapping mode', b'urls: ["https://msdn.microsoft.com/en-us/library/cc250483.aspx"]', b'attributes:', b' byte_order: little-endian', b'members:', b'- name: record_size', b' data_type: uint32', b'- name: record_type', b' data_type: uint16', # TODO: or support wmf_record_type.META_SETMAPMODE b' value: 0x0103', b'- name: map_mode', b' data_type: uint16', # TODO: map to wmf_map_mode ]) _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric( yaml_definition=_DATA_TYPE_FABRIC_DEFINITION) _HEADER = _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_header') _HEADER_SIZE = _HEADER.GetByteSize() _PLACEABLE = _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_placeable') _PLACEABLE_SIZE = _PLACEABLE.GetByteSize() _RECORD_TYPE = _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_record_type') _RECORD_HEADER = _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_record_header') _RECORD_HEADER_SIZE = _RECORD_HEADER.GetByteSize() _WMF_PLACEABLE_SIGNATURE = b'\xd7\xcd\xc6\x9a' _MAP_MODE = _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_map_mode') _STRETCH_MODE = _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_stretch_mode') # record_size == ((record_type >> 8) + 3) # DIB: https://msdn.microsoft.com/en-us/library/cc250593.aspx # Here None represents that the record has no additional data. _WMF_RECORD_DATA_STRUCT_TYPES = { 0x0000: None, 0x001e: None, 0x0103: _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_setmapmode'), 0x0107: _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_setstretchbltmode'), 0x0127: _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_restoredc'), 0x020b: _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_setwindoworg'), 0x020c: _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_setwindowext'), 0x0b41: _DATA_TYPE_FABRIC.CreateDataTypeMap('wmf_dibstretchblt') } # Reverse Polish wmf_raster_operation_code _WMF_RASTER_OPERATIONS = { 0x00000042: 'BLACKNESS', 0x00010289: 'DPSOO', 0x00020C89: 'DPSON', 0x000300AA: 'PSO', 0x00040C88: 'SDPON', 0x000500A9: 'DPO', 0x00060865: 'PDSXNO', 0x000702C5: 'PDSAO', 0x00080F08: 'SDPNA', 0x00090245: 'PDSXO', 0x000A0329: 'DPN', 0x000B0B2A: 'PSDNAO', 0x000C0324: 'SPN', 0x000D0B25: 'PDSNAO', 0x000E08A5: 'PDSONO', 0x000F0001: 'P', 0x00100C85: 'PDSON', 0x001100A6: 'NOTSRCERAS', 0x00120868: 'SDPXNO', 0x001302C8: 'SDPAO', 0x00140869: 'DPSXNO', 0x001502C9: 'DPSAO', 0x00165CCA: 'PSDPSANAX', 0x00171D54: 'SSPXDSXAX', 0x00180D59: 'SPXPDX', 0x00191CC8: 'SDPSANAX', 0x001A06C5: 'PDSPAO', 0x001B0768: 'SDPSXAX', 0x001C06CA: 'PSDPAO', 0x001D0766: 'DSPDXAX', 0x001E01A5: 'PDSO', 0x001F0385: 'PDSOA', 0x00200F09: 'DPSNA', 0x00210248: 'SDPXO', 0x00220326: 'DSN', 0x00230B24: 'SPDNAO', 0x00240D55: 'SPXDSX', 0x00251CC5: 'PDSPANAX', 0x002606C8: 'SDPSAO', 0x00271868: 'SDPSXNOX', 0x00280369: 'DPSXA', 0x002916CA: 'PSDPSAOXXN', 0x002A0CC9: 'DPSANA', 0x002B1D58: 'SSPXPDXAXN', 0x002C0784: 'SPDSOAX', 0x002D060A: 'PSDNOX', 0x002E064A: 'PSDPXOX', 0x002F0E2A: 'PSDNOAN', 0x0030032A: 'PSNA', 0x00310B28: 'SDPNAON', 0x00320688: 'SDPSOOX', 0x00330008: 'NOTSRCCOPY', 0x003406C4: 'SPDSAOX', 0x00351864: 'SPDSXNOX', 0x003601A8: 'SDPOX', 0x00370388: 'SDPOAN', 0x0038078A: 'PSDPOAX', 0x00390604: 'SPDNOX', 0x003A0644: 'SPDSXOX', 0x003B0E24: 'SPDNOAN', 0x003C004A: 'PSX', 0x003D18A4: 'SPDSONOX', 0x003E1B24: 'SPDSNAOX', 0x003F00EA: 'PSAN', 0x00400F0A: 'PSDNAA', 0x00410249: 'DPSXON', 0x00420D5D: 'SDXPDXA', 0x00431CC4: 'SPDSANAXN', 0x00440328: 'SRCERASE', 0x00450B29: 'DPSNAON', 0x004606C6: 'DSPDAOX', 0x0047076A: 'PSDPXAXN', 0x00480368: 'SDPXA', 0x004916C5: 'PDSPDAOXXN', 0x004A0789: 'DPSDOAX', 0x004B0605: 'PDSNOX', 0x004C0CC8: 'SDPANA', 0x004D1954: 'SSPXDSXOXN', 0x004E0645: 'PDSPXOX', 0x004F0E25: 'PDSNOAN', 0x00500325: 'PDNA', 0x00510B26: 'DSPNAON', 0x005206C9: 'DPSDAOX', 0x00530764: 'SPDSXAXN', 0x005408A9: 'DPSONON', 0x00550009: 'DSTINVERT', 0x005601A9: 'DPSOX', 0x00570389: 'DPSOAN', 0x00580785: 'PDSPOAX', 0x00590609: 'DPSNOX', 0x005A0049: 'PATINVERT', 0x005B18A9: 'DPSDONOX', 0x005C0649: 'DPSDXOX', 0x005D0E29: 'DPSNOAN', 0x005E1B29: 'DPSDNAOX', 0x005F00E9: 'DPAN', 0x00600365: 'PDSXA', 0x006116C6: 'DSPDSAOXXN', 0x00620786: 'DSPDOAX', 0x00630608: 'SDPNOX', 0x00640788: 'SDPSOAX', 0x00650606: 'DSPNOX', 0x00660046: 'SRCINVERT', 0x006718A8: 'SDPSONOX', 0x006858A6: 'DSPDSONOXXN', 0x00690145: 'PDSXXN', 0x006A01E9: 'DPSAX', 0x006B178A: 'PSDPSOAXXN', 0x006C01E8: 'SDPAX', 0x006D1785: 'PDSPDOAXXN', 0x006E1E28: 'SDPSNOAX', 0x006F0C65: 'PDXNAN', 0x00700CC5: 'PDSANA', 0x00711D5C: 'SSDXPDXAXN', 0x00720648: 'SDPSXOX', 0x00730E28: 'SDPNOAN', 0x00740646: 'DSPDXOX', 0x00750E26: 'DSPNOAN', 0x00761B28: 'SDPSNAOX', 0x007700E6: 'DSAN', 0x007801E5: 'PDSAX', 0x00791786: 'DSPDSOAXXN', 0x007A1E29: 'DPSDNOAX', 0x007B0C68: 'SDPXNAN', 0x007C1E24: 'SPDSNOAX', 0x007D0C69: 'DPSXNAN', 0x007E0955: 'SPXDSXO', 0x007F03C9: 'DPSAAN', 0x008003E9: 'DPSAA', 0x00810975: 'SPXDSXON', 0x00820C49: 'DPSXNA', 0x00831E04: 'SPDSNOAXN', 0x00840C48: 'SDPXNA', 0x00851E05: 'PDSPNOAXN', 0x008617A6: 'DSPDSOAXX', 0x008701C5: 'PDSAXN', 0x008800C6: 'SRCAND', 0x00891B08: 'SDPSNAOXN', 0x008A0E06: 'DSPNOA', 0x008B0666: 'DSPDXOXN', 0x008C0E08: 'SDPNOA', 0x008D0668: 'SDPSXOXN', 0x008E1D7C: 'SSDXPDXAX', 0x008F0CE5: 'PDSANAN', 0x00900C45: 'PDSXNA', 0x00911E08: 'SDPSNOAXN', 0x009217A9: 'DPSDPOAXX', 0x009301C4: 'SPDAXN', 0x009417AA: 'PSDPSOAXX', 0x009501C9: 'DPSAXN', 0x00960169: 'DPSXX', 0x0097588A: 'PSDPSONOXX', 0x00981888: 'SDPSONOXN', 0x00990066: 'DSXN', 0x009A0709: 'DPSNAX', 0x009B07A8: 'SDPSOAXN', 0x009C0704: 'SPDNAX', 0x009D07A6: 'DSPDOAXN', 0x009E16E6: 'DSPDSAOXX', 0x009F0345: 'PDSXAN', 0x00A000C9: 'DPA', 0x00A11B05: 'PDSPNAOXN', 0x00A20E09: 'DPSNOA', 0x00A30669: 'DPSDXOXN', 0x00A41885: 'PDSPONOXN', 0x00A50065: 'PDXN', 0x00A60706: 'DSPNAX', 0x00A707A5: 'PDSPOAXN', 0x00A803A9: 'DPSOA', 0x00A90189: 'DPSOXN', 0x00AA0029: 'D', 0x00AB0889: 'DPSONO', 0x00AC0744: 'SPDSXAX', 0x00AD06E9: 'DPSDAOXN', 0x00AE0B06: 'DSPNAO', 0x00AF0229: 'DPNO', 0x00B00E05: 'PDSNOA', 0x00B10665: 'PDSPXOXN', 0x00B21974: 'SSPXDSXOX', 0x00B30CE8: 'SDPANAN', 0x00B4070A: 'PSDNAX', 0x00B507A9: 'DPSDOAXN', 0x00B616E9: 'DPSDPAOXX', 0x00B70348: 'SDPXAN', 0x00B8074A: 'PSDPXAX', 0x00B906E6: 'DSPDAOXN', 0x00BA0B09: 'DPSNAO', 0x00BB0226: 'MERGEPAINT', 0x00BC1CE4: 'SPDSANAX', 0x00BD0D7D: 'SDXPDXAN', 0x00BE0269: 'DPSXO', 0x00BF08C9: 'DPSANO', 0x00C000CA: 'MERGECOPY', 0x00C11B04: 'SPDSNAOXN', 0x00C21884: 'SPDSONOXN', 0x00C3006A: 'PSXN', 0x00C40E04: 'SPDNOA', 0x00C50664: 'SPDSXOXN', 0x00C60708: 'SDPNAX', 0x00C707AA: 'PSDPOAXN', 0x00C803A8: 'SDPOA', 0x00C90184: 'SPDOXN', 0x00CA0749: 'DPSDXAX', 0x00CB06E4: 'SPDSAOXN', 0x00CC0020: 'SRCCOPY', 0x00CD0888: 'SDPONO', 0x00CE0B08: 'SDPNAO', 0x00CF0224: 'SPNO', 0x00D00E0A: 'PSDNOA', 0x00D1066A: 'PSDPXOXN', 0x00D20705: 'PDSNAX', 0x00D307A4: 'SPDSOAXN', 0x00D41D78: 'SSPXPDXAX', 0x00D50CE9: 'DPSANAN', 0x00D616EA: 'PSDPSAOXX', 0x00D70349: 'DPSXAN', 0x00D80745: 'PDSPXAX', 0x00D906E8: 'SDPSAOXN', 0x00DA1CE9: 'DPSDANAX', 0x00DB0D75: 'SPXDSXAN', 0x00DC0B04: 'SPDNAO', 0x00DD0228: 'SDNO', 0x00DE0268: 'SDPXO', 0x00DF08C8: 'SDPANO', 0x00E003A5: 'PDSOA', 0x00E10185: 'PDSOXN', 0x00E20746: 'DSPDXAX', 0x00E306EA: 'PSDPAOXN', 0x00E40748: 'SDPSXAX', 0x00E506E5: 'PDSPAOXN', 0x00E61CE8: 'SDPSANAX', 0x00E70D79: 'SPXPDXAN', 0x00E81D74: 'SSPXDSXAX', 0x00E95CE6: 'DSPDSANAXXN', 0x00EA02E9: 'DPSAO', 0x00EB0849: 'DPSXNO', 0x00EC02E8: 'SDPAO', 0x00ED0848: 'SDPXNO', 0x00EE0086: 'SRCPAINT', 0x00EF0A08: 'SDPNOO', 0x00F00021: 'PATCOPY', 0x00F10885: 'PDSONO', 0x00F20B05: 'PDSNAO', 0x00F3022A: 'PSNO', 0x00F40B0A: 'PSDNAO', 0x00F50225: 'PDNO', 0x00F60265: 'PDSXO', 0x00F708C5: 'PDSANO', 0x00F802E5: 'PDSAO', 0x00F90845: 'PDSXNO', 0x00FA0089: 'DPO', 0x00FB0A09: 'PATPAINT', 0x00FC008A: 'PSO', 0x00FD0A0A: 'PSDNOO', 0x00FE02A9: 'DPSOO', 0x00FF0062: 'WHITENESS' } def _DebugPrintHeader(self, file_header): """Prints header debug information. Args: file_header (wmf_header): file header. """ value_string = '0x{0:04x}'.format(file_header.file_type) self._DebugPrintValue('File type', value_string) value_string = '{0:d}'.format(file_header.record_size) self._DebugPrintValue('Record size', value_string) value_string = '{0:d}'.format(file_header.format_version) self._DebugPrintValue('Format version', value_string) value_string = '{0:d}'.format(file_header.file_size_lower) self._DebugPrintValue('File size lower', value_string) value_string = '{0:d}'.format(file_header.file_size_upper) self._DebugPrintValue('File size upper', value_string) value_string = '{0:d}'.format(file_header.maximum_number_of_objects) self._DebugPrintValue('Maximum number of object', value_string) value_string = '{0:d}'.format(file_header.largest_record_size) self._DebugPrintValue('Largest record size', value_string) value_string = '{0:d}'.format(file_header.number_of_records) self._DebugPrintValue('Number of records', value_string) self._DebugPrintText('\n') def _DebugPrintPlaceable(self, placeable): """Prints placeable debug information. Args: placeable (wmf_placeable): placeable. """ value_string = '0x{0:08x}'.format(placeable.signature) self._DebugPrintValue('Signature', value_string) value_string = '0x{0:04x}'.format(placeable.resource_handle) self._DebugPrintValue('Resource handle', value_string) self._DebugPrintData('Bounding box', placeable.bounding_box) value_string = '{0:d}'.format(placeable.number_of_units_per_inch) self._DebugPrintValue('Number of units per inch', value_string) value_string = '0x{0:08x}'.format(placeable.unknown1) self._DebugPrintValue('Unknown1', value_string) value_string = '0x{0:04x}'.format(placeable.checksum) self._DebugPrintValue('Checksum', value_string) self._DebugPrintText('\n') def _DebugPrintRecordHeader(self, record_header): """Prints record header debug information. Args: record_header (wmf_record_header): record header. """ value_string = '{0:d} ({1:d} bytes)'.format( record_header.record_size, record_header.record_size * 2) self._DebugPrintValue('Record size', value_string) record_type_string = self._RECORD_TYPE.GetName( record_header.record_type) value_string = '0x{0:04x} ({1:s})'.format( record_header.record_type, record_type_string or 'UNKNOWN') self._DebugPrintValue('Record type', value_string) self._DebugPrintText('\n') def _ReadHeader(self, file_object): """Reads a header. Args: file_object (file): file-like object. Raises: ParseError: if the header cannot be read. """ file_offset = file_object.tell() file_header = self._ReadStructure(file_object, file_offset, self._HEADER_SIZE, self._HEADER, 'header') if self._debug: self._DebugPrintHeader(file_header) if file_header.file_type not in (1, 2): raise errors.ParseError('Unsupported file type: {0:d}'.format( file_header.file_type)) if file_header.record_size != 9: raise errors.ParseError('Unsupported record size: {0:d}'.format( file_header.record_size)) def _ReadPlaceable(self, file_object): """Reads a placeable. Args: file_object (file): file-like object. Raises: ParseError: if the placeable cannot be read. """ file_offset = file_object.tell() placeable = self._ReadStructure(file_object, file_offset, self._PLACEABLE_SIZE, self._PLACEABLE, 'placeable') if self._debug: self._DebugPrintPlaceable(placeable) def _ReadRecord(self, file_object, file_offset): """Reads a record. Args: file_object (file): file-like object. file_offset (int): offset of the data relative to the start of the file-like object. Raises: ParseError: if the record cannot be read. """ record_header = self._ReadStructure(file_object, file_offset, self._RECORD_HEADER_SIZE, self._RECORD_HEADER, 'record header') if self._debug: self._DebugPrintRecordHeader(record_header) record_size = record_header.record_size * 2 data_offset = file_offset + self._RECORD_HEADER_SIZE data_size = record_size - self._RECORD_HEADER_SIZE if self._debug: self._ReadRecordData(file_object, record_header.record_type, data_size) return Record(record_header.record_type, record_size, data_offset, data_size) def _ReadRecordData(self, file_object, record_type, data_size): """Reads a record. Args: file_object (file): file-like object. record_type (int): record type. data_size (int): size of the record data. Raises: ParseError: if the record cannot be read. """ record_data = file_object.read(data_size) if self._debug and data_size > 0: self._DebugPrintData('Record data', record_data) # TODO: use lookup dict with callback. data_type_map = self._WMF_RECORD_DATA_STRUCT_TYPES.get( record_type, None) if not data_type_map: return try: record = data_type_map.MapByteStream(record_data) except dtfabric_errors.MappingError as exception: raise errors.ParseError( ('Unable to parse record data with error: {0:s}' ).format(exception)) if self._debug: if record_type == 0x0103: map_mode_string = self._MAP_MODE.GetName(record.map_mode) value_string = '0x{0:04x} ({1:s})'.format( record.map_mode, map_mode_string or 'UNKNOWN') self._DebugPrintValue('Map mode', value_string) elif record_type == 0x0107: stretch_mode_string = self._MAP_MODE.GetName( record.stretch_mode) value_string = '0x{0:04x} ({1:s})'.format( record.stretch_mode, stretch_mode_string or 'UNKNOWN') self._DebugPrintValue('Stretch mode', value_string) elif record_type == 0x0127: value_string = '{0:d}'.format( record.number_of_saved_device_context) self._DebugPrintValue('Number of saved device context', value_string) elif record_type in (0x020b, 0x020c): value_string = '{0:d}'.format(record.x_coordinate) self._DebugPrintValue('X coordinate', value_string) value_string = '{0:d}'.format(record.y_coordinate) self._DebugPrintValue('Y coordinate', value_string) elif record_type == 0x0b41: raster_operation_string = self._WMF_RASTER_OPERATIONS.get( record.raster_operation, 'UNKNOWN') value_string = '0x{0:08x} ({1:s})'.format( record.raster_operation, raster_operation_string) self._DebugPrintValue('Raster operation', value_string) value_string = '{0:d}'.format(record.source_height) self._DebugPrintValue('Source height', value_string) value_string = '{0:d}'.format(record.source_width) self._DebugPrintValue('Source width', value_string) value_string = '{0:d}'.format(record.source_x_coordinate) self._DebugPrintValue('Source X coordinate', value_string) value_string = '{0:d}'.format(record.source_y_coordinate) self._DebugPrintValue('Source Y coordinate', value_string) value_string = '{0:d}'.format(record.destination_height) self._DebugPrintValue('Destination height', value_string) value_string = '{0:d}'.format(record.destination_width) self._DebugPrintValue('Destination width', value_string) value_string = '{0:d}'.format(record.destination_x_coordinate) self._DebugPrintValue('Destination X coordinate', value_string) value_string = '{0:d}'.format(record.destination_y_coordinate) self._DebugPrintValue('Destination Y coordinate', value_string) self._DebugPrintText('\n') def ReadFileObject(self, file_object): """Reads a Windows Metafile Format (WMF) file-like object. Args: file_object (file): file-like object. Raises: ParseError: if the file cannot be read. """ try: signature = file_object.read(4) file_object.seek(-4, os.SEEK_CUR) except IOError as exception: raise errors.ParseError( 'Unable to read file signature with error: {0!s}'.format( exception)) if signature == self._WMF_PLACEABLE_SIGNATURE: self._ReadPlaceable(file_object) self._ReadHeader(file_object) file_offset = file_object.tell() while file_offset < self._file_size: record = self._ReadRecord(file_object, file_offset) file_offset += record.size
class DtFabricHelperTest(test_lib.BaseTestCase): """dtFabric format definition helper mix-in tests.""" # pylint: disable=protected-access _DATA_TYPE_FABRIC_DEFINITION = b"""\ name: uint32 type: integer attributes: format: unsigned size: 4 units: bytes --- name: point3d type: structure attributes: byte_order: little-endian members: - name: x data_type: uint32 - name: y data_type: uint32 - name: z data_type: uint32 --- name: shape3d type: structure attributes: byte_order: little-endian members: - name: number_of_points data_type: uint32 - name: points type: sequence element_data_type: point3d number_of_elements: shape3d.number_of_points """ _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric( yaml_definition=_DATA_TYPE_FABRIC_DEFINITION) _POINT3D = _DATA_TYPE_FABRIC.CreateDataTypeMap('point3d') _POINT3D_SIZE = _POINT3D.GetByteSize() _SHAPE3D = _DATA_TYPE_FABRIC.CreateDataTypeMap('shape3d') def testFormatPackedIPv4Address(self): """Tests the _FormatPackedIPv4Address function.""" test_helper = dtfabric_helper.DtFabricHelper() ip_address = test_helper._FormatPackedIPv4Address([0xc0, 0xa8, 0xcc, 0x62]) self.assertEqual(ip_address, '192.168.204.98') def testFormatPackedIPv6Address(self): """Tests the _FormatPackedIPv6Address function.""" test_helper = dtfabric_helper.DtFabricHelper() ip_address = test_helper._FormatPackedIPv6Address([ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x42, 0x83, 0x29]) self.assertEqual(ip_address, '2001:0db8:0000:0000:0000:ff00:0042:8329') # TODO: add tests for _GetDataTypeMap def testReadData(self): """Tests the _ReadData function.""" test_helper = dtfabric_helper.DtFabricHelper() file_object = io.BytesIO( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00') test_helper._ReadData(file_object, 0, self._POINT3D_SIZE) # Test with missing file-like object. with self.assertRaises(ValueError): test_helper._ReadData(None, 0, self._POINT3D_SIZE) # Test with file-like object with insufficient data. file_object = io.BytesIO( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00') with self.assertRaises(errors.ParseError): test_helper._ReadData(file_object, 0, self._POINT3D_SIZE) # Test with file-like object that raises an IOError. file_object = ErrorBytesIO( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00') with self.assertRaises(errors.ParseError): test_helper._ReadData(file_object, 0, self._POINT3D_SIZE) # TODO: add tests for _ReadDefinitionFile def testReadStructureFromByteStream(self): """Tests the _ReadStructureFromByteStream function.""" test_helper = dtfabric_helper.DtFabricHelper() test_helper._ReadStructureFromByteStream( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0, self._POINT3D) # Test with missing byte stream. with self.assertRaises(ValueError): test_helper._ReadStructureFromByteStream(None, 0, self._POINT3D) # Test with missing data map type. with self.assertRaises(ValueError): test_helper._ReadStructureFromByteStream( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0, None) # Test with data type map that raises an dtfabric.MappingError. data_type_map = ErrorDataTypeMap(None) with self.assertRaises(errors.ParseError): test_helper._ReadStructureFromByteStream( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0, data_type_map) def testReadStructureFromFileObject(self): """Tests the _ReadStructureFromFileObject function.""" test_helper = dtfabric_helper.DtFabricHelper() file_object = io.BytesIO( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00') test_helper._ReadStructureFromFileObject(file_object, 0, self._POINT3D) file_object = io.BytesIO( b'\x03\x00\x00\x00' b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00' b'\x04\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00' b'\x06\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00') test_helper._ReadStructureFromFileObject(file_object, 0, self._SHAPE3D)
class ChromeCacheParser(object): """Chrome Cache parser.""" _DATA_TYPE_FABRIC_DEFINITION_FILE = os.path.join(os.path.dirname(__file__), 'chrome_cache.yaml') with open(_DATA_TYPE_FABRIC_DEFINITION_FILE, 'rb') as file_object: _DATA_TYPE_FABRIC_DEFINITION = file_object.read() _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric( yaml_definition=_DATA_TYPE_FABRIC_DEFINITION) _UINT32LE = _DATA_TYPE_FABRIC.CreateDataTypeMap('uint32le') _UINT32LE_SIZE = _UINT32LE.GetByteSize() def __init__(self, debug=False, output_writer=None): """Initializes a Chrome Cache parser. Args: debug (Optional[bool]): True if debug information should be written. output_writer (Optional[OutputWriter]): output writer. """ super(ChromeCacheParser, self).__init__() self._debug = debug self._output_writer = output_writer def ParseDirectory(self, path): """Parses a Chrome Cache directory. Args: path (str): path of the directory. Raises: ParseError: if the directory cannot be read. """ index_file_path = os.path.join(path, 'index') if not os.path.exists(index_file_path): raise errors.ParseError( 'Missing index file: {0:s}'.format(index_file_path)) index_file = IndexFile(debug=self._debug, output_writer=self._output_writer) index_file.Open(index_file_path) data_block_files = {} have_all_data_block_files = True for cache_address in iter(index_file.index_table.values()): if cache_address.filename not in data_block_files: data_block_file_path = os.path.join(path, cache_address.filename) if not os.path.exists(data_block_file_path): logging.error('Missing data block file: {0:s}'.format( data_block_file_path)) have_all_data_block_files = False else: data_block_file = DataBlockFile( debug=self._debug, output_writer=self._output_writer) data_block_file.Open(data_block_file_path) data_block_files[cache_address.filename] = data_block_file if have_all_data_block_files: # TODO: read the cache entries from the data block files for cache_address in iter(index_file.index_table.values()): cache_address_chain_length = 0 while cache_address.value != 0x00000000: if cache_address_chain_length >= 64: logging.error( 'Maximum allowed cache address chain length reached.' ) break data_file = data_block_files.get(cache_address.filename, None) if not data_file: logging.warning( 'Cache address: 0x{0:08x} missing filename.'. format(cache_address.value)) break # print('Cache address\t: {0:s}'.format( # cache_address.GetDebugString())) cache_entry = data_file.ReadCacheEntry( cache_address.block_offset) try: cache_entry_key = cache_entry.key.decode('ascii') except UnicodeDecodeError: logging.warning(( 'Unable to decode cache entry key at cache address: ' '0x{0:08x}. Characters that cannot be decoded will be ' 'replaced with "?" or "\\ufffd".').format( cache_address.value)) cache_entry_key = cache_entry.key.decode( 'ascii', errors='replace') # TODO: print('Url\t\t: {0:s}'.format(cache_entry_key)) _ = cache_entry_key date_string = (datetime.datetime(1601, 1, 1) + datetime.timedelta( microseconds=cache_entry.creation_time)) # print('Creation time\t: {0!s}'.format(date_string)) # print('') print('{0!s}\t{1:s}'.format(date_string, cache_entry.key)) cache_address = cache_entry.next cache_address_chain_length += 1 for data_block_file in iter(data_block_files.values()): data_block_file.Close() index_file.Close() if not have_all_data_block_files: raise errors.ParseError('Missing data block files.') def ParseFile(self, path): """Parses a Chrome Cache file. Args: path (str): path of the file. Raises: ParseError: if the file cannot be read. """ with open(path, 'rb') as file_object: signature_data = file_object.read(self._UINT32LE_SIZE) try: signature = self._UINT32LE.MapByteStream(signature_data) except dtfabric_errors.MappingError as exception: raise errors.ParseError( 'Unable to signature with error: {0!s}'.format(exception)) if signature not in (DataBlockFile.SIGNATURE, IndexFile.SIGNATURE): raise errors.ParseError( 'Unsupported signature: 0x{0:08x}'.format(signature)) if signature == DataBlockFile.SIGNATURE: chrome_cache_file = DataBlockFile( debug=self._debug, output_writer=self._output_writer) elif signature == IndexFile.SIGNATURE: chrome_cache_file = IndexFile( debug=self._debug, output_writer=self._output_writer) chrome_cache_file.ReadFileObject(file_object)
class FakeWinRegistryValue(interface.WinRegistryValue): """Fake implementation of a Windows Registry value.""" _DATA_TYPE_FABRIC_DEFINITION_FILE = os.path.join( os.path.dirname(__file__), 'dtfabric.yaml') with open(_DATA_TYPE_FABRIC_DEFINITION_FILE, 'rb') as file_object: _DATA_TYPE_FABRIC_DEFINITION = file_object.read() _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric( yaml_definition=_DATA_TYPE_FABRIC_DEFINITION) _INT32_BIG_ENDIAN = _DATA_TYPE_FABRIC.CreateDataTypeMap('int32be') _INT32_LITTLE_ENDIAN = _DATA_TYPE_FABRIC.CreateDataTypeMap('int32le') _INT64_LITTLE_ENDIAN = _DATA_TYPE_FABRIC.CreateDataTypeMap('int64le') def __init__(self, name, data=b'', data_type=definitions.REG_NONE, offset=0): """Initializes a Windows Registry value. Args: name (str): name of the Windows Registry value. data (Optional[bytes]): value data. data_type (Optional[int]): value data type. offset (Optional[int]): offset of the value within the Windows Registry file. """ super(FakeWinRegistryValue, self).__init__() self._data = data self._data_type = data_type self._data_size = len(data) self._name = name self._offset = offset @property def data(self): """bytes: value data as a byte string.""" return self._data @property def data_type(self): """int: data type.""" return self._data_type @property def name(self): """str: name of the value.""" return self._name @property def offset(self): """int: offset of the value within the Windows Registry file.""" return self._offset def GetDataAsObject(self): """Retrieves the data as an object. Returns: object: data as a Python type or None if not available. Raises: WinRegistryValueError: if the value data cannot be read. """ if not self._data: return None if self._data_type in self._STRING_VALUE_TYPES: try: return self._data.decode('utf-16-le') # AttributeError is raised when self._data has no decode method. except AttributeError as exception: raise errors.WinRegistryValueError(( 'Unsupported data type: {0!s} of value: {1!s} with error: ' '{2!s}').format(type(self._data), self._name, exception)) except UnicodeError as exception: raise errors.WinRegistryValueError( 'Unable to decode data of value: {0!s} with error: {1!s}'.format( self._name, exception)) elif (self._data_type == definitions.REG_DWORD and self._data_size == 4): return self._INT32_LITTLE_ENDIAN.MapByteStream(self._data) elif (self._data_type == definitions.REG_DWORD_BIG_ENDIAN and self._data_size == 4): return self._INT32_BIG_ENDIAN.MapByteStream(self._data) elif (self._data_type == definitions.REG_QWORD and self._data_size == 8): return self._INT64_LITTLE_ENDIAN.MapByteStream(self._data) elif self._data_type == definitions.REG_MULTI_SZ: try: utf16_string = self._data.decode('utf-16-le') # TODO: evaluate the use of filter here is appropriate behavior. return list(filter(None, utf16_string.split('\x00'))) # AttributeError is raised when self._data has no decode method. except AttributeError as exception: raise errors.WinRegistryValueError(( 'Unsupported data type: {0!s} of value: {1!s} with error: ' '{2!s}').format(type(self._data), self._name, exception)) except UnicodeError as exception: raise errors.WinRegistryValueError( 'Unable to read data from value: {0!s} with error: {1!s}'.format( self._name, exception)) return self._data
class GzipMember(data_format.DataFormat): """Gzip member. Gzip files have no index of members, so each member must be read sequentially before metadata and random seeks are possible. This class provides caching of gzip member data during the initial read of each member. Attributes: comment (str): comment stored in the member. member_end_offset (int): offset to the end of the member in the parent file object. member_start_offset (int): offset to the start of the member in the parent file object. operating_system (int): type of file system on which the compression took place. original_filename (str): original filename of the uncompressed file. uncompressed_data_offset (int): offset of the start of the uncompressed data in this member relative to the whole gzip file's uncompressed data. uncompressed_data_size (int): total size of the data in this gzip member after decompression. """ _DATA_TYPE_FABRIC_DEFINITION_FILE = os.path.join(os.path.dirname(__file__), 'gzipfile.yaml') with open(_DATA_TYPE_FABRIC_DEFINITION_FILE, 'rb') as file_object: _DATA_TYPE_FABRIC_DEFINITION = file_object.read() _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric( yaml_definition=_DATA_TYPE_FABRIC_DEFINITION) _MEMBER_HEADER = _DATA_TYPE_FABRIC.CreateDataTypeMap('gzip_member_header') _MEMBER_HEADER_SIZE = _MEMBER_HEADER.GetByteSize() _MEMBER_FOOTER = _DATA_TYPE_FABRIC.CreateDataTypeMap('gzip_member_footer') _MEMBER_FOOTER_SIZE = _MEMBER_FOOTER.GetByteSize() _UINT16LE = _DATA_TYPE_FABRIC.CreateDataTypeMap('uint16le') _UINT16LE_SIZE = _UINT16LE.GetByteSize() _CSTRING = _DATA_TYPE_FABRIC.CreateDataTypeMap('cstring') _GZIP_SIGNATURE = 0x8b1f _COMPRESSION_METHOD_DEFLATE = 8 _FLAG_FTEXT = 0x01 _FLAG_FHCRC = 0x02 _FLAG_FEXTRA = 0x04 _FLAG_FNAME = 0x08 _FLAG_FCOMMENT = 0x10 # The maximum size of the uncompressed data cache. _UNCOMPRESSED_DATA_CACHE_SIZE = 2 * 1024 * 1024 def __init__(self, file_object, member_start_offset, uncompressed_data_offset): """Initializes a gzip member. Args: file_object (FileIO): file-like object, containing the gzip member. member_start_offset (int): offset to the beginning of the gzip member in the containing file. uncompressed_data_offset (int): offset of the start of the uncompressed data in this member relative to the whole gzip file's uncompressed data. """ self._cache = b'' # End offset of the cached uncompressed data of the member. self._cache_end_offset = None # Start offset of the cached uncompressed data of the member. self._cache_start_offset = None self.comment = None self.modification_time = None self.operating_system = None self.original_filename = None file_size = file_object.get_size() file_object.seek(member_start_offset, os.SEEK_SET) self._ReadMemberHeader(file_object) data_offset = 0 uncompressed_data_size = 0 compressed_data_offset = file_object.get_offset() decompressor_state = _GzipDecompressorState(compressed_data_offset) # Read the member data to determine the uncompressed data size and # the offset of the member footer. file_offset = compressed_data_offset while file_offset < file_size: data_offset += uncompressed_data_size decompressed_data = decompressor_state.Read(file_object) uncompressed_data_size += len(decompressed_data) # Note that unused data will be set when the decompressor reads beyond # the end of the compressed data stream. unused_data = decompressor_state.GetUnusedData() if unused_data: file_object.seek(-len(unused_data), os.SEEK_CUR) file_offset = file_object.get_offset() break file_offset = file_object.get_offset() # Do not read the the last member footer if it is missing, which is # a common corruption scenario. if file_offset < file_size: self._ReadStructure(file_object, file_offset, self._MEMBER_FOOTER_SIZE, self._MEMBER_FOOTER, 'member footer') member_end_offset = file_object.get_offset() # Initialize the member with data. self._file_object = file_object self._file_object.seek(member_start_offset, os.SEEK_SET) # Cache uncompressed data of gzip files that fit entirely in the cache. if (data_offset == 0 and uncompressed_data_size < self._UNCOMPRESSED_DATA_CACHE_SIZE): self._cache = decompressed_data self._cache_start_offset = 0 self._cache_end_offset = uncompressed_data_size # Offset to the beginning of the compressed data in the file object. self._compressed_data_start = compressed_data_offset self._decompressor_state = _GzipDecompressorState( compressed_data_offset) # Offset to the start of the member in the parent file object. self.member_start_offset = member_start_offset # Offset to the end of the member in the parent file object. self.member_end_offset = member_end_offset # Total size of the data in this gzip member after decompression. self.uncompressed_data_size = uncompressed_data_size # Offset of the start of the uncompressed data in this member relative to # the whole gzip file's uncompressed data. self.uncompressed_data_offset = uncompressed_data_offset def _ReadMemberHeader(self, file_object): """Reads a member header. Args: file_object (FileIO): file-like object to read from. Raises: FileFormatError: if the member header cannot be read. """ file_offset = file_object.get_offset() member_header = self._ReadStructure(file_object, file_offset, self._MEMBER_HEADER_SIZE, self._MEMBER_HEADER, 'member header') if member_header.signature != self._GZIP_SIGNATURE: raise errors.FileFormatError( 'Unsupported signature: 0x{0:04x}.'.format( member_header.signature)) if member_header.compression_method != self._COMPRESSION_METHOD_DEFLATE: raise errors.FileFormatError( 'Unsupported compression method: {0:d}.'.format( member_header.compression_method)) self.modification_time = member_header.modification_time self.operating_system = member_header.operating_system if member_header.flags & self._FLAG_FEXTRA: file_offset = file_object.get_offset() extra_field_data_size = self._ReadStructure( file_object, file_offset, self._UINT16LE_SIZE, self._UINT16LE, 'extra field data size') file_object.seek(extra_field_data_size, os.SEEK_CUR) if member_header.flags & self._FLAG_FNAME: file_offset = file_object.get_offset() string_value = self._ReadString(file_object, file_offset, self._CSTRING, 'original filename') self.original_filename = string_value.rstrip('\x00') if member_header.flags & self._FLAG_FCOMMENT: file_offset = file_object.get_offset() string_value = self._ReadString(file_object, file_offset, self._CSTRING, 'comment') self.comment = string_value.rstrip('\x00') if member_header.flags & self._FLAG_FHCRC: file_object.read(2) def _ResetDecompressorState(self): """Resets the state of the internal decompression object.""" self._decompressor_state = _GzipDecompressorState( self._compressed_data_start) def FlushCache(self): """Empties the cache that holds cached decompressed data.""" self._cache = b'' self._cache_start_offset = None self._cache_end_offset = None self._ResetDecompressorState() def GetCacheSize(self): """Determines the size of the uncompressed cached data. Returns: int: number of cached bytes. """ if not self._cache_start_offset or not self._cache_end_offset: return 0 return self._cache_end_offset - self._cache_start_offset def IsCacheFull(self): """Checks whether the uncompressed data cache is full. Returns: bool: True if the cache is full. """ return self.GetCacheSize() >= self._UNCOMPRESSED_DATA_CACHE_SIZE def ReadAtOffset(self, offset, size=None): """Reads a byte string from the gzip member at the specified offset. The function will read a byte string of the specified size or all of the remaining data if no size was specified. Args: offset (int): offset within the uncompressed data in this member to read from. size (Optional[int]): maximum number of bytes to read, where None represents all remaining data, to a maximum of the uncompressed cache size. Returns: bytes: data read. Raises: IOError: if the read failed. ValueError: if a negative read size or offset is specified. """ if size is not None and size < 0: raise ValueError('Invalid size value {0!s}'.format(size)) if offset < 0: raise ValueError('Invalid offset value {0!s}'.format(offset)) if size == 0 or offset >= self.uncompressed_data_size: return b'' if self._cache_start_offset is None: self._LoadDataIntoCache(self._file_object, offset) if offset > self._cache_end_offset or offset < self._cache_start_offset: self.FlushCache() self._LoadDataIntoCache(self._file_object, offset) cache_offset = offset - self._cache_start_offset if not size: return self._cache[cache_offset:] data_end_offset = cache_offset + size if data_end_offset > self._cache_end_offset: return self._cache[cache_offset:] return self._cache[cache_offset:data_end_offset] def _LoadDataIntoCache(self, file_object, minimum_offset): """Reads and decompresses the data in the member. This function already loads as much data as possible in the cache, up to UNCOMPRESSED_DATA_CACHE_SIZE bytes. Args: file_object (FileIO): file-like object. minimum_offset (int): offset into this member's uncompressed data at which the cache should start. """ # Decompression can only be performed from beginning to end of the stream. # So, if data before the current position of the decompressor in the stream # is required, it's necessary to throw away the current decompression # state and start again. if minimum_offset < self._decompressor_state.uncompressed_offset: self._ResetDecompressorState() while not self.IsCacheFull(): decompressed_data = self._decompressor_state.Read(file_object) # Note that decompressed_data will be empty if there is no data left # to read and decompress. if not decompressed_data: break decompressed_data_length = len(decompressed_data) decompressed_end_offset = self._decompressor_state.uncompressed_offset decompressed_start_offset = (decompressed_end_offset - decompressed_data_length) data_to_add = decompressed_data added_data_start_offset = decompressed_start_offset if decompressed_start_offset < minimum_offset: data_to_add = None if decompressed_start_offset < minimum_offset < decompressed_end_offset: data_add_offset = decompressed_end_offset - minimum_offset data_to_add = decompressed_data[-data_add_offset:] added_data_start_offset = decompressed_end_offset - data_add_offset if not self.IsCacheFull() and data_to_add: self._cache = b''.join([self._cache, data_to_add]) if self._cache_start_offset is None: self._cache_start_offset = added_data_start_offset if self._cache_end_offset is None: self._cache_end_offset = self._cache_start_offset + len( data_to_add) else: self._cache_end_offset += len(data_to_add) # If there's no more data in the member, the unused_data value is # populated in the decompressor. When this situation arises, we rewind # to the end of the compressed_data section. unused_data = self._decompressor_state.GetUnusedData() if unused_data: seek_offset = -len(unused_data) file_object.seek(seek_offset, os.SEEK_CUR) self._ResetDecompressorState() break
class BinaryDataFormatTest(test_lib.BaseTestCase): """Binary data format tests.""" # pylint: disable=protected-access _DATA_TYPE_FABRIC_DEFINITION = b"""\ name: uint32 type: integer attributes: format: unsigned size: 4 units: bytes --- name: point3d type: structure attributes: byte_order: little-endian members: - name: x data_type: uint32 - name: y data_type: uint32 - name: z data_type: uint32 --- name: shape3d type: structure attributes: byte_order: little-endian members: - name: number_of_points data_type: uint32 - name: points type: sequence element_data_type: point3d number_of_elements: shape3d.number_of_points """ _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric( yaml_definition=_DATA_TYPE_FABRIC_DEFINITION) _POINT3D = _DATA_TYPE_FABRIC.CreateDataTypeMap('point3d') _POINT3D_SIZE = _POINT3D.GetByteSize() _SHAPE3D = _DATA_TYPE_FABRIC.CreateDataTypeMap('shape3d') def testDebugPrintData(self): """Tests the _DebugPrintData function.""" output_writer = test_lib.TestOutputWriter() test_format = data_format.BinaryDataFormat(output_writer=output_writer) data = b'\x00\x01\x02\x03\x04\x05\x06' test_format._DebugPrintData('Description', data) expected_output = [ 'Description:\n', ('0x00000000 00 01 02 03 04 05 06 ' '.......\n\n') ] self.assertEqual(output_writer.output, expected_output) def testDebugPrintDecimalValue(self): """Tests the _DebugPrintDecimalValue function.""" output_writer = test_lib.TestOutputWriter() test_format = data_format.BinaryDataFormat(output_writer=output_writer) test_format._DebugPrintDecimalValue('Description', 1) expected_output = ['Description\t\t\t\t\t\t\t\t: 1\n'] self.assertEqual(output_writer.output, expected_output) # TODO add tests for _DebugPrintFiletimeValue # TODO add tests for _DebugPrintPosixTimeValue def testDebugPrintValue(self): """Tests the _DebugPrintValue function.""" output_writer = test_lib.TestOutputWriter() test_format = data_format.BinaryDataFormat(output_writer=output_writer) test_format._DebugPrintValue('Description', 'Value') expected_output = ['Description\t\t\t\t\t\t\t\t: Value\n'] self.assertEqual(output_writer.output, expected_output) def testDebugPrintText(self): """Tests the _DebugPrintText function.""" output_writer = test_lib.TestOutputWriter() test_format = data_format.BinaryDataFormat(output_writer=output_writer) test_format._DebugPrintText('Text') expected_output = ['Text'] self.assertEqual(output_writer.output, expected_output) def testFormatDataInHexadecimal(self): """Tests the _FormatDataInHexadecimal function.""" test_format = data_format.BinaryDataFormat() data = b'\x00\x01\x02\x03\x04\x05\x06' expected_formatted_data = ( '0x00000000 00 01 02 03 04 05 06 ' '.......\n' '\n') formatted_data = test_format._FormatDataInHexadecimal(data) self.assertEqual(formatted_data, expected_formatted_data) data = b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09' expected_formatted_data = ( '0x00000000 00 01 02 03 04 05 06 07 08 09 ' '..........\n' '\n') formatted_data = test_format._FormatDataInHexadecimal(data) self.assertEqual(formatted_data, expected_formatted_data) data = b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f' expected_formatted_data = ( '0x00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ' '................\n' '\n') formatted_data = test_format._FormatDataInHexadecimal(data) self.assertEqual(formatted_data, expected_formatted_data) data = ( b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f' b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f' b'\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f' ) expected_formatted_data = ( '0x00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ' '................\n' '...\n' '0x00000020 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ' '................\n' '\n') formatted_data = test_format._FormatDataInHexadecimal(data) self.assertEqual(formatted_data, expected_formatted_data) def testFormatPackedIPv4Address(self): """Tests the _FormatPackedIPv4Address function.""" test_format = data_format.BinaryDataFormat() ip_address = test_format._FormatPackedIPv4Address( [0xc0, 0xa8, 0xcc, 0x62]) self.assertEqual(ip_address, '192.168.204.98') def testFormatPackedIPv6Address(self): """Tests the _FormatPackedIPv6Address function.""" test_format = data_format.BinaryDataFormat() ip_address = test_format._FormatPackedIPv6Address([ 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00, 0x42, 0x83, 0x29 ]) self.assertEqual(ip_address, '2001:0db8:0000:0000:0000:ff00:0042:8329') # TODO: add tests for _GetDataTypeMap def testReadData(self): """Tests the _ReadData function.""" output_writer = test_lib.TestOutputWriter() test_format = data_format.BinaryDataFormat(debug=True, output_writer=output_writer) file_object = io.BytesIO( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00') test_format._ReadData(file_object, 0, self._POINT3D_SIZE, 'point3d') # Test with missing file-like object. with self.assertRaises(ValueError): test_format._ReadData(None, 0, self._POINT3D_SIZE, 'point3d') # Test with file-like object with insufficient data. file_object = io.BytesIO( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00') with self.assertRaises(errors.ParseError): test_format._ReadData(file_object, 0, self._POINT3D_SIZE, 'point3d') # Test with file-like object that raises an IOError. file_object = ErrorBytesIO( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00') with self.assertRaises(errors.ParseError): test_format._ReadData(file_object, 0, self._POINT3D_SIZE, 'point3d') # TODO: add tests for _ReadDefinitionFile def testReadStructure(self): """Tests the _ReadStructure function.""" output_writer = test_lib.TestOutputWriter() test_format = data_format.BinaryDataFormat(debug=True, output_writer=output_writer) file_object = io.BytesIO( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00') test_format._ReadStructure(file_object, 0, self._POINT3D_SIZE, self._POINT3D, 'point3d') def testReadStructureFromByteStream(self): """Tests the _ReadStructureFromByteStream function.""" output_writer = test_lib.TestOutputWriter() test_format = data_format.BinaryDataFormat(debug=True, output_writer=output_writer) test_format._ReadStructureFromByteStream( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0, self._POINT3D, 'point3d') # Test with missing byte stream. with self.assertRaises(ValueError): test_format._ReadStructureFromByteStream(None, 0, self._POINT3D, 'point3d') # Test with missing data map type. with self.assertRaises(ValueError): test_format._ReadStructureFromByteStream( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0, None, 'point3d') # Test with data type map that raises an dtfabric.MappingError. data_type_map = ErrorDataTypeMap(None) with self.assertRaises(errors.ParseError): test_format._ReadStructureFromByteStream( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0, data_type_map, 'point3d') def testReadStructureFromFileObject(self): """Tests the _ReadStructureFromFileObject function.""" output_writer = test_lib.TestOutputWriter() test_format = data_format.BinaryDataFormat(debug=True, output_writer=output_writer) file_object = io.BytesIO( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00') test_format._ReadStructureFromFileObject(file_object, 0, self._POINT3D, "point3d") file_object = io.BytesIO( b'\x03\x00\x00\x00' b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00' b'\x04\x00\x00\x00\x05\x00\x00\x00\x06\x00\x00\x00' b'\x06\x00\x00\x00\x07\x00\x00\x00\x08\x00\x00\x00') test_format._ReadStructureFromFileObject(file_object, 0, self._SHAPE3D, "shape3d")
class WindowsTaskSchedularJobFile(data_format.BinaryDataFile): """Windows Task Scheduler job (.job) file.""" _DATA_TYPE_FABRIC_DEFINITION_FILE = os.path.join(os.path.dirname(__file__), 'job.yaml') with open(_DATA_TYPE_FABRIC_DEFINITION_FILE, 'rb') as file_object: _DATA_TYPE_FABRIC_DEFINITION = file_object.read() # TODO: add format definition. # https://msdn.microsoft.com/en-us/library/cc248285.aspx # TODO: add job signature # https://msdn.microsoft.com/en-us/library/cc248299.aspx _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric( yaml_definition=_DATA_TYPE_FABRIC_DEFINITION) _FIXED_LENGTH_DATA_SECTION = _DATA_TYPE_FABRIC.CreateDataTypeMap( 'job_fixed_length_data_section') _FIXED_LENGTH_DATA_SECTION_SIZE = _FIXED_LENGTH_DATA_SECTION.GetByteSize() _VARIABLE_LENGTH_DATA_SECTION = _DATA_TYPE_FABRIC.CreateDataTypeMap( 'job_variable_length_data_section') _DEBUG_INFO_FIXED_LENGTH_DATA_SECTION = [ ('signature', 'Signature', '_FormatIntegerAsProductVersion'), ('format_version', 'Format version', '_FormatIntegerAsDecimal'), ('job_identifier', 'Job identifier', '_FormatUUIDAsString'), ('application_name_offset', 'Application name offset', '_FormatIntegerAsHexadecimal4'), ('triggers_offset', 'Triggers offset', '_FormatIntegerAsHexadecimal4'), ('error_retry_count', 'Error retry count', '_FormatIntegerAsDecimal'), ('error_retry_interval', 'Error retry interval', '_FormatIntegerAsIntervalInMinutes'), ('idle_deadline', 'Idle deadline', '_FormatIntegerAsIntervalInMinutes'), ('idle_wait', 'Idle wait', '_FormatIntegerAsIntervalInMinutes'), ('priority', 'Priority', '_FormatIntegerAsHexadecimal8'), ('maximum_run_time', 'Maximum run time', '_FormatIntegerAsIntervalInMilliseconds'), ('exit_code', 'Exit code', '_FormatIntegerAsHexadecimal8'), ('status', 'Status', '_FormatIntegerAsHexadecimal8'), ('flags', 'Flags', '_FormatIntegerAsHexadecimal8'), ('last_run_time', 'Last run time', '_FormatSystemTime') ] _DEBUG_INFO_TRIGGER = [ ('size', 'Size', '_FormatIntegerAsDecimal'), ('reserved1', 'Reserved1', '_FormatIntegerAsHexadecimal4'), ('start_date', 'Start date', '_FormatDate'), ('end_date', 'End date', '_FormatDate'), ('start_time', 'Start time', '_FormatTime'), ('duration', 'Duration', '_FormatIntegerAsIntervalInMinutes'), ('interval', 'Interval', '_FormatIntegerAsIntervalInMinutes'), ('trigger_flags', 'Trigger flags', '_FormatIntegerAsHexadecimal8'), ('trigger_type', 'Trigger type', '_FormatIntegerAsHexadecimal8'), ('trigger_arg0', 'Trigger arg0', '_FormatIntegerAsHexadecimal4'), ('trigger_arg1', 'Trigger arg1', '_FormatIntegerAsHexadecimal4'), ('trigger_arg2', 'Trigger arg2', '_FormatIntegerAsHexadecimal4'), ('trigger_padding', 'Trigger padding', '_FormatIntegerAsHexadecimal4'), ('trigger_reserved2', 'Trigger reserved2', '_FormatIntegerAsHexadecimal4'), ('trigger_reserved3', 'Trigger reserved3', '_FormatIntegerAsHexadecimal4') ] _DEBUG_INFO_VARIABLE_LENGTH_DATA_SECTION = [ ('running_instance_count', 'Running instance count', '_FormatIntegerAsDecimal'), ('application_name', 'Application name', '_FormatString'), ('parameters', 'Parameters', '_FormatString'), ('working_directory', 'Working directory', '_FormatString'), ('author', 'Author', '_FormatString'), ('comment', 'Comment', '_FormatString'), ('user_data', 'User data', '_FormatDataStream'), ('reserved_data', 'Reserved data', '_FormatDataStream'), ('number_of_triggers', 'Number of triggers', '_FormatIntegerAsDecimal') ] def _FormatDataStream(self, data_stream): """Formats a data stream structure Args: data_stream (job_reserved_data|job_user_data): data stream structure Returns: str: formatted data stream structure """ # TODO: print data_stream.size on a separate line return self._FormatDataInHexadecimal(data_stream.stream) def _FormatDate(self, date): """Formats a date structure. Args: date (job_trigger_date): date structure. Returns: str: formatted date structure. """ return '{0:04d}-{1:02d}-{2:02d}'.format(date.year, date.month, date.day_of_month) def _FormatIntegerAsIntervalInMilliseconds(self, integer): """Formats an integer as an interval in milliseconds. Args: integer (int): integer. Returns: str: integer formatted as an interval in milliseconds. """ return '{0:d} milliseconds'.format(integer) def _FormatIntegerAsIntervalInMinutes(self, integer): """Formats an integer as an interval in minutes. Args: integer (int): integer. Returns: str: integer formatted as an interval in minutes. """ return '{0:d} minutes'.format(integer) def _FormatIntegerAsProductVersion(self, integer): """Formats an integer as a product version. Args: integer (int): integer. Returns: str: integer formatted as a product version. """ return '0x{0:04x} ({1:d}.{2:d})'.format(integer, (integer >> 8) & 0xff, integer & 0xff) def _FormatString(self, string): """Formats a string structure Args: string (job_string): string structure Returns: str: formatted string structure """ # TODO: print string.number_of_characters on a separate line return '({0:d} bytes) {1:s}'.format(string.number_of_characters * 2, string.string) def _FormatSystemTime(self, systemtime): """Formats a SYSTEMTIME structure. Args: systemtime (system_time): SYSTEMTIME structure. Returns: str: formatted SYSTEMTIME structure. """ return '{0:04d}-{1:02d}-{2:02d} {3:02d}:{4:02d}:{5:02d}.{6:03d}'.format( systemtime.year, systemtime.month, systemtime.day_of_month, systemtime.hours, systemtime.minutes, systemtime.seconds, systemtime.milliseconds) def _FormatTime(self, time): """Formats a time structure. Args: time (job_trigger_time): time structure. Returns: str: formatted time structure. """ return '{0:02d}:{1:02d}'.format(time.hours, time.minutes) def _ReadFixedLengthDataSection(self, file_object): """Reads the fixed-length data section. Args: file_object (file): file-like object. Raises: IOError: if the fixed-length data section cannot be read. """ file_offset = file_object.tell() data_section = self._ReadStructure( file_object, file_offset, self._FIXED_LENGTH_DATA_SECTION_SIZE, self._FIXED_LENGTH_DATA_SECTION, 'fixed-length data section') if self._debug: self._DebugPrintStructureObject( data_section, self._DEBUG_INFO_FIXED_LENGTH_DATA_SECTION) def _ReadVariableLengthDataSection(self, file_object): """Reads the variable-length data section. Args: file_object (file): file-like object. Raises: IOError: if the variable-length data section cannot be read. """ file_offset = file_object.tell() data_size = self._file_size - file_offset data_section = self._ReadStructure(file_object, file_offset, data_size, self._VARIABLE_LENGTH_DATA_SECTION, 'variable-length data section') if self._debug: self._DebugPrintStructureObject( data_section, self._DEBUG_INFO_VARIABLE_LENGTH_DATA_SECTION) for trigger in data_section.triggers.triggers_array: self._DebugPrintStructureObject(trigger, self._DEBUG_INFO_TRIGGER) def ReadFileObject(self, file_object): """Reads a Windows Task Scheduler job file-like object. Args: file_object (file): file-like object. Raises: ParseError: if the file cannot be read. """ self._ReadFixedLengthDataSection(file_object) self._ReadVariableLengthDataSection(file_object)
class BinaryDataFormatTest(test_lib.BaseTestCase): """Binary data format tests.""" # pylint: disable=protected-access _DATA_TYPE_FABRIC_DEFINITION = b"""\ name: uint32 type: integer attributes: format: unsigned size: 4 units: bytes --- name: point3d type: structure attributes: byte_order: little-endian members: - name: x data_type: uint32 - name: y data_type: uint32 - name: z data_type: uint32 --- name: shape3d type: structure attributes: byte_order: little-endian members: - name: number_of_points data_type: uint32 - name: points type: sequence element_data_type: point3d number_of_elements: shape3d.number_of_points """ _DATA_TYPE_FABRIC = dtfabric_fabric.DataTypeFabric( yaml_definition=_DATA_TYPE_FABRIC_DEFINITION) def testDebugPrintData(self): """Tests the _DebugPrintData function.""" output_writer = test_lib.TestOutputWriter() test_format = data_format.BinaryDataFormat(output_writer=output_writer) data = b'\x00\x01\x02\x03\x04\x05\x06' test_format._DebugPrintData('Description', data) expected_output = [ 'Description:\n', ('0x00000000 00 01 02 03 04 05 06 ' '.......\n\n') ] self.assertEqual(output_writer.output, expected_output) def testDebugPrintDecimalValue(self): """Tests the _DebugPrintDecimalValue function.""" output_writer = test_lib.TestOutputWriter() test_format = data_format.BinaryDataFormat(output_writer=output_writer) test_format._DebugPrintDecimalValue('Description', 1) expected_output = ['Description\t\t\t\t\t\t\t\t: 1\n'] self.assertEqual(output_writer.output, expected_output) # TODO add tests for _DebugPrintFiletimeValue def testDebugPrintValue(self): """Tests the _DebugPrintValue function.""" output_writer = test_lib.TestOutputWriter() test_format = data_format.BinaryDataFormat(output_writer=output_writer) test_format._DebugPrintValue('Description', 'Value') expected_output = ['Description\t\t\t\t\t\t\t\t: Value\n'] self.assertEqual(output_writer.output, expected_output) def testDebugPrintText(self): """Tests the _DebugPrintText function.""" output_writer = test_lib.TestOutputWriter() test_format = data_format.BinaryDataFormat(output_writer=output_writer) test_format._DebugPrintText('Text') expected_output = ['Text'] self.assertEqual(output_writer.output, expected_output) # TODO: add tests for _GetDataTypeMap # TODO: add tests for _ReadDefinitionFile def testReadStructureFromByteStream(self): """Tests the _ReadStructureFromByteStream function.""" output_writer = test_lib.TestOutputWriter() test_format = data_format.BinaryDataFormat(debug=True, output_writer=output_writer) data_type_map = self._DATA_TYPE_FABRIC.CreateDataTypeMap('point3d') test_format._ReadStructureFromByteStream( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0, data_type_map, 'point3d') # Test with missing byte stream. with self.assertRaises(ValueError): test_format._ReadStructureFromByteStream(None, 0, data_type_map, 'point3d') # Test with missing data map type. with self.assertRaises(ValueError): test_format._ReadStructureFromByteStream( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0, None, 'point3d') # Test with data type map that raises an dtfabric.MappingError. data_type_map = ErrorDataTypeMap(None) with self.assertRaises(errors.ParseError): test_format._ReadStructureFromByteStream( b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00', 0, data_type_map, 'point3d')