Source code for volatility3.framework.objects

# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#

import collections
import collections.abc
import logging
import struct
from typing import (
    Any,
    ClassVar,
    Dict,
    Iterable,
    List,
    Optional,
    Tuple,
    Type,
    Union as TUnion,
    overload,
)

from volatility3.framework import constants, interfaces
from volatility3.framework.objects import templates

vollog = logging.getLogger(__name__)

DataFormatInfo = collections.namedtuple(
    "DataFormatInfo", ["length", "byteorder", "signed"]
)


[docs]def convert_data_to_value( data: bytes, struct_type: Type[TUnion[int, float, bytes, str, bool]], data_format: DataFormatInfo, ) -> TUnion[int, float, bytes, str, bool]: """Converts a series of bytes to a particular type of value.""" if struct_type == int: return int.from_bytes( data, byteorder=data_format.byteorder, signed=data_format.signed ) if struct_type == bool: struct_format = "?" elif struct_type == float: float_vals = "zzezfzzzd" if ( data_format.length > len(float_vals) or float_vals[data_format.length] not in "efd" ): raise ValueError("Invalid float size") struct_format = ( "<" if data_format.byteorder == "little" else ">" ) + float_vals[data_format.length] elif struct_type in [bytes, str]: struct_format = str(data_format.length) + "s" else: raise TypeError(f"Cannot construct struct format for type {type(struct_type)}") return struct.unpack(struct_format, data)[0]
[docs]def convert_value_to_data( value: TUnion[int, float, bytes, str, bool], struct_type: Type[TUnion[int, float, bytes, str, bool]], data_format: DataFormatInfo, ) -> bytes: """Converts a particular value to a series of bytes.""" if not isinstance(value, struct_type): raise TypeError( f"Written value is not of the correct type for {struct_type.__name__}" ) if struct_type == int and isinstance(value, int): # Doubling up on the isinstance is for mypy return int.to_bytes( value, length=data_format.length, byteorder=data_format.byteorder, signed=data_format.signed, ) if struct_type == bool: struct_format = "?" elif struct_type == float: float_vals = "zzezfzzzd" if ( data_format.length > len(float_vals) or float_vals[data_format.length] not in "efd" ): raise ValueError("Invalid float size") struct_format = ( "<" if data_format.byteorder == "little" else ">" ) + float_vals[data_format.length] elif struct_type in [bytes, str]: if isinstance(value, str): value = bytes(value, "latin-1") struct_format = str(data_format.length) + "s" else: raise TypeError(f"Cannot construct struct format for type {type(struct_type)}") return struct.pack(struct_format, value)
[docs]class Void(interfaces.objects.ObjectInterface): """Returns an object to represent void/unknown types."""
[docs] class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy):
[docs] @classmethod def size(cls, template: interfaces.objects.Template) -> int: """Dummy size for Void objects. According to http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf, void is an incomplete type, and therefore sizeof(void) should fail. However, we need to be able to construct voids to be able to cast them, so we return a useless size. It shouldn't cause errors, but it also shouldn't be common, it is logged at the lowest level. """ vollog.log(constants.LOGLEVEL_VVVV, "Void size requested") return 0
[docs] def write(self, value: Any) -> None: """Dummy method that does nothing for Void objects.""" raise TypeError("Cannot write data to a void, recast as another object")
[docs]class Function(interfaces.objects.ObjectInterface): """"""
[docs]class PrimitiveObject(interfaces.objects.ObjectInterface): """PrimitiveObject is an interface for any objects that should simulate a Python primitive.""" _struct_type: ClassVar[Type] = int def __init__( self, context: interfaces.context.ContextInterface, type_name: str, object_info: interfaces.objects.ObjectInformation, data_format: DataFormatInfo, ) -> None: super().__init__( context=context, type_name=type_name, object_info=object_info, data_format=data_format, ) self._data_format = data_format def __new__( cls: Type, context: interfaces.context.ContextInterface, type_name: str, object_info: interfaces.objects.ObjectInformation, data_format: DataFormatInfo, new_value: TUnion[int, float, bool, bytes, str] = None, **kwargs, ) -> "PrimitiveObject": """Creates the appropriate class and returns it so that the native type is inherited. The only reason the kwargs is added, is so that the inheriting types can override __init__ without needing to override __new__ We also sneak in new_value, so that we don't have to do expensive (read: impossible) context reads when unpickling. """ if new_value is None: value = cls._unmarshall(context, data_format, object_info) else: value = new_value result = cls._struct_type.__new__(cls, value) # This prevents us having to go read a context layer when recreating after unpickling # Mypy complains that result doesn't have a __new_value, but using setattr causes pycharm to complain further down result.__new_value = value # type: ignore return result def __getnewargs_ex__(self): """Make sure that when pickling, all appropriate parameters for new are provided.""" kwargs = {} for k, v in self._vol.maps[-1].items(): if k not in ["context", "data_format", "object_info", "type_name"]: kwargs[k] = v kwargs["new_value"] = self.__new_value return ( self._context, self._vol.maps[-3]["type_name"], self._vol.maps[-2], self._data_format, ), kwargs @classmethod def _unmarshall( cls, context: interfaces.context.ContextInterface, data_format: DataFormatInfo, object_info: interfaces.objects.ObjectInformation, ) -> TUnion[int, float, bool, bytes, str]: # Don't try to lookup a 0 length data format, incase it's at an invalid offset. Length 0 means b'' data = b"" if data_format.length > 0: data = context.layers.read( object_info.layer_name, object_info.offset, data_format.length ) return convert_data_to_value(data, cls._struct_type, data_format)
[docs] class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy):
[docs] @classmethod def size(cls, template: interfaces.objects.Template) -> int: """Returns the size of the templated object.""" return template.vol.data_format.length
[docs] def write( self, value: TUnion[int, float, bool, bytes, str] ) -> interfaces.objects.ObjectInterface: """Writes the object into the layer of the context at the current offset.""" data = convert_value_to_data(value, self._struct_type, self._data_format) self._context.layers.write(self.vol.layer_name, self.vol.offset, data) return self.cast(self.vol.type_name)
# This must be int (and the _struct_type must be int) because bool cannot be inherited from: # https://mail.python.org/pipermail/python-dev/2002-March/020822.html # https://mail.python.org/pipermail/python-dev/2004-February/042537.html
[docs]class Boolean(PrimitiveObject, int): """Primitive Object that handles boolean types.""" _struct_type: ClassVar[Type] = int
[docs]class Integer(PrimitiveObject, int): """Primitive Object that handles standard numeric types."""
[docs]class Float(PrimitiveObject, float): """Primitive Object that handles double or floating point numbers.""" _struct_type: ClassVar[Type] = float
[docs]class Char(PrimitiveObject, int): """Primitive Object that handles characters.""" _struct_type: ClassVar[Type] = int
[docs]class Bytes(PrimitiveObject, bytes): """Primitive Object that handles specific series of bytes.""" _struct_type: ClassVar[Type] = bytes def __init__( self, context: interfaces.context.ContextInterface, type_name: str, object_info: interfaces.objects.ObjectInformation, length: int = 1, ) -> None: super().__init__( context=context, type_name=type_name, object_info=object_info, data_format=DataFormatInfo(length, "big", False), ) self._vol["length"] = length def __new__( cls: Type, context: interfaces.context.ContextInterface, type_name: str, object_info: interfaces.objects.ObjectInformation, length: int = 1, **kwargs, ) -> "Bytes": """Creates the appropriate class and returns it so that the native type is inherited. The only reason the kwargs is added, is so that the inheriting types can override __init__ without needing to override __new__ """ return cls._struct_type.__new__( cls, cls._unmarshall( context, data_format=DataFormatInfo(length, "big", False), object_info=object_info, ), )
[docs] class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy):
[docs] @classmethod def size(cls, template: interfaces.objects.Template) -> int: return template.vol.length
[docs]class String(PrimitiveObject, str): """Primitive Object that handles string values. Args: max_length: specifies the maximum possible length that the string could hold within memory (for multibyte characters, this will not be the maximum length of the string) """ _struct_type: ClassVar[Type] = str def __init__( self, context: interfaces.context.ContextInterface, type_name: str, object_info: interfaces.objects.ObjectInformation, max_length: int = 1, encoding: str = "utf-8", errors: str = "strict", ) -> None: super().__init__( context=context, type_name=type_name, object_info=object_info, data_format=DataFormatInfo(max_length, "big", False), ) self._vol["max_length"] = max_length self._vol["encoding"] = encoding self._vol["errors"] = errors def __new__( cls: Type, context: interfaces.context.ContextInterface, type_name: str, object_info: interfaces.objects.ObjectInformation, max_length: int = 1, encoding: str = "utf-8", errors: str = "strict", **kwargs, ) -> "String": """Creates the appropriate class and returns it so that the native type is inherited. The only reason the kwargs is added, is so that the inheriting types can override __init__ without needing to override __new__ """ params = {} if encoding: params["encoding"] = encoding if errors: params["errors"] = errors # Pass the encoding and error parameters to the string constructor to appropriately encode the string value = cls._struct_type.__new__( cls, cls._unmarshall( context, data_format=DataFormatInfo(max_length, "big", False), object_info=object_info, ), **params, ) if value.find("\x00") >= 0: value = value[: value.find("\x00")] return value
[docs] class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy):
[docs] @classmethod def size(cls, template: interfaces.objects.Template) -> int: """Returns the size of the templated object.""" return template.vol.max_length
[docs]class Pointer(Integer): """Pointer which points to another object.""" def __init__( self, context: interfaces.context.ContextInterface, type_name: str, object_info: interfaces.objects.ObjectInformation, data_format: DataFormatInfo, subtype: Optional[templates.ObjectTemplate] = None, ) -> None: super().__init__( context=context, object_info=object_info, type_name=type_name, data_format=data_format, ) self._vol["subtype"] = subtype self._cache: Dict[str, interfaces.objects.ObjectInterface] = {} @classmethod def _unmarshall( cls, context: interfaces.context.ContextInterface, data_format: DataFormatInfo, object_info: interfaces.objects.ObjectInformation, ) -> Any: """Ensure that pointer values always fall within the domain 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, endian, signed = data_format if signed: raise ValueError("Pointers cannot have signed values") mask = context.layers[object_info.native_layer_name].address_mask data = context.layers.read(object_info.layer_name, object_info.offset, length) value = int.from_bytes(data, byteorder=endian, signed=signed) return value & mask
[docs] def dereference( self, layer_name: Optional[str] = None ) -> interfaces.objects.ObjectInterface: """Dereferences the pointer. Layer_name is identifies the appropriate layer within the context that the pointer points to. If layer_name is None, it defaults to the same layer that the pointer is currently instantiated in. """ # Do our own caching because lru_cache doesn't seem to memoize correctly across multiple uses # Cache clearing should be done by a cast (we can add a specific method to reset a pointer, # but hopefully it's not necessary) if layer_name is None: layer_name = self.vol.native_layer_name if self._cache.get(layer_name, None) is None: layer_name = layer_name or self.vol.native_layer_name mask = self._context.layers[layer_name].address_mask offset = self & mask self._cache[layer_name] = self.vol.subtype( context=self._context, object_info=interfaces.objects.ObjectInformation( layer_name=layer_name, offset=offset, parent=self, size=self.vol.subtype.size, ), ) return self._cache[layer_name]
[docs] def is_readable(self, layer_name: Optional[str] = None) -> bool: """Determines whether the address of this pointer can be read from memory.""" layer_name = layer_name or self.vol.native_layer_name return self._context.layers[layer_name].is_valid(self, self.vol.subtype.size)
def __getattr__(self, attr: str) -> Any: """Convenience function to access unknown attributes by getting them from the subtype object.""" if attr in ["vol", "_vol", "_cache"]: raise AttributeError("Pointer not initialized before use") return getattr(self.dereference(), attr)
[docs] def has_member(self, member_name: str) -> bool: """Returns whether the dereferenced type has this member.""" return self._vol["subtype"].has_member(member_name)
[docs] class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy):
[docs] @classmethod def size(cls, template: interfaces.objects.Template) -> int: return Integer.VolTemplateProxy.size(template)
[docs] @classmethod def children( cls, template: interfaces.objects.Template ) -> List[interfaces.objects.Template]: """Returns the children of the template.""" if "subtype" in template.vol: return [template.vol.subtype] return []
[docs] @classmethod def replace_child( cls, template: interfaces.objects.Template, old_child: interfaces.objects.Template, new_child: interfaces.objects.Template, ) -> None: """Substitutes the old_child for the new_child.""" if "subtype" in template.vol: if template.vol.subtype == old_child: template.update_vol(subtype=new_child)
[docs] @classmethod def has_member( cls, template: interfaces.objects.Template, member_name: str ) -> bool: return template.vol["subtype"].has_member(member_name)
[docs]class BitField(interfaces.objects.ObjectInterface, int): """Object containing a field which is made up of bits rather than whole bytes.""" def __init__( self, context: interfaces.context.ContextInterface, type_name: str, object_info: interfaces.objects.ObjectInformation, base_type: interfaces.objects.Template, start_bit: int = 0, end_bit: int = 0, ) -> None: super().__init__(context, type_name, object_info) self._vol["base_type"] = base_type self._vol["start_bit"] = start_bit self._vol["end_bit"] = end_bit def __new__( cls, context: interfaces.context.ContextInterface, type_name: str, object_info: interfaces.objects.ObjectInformation, base_type: interfaces.objects.Template, start_bit: int = 0, end_bit: int = 0, **kwargs, ) -> "BitField": value = base_type(context=context, object_info=object_info) return int.__new__(cls, ((value & ((1 << end_bit) - 1)) >> start_bit)) # type: ignore
[docs] def write(self, value): raise NotImplementedError("Writing to BitFields is not yet implemented")
[docs] class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy):
[docs] @classmethod def size(cls, template: interfaces.objects.Template) -> int: return template.vol.base_type.size
[docs] @classmethod def children( cls, template: interfaces.objects.Template ) -> List[interfaces.objects.Template]: """Returns the children of the template.""" if "base_type" in template.vol: return [template.vol.base_type] return []
[docs] @classmethod def replace_child( cls, template: interfaces.objects.Template, old_child: interfaces.objects.Template, new_child: interfaces.objects.Template, ) -> None: """Substitutes the old_child for the new_child.""" if "base_type" in template.vol: if template.vol.base_type == old_child: template.update_vol(base_type=new_child)
[docs]class Enumeration(interfaces.objects.ObjectInterface, int): """Returns an object made up of choices.""" def __new__( cls, context: interfaces.context.ContextInterface, type_name: str, object_info: interfaces.objects.ObjectInformation, base_type: interfaces.objects.Template, choices: Dict[str, int], **kwargs, ) -> "Enumeration": value = base_type(context=context, object_info=object_info) return int.__new__(cls, value) # type: ignore def __init__( self, context: interfaces.context.ContextInterface, type_name: str, object_info: interfaces.objects.ObjectInformation, base_type: Integer, choices: Dict[str, int], ) -> None: super().__init__(context, type_name, object_info) self._inverse_choices = self._generate_inverse_choices(choices) self._vol["choices"] = choices self._vol["base_type"] = base_type def __eq__(self, other): """An enumeration must be equivalent to its value, even if the other value is not an enumeration""" return int(self) == other def __hash__(self): """Enumerations must be hashed as equivalent to their integer counterparts""" return super().__hash__() @classmethod def _generate_inverse_choices(cls, choices: Dict[str, int]) -> Dict[int, str]: """Generates the inverse choices for the object.""" inverse_choices: Dict[int, str] = {} for k, v in choices.items(): if v in inverse_choices: vollog.log( constants.LOGLEVEL_VVV, f"Enumeration value {v} duplicated as {k}. Keeping name {inverse_choices[v]}", ) continue inverse_choices[v] = k return inverse_choices
[docs] def lookup(self, value: int = None) -> str: """Looks up an individual value and returns the associated name. If multiple identifiers map to the same value, the first matching identifier will be returned """ if value is None: return self.lookup(self) if value in self._inverse_choices: return self._inverse_choices[value] raise ValueError("The value of the enumeration is outside the possible choices")
@property def description(self) -> str: """Returns the chosen name for the value this object contains.""" return self.lookup(self) @property def choices(self) -> Dict[str, int]: return self._vol["choices"] @property def is_valid_choice(self) -> bool: """Returns whether the value for the object is a valid choice""" return self in self.choices.values() def __getattr__(self, attr: str) -> str: """Returns the value for a specific name.""" if attr in self._vol["choices"]: return self._vol["choices"][attr] raise AttributeError( f"Unknown attribute {attr} for Enumeration {self._vol['type_name']}" )
[docs] def write(self, value: bytes): raise NotImplementedError("Writing to Enumerations is not yet implemented")
[docs] class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy): _methods = ["lookup"]
[docs] @classmethod def lookup(cls, template: interfaces.objects.Template, value: int) -> str: """Looks up an individual value and returns the associated name. If multiple identifiers map to the same value, the first matching identifier will be returned """ _inverse_choices = Enumeration._generate_inverse_choices( template.vol["choices"] ) if value in _inverse_choices: return _inverse_choices[value] raise ValueError( "The value of the enumeration is outside the possible choices" )
[docs] @classmethod def size(cls, template: interfaces.objects.Template) -> int: return template.vol["base_type"].size
[docs] @classmethod def children( cls, template: interfaces.objects.Template ) -> List[interfaces.objects.Template]: """Returns the children of the template.""" if "base_type" in template.vol: return [template.vol.base_type] return []
[docs] @classmethod def replace_child( cls, template: interfaces.objects.Template, old_child: interfaces.objects.Template, new_child: interfaces.objects.Template, ) -> None: """Substitutes the old_child for the new_child.""" if "base_type" in template.vol: if template.vol.base_type == old_child: template.update_vol(base_type=new_child)
[docs]class Array(interfaces.objects.ObjectInterface, collections.abc.Sequence): """Object which can contain a fixed number of an object type.""" def __init__( self, context: interfaces.context.ContextInterface, type_name: str, object_info: interfaces.objects.ObjectInformation, count: int = 0, subtype: templates.ObjectTemplate = None, ) -> None: super().__init__(context=context, type_name=type_name, object_info=object_info) self._vol["count"] = count self._vol["subtype"] = subtype self._vol["size"] = 0 if subtype is not None: self._vol["size"] = count * subtype.size # This overrides the little known Sequence.count(val) that returns the number of items in the list that match val # Changing the name would be confusing (since we use count of an array everywhere else), so this is more important @property def count(self) -> int: """Returns the count dynamically.""" return self.vol.count @count.setter def count(self, value: int) -> None: """Sets the count to a specific value.""" self._vol["count"] = value self._vol["size"] = value * self._vol["subtype"].size def __repr__(self) -> str: """Describes the object appropriately""" return AggregateType.__repr__(self)
[docs] class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy):
[docs] @classmethod def size(cls, template: interfaces.objects.Template) -> int: """Returns the size of the array, based on the count and the subtype.""" if "subtype" not in template.vol and "count" not in template.vol: raise ValueError( "Array ObjectTemplate must be provided a count and subtype" ) return template.vol.get("subtype", None).size * template.vol.get("count", 0)
[docs] @classmethod def children( cls, template: interfaces.objects.Template ) -> List[interfaces.objects.Template]: """Returns the children of the template.""" if "subtype" in template.vol: return [template.vol.subtype] return []
[docs] @classmethod def replace_child( cls, template: interfaces.objects.Template, old_child: interfaces.objects.Template, new_child: interfaces.objects.Template, ) -> None: """Substitutes the old_child for the new_child.""" if "subtype" in template.vol: if template.vol["subtype"] == old_child: template.update_vol(subtype=new_child)
[docs] @classmethod def relative_child_offset( cls, template: interfaces.objects.Template, child: str ) -> int: """Returns the relative offset from the head of the parent data to the child member.""" if "subtype" in template.vol and child == "subtype": return 0 raise IndexError(f"Member not present in array template: {child}")
[docs] @classmethod def child_template( cls, template: interfaces.objects.Template, child: str ) -> interfaces.objects.Template: """Returns the template of the child member.""" if "subtype" in template.vol and child == "subtype": return template.vol.subtype raise IndexError(f"Member not present in array template: {child}")
@overload def __getitem__(self, i: int) -> interfaces.objects.Template: ... @overload def __getitem__(self, s: slice) -> List[interfaces.objects.Template]: ... def __getitem__(self, i): """Returns the i-th item from the array.""" result: List[interfaces.objects.Template] = [] mask = self._context.layers[self.vol.layer_name].address_mask # We use the range function to deal with slices for us series = range(self.vol.count)[i] return_list = True if isinstance(series, int): return_list = False series = [series] for index in series: object_info = interfaces.objects.ObjectInformation( layer_name=self.vol.layer_name, offset=mask & (self.vol.offset + (self.vol.subtype.size * index)), parent=self, native_layer_name=self.vol.native_layer_name, size=self.vol.subtype.size, ) result += [self.vol.subtype(context=self._context, object_info=object_info)] if not return_list: return result[0] return result def __len__(self) -> int: """Returns the length of the array.""" return self.vol.count
[docs] def write(self, value) -> None: if not isinstance(value, collections.abc.Sequence): raise TypeError("Only Sequences can be written to arrays") self.count = len(value) for index in range(len(value)): self[index].write(value[index])
[docs]class AggregateType(interfaces.objects.ObjectInterface): """Object which can contain members that are other objects. Keep the number of methods in this class low or very specific, since each one could overload a valid member. """ def __init__( self, context: interfaces.context.ContextInterface, type_name: str, object_info: interfaces.objects.ObjectInformation, size: int, members: Dict[str, Tuple[int, interfaces.objects.Template]], ) -> None: super().__init__( context=context, type_name=type_name, object_info=object_info, size=size, members=members, ) # self._check_members(members) self._concrete_members: Dict[str, Dict] = {}
[docs] def has_member(self, member_name: str) -> bool: """Returns whether the object would contain a member called member_name.""" return member_name in self.vol.members
def __repr__(self) -> str: """Describes the object appropriately""" extras = member_name = "" if self.vol.native_layer_name != self.vol.layer_name: extras += f" (Native: {self.vol.native_layer_name})" if self.vol.member_name: member_name = f" (.{self.vol.member_name})" return f"<{self.__class__.__name__} {self.vol.type_name}{member_name}: {self.vol.layer_name} @ 0x{self.vol.offset:x} #{self.vol.size}{extras}>"
[docs] class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy):
[docs] @classmethod def size(cls, template: interfaces.objects.Template) -> int: """Method to return the size of this type.""" if template.vol.get("size", None) is None: raise ValueError("ObjectTemplate not provided with a size") return template.vol.size
[docs] @classmethod def children( cls, template: interfaces.objects.Template ) -> List[interfaces.objects.Template]: """Method to list children of a template.""" return [member for _, member in template.vol.members.values()]
[docs] @classmethod def replace_child( cls, template: interfaces.objects.Template, old_child: interfaces.objects.Template, new_child: interfaces.objects.Template, ) -> None: """Replace a child elements within the arguments handed to the template.""" for member in template.vol.members.get("members", {}): relative_offset, member_template = template.vol.members[member] if member_template == old_child: # Members will give access to the mutable members list, # but in case that ever changes, do the update correctly tmp_list = template.vol.members tmp_list[member] = (relative_offset, new_child) # If there's trouble with mutability, consider making update_vol return a clone with the changes # (there will be a few other places that will be necessary) and/or making these part of the # permanent dictionaries rather than the non-cloneable ones template.update_vol(members=tmp_list)
[docs] @classmethod def relative_child_offset( cls, template: interfaces.objects.Template, child: str ) -> int: """Returns the relative offset of a child to its parent.""" retlist = template.vol.members.get(child, None) if retlist is None: raise IndexError(f"Member not present in template: {child}") return retlist[0]
[docs] @classmethod def child_template( cls, template: interfaces.objects.Template, child: str ) -> interfaces.objects.Template: """Returns the template of a child to its parent.""" retlist = template.vol.members.get(child, None) if retlist is None: raise IndexError(f"Member not present in template: {child}") return retlist[1]
[docs] @classmethod def has_member( cls, template: interfaces.objects.Template, member_name: str ) -> bool: """Returns whether the object would contain a member called member_name.""" return member_name in template.vol.members
@classmethod def _check_members( cls, members: Dict[str, Tuple[int, interfaces.objects.Template]] ) -> None: # Members should be an iterable mapping of symbol names to tuples of (relative_offset, ObjectTemplate) # An object template is a callable that when called with a context, offset, layer_name and type_name # We duplicate this code to avoid polluting the methodspace agg_name = "AggregateType" for agg_type in AggregateTypes: if isinstance(cls, agg_type): agg_name = agg_type.__name__ assert isinstance( members, collections.abc.Mapping ), f"{agg_name} members parameter must be a mapping: {type(members)}" assert all( (isinstance(member, tuple) and len(member) == 2) for member in members.values() ), f"{agg_name} members must be a tuple of relative_offsets and templates"
[docs] def member(self, attr: str = "member") -> object: """Specifically named method for retrieving members.""" return self.__getattr__(attr)
def __getattr__(self, attr: str) -> Any: """Method for accessing members of the type.""" if attr in ["_concrete_members", "vol"]: raise AttributeError("Object has not been properly initialized") if attr in self._concrete_members: return self._concrete_members[attr] if attr.startswith("_") and not attr.startswith("__") and "__" in attr: attr = attr[attr.find("__", 1) :] # See issue #522 if attr in self.vol.members: mask = self._context.layers[self.vol.layer_name].address_mask relative_offset, template = self.vol.members[attr] if isinstance(template, templates.ReferenceTemplate): template = self._context.symbol_space.get_type(template.vol.type_name) object_info = interfaces.objects.ObjectInformation( layer_name=self.vol.layer_name, offset=mask & (self.vol.offset + relative_offset), member_name=attr, parent=self, native_layer_name=self.vol.native_layer_name, size=template.size, ) member = template(context=self._context, object_info=object_info) self._concrete_members[attr] = member return member # We duplicate this code to avoid polluting the methodspace agg_name = "AggregateType" for agg_type in AggregateTypes: if isinstance(self, agg_type): agg_name = agg_type.__name__ raise AttributeError( f"{agg_name} has no attribute: {self.vol.type_name}.{attr}" ) # Disable messing around with setattr until the consequences have been considered properly # For example pdbutil constructs objects and then sets values for them # Some don't always match the type (for example, the data read is encoded and interpreted) # # def __setattr__(self, name, value): # """Method for writing specific members of a structure""" # if name in ['_concrete_members', 'vol', '_vol'] or not self.has_member(name): # return super().__setattr__(name, value) # attr = self.__getattr__(name) # return attr.write(value) def __dir__(self) -> Iterable[str]: """Returns a complete list of members when dir is called.""" return list(super().__dir__()) + list(self.vol.members)
[docs] def write(self, value): # We duplicate this code to avoid polluting the methodspace agg_name = "AggregateType" for agg_type in AggregateTypes: if isinstance(self, agg_type): agg_name = agg_type.__name__ raise TypeError( f"{agg_name}s cannot be written to directly, individual members must be written instead" )
[docs]class StructType(AggregateType): pass
[docs]class UnionType(AggregateType): pass
[docs]class ClassType(AggregateType): pass
AggregateTypes = {StructType: "struct", UnionType: "union", ClassType: "class"}