From 897d8bd5740bb4671fbcd3307d5e29abbbbeaa57 Mon Sep 17 00:00:00 2001 From: Mike Auty Date: Mon, 28 Sep 2015 00:57:06 +0100 Subject: [PATCH] Initial imlementation of a mapping from volatility config trees to Argparse arguments. --- test_rig.py | 6 ++-- volatility/cli/__init__.py | 22 +++++++----- volatility/cli/argparse_adapter.py | 38 +++++++++++++++++++++ volatility/framework/config.py | 2 +- volatility/framework/contexts/__init__.py | 10 +++--- volatility/framework/interfaces/__init__.py | 2 +- volatility/framework/interfaces/config.py | 29 ++++++++-------- volatility/framework/interfaces/plugins.py | 5 ++- 8 files changed, 78 insertions(+), 36 deletions(-) create mode 100644 volatility/cli/argparse_adapter.py diff --git a/test_rig.py b/test_rig.py index caf573946..23491e5b2 100644 --- a/test_rig.py +++ b/test_rig.py @@ -33,9 +33,9 @@ def test_symbols(): def utils_load_as(): # TODO: This should hold the smarts for determining the physical layers and guessing at various values and so on c = framework.contexts - factory = c.LayerFactory([c.physical.PhysicalContextModifier(None), - c.intel.IntelContextModifier(None), - c.windows.WindowsContextModifier(None)]) + factory = c.LayerFactory([c.physical.PhysicalContextModifier, + c.intel.IntelContextModifier, + c.windows.WindowsContextModifier]) return factory() diff --git a/volatility/cli/__init__.py b/volatility/cli/__init__.py index 0bc7b1cd9..e9fcb4e88 100644 --- a/volatility/cli/__init__.py +++ b/volatility/cli/__init__.py @@ -1,3 +1,7 @@ +import argparse + +from volatility.cli import argparse_adapter + __author__ = 'mike' import sys @@ -27,6 +31,14 @@ class CommandLine(object): # TODO: Choose a plugin plugin = volatility.plugins.windows.pslist.PsList context = self.handle_plugin_requirements(plugin) + parser = argparse.ArgumentParser(prog = 'volatility', + description = "An open-source memory forensics framework") + argparse_adapter.adapt_config(context.config, parser) + + # Run the argparser + parser.parse_args() + + # Validate the requirement # Construct the plugin runner = plugin(context) @@ -46,7 +58,6 @@ class CommandLine(object): context = contexts.Context() for req in reqs: - context.config.add_item(req, plugin.__name__) if isinstance(req, config.TranslationLayerRequirement): # Choose an appropriate LayerFactory (add layer to the req.name so we don't blat the requirement itself namespace = config.namespace_join([plugin.__name__, req.name + "_layer"]) @@ -54,13 +65,8 @@ class CommandLine(object): facreqs = factory.requirements() for facreq in facreqs: context.config.add_item(facreq, namespace = namespace) - print(facreq.name) - - # TODO: Allow for values to be set - for facreq in facreqs: - facreq.validate(context) - context = factory(context) - context.config.get(config.namespace_join([plugin.__name__, req.name])).value = 'intel' + else: + context.config.add_item(req, plugin.__name__) return context diff --git a/volatility/cli/argparse_adapter.py b/volatility/cli/argparse_adapter.py new file mode 100644 index 000000000..c4811c0bb --- /dev/null +++ b/volatility/cli/argparse_adapter.py @@ -0,0 +1,38 @@ +import argparse + +from volatility.framework import interfaces + +__author__ = 'mike' + + +def StoreItemFactory(config_item): + class StoreItemAction(argparse.Action): + def __init__(self, option_strings, dest, nargs = None, **kwargs): + super(StoreItemAction, self).__init__(option_strings, dest, **kwargs) + + def __call__(self, parser, namespace, values, option_string = None): + config_item.value = values[0] + + return StoreItemAction + + +def adapt_config(config, parser, group = None): + """Constructs an argument parser based on a volatility configuration""" + if not group and not isinstance(config, interfaces.config.ConfigurationGroup): + raise TypeError("adapt_config expects a ConfigurationItem, not a " + type(config).__name__) + + for item in flatten_configuration(config): + parser.add_argument("--" + item.replace('.', '-'), + default = config[item].default, + action = StoreItemFactory(config[item])) + + +def flatten_configuration(config): + output = {} + for item in config: + if isinstance(config[item], interfaces.config.ConfigurationGroup): + for k, v in flatten_configuration(config[item]).items(): + output[item + "." + k] = v + else: + output[item] = config[item] + return output diff --git a/volatility/framework/config.py b/volatility/framework/config.py index f9048d6cd..3fc1d2e03 100644 --- a/volatility/framework/config.py +++ b/volatility/framework/config.py @@ -5,7 +5,7 @@ Created on 7 May 2013 """ import re -from volatility.framework.interfaces.config import GenericRequirement, ConfigGroup +from volatility.framework.interfaces.config import GenericRequirement, ConfigurationGroup # Intentionally reimport namespace_join so it's accessible from the main config module from volatility.framework.interfaces.config import namespace_join diff --git a/volatility/framework/contexts/__init__.py b/volatility/framework/contexts/__init__.py index 467f4cc71..fc3191f47 100644 --- a/volatility/framework/contexts/__init__.py +++ b/volatility/framework/contexts/__init__.py @@ -39,7 +39,7 @@ class LayerFactory(validity.ValidityRoutines, list): groups = [] for index in range(len(self)): modifier = self[index] - group = config.ConfigGroup(modifier.__name__ + str(index)) + group = config.ConfigurationGroup(modifier.__name__ + str(index)) for req in modifier.requirements(): group.add_item(req) groups.append(group) @@ -76,7 +76,7 @@ class Context(interfaces.context.ContextInterface): interfaces.context.ContextInterface.__init__(self) self._symbol_space = symbols.SymbolSpace(natives) self._memory = layers.Memory() - self._config = config.ConfigGroup(name = 'volatility') + self._config = config.ConfigurationGroup(name = 'volatility') # ## Symbol Space Functions @@ -87,8 +87,8 @@ class Context(interfaces.context.ContextInterface): @config.setter def config(self, value): - if not isinstance(value, config.ConfigGroup): - raise TypeError("Configuration must of type ConfigGroup") + if not isinstance(value, config.ConfigurationGroup): + raise TypeError("Configuration must of type ConfigurationGroup") self._config = value @property @@ -129,4 +129,4 @@ class Context(interfaces.context.ContextInterface): object_template.update_vol(**arguments) return object_template(context = self, object_info = interfaces.objects.ObjectInformation(layer_name = layer_name, - offset = offset)) \ No newline at end of file + offset = offset)) diff --git a/volatility/framework/interfaces/__init__.py b/volatility/framework/interfaces/__init__.py index d538b37c2..eea2a0c98 100644 --- a/volatility/framework/interfaces/__init__.py +++ b/volatility/framework/interfaces/__init__.py @@ -7,4 +7,4 @@ Created on 12 Apr 2013 # Import the submodules we want people to be able to use without importing them themselves # This will also avoid namespace issues, because people can use interfaces.layers to # avoid clashing with the layers package -from volatility.framework.interfaces import layers, symbols, context, objects, plugins, renderers +from volatility.framework.interfaces import config, context, layers, objects, plugins, renderers, symbols diff --git a/volatility/framework/interfaces/config.py b/volatility/framework/interfaces/config.py index 4241fc1e7..13f60532d 100644 --- a/volatility/framework/interfaces/config.py +++ b/volatility/framework/interfaces/config.py @@ -15,7 +15,7 @@ def namespace_join(pathlist): class ConfigurationItem(validity.ValidityRoutines): """Class to distinguish configuration elements from everything else""" - def __init__(self, name, optional = False): + def __init__(self, name, optional): validity.ValidityRoutines.__init__(self) self._type_check(name, str) if NAMESPACE_DIVIDER in name: @@ -28,18 +28,18 @@ class ConfigurationItem(validity.ValidityRoutines): """The name of the Option.""" return self._name - @property - def optional(self): - """Whether the option is required for or not""" - return self._optional - @abstractmethod def validate(self, context): """Validates the currently set value""" pass + @property + def optional(self): + """Whether the option is required for or not""" + return self._optional -class ConfigGroup(ConfigurationItem, collections.abc.Mapping): + +class ConfigurationGroup(ConfigurationItem, collections.abc.Mapping): """Class to hold and provide a namespace for plugins and core options""" def __init__(self, name): @@ -48,11 +48,11 @@ class ConfigGroup(ConfigurationItem, collections.abc.Mapping): def add_item(self, item, namespace = None): if not isinstance(item, ConfigurationItem): - raise TypeError("Only ConfigurationItem objects can be added to a ConfigGroup") + raise TypeError("Only ConfigurationItem objects can be added to a ConfigurationGroup") if namespace: ns_split = namespace.split(NAMESPACE_DIVIDER) if ns_split[0] not in self: - self._namespace[ns_split[0]] = ConfigGroup(ns_split[0]) + self._namespace[ns_split[0]] = ConfigurationGroup(ns_split[0]) return self._namespace[ns_split[0]].add_item(item, namespace_join(ns_split[1:])) self._namespace[item.name] = item @@ -91,13 +91,12 @@ class ConfigGroup(ConfigurationItem, collections.abc.Mapping): class GenericRequirement(ConfigurationItem, metaclass = ABCMeta): """Class to handle a single specific configuration option""" - def __init__(self, name, description = None, default = None, optional = False): + def __init__(self, name, description = None, default = None, optional = None): """Creates a new option""" - ConfigurationItem.__init__(self, name) + ConfigurationItem.__init__(self, name, optional) self._default = default self._description = description self._value = None - self._optional = optional @property def value(self): @@ -112,9 +111,9 @@ class GenericRequirement(ConfigurationItem, metaclass = ABCMeta): self._value = data @property - def optional(self): - """Whether the option is required for or not""" - return self._optional + def default(self): + """Returns the default value if one is set""" + return self._default @property def description(self): diff --git a/volatility/framework/interfaces/plugins.py b/volatility/framework/interfaces/plugins.py index 09446274b..6c8aea8ea 100644 --- a/volatility/framework/interfaces/plugins.py +++ b/volatility/framework/interfaces/plugins.py @@ -5,9 +5,10 @@ Created on 6 May 2013 """ from abc import abstractmethod, ABCMeta -from volatility.framework import validity, config +from volatility.framework import validity from volatility.framework.interfaces import context as interfaces_context + # # Plugins # - Take in relevant number of TranslationLayers (of specified type) @@ -48,7 +49,6 @@ class PluginInterface(validity.ValidityRoutines, metaclass = ABCMeta): def validate_inputs(self): for option in self.requirements(): if not option.optional: - print("Validate", option.name) option.validate_input(self.config.get_value(option.name), self.context) @abstractmethod @@ -61,7 +61,6 @@ class PluginInterface(validity.ValidityRoutines, metaclass = ABCMeta): :rtype: TreeGrid """ - # TODO: Needs to say what it can/can't handle (validate context) # TODO: Needs to offer available options' # TODO: Figure out how to handle global config options