示例#1
0
 def create_stackers_list(
         self) -> List[Type[interfaces.automagic.StackerLayerInterface]]:
     """Creates the list of stackers to use based on the config option"""
     stack_set = sorted(framework.class_subclasses(
         interfaces.automagic.StackerLayerInterface),
                        key=lambda x: x.stack_order)
     stacker_list = self.config.get('stackers', [])
     if len(stacker_list):
         result = []
         for stacker in stack_set:
             if stacker.__name__ in stacker_list:
                 result.append(stacker)
         stack_set = result
     return stack_set
示例#2
0
    def _generator(self):
        categories = {
            'Automagic': interfaces.automagic.AutomagicInterface,
            'Requirement': interfaces.configuration.RequirementInterface,
            'Layer': interfaces.layers.DataLayerInterface,
            'LayerStacker': interfaces.automagic.StackerLayerInterface,
            'Object': interfaces.objects.ObjectInterface,
            'Plugin': interfaces.plugins.PluginInterface,
            'Renderer': interfaces.renderers.Renderer
        }

        for category, module_interface in categories.items():
            yield (0, (category, ))
            for clazz in framework.class_subclasses(module_interface):
                yield (1, (clazz.__name__, ))
示例#3
0
def choose_os_stackers(
        plugin: Type[interfaces.plugins.PluginInterface]) -> List[str]:
    """Identifies the stackers that should be run, based on the plugin (and thus os) provided"""
    plugin_first_level = plugin.__module__.split('.')[2]

    # Ensure all stackers are loaded
    framework.import_files(sys.modules['volatility3.framework.layers'])

    result = []
    for stacker in sorted(framework.class_subclasses(
            interfaces.automagic.StackerLayerInterface),
                          key=lambda x: x.stack_order):
        if plugin_first_level in stacker.exclusion_list:
            continue
        result.append(stacker.__name__)
    return result
示例#4
0
    def __init__(self,
                 progress_callback: Optional[
                     constants.ProgressCallback] = None,
                 context: Optional[ssl.SSLContext] = None) -> None:
        """Creates a resource accessor.

        Note: context is an SSL context, not a volatility context
        """
        self._progress_callback = progress_callback
        self._context = context
        self._handlers = list(
            framework.class_subclasses(urllib.request.BaseHandler))
        if self.list_handlers:
            vollog.log(
                constants.LOGLEVEL_VVV, "Available URL handlers: {}".format(
                    ", ".join([x.__name__ for x in self._handlers])))
            self.__class__.list_handlers = False
示例#5
0
def available(context: interfaces.context.ContextInterface) -> List[interfaces.automagic.AutomagicInterface]:
    """Returns an ordered list of all subclasses of
    :class:`~volatility3.framework.interfaces.automagic.AutomagicInterface`.

    The order is based on the priority attributes of the subclasses, in order to ensure the automagics are listed in
    an appropriate order.

    Args:
        context: The context that will contain any automagic configuration values.
    """
    import_files(sys.modules[__name__])
    config_path = constants.AUTOMAGIC_CONFIG_PATH
    return sorted([
        clazz(context, interfaces.configuration.path_join(config_path, clazz.__name__))
        for clazz in class_subclasses(interfaces.automagic.AutomagicInterface)
    ],
                  key = lambda x: x.priority)
示例#6
0
    def get_usable_plugins(cls, selected_list: List[str] = None) -> List[Type]:
        # Initialize for the run
        plugin_list = list(framework.class_subclasses(TimeLinerInterface))

        # Get the filter from the configuration
        def passthrough(name: str, selected: List[str]) -> bool:
            return True

        filter_func = passthrough
        if selected_list:

            def filter_plugins(name: str, selected: List[str]) -> bool:
                return any([s in name for s in selected])

            filter_func = filter_plugins
        else:
            selected_list = []

        return [plugin_class for plugin_class in plugin_list if filter_func(plugin_class.__name__, selected_list)]
示例#7
0
    def run_all(
        cls, context: interfaces.context.ContextInterface,
        config: interfaces.configuration.HierarchicalDict
    ) -> Iterator[Tuple[str, str, str, str, str]]:
        """It calls each subclass symtab_checks() to test the required
        conditions to that specific kernel implementation.

        Args:
            context: The volatility3 context on which to operate
            config: Core configuration

        Yields:
            kmsg records
        """
        vmlinux = context.modules[config['kernel']]

        kmsg_inst = None  # type: ignore
        for subclass in class_subclasses(cls):
            if not subclass.symtab_checks(vmlinux=vmlinux):
                vollog.log(
                    constants.LOGLEVEL_VVVV,
                    "Kmsg implementation '%s' doesn't match this memory dump",
                    subclass.__name__)
                continue

            vollog.log(constants.LOGLEVEL_VVVV,
                       "Kmsg implementation '%s' matches!", subclass.__name__)
            kmsg_inst = subclass(context=context, config=config)
            # More than one class could be executed for an specific kernel
            # version i.e. Netfilter Ingress hooks
            # We expect just one implementation to be executed for an specific kernel
            yield from kmsg_inst.run()
            break

        if kmsg_inst is None:
            vollog.error("Unsupported Netfilter kernel implementation")
