From c88044db5b1e9cb2f0bbce91fa7620d19074306c Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Wed, 4 Sep 2019 00:34:55 +0100 Subject: [PATCH] Update more API documentation. --- volatility/framework/contexts/__init__.py | 13 ++- .../framework/interfaces/configuration.py | 101 +++++++++++++++--- volatility/framework/interfaces/context.py | 65 +++++++++-- volatility/framework/interfaces/layers.py | 17 ++- volatility/framework/interfaces/objects.py | 24 ++++- volatility/framework/interfaces/plugins.py | 20 +++- volatility/framework/interfaces/renderers.py | 6 ++ volatility/framework/interfaces/symbols.py | 21 ++++ 8 files changed, 237 insertions(+), 30 deletions(-) diff --git a/volatility/framework/contexts/__init__.py b/volatility/framework/contexts/__init__.py index da38e99c0..9eb9ae78a 100644 --- a/volatility/framework/contexts/__init__.py +++ b/volatility/framework/contexts/__init__.py @@ -88,7 +88,7 @@ class Context(interfaces.context.ContextInterface): object_type: The name (or template) of the symbol type on which to construct the object. If this is a name, it should contain an explicit table name. layer_name: The name of the layer on which to construct the object offset: The offset within the layer at which the data used to create the object lives - + native_layer_name: The name of the layer the object references (for pointers) if different to layer_name Returns: A fully constructed object @@ -116,7 +116,16 @@ class Context(interfaces.context.ContextInterface): offset: int, native_layer_name: Optional[str] = None, size: Optional[int] = None) -> interfaces.context.ModuleInterface: - """Creates a module object""" + """ + Constructs a new os-independent module + + Args: + module_name: The name of the module + layer_name: The layer within the context in which the module exists + offset: The offset at which the module exists in the layer + native_layer_name: The default native layer for objects constructed by the module + size: The size, in bytes, that the module occupys from offset location within the layer named layer_name + """ if size: return SizedModule( self, diff --git a/volatility/framework/interfaces/configuration.py b/volatility/framework/interfaces/configuration.py index fa6875a41..f61f56ef0 100644 --- a/volatility/framework/interfaces/configuration.py +++ b/volatility/framework/interfaces/configuration.py @@ -60,7 +60,13 @@ class HierarchicalDict(collections.abc.Mapping): """ - def __init__(self, initial_dict: Dict = None, separator: str = CONFIG_SEPARATOR) -> None: + def __init__(self, initial_dict: Dict[str, 'SimpleTypeRequirement'] = None, + separator: str = CONFIG_SEPARATOR) -> None: + """ + Args: + initial_dict: A dictionary to populate the HierachicalDict with initially + separator: A custom hierarchy separator (defaults to CONFIG_SEPARATOR) + """ if not (isinstance(separator, str) and len(separator) == 1): raise TypeError("Separator must be a one character string: {}".format(separator)) self._separator = separator @@ -107,7 +113,11 @@ class HierarchicalDict(collections.abc.Mapping): return self.generator() def generator(self) -> Generator[str, None, None]: - """A generator for the data in this level and lower levels of this mapping""" + """A generator for the data in this level and lower levels of this mapping + + Returns: + Returns each item in the top level data, and then all subkeys in a depth first order + """ for key in self._data: yield key for subdict_key in self._subdict: @@ -201,7 +211,11 @@ class HierarchicalDict(collections.abc.Mapping): Higher layers are not prefixed with the location of earlier layers, so branching a hierarchy containing `a.b.c.d` on `a.b` would return a hierarchy containing `c.d`, not `a.b.c.d`. - @param key: The location within the hierarchy to return higher layers. + Args: + key: The location within the hierarchy to return higher layers. + + Returns: + The HierarchicalDict underneath the specified key (not just the data at that key location in the tree) """ try: if self.separator in key: @@ -222,27 +236,32 @@ class HierarchicalDict(collections.abc.Mapping): raise TypeError("Splice requires a string key and HierarchicalDict value") self._setitem(key, value, False) - def merge(self, key: str, value: 'HierarchicalDict', overwrite: bool = False): + def merge(self, key: str, value: 'HierarchicalDict', overwrite: bool = False) -> None: """Acts similarly to splice, but maintains previous values If overwrite is true, then entries in the new value are used over those that exist within key already - @param key: The location within the hierarchy at which to merge the `value` - @type key: str - @param value: HierarchicalDict to be merged under the key node - @type value: HierarchicalDict + Args: + key: The location within the hierarchy at which to merge the `value` + value: HierarchicalDict to be merged under the key node + overwrite: A boolean defining whether the value will be overwritten if it already exists + """ if not isinstance(key, str) or not isinstance(value, HierarchicalDict): raise TypeError("Splice requires a string key and HierarchicalDict value") for item in dict(value): - if self.get(key + self._separator + item, None): + if self.get(key + self._separator + item, None) is not None: if overwrite: self[key + self._separator + item] = value[item] else: self[key + self._separator + item] = value[item] def clone(self) -> 'HierarchicalDict': - """Duplicates the configuration, allowing changes without affecting the original""" + """Duplicates the configuration, allowing changes without affecting the original + + Returns: + A duplicate HierarchicalDict of this object + """ return copy.deepcopy(self) def __str__(self) -> str: @@ -267,6 +286,14 @@ class RequirementInterface(metaclass = ABCMeta): description: str = None, default: Optional[ConfigSimpleType] = None, optional: bool = False) -> None: + """ + + Args: + name: The name of the requirement + description: A short textual description of the requirement + default: The default value for the requirement is none is provided + optional: Whether the requirement must be satisfied or not + """ super().__init__() if CONFIG_SEPARATOR in name: raise ValueError("Name cannot contain the config-hierarchy divider ({})".format(CONFIG_SEPARATOR)) @@ -281,7 +308,8 @@ class RequirementInterface(metaclass = ABCMeta): @property def name(self) -> str: - """The name of the Requirement. Names cannot contain "." since this is used within the configuration hierarchy.""" + """The name of the Requirement. Names cannot contain CONFIG_SEPARATOR ('.' by default) since this + is used within the configuration hierarchy.""" return self._name @property @@ -306,7 +334,13 @@ class RequirementInterface(metaclass = ABCMeta): def config_value(self, context: ContextInterface, config_path: str, default: ConfigSimpleType = None) -> ConfigSimpleType: - """Returns the value for this Requirement from its config path""" + """Returns the value for this Requirement from its config path + + Args: + context: the configuration store to find the value for this requirement + config_path: the configuration path of the instance of the requirement to be recovered + default: a default value to provide if the requirement's configuration value is not found + """ return context.config.get(config_path, default) # Child operations @@ -316,15 +350,32 @@ class RequirementInterface(metaclass = ABCMeta): return self._requirements.copy() def add_requirement(self, requirement: 'RequirementInterface') -> None: - """Adds a child to the list of requirements""" + """Adds a child to the list of requirements + + Args: + requirement: The requirement to add as a child-requirement + + """ self._requirements[requirement.name] = requirement def remove_requirement(self, requirement: 'RequirementInterface') -> None: - """Removes a child from the list of requirements""" + """Removes a child from the list of requirements + + Args: + requirement: The requirement to remove as a child-requirement + """ del self._requirements[requirement.name] def unsatisfied_children(self, context: ContextInterface, config_path: str) -> Dict[str, 'RequirementInterface']: - """Method that will validate all child requirements""" + """Method that will validate all child requirements + + Args: + context: the context containing the configuration data for this requirement + config_path: the configuration path of this instance of the requirement + + Returns: + A dictionary of full configuration paths for each unsatisfied child-requirement + """ result = {} for requirement in self.requirements.values(): if not requirement.optional: @@ -338,6 +389,13 @@ class RequirementInterface(metaclass = ABCMeta): """Method to validate the value stored at config_path for the configuration object against a context Returns a list containing its own name (or multiple unsatisfied requirement names) when invalid + + Args: + context: The context object containing the configuration for this requirement + config_path: The configuration path for this requirement to test satisfaction + + Returns: + A dictionary of configuration-paths to requirements that could not be satisfied """ @@ -377,6 +435,7 @@ class ClassRequirement(RequirementInterface): @property def cls(self) -> Type: + """Contains the actual chosen class based on the configuration value's class name""" return self._cls def unsatisfied(self, context: ContextInterface, config_path: str) -> Dict[str, RequirementInterface]: @@ -417,11 +476,20 @@ class ConstructableRequirementInterface(RequirementInterface): @abstractmethod def construct(self, context: ContextInterface, config_path: str) -> None: - """Method for constructing within the context any required elements from subrequirements""" + """Method for constructing within the context any required elements from subrequirements + + Args: + context: The context object containing the configuration data for the constructable + config_path: The configuration path for the specific instance of this constructable + """ def _validate_class(self, context: ContextInterface, config_path: str) -> None: """Method to check if the class Requirement is valid and if so populate the other requirements (but no need to validate, since we're invalid already) + + Args: + context: The context object containing the configuration data for the constructable + config_path: The configuration path for the specific instance of this constructable """ class_req = self.requirements['class'] subreq_config_path = path_join(config_path, self.name) @@ -493,6 +561,7 @@ class ConfigurableInterface(metaclass = ABCMeta): @config_path.setter def config_path(self, value: str) -> None: + """The configuration path on which this configurable lives""" self._config_path = value self._config_cache = None diff --git a/volatility/framework/interfaces/context.py b/volatility/framework/interfaces/context.py index 7c0fad321..9c5a97383 100644 --- a/volatility/framework/interfaces/context.py +++ b/volatility/framework/interfaces/context.py @@ -11,10 +11,10 @@ import copy from abc import ABCMeta, abstractmethod from typing import Optional, Union -from volatility.framework import interfaces, constants +from volatility.framework import interfaces -class ContextInterface(object, metaclass = ABCMeta): +class ContextInterface(metaclass = ABCMeta): """All context-like objects must adhere to the following interface. This interface is present to avoid import dependency cycles. @@ -68,7 +68,14 @@ class ContextInterface(object, metaclass = ABCMeta): Looks up the layer_name in the context, finds the object template based on the symbol, and constructs an object using the object template on the layer at the offset. - Returns a fully constructed object + Args: + object_type: Either a string name of the type, or a Template of the type to be constructed + layer_name: The name of the layer on which to construct the object + offset: The address within the layer at which to construct the object + native_layer_name: The layer this object references (should it be a pointer or similar) + + Returns: + A fully constructed object """ def clone(self) -> 'ContextInterface': @@ -84,7 +91,22 @@ class ContextInterface(object, metaclass = ABCMeta): offset: int, native_layer_name: Optional[str] = None, size: Optional[int] = None) -> 'ModuleInterface': - """Create a module object """ + """Create a module object + + A module object is associated with a symbol table, and acts like a context, but offsets locations by a known value + and looks up symbols, by default within the associated symbol table. It can also be sized should that information + be available. + + Args: + module_name: The name of the module + layer_name: The layer the module is associated with (which layer the module lives within) + offset: The initial/base offset of the module (used as the offset for relative symbols) + native_layer_name: The default native_layer_name to use when the module constructs objects + size: The size, in bytes, that the module occupys from offset location within the layer named layer_name + + Returns: + A module object + """ class ModuleInterface(metaclass = ABCMeta): @@ -100,6 +122,17 @@ class ModuleInterface(metaclass = ABCMeta): offset: int, symbol_table_name: Optional[str] = None, native_layer_name: Optional[str] = None) -> None: + """ + Constructs a new os-independent module + + Args: + context: The context within which this module will exist + module_name: The name of the module + layer_name: The layer within the context in which the module exists + offset: The offset at which the module exists in the layer + symbol_table_name: The name of an associated symbol table + native_layer_name: The default native layer for objects constructed by the module + """ self._context = context self._module_name = module_name self._layer_name = layer_name @@ -112,6 +145,7 @@ class ModuleInterface(metaclass = ABCMeta): @property def name(self) -> str: + """The name of the constructed module""" return self._module_name @property @@ -136,7 +170,17 @@ class ModuleInterface(metaclass = ABCMeta): native_layer_name: Optional[str] = None, absolute: bool = False, **kwargs) -> 'interfaces.objects.ObjectInterface': - """Returns an object created using the symbol_table_name and layer_name of the Module""" + """Returns an object created using the symbol_table_name and layer_name of the Module + + Args: + object_type: The name of object type to construct (using the module's symbol_table) + offset: the offset (unless absolute is set) from the start of the module + native_layer_name: The native layer for objects that reference a different layer (if not the default provided during module construction) + absolute: A boolean specifying whether the offset is absolute within the layer, or relative to the start of the module + + Returns: + The constructed object + """ @abstractmethod def object_from_symbol(self, @@ -144,7 +188,16 @@ class ModuleInterface(metaclass = ABCMeta): native_layer_name: Optional[str] = None, absolute: bool = False, **kwargs) -> 'interfaces.objects.ObjectInterface': - """Returns an object created usnig the symbol_table_name and layer_name of the Module""" + """Returns an object created using the symbol_table_name and layer_name of the Module + + Args: + symbol_name: The name of a symbol (that must be present in the module's symbol table). The symbol's associated type will be used to construct an object at the symbol's offset. + native_layer_name: The native layer for objects that reference a different layer (if not the default provided during module construction) + absolute: A boolean specifying whether the offset is absolute within the layer, or relative to the start of the module + + Returns: + The constructed object + """ def get_type(self, name: str) -> 'interfaces.objects.Template': """Returns a type from the module""" diff --git a/volatility/framework/interfaces/layers.py b/volatility/framework/interfaces/layers.py index 6df5f6db6..b6e943cca 100644 --- a/volatility/framework/interfaces/layers.py +++ b/volatility/framework/interfaces/layers.py @@ -134,7 +134,15 @@ class DataLayerInterface(interfaces.configuration.ConfigurableInterface, metacla @abstractmethod def is_valid(self, offset: int, length: int = 1) -> bool: - """Returns a boolean based on whether the entire chunk of data (from offset to length) is valid or not""" + """Returns a boolean based on whether the entire chunk of data (from offset to length) is valid or not + + Args: + offset: The address to start determining whether bytes are readable/valid + length: The number of bytes from offset of which to test the validity + + Returns: + Whether the bytes are valid and accessible + """ @abstractmethod def read(self, offset: int, length: int, pad: bool = False) -> bytes: @@ -161,7 +169,7 @@ class DataLayerInterface(interfaces.configuration.ConfigurableInterface, metacla """ def destroy(self) -> None: - """Allows DataLayers to close any open handles, etc. + """Causes a DataLayer to close any open handles, etc. Systems that make use of Data Layers should call destroy when they are done with them. This will close all handles, and make the object unreadable @@ -175,7 +183,10 @@ class DataLayerInterface(interfaces.configuration.ConfigurableInterface, metacla @property def dependencies(self) -> List[str]: - """DataLayers must never define on other layers""" + """A list of other layer names required by this layer. + + Note: + DataLayers must never define other layers""" return [] # ## General scanning methods diff --git a/volatility/framework/interfaces/objects.py b/volatility/framework/interfaces/objects.py index 6e64f8ae6..b83ee7503 100644 --- a/volatility/framework/interfaces/objects.py +++ b/volatility/framework/interfaces/objects.py @@ -62,6 +62,15 @@ class ObjectInformation(ReadOnlyMapping): member_name: Optional[str] = None, parent: Optional['ObjectInterface'] = None, native_layer_name: Optional[str] = None): + """Constructs a container for basic information about an object + + Args: + layer_name: Layer from which the data for the object will be read + offset: Offset within the layer at which the data for the object will be read + member_name: If the object was accessed as a member of a parent object, this was the name used to access it + parent: If the object was accessed as a member of a parent object, this is the parent object + native_layer_name: If this object references other objects (such as a pointer), what layer those objects live in + """ super().__init__({ 'layer_name': layer_name, 'offset': offset, @@ -76,6 +85,13 @@ class ObjectInterface(metaclass = ABCMeta): def __init__(self, context: 'interfaces_context.ContextInterface', type_name: str, object_info: 'ObjectInformation', **kwargs) -> None: + """Constructs an Object adhereing to the ObjectInterface + + Args: + context: The context associated with the object + type_name: The name of the type structure for the object + object_info: Basic information relevant to the object (layer, offset, member_name, parent, etc) + """ # Since objects are likely to be instantiated often, # we're reliant on type_checking to ensure correctness of context, offset and parent # Everything else may be wrong, but that will get caught later on @@ -147,7 +163,11 @@ class ObjectInterface(metaclass = ABCMeta): return object_template(context = self._context, object_info = object_info) def has_member(self, member_name: str) -> bool: - """Returns whether the object would contain a member called member_name""" + """Returns whether the object would contain a member called member_name + + Args: + member_name: Name to test whether a member exists within the type structure + """ return False class VolTemplateProxy(metaclass = abc.ABCMeta): @@ -213,7 +233,7 @@ class Template: """ def __init__(self, type_name: str, **arguments) -> None: - """Stores the keyword arguments for later use""" + """Stores the keyword arguments for later object creation""" # Allow the updating of template arguments whilst still in template form super().__init__() self._arguments = arguments diff --git a/volatility/framework/interfaces/plugins.py b/volatility/framework/interfaces/plugins.py index a6bcbd9ca..1160ef58a 100644 --- a/volatility/framework/interfaces/plugins.py +++ b/volatility/framework/interfaces/plugins.py @@ -24,6 +24,12 @@ class FileInterface(metaclass = ABCMeta): """Class for storing Files in the plugin as a means to output a file or files when necessary""" def __init__(self, filename: str, data: bytes = None) -> None: + """ + + Args: + filename: The requested name of the filename for the data + data: The data to be stored in a file + """ self.preferred_filename = filename if data is None: data = b'' @@ -38,7 +44,11 @@ class FileConsumerInterface(object): """ def consume_file(self, file: FileInterface) -> None: - """Consumes a file as passed back to a UI by a plugin""" + """Consumes a file as passed back to a UI by a plugin + + Args: + file: A FileInterface object with the data to write to a file + """ # @@ -70,6 +80,13 @@ class PluginInterface(interfaces_configuration.ConfigurableInterface, metaclass context: interfaces_context.ContextInterface, config_path: str, progress_callback: constants.ProgressCallback = None) -> None: + """ + + Args: + context: The context that the plugin will operate within + config_path: The path to configuration data within the context configuration data + progress_callback: A callable that can provide feedback at progress points + """ super().__init__(context, config_path) self._progress_callback = progress_callback or (lambda f, s: None) # Plugins self validate on construction, it makes it more difficult to work with them, but then @@ -80,6 +97,7 @@ class PluginInterface(interfaces_configuration.ConfigurableInterface, metaclass self._file_consumer = None # type: Optional[FileConsumerInterface] def set_file_consumer(self, consumer: FileConsumerInterface) -> None: + """Sets the file consumer to be used by this plugin""" self._file_consumer = consumer def produce_file(self, filedata: FileInterface) -> None: diff --git a/volatility/framework/interfaces/renderers.py b/volatility/framework/interfaces/renderers.py index b3ae84c17..1ae782325 100644 --- a/volatility/framework/interfaces/renderers.py +++ b/volatility/framework/interfaces/renderers.py @@ -192,4 +192,10 @@ class TreeGrid(object, metaclass = ABCMeta): The order of that the nodes are visited is always depth first, however, the order children are traversed can be set based on a sort_key function which should accept a node's values and return something that can be sorted to receive the desired order (similar to the sort/sorted key). + + Args: + node: The initial node to be visited + function: The visitor to apply to the nodes under the initial node + initial_accumulator: An accumulator that allows data to be transfered between one visitor call to the next + sort_key: Information about the sort order of columns in order to determine the ordering of results """ diff --git a/volatility/framework/interfaces/symbols.py b/volatility/framework/interfaces/symbols.py index 1b6bfa7b1..5e5fa0177 100644 --- a/volatility/framework/interfaces/symbols.py +++ b/volatility/framework/interfaces/symbols.py @@ -20,6 +20,14 @@ class SymbolInterface: address: int, type: Optional[objects.Template] = None, constant_data: Optional[bytes] = None) -> None: + """ + + Args: + name: Name of the symbol + address: Numeric address value of the symbol + type: Optional type structure information associated with the symbol + constant_data: Potential constant data the symbol points at + """ self._name = name if constants.BANG in self._name: raise ValueError("Symbol names cannot contain the symbol differentiator ({})".format(constants.BANG)) @@ -55,6 +63,7 @@ class SymbolInterface: @property def constant_data(self) -> Optional[bytes]: + """Returns any constant data associated with the symbol""" return self._constant_data @@ -72,6 +81,14 @@ class BaseSymbolTableInterface: native_types: 'NativeTableInterface', table_mapping: Optional[Dict[str, str]] = None, class_types: Optional[Dict[str, Type[objects.ObjectInterface]]] = None) -> None: + """ + + Args: + name: Name of the symbol table + native_types: The native symbol table used to resolve any base/native types + table_mapping: A dictionary mapping names of tables (which when present within the table will be changed to the mapped table) + class_types: A dictionary of types and classes that should be instantiated instead of Struct to construct them + """ self.name = name if table_mapping is None: table_mapping = {} @@ -140,6 +157,10 @@ class BaseSymbolTableInterface: """Overrides the object class for a specific Symbol type Name *must* be present in self.types + + Args: + name: The name of the type to override the class for + clazz: The actual class to override for the provided type name """ raise NotImplementedError("Abstract method set_type_class not implemented yet.")