Files
volatility3/volatility/cli/__init__.py
T
Mike Auty 5e15d522bd Remove automatic IDE-inserted attributions.
They weren't consistent and they show just remind me how slowly
I've been getting vol 3 written!  5:S

There's git commits to track all of that stuff if we need it.
2017-05-14 11:40:49 +01:00

199 lines
8.9 KiB
Python

"""A CommandLine User Interface for the volatility framework
User interfaces make use of the framework to:
* determine available plugins
* request necessary information for those plugins from the user
* determine what "automagic" modules will be used to populate information the user does not provide
* run the plugin
* display the results
"""
import argparse
import inspect
import json
import logging
import sys
import volatility.framework
import volatility.plugins
from volatility.framework import automagic, constants, contexts, interfaces
from volatility.framework.configuration import requirements
from volatility.framework.interfaces.configuration import HierarchicalDict
from volatility.framework.renderers.text import QuickTextRenderer
# Make sure we log everything
logging.basicConfig(filename = 'example.log',
format = '%(asctime)s %(name)-12s %(levelname)-8s %(message)s',
datefmt = '%m-%d %H:%M',
level = 0)
vollog = logging.getLogger()
console = logging.StreamHandler()
# Trim the console down by default
console.setLevel(logging.WARNING)
formatter = logging.Formatter('%(levelname)-8s %(name)-12s: %(message)s')
console.setFormatter(formatter)
vollog.addHandler(console)
class CommandLine(object):
"""Constructs a command-line interface object for users to run plugins"""
def __init__(self):
pass
def run(self):
"""Executes the command line module, taking the system arguments, determining the plugin to run and then running it"""
sys.stdout.write("Volatility Framework {}\n".format(constants.PACKAGE_VERSION))
volatility.framework.require_interface_version(0, 0, 0)
# Do the initialization
ctx = contexts.Context() # Construct a blank context
volatility.framework.import_files(volatility.plugins) # Will not log as console's default level is WARNING
automagics = automagic.available(ctx)
plugin_list = {}
for plugin in volatility.framework.class_subclasses(interfaces.plugins.PluginInterface):
plugin_name = plugin.__module__ + "." + plugin.__name__
if plugin_name.startswith("volatility.plugins."):
plugin_name = plugin_name[len("volatility.plugins."):]
plugin_list[plugin_name] = plugin
parser = argparse.ArgumentParser(prog = 'volatility',
description = "An open-source memory forensics framework")
parser.add_argument("-c", "--config", help = "Load the configuration from a json file", default = None,
type = str)
parser.add_argument("-v", "--verbosity", help = "Increase output verbosity", default = 0, action = "count")
seen_automagics = set()
configurables_list = {}
for amagic in automagics:
if amagic in seen_automagics:
continue
seen_automagics.add(amagic)
if isinstance(amagic, interfaces.configuration.ConfigurableInterface):
self.populate_requirements_argparse(parser, amagic.__class__)
configurables_list[amagic.__class__.__name__] = amagic
subparser = parser.add_subparsers(title = "Plugins", dest = "plugin")
for plugin in plugin_list:
plugin_parser = subparser.add_parser(plugin, help = plugin_list[plugin].__doc__)
self.populate_requirements_argparse(plugin_parser, plugin_list[plugin])
configurables_list[plugin] = plugin_list[plugin]
###
# PASS TO UI
###
# Hand the plugin requirements over to the CLI (us) and let it construct the config tree
# Run the argparser
args = parser.parse_args()
if args.plugin is None:
parser.error("Please select a plugin to run")
console.setLevel(30 - (args.verbosity * 10))
plugin = plugin_list[args.plugin]
plugin_config_path = interfaces.configuration.path_join('plugins', plugin.__name__)
# UI fills in the config, here we load it from the config file and do it before we process the CL parameters
if args.config:
with open(args.config, "r") as f:
json_val = json.load(f)
ctx.config.splice(plugin_config_path, HierarchicalDict(json_val))
# Populate the context config based on the returned args
# We have already determined these elements must be descended from ConfigurableInterface
vargs = vars(args)
for configurable in configurables_list:
for requirement in configurables_list[configurable].get_requirements():
value = vargs.get(requirement.name, None)
if value is not None:
if not inspect.isclass(configurables_list[configurable]):
config_path = configurables_list[configurable].config_path
else:
# We must be the plugin, so name it appropriately:
config_path = plugin_config_path
extended_path = interfaces.configuration.path_join(config_path, requirement.name)
ctx.config[extended_path] = value
###
# BACK TO THE FRAMEWORK
###
# Clever magic figures out how to fulfill each requirement that might not be fulfilled
automagic.run(automagics, ctx, plugin, "plugins", progress_callback = progress_callback)
# Check all the requirements and/or go back to the automagic step
unsatisfied = plugin.unsatisfied(ctx, plugin_config_path)
if unsatisfied:
raise RuntimeError("Unable to validate the plugin configuration: {}".format(unsatisfied))
print("\n\n")
constructed = plugin(ctx, plugin_config_path)
if args.verbosity >= 2:
vollog.debug("Writing out configuration data to config.json")
with open("config.json", "w") as f:
json.dump(dict(constructed.build_configuration()), f, sort_keys = True, indent = 2)
# Construct and run the plugin
QuickTextRenderer().render(constructed.run())
def populate_requirements_argparse(self, parser, configurable):
"""Adds the plugin's simple requirements to the provided parser
:param parser: The parser to add the plugin's (simple) requirements to
:type parser: argparse.ArgumentParser
:param configurable: The plugin object to pull the requirements from
:type configurable: volatility.framework.interfaces.plugins.PluginInterface
"""
if not issubclass(configurable, interfaces.configuration.ConfigurableInterface):
raise TypeError("Expected ConfigurableInterface type, not: {}".format(type(configurable)))
# Construct an argparse group
for requirement in configurable.get_requirements():
additional = {}
if not isinstance(requirement, interfaces.configuration.RequirementInterface):
raise TypeError(
"Plugin contains requirements that are not RequirementInterfaces: {}".format(configurable.__name__))
if isinstance(requirement, interfaces.configuration.InstanceRequirement):
additional["type"] = requirement.instance_type
if isinstance(requirement, requirements.BooleanRequirement):
additional["action"] = "store_true"
elif isinstance(requirement, requirements.ListRequirement):
if requirement.min_elements != requirement.max_elements:
if requirement.min_elements > 0:
additional["nargs"] = "+"
additional["nargs"] = "*"
# We can't test for min_elements > 1 or going over max_elements but rather than warning here,
# we expect the plugin to fail validation instead
else:
additional["nargs"] = requirement.max_elements
additional["type"] = requirement.element_type.instance_type
elif isinstance(requirement, requirements.ChoiceRequirement):
additional["type"] = str
additional["choices"] = requirement.choices
else:
continue
parser.add_argument("--" + requirement.name.replace('_', '-'), help = requirement.description,
default = requirement.default, dest = requirement.name,
required = not requirement.optional, **additional)
def progress_callback(progress, description = None):
""" A sinmple function for providing text-based feedback
.. warning:: Only for development use.
:param progress: Percentage of progress of the current procedure
:type progress: int or float
"""
print("\rProgress: ", round(progress, 2), "\t\t", description or '', end = '\n')
def main():
"""A convenience function for constructing and running the :class:`CommandLine`'s run method"""
CommandLine().run()