示例#8
0
    def __init__(self,
                 context: interfaces.context.ContextInterface,
                 config_path: str,
                 name: str,
                 isf_url: str,
                 native_types: interfaces.symbols.NativeTableInterface = None,
                 table_mapping: Optional[Dict[str, str]] = None,
                 validate: bool = True,
                 class_types: Optional[Mapping[
                     str, Type[interfaces.objects.ObjectInterface]]] = None,
                 symbol_mask: int = 0) -> None:
        """Instantiates a SymbolTable based on an IntermediateSymbolFormat JSON file.  This is validated against the
        appropriate schema.  The validation can be disabled by passing validate = False, but this should almost never be
        done.

        Args:
            context: The volatility context for the symbol table
            config_path: The configuration path for the symbol table
            name: The name for the symbol table (this is used in symbols e.g. table!symbol )
            isf_url: The URL pointing to the ISF file location
            native_types: The NativeSymbolTable that contains the native types for this symbol table
            table_mapping: A dictionary linking names referenced in the file with symbol tables in the context
            validate: Determines whether the ISF file will be validated against the appropriate schema
            class_types: A dictionary of type names and classes that override StructType when they are instantiated
            symbol_mask: An address mask used for all returned symbol offsets from this table (a mask of 0 disables masking)
        """
        # Check there are no obvious errors
        # Open the file and test the version
        self._versions = dict([(x.version, x)
                               for x in class_subclasses(ISFormatTable)])
        fp = resources.ResourceAccessor().open(isf_url)
        reader = codecs.getreader("utf-8")
        json_object = json.load(reader(fp))  # type: ignore
        fp.close()

        # Validation is expensive, but we cache to store the hashes of successfully validated json objects
        if validate and not schemas.validate(json_object):
            raise exceptions.SymbolSpaceError(
                f"File does not pass version validation: {isf_url}")

        metadata = json_object.get('metadata', None)

        # Determine the delegate or throw an exception
        self._delegate = self._closest_version(metadata.get(
            'format', "0.0.0"), self._versions)(context, config_path, name,
                                                json_object, native_types,
                                                table_mapping)
        if self._delegate.version < constants.ISF_MINIMUM_SUPPORTED:
            raise RuntimeError(
                "ISF version {} is no longer supported: {}".format(
                    metadata.get('format', "0.0.0"), isf_url))
        elif self._delegate.version < constants.ISF_MINIMUM_DEPRECATED:
            vollog.warning(
                f"ISF version {metadata.get('format', '0.0.0')} has been deprecated: {isf_url}"
            )

        # Inherit
        super().__init__(context,
                         config_path,
                         name,
                         native_types or self._delegate.natives,
                         table_mapping=table_mapping,
                         class_types=class_types)

        # Since we've been created with parameters, ensure our config is populated likewise
        self.config['isf_url'] = isf_url
        self.config['symbol_mask'] = symbol_mask
示例#9
0
 def _non_cached_schemes() -> List[str]:
     """Returns the list of schemes not to be cached"""
     result = ['file']
     for clazz in framework.class_subclasses(VolatilityHandler):
         result += clazz.non_cached_schemes()
     return result
