Merge pull request #754 from volatilityfoundation/feature/cli-config-hierarchy

CLI: Add support for a configuration options file
This commit is contained in:
ikelos
2024-02-08 12:05:24 +00:00
committed by GitHub
3 changed files with 121 additions and 30 deletions
+15
View File
@@ -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.
+68 -14
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
@@ -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
+38 -16
View File
@@ -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(