diff --git a/doc/source/vol-cli.rst b/doc/source/vol-cli.rst index cc6f7fe6a..7b91e815d 100644 --- a/doc/source/vol-cli.rst +++ b/doc/source/vol-cli.rst @@ -143,3 +143,18 @@ Options `hivescan` would match `windows.registry.hivescan.HiveScan`, but `pslist` is ambiguous because it could match `windows.pslist` or `linux.pslist`. + +Overriding options +------------------ + +The default values for the command line interface are defined by constants within the code, +but can be overridden by creating a JSON file (`%APPDATA%/volatility3/vol.json` for Windows +systems, or `~/.config/volatility3/vol.json` or `volshell.json` for all others). + +The format of this file is a JSON dictionary, containing the options above and their value. +It should be noted that the ordering is (`<` means is overridden by): + +`in-built default value < config file value < command line parameter` + +It should also be noted that boolean flags (such as `offline`) that are overridden as true will +not be unset by not specifying the command line flag. diff --git a/volatility3/cli/__init__.py b/volatility3/cli/__init__.py index 91bda7c66..9393c0805 100644 --- a/volatility3/cli/__init__.py +++ b/volatility3/cli/__init__.py @@ -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 @@ -105,6 +105,9 @@ class CommandLine: ] ) + # Load up system defaults + delayed_logs, default_config = self.load_system_defaults("vol.json") + parser = volargparse.HelpfulArgParser( add_help=False, prog=self.CLI_NAME, @@ -231,6 +234,8 @@ class CommandLine: 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"] @@ -241,19 +246,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) @@ -271,6 +264,23 @@ 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__}") @@ -463,6 +473,50 @@ class CommandLine: ) return requirements.URIRequirement.location_from_file(filename) + 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): + with open(default_config_path, "rb") as config_json: + result = json.load(config_json) + 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 diff --git a/volatility3/cli/volshell/__init__.py b/volatility3/cli/volshell/__init__.py index 3b982394f..9e74acfec 100644 --- a/volatility3/cli/volshell/__init__.py +++ b/volatility3/cli/volshell/__init__.py @@ -22,12 +22,14 @@ from volatility3.framework import ( ) # Make sure we log everything + +rootlog = logging.getLogger() vollog = logging.getLogger() vollog.setLevel(0) -# Trim the console down by default console = logging.StreamHandler() console.setLevel(logging.WARNING) formatter = logging.Formatter("%(levelname)-8s %(name)-12s: %(message)s") +# Trim the console down by default console.setFormatter(formatter) vollog.addHandler(console) @@ -53,6 +55,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", @@ -146,6 +151,12 @@ class VolShell(cli.CommandLine): 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) @@ -167,26 +178,14 @@ class VolShell(cli.CommandLine): "-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) - 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__}") + ### Start up logging if partial_args.log: file_logger = logging.FileHandler(partial_args.log) file_logger.setLevel(0) @@ -203,9 +202,32 @@ class VolShell(cli.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__}") + 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(