示例#10
0
    def run(self):
        """Executes the command line module, taking the system arguments,
        determining the plugin to run and then running it."""

        volatility3.framework.require_interface_version(1, 0, 0)

        renderers = dict([(x.name.lower(), x) for x in framework.class_subclasses(text_renderer.CLIRenderer)])

        parser = volargparse.HelpfulArgParser(add_help = False,
                                              prog = self.CLI_NAME,
                                              description = "An open-source memory forensics framework")
        parser.add_argument(
            "-h",
            "--help",
            action = "help",
            default = argparse.SUPPRESS,
            help = "Show this help message and exit, for specific plugin options use '{} <pluginname> --help'".format(
                parser.prog))
        parser.add_argument("-c",
                            "--config",
                            help = "Load the configuration from a json file",
                            default = None,
                            type = str)
        parser.add_argument("--parallelism",
                            help = "Enables parallelism (defaults to off if no argument given)",
                            nargs = '?',
                            choices = ['processes', 'threads', 'off'],
                            const = 'processes',
                            default = None,
                            type = str)
        parser.add_argument("-e",
                            "--extend",
                            help = "Extend the configuration with a new (or changed) setting",
                            default = None,
                            action = 'append')
        parser.add_argument("-p",
                            "--plugin-dirs",
                            help = "Semi-colon separated list of paths to find plugins",
                            default = "",
                            type = str)
        parser.add_argument("-s",
                            "--symbol-dirs",
                            help = "Semi-colon separated list of paths to find symbols",
                            default = "",
                            type = str)
        parser.add_argument("-v", "--verbosity", help = "Increase output verbosity", default = 0, action = "count")
        parser.add_argument("-l",
                            "--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.getcwd(),
                            type = str)
        parser.add_argument("-q", "--quiet", help = "Remove progress feedback", default = False, action = 'store_true')
        parser.add_argument("-r",
                            "--renderer",
                            metavar = 'RENDERER',
                            help = "Determines how to render the output ({})".format(", ".join(list(renderers))),
                            default = "quick",
                            choices = list(renderers))
        parser.add_argument("-f",
                            "--file",
                            metavar = 'FILE',
                            default = None,
                            type = str,
                            help = "Shorthand for --single-location=file:// if single-location is not defined")
        parser.add_argument("--write-config",
                            help = "Write configuration JSON file out to config.json",
                            default = False,
                            action = 'store_true')
        parser.add_argument("--clear-cache",
                            help = "Clears out all short-term cached items",
                            default = False,
                            action = 'store_true')
        parser.add_argument("--cache-path",
                            help = "Change the default path ({}) used to store the cache".format(constants.CACHE_PATH),
                            default = constants.CACHE_PATH,
                            type = str)

        # 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)

        banner_output = sys.stdout
        if renderers[partial_args.renderer].structured_output:
            banner_output = sys.stderr
        banner_output.write("Volatility 3 Framework {}\n".format(constants.PACKAGE_VERSION))

        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

        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)
            vollog.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))

        vollog.info("Volatility plugins path: {}".format(volatility3.plugins.__path__))
        vollog.info("Volatility symbols path: {}".format(volatility3.symbols.__path__))

        # Set the PARALLELISM
        if partial_args.parallelism == 'processes':
            constants.PARALLELISM = constants.Parallelism.Multiprocessing
        elif partial_args.parallelism == 'threads':
            constants.PARALLELISM = constants.Parallelism.Threading
        else:
            constants.PARALLELISM = constants.Parallelism.Off

        if partial_args.clear_cache:
            framework.clear_cache()

        # Do the initialization
        ctx = contexts.Context()  # Construct a blank context
        failures = framework.import_files(volatility3.plugins,
                                          True)  # Will not log as console's default level is WARNING
        if failures:
            parser.epilog = "The following plugins could not be loaded (use -vv to see why): " + \
                            ", ".join(sorted(failures))
            vollog.info(parser.epilog)
        automagics = automagic.available(ctx)

        plugin_list = framework.list_plugins()

        seen_automagics = set()
        chosen_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__)

        subparser = parser.add_subparsers(title = "Plugins",
                                          dest = "plugin",
                                          description = "For plugin specific options, run '{} <plugin> --help'".format(
                                              self.CLI_NAME),
                                          action = volargparse.HelpfulSubparserAction)
        for plugin in sorted(plugin_list):
            plugin_parser = subparser.add_parser(plugin, help = plugin_list[plugin].__doc__)
            self.populate_requirements_argparse(plugin_parser, 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")

        vollog.log(constants.LOGLEVEL_VVV, "Cache directory used: {}".format(constants.CACHE_PATH))

        plugin = plugin_list[args.plugin]
        chosen_configurables_list[args.plugin] = plugin
        base_config_path = "plugins"
        plugin_config_path = interfaces.configuration.path_join(base_config_path, plugin.__name__)

        # Special case the -f argument because people use is so frequently
        # It has to go here so it can be overridden by single-location if it's defined
        # NOTE: This will *BREAK* if LayerStacker, or the automagic configuration system, changes at all
        ###
        if args.file:
            file_name = os.path.abspath(args.file)
            if not os.path.exists(file_name):
                vollog.log(logging.INFO, "File does not exist: {}".format(file_name))
            else:
                single_location = "file:" + request.pathname2url(file_name)
                ctx.config['automagic.LayerStacker.single_location'] = single_location

        # 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, interfaces.configuration.HierarchicalDict(json_val))

        # It should be up to the UI to determine which automagics to run, so this is before BACK TO THE FRAMEWORK
        automagics = automagic.choose_automagic(automagics, plugin)
        for amagic in automagics:
            chosen_configurables_list[amagic.__class__.__name__] = amagic

        if ctx.config.get('automagic.LayerStacker.stackers', None) is None:
            ctx.config['automagic.LayerStacker.stackers'] = stacker.choose_os_stackers(plugin)
        self.output_dir = args.output_dir
        if not os.path.exists(self.output_dir):
            parser.error("The output directory specified does not exist: {}".format(self.output_dir))

        self.populate_config(ctx, chosen_configurables_list, args, plugin_config_path)

        if args.extend:
            for extension in args.extend:
                if '=' not in extension:
                    raise ValueError("Invalid extension (extensions must be of the format \"conf.path.value='value'\")")
                address, value = extension[:extension.find('=')], json.loads(extension[extension.find('=') + 1:])
                ctx.config[address] = value

        ###
        # BACK TO THE FRAMEWORK
        ###
        constructed = None
        try:
            progress_callback = PrintedProgress()
            if args.quiet:
                progress_callback = MuteProgress()

            constructed = plugins.construct_plugin(ctx, automagics, plugin, base_config_path, progress_callback,
                                                   self.file_handler_class_factory())

            if args.write_config:
                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)
        except exceptions.UnsatisfiedException as excp:
            self.process_unsatisfied_exceptions(excp)
            parser.exit(1, "Unable to validate the plugin requirements: {}\n".format([x for x in excp.unsatisfied]))

        try:
            # Construct and run the plugin
            if constructed:
                renderers[args.renderer]().render(constructed.run())
        except (exceptions.VolatilityException) as excp:
            self.process_exceptions(excp)
