Writing more advanced Plugins

There are several common tasks you might wish to accomplish, there is a recommended means of achieving most of these which are discussed below.

Writing Reusable Methods

Classes which inherit from PluginInterface all have a run() method which takes no parameters and will return a TreeGrid. Since most useful functions are parameterized, to provide parameters to a plugin the configuration for the context must be appropriately manipulated. There is scope for this, in order to run multiple plugins (see Writing plugins that run other plugins) but a much simpler method is to provide a parameterized classmethod within the plugin, which will allow the method to yield whatever kind of output it will generate and take whatever parameters it might need.

This is how processes are listed, which is an often used function. The code lives within the PsList plugin but can be used by other plugins by providing the appropriate parameters (see list_processes()). It is up to the author of a plugin to validate that any required plugins are present and are the appropriate version.

Writing plugins that run other plugins

Occasionally plugins will want to process the output from other plugins (for example, the timeliner plugin which runs all other available plugins that feature a Timeliner interface). This can be achieved with the following example code:

automagics = automagic.choose_automagic(automagic.available(self._context), plugin_class)
plugin = plugins.construct_plugin(self.context, automagics, plugin_class, self.config_path,
                            self._progress_callback, self._file_consumer)

This code will first generate suitable automagics for running against the context. Unfortunately this must be re-run for each plugin in order to populate the context’s configuration correctly based on the plugin’s requirements (which may vary between plugins). Once the automagics have been constructed, the plugin can be instantiated using the helper function construct_plugin() providing:

  • the base context (containing the configuration and any already loaded layers or symbol tables),

  • the plugin class to run,

  • the configuration path within the context for the plugin

  • any callback to determine progress in lengthy operations

  • any file consumers for files created during running of the plugin

With the constructed plugin, it can either be run by calling its run() method, or any other known method can be invoked on it.

Writing Scanners

Scanners are objects that adhere to the ScannerInterface. They are passed to the scan() method on layers which will divide the provided range of sections (or the entire layer if none are provided) and call the ScannerInterface()’s call method method with each chunk as a parameter, ensuring a suitable amount of overlap (as defined by the scanner). The offset of the chunk, within the layer, is also provided as a parameter.

Scanners can technically maintain state, but it is not recommended since the ordering that the chunks are scanned is not guaranteed. Scanners may be executed in parallel if they mark themselves as thread_safe although the threading technique may be either standard threading or multiprocessing. Note, the only component of the scans which is parallelized are those that go on within the scan method. As such, any processing carried out on the results yielded by the scanner will be processed in serial. It should also be noted that generating the addresses to be scanned are not iterated in parallel (in full, before the scanning occurs), meaning the smaller the sections to scan the quicker the scan will run.

Empirically it was found that scanners are typically not the most time intensive part of plugins (even those that do extensive scanning) and so parallelism does not offer significant gains. As such, parallelism is not enabled by default but interfaces can easily enable parallelism when desired.

Writing/Using Intermediate Symbol Format Files

It can occasionally be useful to create a data file containing the static structures that can create a Template to be instantiated on a layer. Volatility has all the machinery necessary to construct these for you from properly formatted JSON data.

The JSON format is documented by the JSON schema files located in schemas. These are versioned using standard .so library versioning, so they may not increment as expected. Each schema lists an available version that can be used, which specifies five different sections:

  • Base_types - These are the basic type names that will make up the native/primitive types

  • User_types - These are the standard definitions of type structures, most will go here

  • Symbols - These list offsets that are associated with specific names (and can be associated with specific type names)

  • Enums - Enumerations that offer a number of choices

  • Metadata - This is information about the generator, when the file was generated and similar

Constructing an appropriate file, the file can be loaded into a symbol table as follows:

table_name = intermed.IntermediateSymbolTable.create(context, config_path, 'sub_path', 'filename')

This code will load a JSON file from one of the standard symbol paths (volatility/symbols and volatility/framework/symbols) under the additional directory sub_path, with a name matching filename.json (the extension should not be included in the filename).

The sub_path parameter acts as a filter, so that similarly named symbol tables for each operating system can be addressed separately. The top level directories which sub_path filters are also checked as zipfiles to determine any symbols within them. As such, group of symbol tables can be included in a single zip file. The filename for the symbol tables should not contain an extension, as extensions for JSON (and compressed JSON files) will be tested to find a match.

Additional parameters exist, such as native_types which can be used to provide pre-populated native types.

Another useful parameter is table_mapping which allows for type referenced inside the JSON (such as one_table!type_name) would allow remapping of one_table to another_table by providing a dictionary as follows:

table_name = intermed.IntermediateSymbolTable.create(context, config_path, 'sub_path', 'filename',
    table_mapping = {'one_table': 'another_table'})

The last parameter that can be used is called class_types which allows a particular structure to be instantiated on a class other than StructType, allowing for additional methods to be defined and associated with the type.

The table name can then by used to access the constructed table from the context, such as:

context.symbol_space[table_name]

Writing new translation layers

Writing new Templates and Objects