diff --git a/volatility/framework/objects/__init__.py b/volatility/framework/objects/__init__.py index bb5ee4726..99febbe30 100644 --- a/volatility/framework/objects/__init__.py +++ b/volatility/framework/objects/__init__.py @@ -9,6 +9,49 @@ from volatility.framework.objects import templates vollog = logging.getLogger(__name__) +StructFormatType = typing.Tuple[int, str, bool] + + +def convert_data_to_value(data, struct_type, length, byteorder, signed): + """Converts a series of bytes to a particular type of value""" + if struct_type == int: + return int.from_bytes(data, byteorder = byteorder, signed = signed) + if struct_type == bool: + struct_format = "?" + elif struct_type == float: + float_vals = "zzezfzzzd" + if length > len(float_vals) or float_vals[length] not in "efd": + raise TypeError("Invalid float size") + struct_format = ("<" if byteorder == 'little' else ">") + float_vals[length] + elif struct_type in [bytes, str]: + struct_format = str(length) + "s" + else: + raise TypeError("Cannot construct struct format for type {}".format(type(struct_type))) + + return struct.unpack(struct_format, data)[0] + + +def convert_value_to_data(value, struct_type, length, byteorder, signed): + """Converts a particular value to a series of bytes""" + if not isinstance(value, struct_type): + raise TypeError("Written value is not of the correct type for {}".format(self.__class__.__name__)) + + if struct_type == int: + return int.to_bytes(value, length = length, byteorder = byteorder, signed = signed) + if struct_type == bool: + struct_format = "?" + elif struct_type == float: + float_vals = "zzezfzzzd" + if length > len(float_vals) or float_vals[length] not in "efd": + raise TypeError("Invalid float size") + struct_format = ("<" if byteorder == 'little' else ">") + float_vals[length] + elif struct_type in [bytes, str]: + struct_format = str(length) + "s" + else: + raise TypeError("Cannot construct struct format for type {}".format(type(struct_type))) + + return struct.pack(struct_format, value) + class Void(interfaces.objects.ObjectInterface): """Returns an object to represent void/unknown types""" @@ -36,7 +79,7 @@ class PrimitiveObject(interfaces.objects.ObjectInterface): context: interfaces.context.ContextInterface, type_name: str, object_info: interfaces.objects.ObjectInformation, - struct_format: str) -> None: + struct_format: StructFormatType) -> None: super().__init__(context = context, type_name = type_name, object_info = object_info, @@ -47,7 +90,7 @@ class PrimitiveObject(interfaces.objects.ObjectInterface): context: interfaces.context.ContextInterface, type_name: str, object_info: interfaces.objects.ObjectInformation, - struct_format: str, + struct_format: StructFormatType, new_value: typing.Union[int, float, bool, bytes, str] = None, **kwargs) -> typing.Type['PrimitiveObject']: """Creates the appropriate class and returns it so that the native type is inherited @@ -84,41 +127,28 @@ class PrimitiveObject(interfaces.objects.ObjectInterface): @classmethod def _struct_value(cls, context: interfaces.context.ContextInterface, - struct_format: str, + struct_format: StructFormatType, object_info: ObjectInformation) -> typing.Union[int, float, bool, bytes, str]: - - length = struct.calcsize(struct_format) + length, byteorder, signed = struct_format data = context.memory.read(object_info.layer_name, object_info.offset, length) - - if cls._struct_type == int: - value = int.from_bytes(data, - byteorder = "little" if "<" in struct_format else "big", - signed = (struct_format.lower() == struct_format)) - else: - (value,) = struct.unpack(struct_format, data) - - return value + return convert_data_to_value(data, cls._struct_type, length, byteorder, signed) class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy): @classmethod def size(cls, template: interfaces.objects.Template) -> int: """Returns the size of the templated object""" - return struct.calcsize(template.vol.struct_format) + return template.vol.struct_format[0] def write(self, value: typing.Union[int, float, bool, bytes, str]) -> None: """Writes the object into the layer of the context at the current offset""" - if isinstance(value, self._struct_type): - if self._struct_type == int: - data = int.to_bytes(value, - length = struct.calcsize(self._struct_format), - byteorder = "little" if "<" in self._struct_format else "big", - signed = (self._struct_format.lower() == self._struct_format)) - else: - data = struct.pack(self.vol.struct_format, value) - return self._context.memory.write(self.vol.layer_name, self.vol.offset, data) - raise TypeError("Object {} requires a valid {} to be written: {}".format(self.__class__.__name__, - type(self._struct_type), - type(value))) + length, byteorder, signed = self._struct_format + data = convert_value_to_data(value, self._struct_type, length, byteorder, signed) + return self._context.memory.write(self.vol.layer_name, self.vol.offset, data) + + +class Boolean(PrimitiveObject, int): + """Primitive Object that handles boolean types""" + _struct_type = bool # type: typing.ClassVar[typing.Type] class Integer(PrimitiveObject, int): @@ -147,7 +177,7 @@ class Bytes(PrimitiveObject, bytes): super().__init__(context = context, type_name = type_name, object_info = object_info, - struct_format = str(length) + "s") + struct_format = (length, "big", False)) self._vol['length'] = length def __new__(cls: typing.Type, @@ -162,7 +192,7 @@ class Bytes(PrimitiveObject, bytes): without needing to override __new__""" return cls._struct_type.__new__(cls, cls._struct_value(context, - struct_format = str(length) + "s", + struct_format = (length, "big", False), object_info = object_info)) @@ -186,7 +216,7 @@ class String(PrimitiveObject, str): super().__init__(context = context, type_name = type_name, object_info = object_info, - struct_format = str(max_length) + 's') + struct_format = (max_length, "big", False)) self._vol["max_length"] = max_length self._vol['encoding'] = encoding self._vol['errors'] = errors @@ -211,7 +241,7 @@ class String(PrimitiveObject, str): # Pass the encoding and error parameters to the string constructor to appropriately encode the string value = cls._struct_type.__new__(cls, # type: ignore cls._struct_value(context, - struct_format = str(max_length) + "s", + struct_format = (max_length, "big", False), object_info = object_info), **params) if value.find('\x00') >= 0: @@ -226,7 +256,7 @@ class Pointer(Integer): context: interfaces.context.ContextInterface, type_name: str, object_info: interfaces.objects.ObjectInformation, - struct_format: str, + struct_format: StructFormatType, subtype: typing.Optional[templates.ObjectTemplate] = None) -> None: self._check_type(subtype, templates.ObjectTemplate) super().__init__(context = context, @@ -238,17 +268,19 @@ class Pointer(Integer): @classmethod def _struct_value(cls, context: interfaces.context.ContextInterface, - struct_format: str, + struct_format: StructFormatType, object_info: ObjectInformation) -> typing.Any: """Ensure that pointer values always fall within the address space of the layer they're constructed on If there's a need for all the data within the address, the pointer should be recast. The "pointer" must always live within the space (even if the data provided is invalid). """ - length = struct.calcsize(struct_format) + length, endian, signed = struct_format + if signed: + raise TypeError("Pointers cannot have signed values") mask = context.memory[object_info.native_layer_name].address_mask data = context.memory.read(object_info.layer_name, object_info.offset, length) - (value,) = struct.unpack(struct_format, data) + value = int.from_bytes(data, byteorder = endian, signed = signed) return value & mask def dereference(self, layer_name: typing.Optional[str] = None) -> interfaces.objects.ObjectInterface: diff --git a/volatility/framework/symbols/intermed.py b/volatility/framework/symbols/intermed.py index 6fb42de58..0b954d59f 100644 --- a/volatility/framework/symbols/intermed.py +++ b/volatility/framework/symbols/intermed.py @@ -95,11 +95,11 @@ class IntermediateSymbolTable(interfaces.symbols.SymbolTableInterface): json_object, native_types, table_mapping) + # Inherit super().__init__(context, config_path, name, native_types or self._delegate.natives, table_mapping = table_mapping) - def _closest_version(self, version: str, versions: typing.Dict[typing.Tuple[int, int, int], typing.Type['ISFormatTable']]) \ @@ -476,17 +476,11 @@ class Version4Format(Version3Format): age = 0 version = (current - age, age, revision) - format_str_mapping = {'int': ({1: 'b', - 2: 'h', - 4: 'i', - 8: 'q', - 16: 'qq'}, objects.Integer), - 'float': ({2: 'e', - 4: 'f', - 8: 'd'}, objects.Float), - 'void': ({4: 'i'}, objects.Integer), - 'bool': ({1: '?'}, objects.Integer), - 'char': ({1: 'c'}, objects.Char)} + format_mapping = {'int': objects.Integer, + 'float': objects.Float, + 'void': objects.Integer, + 'bool': objects.Boolean, + 'char': objects.Char} def _get_natives(self) -> typing.Optional[interfaces.symbols.NativeTableInterface]: """Determines the appropriate native_types to use from the JSON data""" @@ -497,15 +491,13 @@ class Version4Format(Version3Format): if base_type != 'void': current = base_types[base_type] # TODO: Fix up the typing of this, it bugs out because of the tuple assignment - size_map, object_type = self.format_str_mapping.get(current['kind'], ({}, None)) # type: ignore - format_str = size_map.get(current['size'], None) - if format_str is None or object_type is None: - raise ValueError("Unsupported kind/size combination in base_type {}".format(base_type)) - format_str = format_str.lower() if current['signed'] or current['kind'] != 'int' else format_str.upper() - format_str = ('<' if current['endian'] == 'little' else '>') + format_str + if current['kind'] not in self.format_mapping: + raise ValueError("Unsupported base kind") + format_val = (current['size'], current['endian'], current['signed']) + object_type = self.format_mapping[current['kind']] if base_type == 'pointer': object_type = objects.Pointer - native_dict[base_type] = (object_type, format_str) + native_dict[base_type] = (object_type, format_val) return native.NativeTable(name = "native", native_dictionary = native_dict)