CLI: Add support for a configuration options file

This commit is contained in:
Mike Auty
2022-05-29 23:44:29 +01:00
parent 13a5cfdf52
commit 48e29ae530
2 changed files with 83 additions and 26 deletions
+46 -12
View File
@@ -19,7 +19,7 @@ import os
import sys
import tempfile
import traceback
from typing import Any, Dict, Type, Union
from typing import Any, Dict, List, Tuple, Type, Union
from urllib import parse, request
import volatility3.plugins
@@ -92,6 +92,9 @@ class CommandLine:
renderers = dict([(x.name.lower(), x) for x in framework.class_subclasses(text_renderer.CLIRenderer)])
# Load up system defaults
delayed_logs, default_config = self.load_system_defaults('vol.json')
parser = volargparse.HelpfulArgParser(add_help = False,
prog = self.CLI_NAME,
description = "An open-source memory forensics framework")
@@ -174,6 +177,8 @@ class CommandLine:
default = False,
action = 'store_true')
parser.set_defaults(**default_config)
# We have to filter out help, otherwise parse_known_args will trigger the help message before having
# processed the plugin choice or had the plugin subparser added.
known_args = [arg for arg in sys.argv if arg != '--help' and arg != '-h']
@@ -184,17 +189,7 @@ class CommandLine:
banner_output = sys.stderr
banner_output.write(f"Volatility 3 Framework {constants.PACKAGE_VERSION}\n")
if partial_args.plugin_dirs:
volatility3.plugins.__path__ = [os.path.abspath(p)
for p in partial_args.plugin_dirs.split(";")] + constants.PLUGINS_PATH
if partial_args.symbol_dirs:
volatility3.symbols.__path__ = [os.path.abspath(p)
for p in partial_args.symbol_dirs.split(";")] + constants.SYMBOL_BASEPATHS
if partial_args.cache_path:
constants.CACHE_PATH = partial_args.cache_path
### Start up logging
if partial_args.log:
file_logger = logging.FileHandler(partial_args.log)
file_logger.setLevel(1)
@@ -210,6 +205,21 @@ class CommandLine:
else:
console.setLevel(10 - (partial_args.verbosity - 2))
for level, msg in delayed_logs:
vollog.log(level, msg)
### Alter constants if necessary
if partial_args.plugin_dirs:
volatility3.plugins.__path__ = [os.path.abspath(p)
for p in partial_args.plugin_dirs.split(";")] + constants.PLUGINS_PATH
if partial_args.symbol_dirs:
volatility3.symbols.__path__ = [os.path.abspath(p)
for p in partial_args.symbol_dirs.split(";")] + constants.SYMBOL_BASEPATHS
if partial_args.cache_path:
constants.CACHE_PATH = partial_args.cache_path
vollog.info(f"Volatility plugins path: {volatility3.plugins.__path__}")
vollog.info(f"Volatility symbols path: {volatility3.symbols.__path__}")
@@ -366,6 +376,30 @@ class CommandLine:
raise ValueError(f"File does not exist: {filename}")
return parse.urlunparse(single_location)
def load_system_defaults(self, filename: str) -> Tuple[List[Tuple[int, str]], Dict[str, Any]]:
"""Modify the main configuration based on the default configuration override"""
# Build the config path
default_config_path = os.path.join(os.path.expanduser("~"), ".config", "volatility3", filename)
if sys.platform == 'win32':
default_config_path = os.path.join(os.environ.get("APPDATA", os.path.expanduser("~")), "volatility3",
filename)
delayed_logs = []
# Process it if the files exist
if os.path.exists(default_config_path):
result = json.load(open(default_config_path, 'rb'))
if not isinstance(result, dict):
delayed_logs.append((logging.INFO,
f'Default configuration file {default_config_path} does not contain a dictionary'))
else:
delayed_logs.append(
(logging.INFO, f"Loading default configuration options from {default_config_path}"))
delayed_logs.append((logging.DEBUG,
f"Loaded configuration: {json.dumps(result, indent = 2, sort_keys = True)}"))
return delayed_logs, result
return delayed_logs, {}
def process_exceptions(self, excp):
"""Provide useful feedback if an exception occurs during a run of a plugin."""
# Ensure there's nothing in the cache
+37 -14
View File
@@ -45,6 +45,9 @@ class VolShell(cli.CommandLine):
framework.require_interface_version(2, 0, 0)
# Load up system defaults
delayed_logs, default_config = self.load_system_defaults('volshell.json')
parser = argparse.ArgumentParser(prog = self.CLI_NAME,
description = "A tool for interactivate forensic analysis of memory images")
parser.add_argument("-c",
@@ -68,13 +71,16 @@ class VolShell(cli.CommandLine):
default = "",
type = str)
parser.add_argument("-v", "--verbosity", help = "Increase output verbosity", default = 0, action = "count")
parser.add_argument("--log",
help = "Log output to a file as well as the console",
default = None,
type = str)
parser.add_argument("-o",
"--output-dir",
help = "Directory in which to output any generated files",
default = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')),
type = str)
parser.add_argument("-q", "--quiet", help = "Remove progress feedback", default = False, action = 'store_true')
parser.add_argument("--log", help = "Log output to a file as well as the console", default = None, type = str)
parser.add_argument("-f",
"--file",
metavar = 'FILE',
@@ -97,6 +103,10 @@ class VolShell(cli.CommandLine):
help = f"Change the default path ({constants.CACHE_PATH}) used to store the cache",
default = constants.CACHE_PATH,
type = str)
parser.add_argument("--offline",
help = "Do not search online for additional JSON files",
default = False,
action = 'store_true')
# Volshell specific flags
os_specific = parser.add_mutually_exclusive_group(required = False)
@@ -108,10 +118,33 @@ class VolShell(cli.CommandLine):
os_specific.add_argument("-l", "--linux", default = False, action = "store_true", help = "Run a Linux volshell")
os_specific.add_argument("-m", "--mac", default = False, action = "store_true", help = "Run a Mac volshell")
parser.set_defaults(**default_config)
# We have to filter out help, otherwise parse_known_args will trigger the help message before having
# processed the plugin choice or had the plugin subparser added.
known_args = [arg for arg in sys.argv if arg != '--help' and arg != '-h']
partial_args, _ = parser.parse_known_args(known_args)
### Start up logging
if partial_args.log:
file_logger = logging.FileHandler(partial_args.log)
file_logger.setLevel(1)
file_formatter = logging.Formatter(datefmt = '%y-%m-%d %H:%M:%S',
fmt = '%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
file_logger.setFormatter(file_formatter)
rootlog.addHandler(file_logger)
vollog.info("Logging started")
if partial_args.verbosity < 3:
if partial_args.verbosity < 1:
sys.tracebacklimit = None
console.setLevel(30 - (partial_args.verbosity * 10))
else:
console.setLevel(10 - (partial_args.verbosity - 2))
for level, msg in delayed_logs:
vollog.log(level, msg)
### Alter constants if necessary
if partial_args.plugin_dirs:
volatility3.plugins.__path__ = [os.path.abspath(p)
for p in partial_args.plugin_dirs.split(";")] + constants.PLUGINS_PATH
@@ -126,23 +159,13 @@ class VolShell(cli.CommandLine):
vollog.info(f"Volatility plugins path: {volatility3.plugins.__path__}")
vollog.info(f"Volatility symbols path: {volatility3.symbols.__path__}")
if partial_args.log:
file_logger = logging.FileHandler(partial_args.log)
file_logger.setLevel(0)
file_formatter = logging.Formatter(datefmt = '%y-%m-%d %H:%M:%S',
fmt = '%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
file_logger.setFormatter(file_formatter)
vollog.addHandler(file_logger)
vollog.info("Logging started")
if partial_args.verbosity < 3:
console.setLevel(30 - (partial_args.verbosity * 10))
else:
console.setLevel(10 - (partial_args.verbosity - 2))
if partial_args.clear_cache:
framework.clear_cache()
if partial_args.offline:
constants.OFFLINE = partial_args.offline
# Do the initialization
ctx = contexts.Context() # Construct a blank context
failures = framework.import_files(volatility3.plugins,