diff --git a/volatility/framework/objects/__init__.py b/volatility/framework/objects/__init__.py index ee964c709..69ae62f3f 100644 --- a/volatility/framework/objects/__init__.py +++ b/volatility/framework/objects/__init__.py @@ -15,11 +15,11 @@ class Void(interfaces.objects.ObjectInterface): class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy): @classmethod - def size(cls, template): + def size(cls, template: interfaces.objects.Template) -> int: """Dummy size for Void objects""" raise TypeError("Void types are incomplete, cannot contain data and do not have a size") - def write(self, value): + def write(self, value: typing.Any) -> None: """Dummy method that does nothing for Void objects""" raise TypeError("Cannot write data to a void, recast as another object") @@ -30,16 +30,25 @@ class Function(interfaces.objects.ObjectInterface): class PrimitiveObject(interfaces.objects.ObjectInterface): """PrimitiveObject is an interface for any objects that should simulate a Python primitive""" - _struct_type = int # type: typing.Type + _struct_type = int # type: typing.ClassVar[typing.Type] - def __init__(self, context, type_name, object_info, struct_format): + def __init__(self, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + struct_format: str) -> None: super().__init__(context = context, type_name = type_name, object_info = object_info, struct_format = struct_format) self._struct_format = struct_format - def __new__(cls, context, type_name, object_info, struct_format, **kwargs): + def __new__(cls, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + struct_format: str, + **kwargs) -> typing.Type['PrimitiveObject']: """Creates the appropriate class and returns it so that the native type is inherritted The only reason the **kwargs is added, is so that the inherriting types can override __init__ @@ -51,7 +60,11 @@ class PrimitiveObject(interfaces.objects.ObjectInterface): object_info.offset)) @classmethod - def _struct_value(cls, context, struct_format, layer_name, offset): + def _struct_value(cls, + context: interfaces.context.ContextInterface, + struct_format: str, + layer_name: str, + offset: int) -> typing.Union[int, float, bool, bytes, str]: length = struct.calcsize(struct_format) data = context.memory.read(layer_name, offset, length) (value,) = struct.unpack(struct_format, data) @@ -59,11 +72,11 @@ class PrimitiveObject(interfaces.objects.ObjectInterface): class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy): @classmethod - def size(cls, template): + def size(cls, template: interfaces.objects.Template) -> int: """Returns the size of the templated object""" return struct.calcsize(template.vol.struct_format) - def write(self, value): + def write(self, value: bytes) -> None: """Writes the object into the layer of the context at the current offset""" if isinstance(value, self._struct_type): data = struct.pack(self.vol.struct_format, value) @@ -79,26 +92,35 @@ class Integer(PrimitiveObject, int): class Float(PrimitiveObject, float): """Primitive Object that handles double or floating point numbers""" - _struct_type = float + _struct_type = float # type: typing.ClassVar[typing.Type] class Char(PrimitiveObject, bytes): """Primitive Object that handles characters""" - _struct_type = bytes + _struct_type = bytes # type: typing.ClassVar[typing.Type] class Bytes(PrimitiveObject, bytes): """Primitive Object that handles specific series of bytes""" - _struct_type = bytes + _struct_type = bytes # type: typing.ClassVar[typing.Type] - def __init__(self, context, type_name, object_info, length = 1): + 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, struct_format = str(length) + "s") self._vol['length'] = length - def __new__(cls, context, type_name, object_info, length = 1, **kwargs): + def __new__(cls, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + length: int = 1, + **kwargs) -> typing.Type['Bytes']: """Creates the appropriate class and returns it so that the native type is inherritted The only reason the **kwargs is added, is so that the inherriting types can override __init__ @@ -118,9 +140,15 @@ class String(PrimitiveObject, str): :type max_length: int """ - _struct_type = str + _struct_type = str # type: typing.ClassVar[typing.Type] - def __init__(self, context, type_name, object_info, max_length = 1, encoding = "utf-8", errors = "strict"): + 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, @@ -129,7 +157,14 @@ class String(PrimitiveObject, str): self._vol['encoding'] = encoding self._vol['errors'] = errors - def __new__(cls, context, type_name, object_info, max_length = 1, encoding = "utf-8", errors = "strict", **kwargs): + def __new__(cls, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + max_length: int = 1, + encoding: str = "utf-8", + errors: str = "strict", + **kwargs) -> typing.Type['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 inherriting types can override __init__ @@ -140,7 +175,7 @@ class String(PrimitiveObject, str): 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, + value = cls._struct_type.__new__(cls, # type: ignore cls._struct_value(context, struct_format = str(max_length) + "s", layer_name = object_info.layer_name, @@ -154,7 +189,12 @@ class String(PrimitiveObject, str): class Pointer(Integer): """Pointer which points to another object""" - def __init__(self, context, type_name, object_info, struct_format, subtype = None): + def __init__(self, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + struct_format: str, + subtype: typing.Optional[templates.ObjectTemplate] = None) -> None: self._check_type(subtype, templates.ObjectTemplate) super().__init__(context = context, object_info = object_info, @@ -163,7 +203,11 @@ class Pointer(Integer): self._vol['subtype'] = subtype @classmethod - def _struct_value(cls, context, struct_format, layer_name, offset): + def _struct_value(cls, + context: interfaces.context.ContextInterface, + struct_format: str, + layer_name: str, + offset: int) -> 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" @@ -175,7 +219,7 @@ class Pointer(Integer): (value,) = struct.unpack(struct_format, data) return value & mask - def dereference(self, layer_name = None): + def dereference(self, layer_name: typing.Optional[str] = None) -> interfaces.objects.ObjectInterface: """Dereferences the pointer Layer_name is identifies the appropriate layer within the context that the pointer points to. @@ -191,24 +235,27 @@ class Pointer(Integer): offset = offset, parent = self)) - def __getattr__(self, attr): + def __getattr__(self, attr: str) -> typing.Any: """Convenience function to access unknown attributes by getting them from the subtype object""" return getattr(self.dereference(), attr) class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy): @classmethod - def size(cls, template): + def size(cls, template: interfaces.objects.Template) -> int: return Integer.VolTemplateProxy.size(template) @classmethod - def children(cls, template): + def children(cls, template: interfaces.objects.Template) -> typing.List[interfaces.objects.Template]: """Returns the children of the template""" if 'subtype' in template.vol: return [template.vol.subtype] return [] @classmethod - def replace_child(cls, template, old_child, new_child): + 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: @@ -218,36 +265,51 @@ class Pointer(Integer): class BitField(interfaces.objects.ObjectInterface, int): """Object containing a field which is made up of bits rather than whole bytes""" - def __new__(cls, context, type_name, object_info, base_type = None, start_bit = 0, end_bit = 0, - **kwargs): - cls._check_class(base_type.vol.object_class, Integer) - value = base_type(context = context, - object_info = object_info) - return int.__new__(cls, (value >> start_bit) & ((1 << end_bit) - 1)) - - def __init__(self, context, type_name, object_info, base_type = None, start_bit = 0, end_bit = 0): + def __init__(self, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + base_type: typing.Type = int, + 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: typing.Type = int, + start_bit: int = 0, + end_bit: int = 0, + **kwargs) -> typing.Type: + cls._check_class(base_type.vol.object_class, Integer) + value = base_type(context = context, + object_info = object_info) + return int.__new__(cls, (value >> start_bit) & ((1 << end_bit) - 1)) # type: ignore + def write(self, value): raise NotImplementedError("Writing to BitFields is not yet implemented") class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy): @classmethod - def size(cls, template): + def size(cls, template: interfaces.objects.Template) -> int: return Integer.VolTemplateProxy.size(template) @classmethod - def children(cls, template): + def children(cls, template: interfaces.objects.Template) -> typing.List[interfaces.objects.Template]: """Returns the children of the template""" if 'base_type' in template.vol: return [template.vol.base_type] return [] @classmethod - def replace_child(cls, template, old_child, new_child): + 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: @@ -257,16 +319,27 @@ class BitField(interfaces.objects.ObjectInterface, int): class Enumeration(interfaces.objects.ObjectInterface, int): """Returns an object made up of choices""" - def __new__(cls, context, type_name, object_info, base_type = None, choices = None, **kwargs): + def __new__(cls, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + base_type: interfaces.objects.Template = None, + choices: typing.Dict[str, int] = None, + **kwargs) -> typing.Type: cls._check_class(base_type.vol.object_class, Integer) value = base_type(context = context, object_info = object_info) - return int.__new__(cls, value) + return int.__new__(cls, value) # type: ignore - def __init__(self, context, type_name, object_info, base_type = None, choices = None): + def __init__(self, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + base_type: typing.Optional[Integer] = None, + choices: typing.Optional[typing.Dict[str, int]] = None) -> None: super().__init__(context, type_name, object_info) - self._inverse_choices = {} + self._inverse_choices = {} # type: typing.Dict[int, str] for k, v in self._check_type(choices, dict).items(): self._check_type(k, str) self._check_type(v, int) @@ -280,44 +353,47 @@ class Enumeration(interfaces.objects.ObjectInterface, int): self._vol['base_type'] = base_type - def lookup(self, value): + def lookup(self, value: int) -> str: """Looks up an individual value and returns the associated name""" 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): + def description(self) -> str: """Returns the chosen name for the value this object contains""" return self.lookup(self) @property - def choices(self): + def choices(self) -> typing.Dict[str, int]: return self._vol['choices'] - def __getattr__(self, attr): + 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("Unknown attribute {} for Enumeration {}".format(attr, self._vol['type_name'])) - def write(self, value): + def write(self, value: bytes): raise NotImplementedError("Writing to Enumerations is not yet implemented") class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy): @classmethod - def size(cls, template): + def size(cls, template: interfaces.objects.Template) -> int: return template._vol['base_type'].size @classmethod - def children(cls, template): + def children(cls, template: interfaces.objects.Template) -> typing.List[interfaces.objects.Template]: """Returns the children of the template""" if 'base_type' in template.vol: return [template.vol.base_type] return [] @classmethod - def replace_child(cls, template, old_child, new_child): + 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: @@ -327,7 +403,12 @@ class Enumeration(interfaces.objects.ObjectInterface, int): class Array(interfaces.objects.ObjectInterface, abc.Sequence): """Object which can contain a fixed number of an object type""" - def __init__(self, context, type_name, object_info, count = 0, subtype = None): + def __init__(self, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + count: int = 0, + subtype: templates.ObjectTemplate = None) -> None: self._check_type(subtype, templates.ObjectTemplate) super().__init__(context = context, type_name = type_name, @@ -338,47 +419,53 @@ class Array(interfaces.objects.ObjectInterface, abc.Sequence): # 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): + def count(self) -> int: """Returns the count dynamically""" return self.vol.count @count.setter - def count(self, value): + def count(self, value: int) -> None: """Sets the count to a specific value""" self._vol['count'] = self._check_type(value, int) class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy): @classmethod - def size(cls, template): + 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 TypeError("Array ObjectTemplate must be provided a count and subtype") return template.vol.get('subtype', None).size * template.vol.get('count', 0) @classmethod - def children(cls, template): + def children(cls, template: interfaces.objects.Template) -> typing.List[interfaces.objects.Template]: """Returns the children of the template""" if 'subtype' in template.vol: return [template.vol.subtype] return [] @classmethod - def replace_child(cls, template, old_child, new_child): + 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) @classmethod - def relative_child_offset(cls, template, child): + def relative_child_offset(cls, + template: interfaces.objects.Template, + child: interfaces.objects.Template) -> int: """Returns the relative offset from the head of the parent data to the child member""" if 'subtype' in template and child == 'subtype': return 0 raise IndexError("Member not present in array template: {}".format(child)) - def __getitem__(self, i): + def __getitem__(self, i: typing.Union[int, slice]) \ + -> typing.Union[typing.List[interfaces.objects.Template], interfaces.objects.Template]: """Returns the i-th item from the array""" - result = [] + result = [] # type: typing.List[interfaces.objects.Template] mask = self._context.memory[self.vol.layer_name].address_mask # We use the range function to deal with slices for us series = range(self.vol.count)[i] @@ -395,11 +482,11 @@ class Array(interfaces.objects.ObjectInterface, abc.Sequence): return result[0] return result - def __len__(self): + def __len__(self) -> int: """Returns the length of the array""" return self.vol.count - def write(self, value): + def write(self, value) -> None: raise NotImplementedError("Writing to Arrays is not yet implemented") @@ -409,30 +496,38 @@ class Struct(interfaces.objects.ObjectInterface): Keep the number of methods in this class low or very specific, since each one could overload a valid member. """ - def __init__(self, context, type_name, object_info, size, members): + def __init__(self, + context: interfaces.context.ContextInterface, + type_name: str, + object_info: interfaces.objects.ObjectInformation, + size: int, + members: typing.Dict[str, typing.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 = {} + self._concrete_members = {} # type: typing.Dict[str, typing.Dict] class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy): @classmethod - def size(cls, template): + 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 TypeError("Struct ObjectTemplate not provided with a size") return template.vol.size @classmethod - def children(cls, template): + def children(cls, template: interfaces.objects.Template) -> typing.List[interfaces.objects.Template]: """Method to list children of a template""" return [member for _, member in template.vol.members.values()] @classmethod - def replace_child(cls, template, old_child, new_child): + 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] @@ -447,7 +542,9 @@ class Struct(interfaces.objects.ObjectInterface): template.update_vol(members = tmp_list) @classmethod - def relative_child_offset(cls, template, child): + def relative_child_offset(cls, + template: interfaces.objects.Template, + child: interfaces.objects.Template) -> int: """Returns the relative offset of a child to its parent""" retlist = template.vol.members.get(child, None) if retlist is None: @@ -455,7 +552,8 @@ class Struct(interfaces.objects.ObjectInterface): return retlist[0] @classmethod - def _check_members(cls, members): + def _check_members(cls, + members: typing.Dict[str, typing.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 if not isinstance(members, abc.Mapping): @@ -463,11 +561,11 @@ class Struct(interfaces.objects.ObjectInterface): if not all([(isinstance(member, tuple) and len(member) == 2) for member in members.values()]): raise TypeError("Struct members must be a tuple of relative_offsets and templates") - def member(self, attr = 'member'): + def member(self, attr: str = 'member') -> object: """Specifically named method for retrieving members.""" return self.__getattr__(attr) - def __getattribute__(self, attr): + def __getattribute__(self, attr: str) -> typing.Any: """Make sure that class overrides all start with helper_""" if attr == '__dict__' or attr == '__class__' or attr in self.__dict__: return object.__getattribute__(self, attr) @@ -484,7 +582,7 @@ class Struct(interfaces.objects.ObjectInterface): # Change this to an attribute error if we want to prohibit rather than deprecate member collisisons return object.__getattribute__(self, attr) - def __getattr__(self, attr): + def __getattr__(self, attr: str) -> typing.Any: """Method for accessing members of the type""" if attr in self._concrete_members: return self._concrete_members[attr] diff --git a/volatility/framework/objects/templates.py b/volatility/framework/objects/templates.py index 34bf1ff4a..a131a489c 100644 --- a/volatility/framework/objects/templates.py +++ b/volatility/framework/objects/templates.py @@ -17,38 +17,45 @@ class ObjectTemplate(interfaces.objects.Template, validity.ValidityRoutines): * etc """ - def __init__(self, object_class = None, type_name = None, **arguments): + def __init__(self, + object_class: typing.Optional[typing.Type[interfaces.objects.ObjectInterface]] = None, + type_name: str = None, + **arguments) -> None: super().__init__(type_name = type_name, **arguments) self._check_class(object_class, interfaces.objects.ObjectInterface) self._arguments['object_class'] = object_class @property - def size(self): + def size(self) -> int: """Returns the children of the templated object (see :class:`~volatility.framework.interfaces.objects.ObjectInterface.VolTemplateProxy`)""" return self.vol.object_class.VolTemplateProxy.size(self) @property - def children(self): + def children(self) -> typing.List[interfaces.objects.Template]: """Returns the children of the templated object (see :class:`~volatility.framework.interfaces.objects.ObjectInterface.VolTemplateProxy`) """ return self.vol.object_class.VolTemplateProxy.children(self) - def relative_child_offset(self, child): + def relative_child_offset(self, child: interfaces.objects.Template) -> int: """Returns the relative offset of a child of the templated object (see :class:`~volatility.framework.interfaces.objects.ObjectInterface.VolTemplateProxy`) """ return self.vol.object_class.VolTemplateProxy.relative_child_offset(self, child) - def replace_child(self, old_child, new_child): + def replace_child(self, + old_child: interfaces.objects.Template, + new_child: interfaces.objects.Template) -> None: """Replaces `old_child` for `new_child` in the templated object's child list (see :class:`~volatility.framework.interfaces.objects.ObjectInterface.VolTemplateProxy`) """ return self.vol.object_class.VolTemplateProxy.replace_child(self, old_child, new_child) - def __call__(self, context, object_info): + def __call__(self, + context: interfaces.context.ContextInterface, + object_info: interfaces.objects.ObjectInformation) -> interfaces.objects.ObjectInterface: """Constructs the object Returns: an object adhereing to the :class:`~volatility.framework.interfaces.objects.ObjectInterface` """ - arguments = {} + arguments = {} # type: typing.Dict[str, typing.Any] arguments.update(self.vol) del arguments['object_class'] return self.vol.object_class(context = context, @@ -64,7 +71,7 @@ class ReferenceTemplate(interfaces.objects.Template): """ @property - def children(self): + def children(self) -> typing.List[interfaces.objects.Template]: return [] def _unresolved(self, *args, **kwargs) -> typing.Any: @@ -78,6 +85,8 @@ class ReferenceTemplate(interfaces.objects.Template): replace_child = _unresolved # type: typing.ClassVar[typing.Any] relative_child_offset = _unresolved # type: typing.ClassVar[typing.Any] - def __call__(self, context, object_info): + def __call__(self, + context: interfaces.context.ContextInterface, + object_info: interfaces.objects.ObjectInformation): template = context.symbol_space.get_type(self.vol.type_name) return template(context = context, object_info = object_info) diff --git a/volatility/framework/objects/utility.py b/volatility/framework/objects/utility.py index 0e2317b22..5c20b4e93 100644 --- a/volatility/framework/objects/utility.py +++ b/volatility/framework/objects/utility.py @@ -1,8 +1,12 @@ -from volatility.framework import objects +import typing + +from volatility.framework import objects, interfaces from volatility.framework.objects import templates -def array_to_string(array, count = None, errors = 'replace'): +def array_to_string(array: objects.Array, + count: typing.Optional[int] = None, + errors: str = 'replace') -> interfaces.objects.ObjectInterface: """Takes a volatility Array of characters and returns a string""" # TODO: Consider checking the Array's target is a native char if count is None: @@ -12,7 +16,9 @@ def array_to_string(array, count = None, errors = 'replace'): return array.cast("string", max_length = count, errors = errors) -def pointer_to_string(pointer, count, errors = 'replace'): +def pointer_to_string(pointer: objects.Pointer, + count: int, + errors: str = 'replace'): """Takes a volatility Pointer to characters and returns a string""" if not isinstance(pointer, objects.Pointer): raise TypeError("pointer_to_string takes a Pointer") @@ -22,7 +28,10 @@ def pointer_to_string(pointer, count, errors = 'replace'): return char.cast("string", max_length = count, errors = errors) -def array_of_pointers(array, count, subtype = None, context = None): +def array_of_pointers(array: objects.Array, + count: int, + subtype: templates.ObjectTemplate = None, + context: interfaces.context.ContextInterface = None) -> interfaces.objects.ObjectInterface: """Takes an object, and recasts it as an array of pointers to subtype""" if isinstance(subtype, str) and context is not None: subtype = context.symbol_space.get_type(subtype)