mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-08-17 20:35:40 +02:00
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.
199 lines
8.9 KiB
Python
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()
|