From 15b2f6f7176ddac5b7bb2275cf04645ed05f5f99 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Sun, 4 Jan 2015 03:41:37 +0000 Subject: [PATCH] Initial introduction of ObjectInformation class. --- volatility/framework/__init__.py | 7 +- volatility/framework/interfaces/objects.py | 115 ++++---- volatility/framework/objects/__init__.py | 294 +++++++++------------ volatility/framework/objects/templates.py | 26 +- volatility/framework/symbols/__init__.py | 10 +- volatility/framework/symbols/vtypes.py | 8 +- volatility/framework/xp_sp2_x86_vtypes.py | 2 +- 7 files changed, 229 insertions(+), 233 deletions(-) diff --git a/volatility/framework/__init__.py b/volatility/framework/__init__.py index 70ebd2287..1d8d94220 100644 --- a/volatility/framework/__init__.py +++ b/volatility/framework/__init__.py @@ -38,6 +38,7 @@ def require_version(*args): from volatility.framework import interfaces, symbols, layers + class Context(interfaces.context.ContextInterface): """Maintains the context within which to construct objects @@ -86,7 +87,7 @@ class Context(interfaces.context.ContextInterface): # ## Object Factory Functions - def object(self, symbol, layer_name, offset): + def object(self, symbol, layer_name, offset, **kwargs): """Object factory, takes a context, symbol, offset and optional layername Looks up the layername in the context, finds the object template based on the symbol, @@ -96,7 +97,9 @@ class Context(interfaces.context.ContextInterface): :rtype: :py:class:`volatility.framework.interfaces.objects.ObjectInterface` """ object_template = self._symbol_space.get_structure(symbol) - return object_template(self, layer_name = layer_name, offset = offset) + return object_template(self, + interfaces.objects.ObjectInformation(layer_name = layer_name, offset = offset), + **kwargs) diff --git a/volatility/framework/interfaces/objects.py b/volatility/framework/interfaces/objects.py index b47527309..19d951d5d 100644 --- a/volatility/framework/interfaces/objects.py +++ b/volatility/framework/interfaces/objects.py @@ -4,46 +4,76 @@ Created on 6 May 2013 @author: mike """ -import copy +import collections.abc from abc import ABCMeta, abstractmethod from volatility.framework import validity from volatility.framework.interfaces import context as context_module -class TemplateInformation(validity.ValidityRoutines): - def __init__(self, structure_name, size, **additional): - self._structure_name = structure_name - self._size = size - self._additional = additional +class ReadOnlyInformation(validity.ValidityRoutines, collections.abc.Mapping): + """A read-only mapping of various values that offer attribute access as well""" + + def __init__(self, dict): + self._dict = dict + + def __getattr__(self, attr): + """Returns the item as an attribute""" + if attr in self._dict: + return self._dict[attr] + raise AttributeError("'" + self.__class__.__name__ + "' object has no attribute '" + attr + '"') + + def __getitem__(self, name): + """Returns the item requested""" + return self._dict[name] + + def __iter__(self): + """Returns an iterator of the dictionary items""" + return self._dict.__iter__() + + def __len__(self): + """Returns the length of the internal dictionary""" + return len(self._dict) -class ObjectInformation(validity.ValidityRoutines): +class ObjectInformation(ReadOnlyInformation): + """Contains information useful/pertinent only to an individual object (like an instance)""" + def __init__(self, layer_name, offset, member_name = None, parent = None): - self._layer_name = layer_name - self._offset = offset - self._member_name = member_name - self._parent = parent + self._type_check(offset, int) + if parent: + self._type_check(parent, ObjectInterface) + ReadOnlyInformation.__init__(self, {'layer_name': layer_name, + 'offset': offset, + 'member_name': member_name, + 'parent': parent}) class ObjectInterface(validity.ValidityRoutines, metaclass = ABCMeta): """ A base object required to be the ancestor of every object used in volatility """ - def __init__(self, context, layer_name, offset, structure_name, size, parent = None): + def __init__(self, context, object_info, template_info): # Since objects are likely to be instantiated often, # we're only checking that context, offset and parent # Everything else may be wrong, but that will get caught later on self._type_check(context, context_module.ContextInterface) - self._type_check(offset, int) - if parent: - self._type_check(parent, ObjectInterface) + self._type_check(template_info, ReadOnlyInformation) + self._type_check(object_info, ObjectInformation) + # Add an empty dictionary at the start to allow objects to add their own data to the volinfo object + # + # NOTE: + # This allows objects to MASSIVELY MESS with their own internal representation!!! + # Changes to offset, structure_name, etc should NEVER be done + # + self._volinfo = collections.ChainMap({}, object_info, template_info) self._context = context - self._parent = None if not parent else parent - self._offset = offset - self._layer_name = layer_name - self._structure_name = structure_name - self._size = size + + @property + def volinfo(self): + """Returns the volatility specific object information""" + # Wrap the outgoing volinfo in a read-only proxy + return ReadOnlyInformation(self._volinfo) @abstractmethod def write(self, value): @@ -52,65 +82,58 @@ class ObjectInterface(validity.ValidityRoutines, metaclass = ABCMeta): def cast(self, new_structure_name): """Returns a new object at the offset and from the layer that the current object inhabits""" object_template = self._context.symbol_space.get_structure(new_structure_name) - return object_template(context = self._context, layer_name = self._layer_name, offset = self._offset) + return object_template(context = self._context, + layer_name = self.volinfo.layer_name, + offset = self.volinfo.offset) @classmethod @abstractmethod - def template_replace_child(cls, old_child, new_child, arguments): + def template_replace_child(cls, template, old_child, new_child): """Substitutes the old_child for the new_child""" @classmethod @abstractmethod - def template_size(cls, arguments): + def template_size(cls, template): """Returns the size of the template object""" @classmethod @abstractmethod - def template_children(cls, arguments): + def template_children(cls, template): """Returns the children of the template""" @classmethod @abstractmethod - def template_relative_child_offset(cls, arguments, child): + def template_relative_child_offset(cls, template, child): """Returns the relative offset from the head of the parent data to the child member""" -class Template(object): +class Template(validity.ValidityRoutines): """Class for all Factories that take offsets, and data layers and produce objects This is effectively a class for currying object calls """ - def __init__(self, structure_name = None, **kwargs): + def __init__(self, structure_name, **kwargs): """Stores the keyword arguments for later use""" - self._kwargs = kwargs - self._structure_name = structure_name + # Allow the updating of template arguments whilst still in template form + self._volinfo = collections.ChainMap(kwargs, {'structure_name': structure_name}) @property - def structure_name(self): - """Returns the name of the particular symbol""" - return self._structure_name + def volinfo(self): + """Returns a volatility information object, much like the ObjectInterface provides""" + return ReadOnlyInformation(self._volinfo) - @property - def arguments(self): - """Returns the keyword arguments stored earlier""" - return copy.deepcopy(self._kwargs) - - def update_arguments(self, **newargs): + def update_volinfo(self, **newargs): """Updates the keyword arguments""" - self._kwargs.update(newargs) + self._volinfo.update(newargs) - def __call__(self, context, layer_name, offset, parent = None): + def __call__(self, context, object_info, **kwargs): """Constructs the object :type context: framework.interfaces.context.ContextInterface - :type layer_name: str - :type offset: int - :type parent: ObjectInterface + :type object_info: ObjectInformation :param context: - :param layer_name: - :param offset: - :param parent: + :param object_info: :return O Returns: an object adhereing to the Object interface """ diff --git a/volatility/framework/objects/__init__.py b/volatility/framework/objects/__init__.py index 783443464..dc1d47bfb 100644 --- a/volatility/framework/objects/__init__.py +++ b/volatility/framework/objects/__init__.py @@ -15,21 +15,21 @@ class Void(interfaces.objects.ObjectInterface): """Returns an object to represent void/unknown types""" @classmethod - def template_size(cls, arguments): + def template_size(cls, template): """Dummy size for Void objects""" return 0 @classmethod - def template_children(cls, arguments): + def template_children(cls, template): """Returns an empty list for Void objects since they can't have children""" return [] @classmethod - def template_replace_child(cls, old_child, new_child, arguments): + def template_replace_child(cls, template, old_child, new_child): """Dummy method that does nothing for Void objects""" @classmethod - def template_relative_child_offset(cls, arguments, child): + def template_relative_child_offset(cls, template, child): """Dummy method that does nothing for Void objects""" def write(self, value): @@ -39,132 +39,102 @@ class Void(interfaces.objects.ObjectInterface): class PrimitiveObject(interfaces.objects.ObjectInterface): """PrimitiveObject is an interface for any objects that should simulate a Python primitive""" + _struct_format = '> start_bit) & ((1 << end_bit) - 1) + object_info = object_info, + template_info = template_info) + return cls._struct_type.__new__(cls, (value >> start_bit) & ((1 << end_bit) - 1)) + + def __init__(self, context, object_info, template_info, target = None, start_bit = 0, end_bit = 0): + PrimitiveObject.__init__(self, context, object_info, template_info) + self._volinfo['target'] = target + self._volinfo['start_bit'] = start_bit + self._volinfo['end_bit'] = end_bit @classmethod - def template_children(cls, arguments): + def template_children(cls, template): """Returns the target type""" - if 'target' in arguments: - return [arguments['target']] + if 'target' in template.volinfo: + return [template.volinfo.target] return [] def write(self, value): @@ -230,7 +198,7 @@ class Enumeration(interfaces.objects.ObjectInterface): """Returns an object made up of choices""" # FIXME: Add in body for the enumeration object @classmethod - def template_children(cls, arguments): + def template_children(cls, volinfo): return [] def write(self, value): @@ -240,58 +208,53 @@ class Enumeration(interfaces.objects.ObjectInterface): class Array(interfaces.objects.ObjectInterface, collections.Sequence): """Object which can contain a fixed number of an object type""" - def __init__(self, context, layer_name, offset, structure_name, size = None, - parent = None, count = 0, target = None): - if not isinstance(target, templates.ObjectTemplate): - raise TypeError("Array target must be an ObjectTemplate") + def __init__(self, context, object_info, template_info, count = 0, target = None): + self._type_check(target, templates.ObjectTemplate) interfaces.objects.ObjectInterface.__init__(self, context = context, - layer_name = layer_name, - offset = offset, - structure_name = structure_name, - size = size, - parent = parent) - self._count = self._type_check(count, int) - self._target = target + object_info = object_info, + template_info = template_info) + self._volinfo['count'] = self._type_check(count, int) + self._volinfo['target'] = target @classmethod - def template_size(cls, arguments): + def template_size(cls, template): """Returns the size of the array, based on the count and the target""" - if 'target' not in arguments and 'count' not in arguments: + if 'target' not in template.volinfo and 'count' not in template.volinfo: raise TypeError("Array ObjectTemplate must be provided a count and target") - return arguments.get('target', None).size * arguments.get('count', 0) + return template.volinfo.get('target', None).size * template.volinfo.get('count', 0) @classmethod - def template_children(cls, arguments): + def template_children(cls, template): """Returns the children of the template""" - if 'target' in arguments: - return [arguments['target']] + if 'target' in template.volinfo: + return [template.volinfo.target] return [] @classmethod - def template_replace_child(cls, old_child, new_child, arguments): + def template_replace_child(cls, template, old_child, new_child): """Substitutes the old_child for the new_child""" - if 'target' in arguments: - if arguments['target'] == old_child: - arguments['target'] = new_child + if 'target' in template.volinfo: + if template.volinfo['target'] == old_child: + template.update_volinfo(target = new_child) @classmethod - def template_relative_child_offset(cls, arguments, child): + def template_relative_child_offset(cls, template, child): """Returns the relative offset from the head of the parent data to the child member""" - if 'target' in arguments and child == 'target': + if 'target' in template and child == 'target': return 0 raise IndexError("Member " + child + " not present in array template") def __getitem__(self, i): """Returns the i-th item from the array""" - if i >= self._count or 0 > i: + if i >= self.volinfo.count or 0 > i: raise IndexError - return self._target(context = self._context, layer_name = self._layer_name, - offset = self._offset + (self._target.size * i), parent = self) + return self.volinfo.target(context = self._context, layer_name = self.volinfo.layer_name, + offset = self.volinfo.offset + (self.volinfo.target.size * i), parent = self) def __len__(self): """Returns the length of the array""" - return self._count + return self.volinfo.count def write(self, value): raise NotImplementedError("Writing to Arrays is not yet implemented") @@ -303,53 +266,53 @@ 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, layer_name, offset, structure_name, size = None, members = None, parent = None): + def __init__(self, context, object_info, template_info): interfaces.objects.ObjectInterface.__init__(self, context = context, - layer_name = layer_name, - offset = offset, - structure_name = structure_name, - size = size, - parent = parent) - self._check_members(members) - self._members = members + object_info = object_info, + template_info = template_info) + self._check_members(template_info.members) self._concrete_members = {} @classmethod - def template_size(cls, arguments): + def template_size(cls, template): """Method to return the size of this structure""" - if arguments.get('size', None) is None: + if template.volinfo.get('size', None) is None: raise TypeError("Struct ObjectTemplate not provided with a size") - return arguments['size'] + return template.volinfo['size'] @classmethod - def template_children(cls, arguments): + def template_children(cls, template): """Method to list children of a template""" - return [member for _, member in cls._template_members(arguments).values()] + return [member for _, member in cls._template_members(template).values()] @classmethod - def template_replace_child(cls, old_child, new_child, arguments): + def template_replace_child(cls, template, old_child, new_child): """Replace a child elements within the arguments handed to the template""" - for member in arguments.get('members', {}): - relative_offset, member_template = arguments['members'][member] + for member in cls._template_members(template).get('members', {}): + relative_offset, member_template = template.volinfo.members[member] if member_template == old_child: - arguments['members'][member] = (relative_offset, new_child) + # Members will give access to the mutable members list, + # but in case that ever changes, do the update correctly + tmp_list = template.volinfo.members + tmp_list[member] = (relative_offset, new_child) + template.update_volinfo(members = tmp_list) @classmethod - def template_relative_child_offset(cls, arguments, child): + def template_relative_child_offset(cls, template, child): """Returns the relative offset of a child to its parent""" - retlist = cls._template_members(arguments).get(child, None) + retlist = cls._template_members(template).get(child, None) if retlist is None: raise IndexError("Member " + child + " not present in template") return retlist[0] @classmethod - def _template_members(cls, arguments): + def _template_members(cls, template): """Returns the dictionary of member_names to (relative_offset, member) as provided in the template arguments""" - if 'members' not in arguments: + if 'members' not in template.volinfo: raise TypeError("Members not found in template arguments") - cls._check_members(arguments.get('members', None)) - return arguments['members'] + cls._check_members(template.volinfo.members) + return template.volinfo.members.copy() @classmethod def _check_members(cls, members): @@ -368,13 +331,16 @@ class Struct(interfaces.objects.ObjectInterface): """Method for accessing members of the structure""" if attr in self._concrete_members: return self._concrete_members[attr] - elif attr in self._members: - relative_offset, member = self._members[attr] - member = member(context = self._context, layer_name = self._layer_name, - offset = self._offset + relative_offset, parent = self) + elif attr in self.volinfo.members: + relative_offset, member = self.volinfo.members[attr] + member = member(context = self._context, + object_info = interfaces.objects.ObjectInformation(layer_name = self.volinfo.layer_name, + offset = self.volinfo.offset + relative_offset, + member_name = attr, + parent = self)) self._concrete_members[attr] = member return member - raise AttributeError("'" + self._structure_name + "' Struct has no attribute '" + attr + "'") + raise AttributeError("'" + self.volinfo.structure_name + "' Struct has no attribute '" + attr + "'") def write(self, value): raise TypeError("Structs cannot be written to directly, individual members must be written instead") diff --git a/volatility/framework/objects/templates.py b/volatility/framework/objects/templates.py index fb11885e7..19c172e86 100644 --- a/volatility/framework/objects/templates.py +++ b/volatility/framework/objects/templates.py @@ -18,9 +18,11 @@ class ObjectTemplate(interfaces.objects.Template, validity.ValidityRoutines): """ def __init__(self, object_class = None, structure_name = None, **kwargs): - interfaces.objects.Template.__init__(self, structure_name = structure_name, **kwargs) + interfaces.objects.Template.__init__(self, + structure_name = structure_name, + **kwargs) self._class_check(object_class, interfaces.objects.ObjectInterface) - self.object_class = object_class + self.update_volinfo(object_class = object_class) @classmethod def template_children(cls, **kwargs): @@ -33,7 +35,7 @@ class ObjectTemplate(interfaces.objects.Template, validity.ValidityRoutines): @property def size(self): """Returns the size of the template""" - return self.object_class.template_size(self._kwargs) + return self.volinfo.object_class.template_size(self) @property def children(self): @@ -41,33 +43,33 @@ class ObjectTemplate(interfaces.objects.Template, validity.ValidityRoutines): This is used to traverse the template tree """ - return self.object_class.template_children(self._kwargs) + return self.volinfo.object_class.template_children(self) def relative_child_offset(self, child): """A function that returns the relative offset of a child from its parent offset This may throw exceptions including ChildNotFoundException and NotImplementedError """ - return self.object_class.template_relative_child_offset(self._kwargs, child) + return self.volinfo.object_class.template_relative_child_offset(self, child) def replace_child(self, old_child, new_child): """A function for replacing one child with another We pass in the kwargs directly so they can be changed """ - self.object_class.template_replace_child(old_child, new_child, self._kwargs) + self.volinfo.object_class.template_replace_child(self, old_child, new_child) - def __call__(self, context, layer_name, offset, parent = None): + def __call__(self, context, object_info, **kwargs): """Constructs the object Returns: an object adhereing to the Object interface """ # We always use the template size (as calculated by the object class) # over the one passed in by an argument - self._kwargs['size'] = self.size - self._kwargs['structure_name'] = self.structure_name - return self.object_class(context = context, layer_name = layer_name, offset = offset, parent = parent, - **self._kwargs) + return self.volinfo.object_class(context = context, + object_info = object_info, + template_info = self.volinfo, + **kwargs) class ReferenceTemplate(interfaces.objects.Template): @@ -77,5 +79,5 @@ class ReferenceTemplate(interfaces.objects.Template): """ def __call__(self, context, *args, **kwargs): - template = context.symbol_space.get_structure(self._structure_name) + template = context.symbol_space.get_structure(self.volinfo.structure_name) return template(context = context, *args, **kwargs) diff --git a/volatility/framework/symbols/__init__.py b/volatility/framework/symbols/__init__.py index 0a475061f..e3a0d251b 100644 --- a/volatility/framework/symbols/__init__.py +++ b/volatility/framework/symbols/__init__.py @@ -97,16 +97,16 @@ class SymbolSpace(collections.Mapping): if isinstance(child, objects.templates.ReferenceTemplate): # If we haven't seen it before, subresolve it and also add it # to the "symbols that still need traversing" list - if child.structure_name not in self._resolved: - traverse_list.append(child.structure_name) - self._resolved[child.structure_name] = self._weak_resolve(SymbolType.STRUCTURE, - child.structure_name) + if child.volinfo.structure_name not in self._resolved: + traverse_list.append(child.volinfo.structure_name) + self._resolved[child.volinfo.structure_name] = self._weak_resolve(SymbolType.STRUCTURE, + child.volinfo.structure_name) # Stash the replacement replacements.add((traverser, child)) elif child.children: template_traverse_list.append(child) for (parent, child) in replacements: - parent.replace_child(child, self._resolved[child.structure_name]) + parent.replace_child(child, self._resolved[child.volinfo.structure_name]) return self._resolved[structure_name] def get_constant(self, constant_name): diff --git a/volatility/framework/symbols/vtypes.py b/volatility/framework/symbols/vtypes.py index d4a3067aa..a64bb9c97 100644 --- a/volatility/framework/symbols/vtypes.py +++ b/volatility/framework/symbols/vtypes.py @@ -79,7 +79,7 @@ class VTypeSymbolTable(interfaces.symbols.SymbolTableInterface): elif structure_name == 'BitField': update = dictionary[1] update['target'] = self._vtypedict_to_template([update['native_type']]) - native_template.update_arguments(**update) # pylint: disable=W0142 + native_template.update_volinfo(**update) # pylint: disable=W0142 return native_template # Otherwise @@ -104,5 +104,7 @@ class VTypeSymbolTable(interfaces.symbols.SymbolTableInterface): member = (relative_offset, self._vtypedict_to_template(vtypedict)) members[member_name] = member object_class = self.get_structure_class(structure_name) - return objects.templates.ObjectTemplate(object_class = object_class, structure_name = structure_name, - size = size, members = members) + return objects.templates.ObjectTemplate(structure_name = structure_name, + object_class = object_class, + size = size, + members = members) diff --git a/volatility/framework/xp_sp2_x86_vtypes.py b/volatility/framework/xp_sp2_x86_vtypes.py index 69fdb5f6f..b290c17b4 100644 --- a/volatility/framework/xp_sp2_x86_vtypes.py +++ b/volatility/framework/xp_sp2_x86_vtypes.py @@ -4144,7 +4144,7 @@ ntkrnlmp_types = { '_IO_STATUS_BLOCK': [0x8, { 'Status': [0x0, ['long']], 'Pointer': [0x0, ['pointer', ['void']]], - 'Information': [0x4, ['unsigned long']], + 'ReadOnlyInformation': [0x4, ['unsigned long']], }], '_LPCP_MESSAGE': [0x30, { 'Entry': [0x0, ['_LIST_ENTRY']],