示例#11
0
    def stack_layer(cls,
                    context: interfaces.context.ContextInterface,
                    initial_layer: str,
                    stack_set: List[Type[
                        interfaces.automagic.StackerLayerInterface]] = None,
                    progress_callback: constants.ProgressCallback = None):
        """Stacks as many possible layers on top of the initial layer as can be done.

        WARNING: This modifies the context provided and may pollute it with unnecessary layers
        Recommended use is to:
        1. Pass in context.clone() instead of context
        2. When provided the layer list, choose the desired layer
        3. Build the configuration using layer.build_configuration()
        4. Merge the configuration into the original context with context.config.merge()
        5. Call Construction magic to reconstruct the layers from just the configuration

        Args:
            context: The context on which to operate
            initial_layer: The name of the initial layer within the context
            stack_set: A list of StackerLayerInterface objects in the order they should be stacked
            progress_callback: A function to report progress during the process

        Returns:
            A list of layer names that exist in the provided context, stacked in order (highest to lowest)
        """
        # Repeatedly apply "determine what this is" code and build as much up as possible
        stacked = True
        stacked_layers = [initial_layer]
        if stack_set is None:
            stack_set = list(
                framework.class_subclasses(
                    interfaces.automagic.StackerLayerInterface))

        for stacker_item in stack_set:
            if not issubclass(stacker_item,
                              interfaces.automagic.StackerLayerInterface):
                raise TypeError(
                    "Stacker {} is not a descendent of StackerLayerInterface".
                    format(stacker_item.__name__))

        while stacked:
            stacked = False
            new_layer = None
            stacker_cls = None
            for stacker_cls in stack_set:
                stacker = stacker_cls()
                try:
                    vollog.log(
                        constants.LOGLEVEL_VV,
                        "Attempting to stack using {}".format(
                            stacker_cls.__name__))
                    new_layer = stacker.stack(context, initial_layer,
                                              progress_callback)
                    if new_layer:
                        context.layers.add_layer(new_layer)
                        vollog.log(
                            constants.LOGLEVEL_VV,
                            "Stacked {} using {}".format(
                                new_layer.name, stacker_cls.__name__))
                        break
                except Exception as excp:
                    # Stacking exceptions are likely only of interest to developers, so the lowest level of logging
                    fulltrace = traceback.TracebackException.from_exception(
                        excp).format(chain=True)
                    vollog.log(
                        constants.LOGLEVEL_VVV,
                        "Exception during stacking: {}".format(str(excp)))
                    vollog.log(constants.LOGLEVEL_VVVV, "\n".join(fulltrace))
            else:
                stacked = False
            if new_layer and stacker_cls:
                stacked_layers = [new_layer.name] + stacked_layers
                initial_layer = new_layer.name
                stacked = True
                stack_set.remove(stacker_cls)
        return stacked_layers