Fun With volshell

When triaging a collection of memory images, I often find myself running multiple Volatility plugins on each image. Typically I do this by shell script, calling each plugin individually and saving the output in files. The problem with this approach is that Volatility has to re-parse the memory image each time my script calls a new plugin. This adds a lot of overhead and time. I started wondering if I could leverage volshell to run multiple plugins so that I wouldn’t have to pay the startup cost each time.

volshell Basics

volshell is an interactive shell environment for exploring a memory image. Explaining all the features of volshell would fill a book, so we’re just going to focus on the basics of starting up volshell and running plugins.

Starting volshell is straightforward. Specify a memory image with “-f” and the OS it comes from with “-w“, “-m“, or “-l” (Windows, MacOS, Linux, respectively). If we don’t want the progress meter as it’s ingesting the memory image, we can add “-q” (“quiet” mode).

$ volshell -f avml.lime -l -q
Volshell (Volatility 3 Framework) 2.27.1
Readline imported successfully

Call help() to see available functions

Volshell mode : Linux
Current Layer : layer_name
Current Symbol Table : symbol_table_name1
Current Kernel Name : kernel

(layer_name) >>>

As the startup text suggests, help is available at any time by running the help() function:

(layer_name) >>> help()

Methods:
...
* dpo, display_plugin_output
Displays the output for a particular plugin (with keyword arguments)
...

volshell methods generally have both long and abbreviated forms. For example, we’ll be using the display_plugin_output() method to run plugins. But rather than type that long string each time, we can just use dpo() instead.

For a simple example, let’s run the linux.ip.Addr plugin via volshell:

(layer_name) >>> from volatility3.plugins.linux import ip
(layer_name) >>> dpo(ip.Addr, kernel = self.config['kernel'])

NetNS Index Interface MAC Promiscuous IP Prefix Scope Type State

4026531840 1 lo 00:00:00:00:00:00 False 127.0.0.1 8 host UNKNOWN
4026531840 1 lo 00:00:00:00:00:00 False ::1 128 host UNKNOWN
4026531840 2 enp0s3 08:00:27:3a:05:32 False 192.168.4.22 22 global UP
4026531840 2 enp0s3 08:00:27:3a:05:32 False fdb0:fa27:86c5:1:19cf:7bad:b8a6:c5d7 64 global UP
4026531840 2 enp0s3 08:00:27:3a:05:32 False fdb0:fa27:86c5:1:a00:27ff:fe3a:532 64 global UP
4026531840 2 enp0s3 08:00:27:3a:05:32 False fe80::a00:27ff:fe3a:532 64 link UP
4026532287 1 lo 00:00:00:00:00:00 False 127.0.0.1 8 host UNKNOWN
4026532287 1 lo 00:00:00:00:00:00 False ::1 128 host UNKNOWN
4026532345 1 lo 00:00:00:00:00:00 False 127.0.0.1 8 host UNKNOWN
4026532345 1 lo 00:00:00:00:00:00 False ::1 128 host UNKNOWN
4026532403 1 lo 00:00:00:00:00:00 False 127.0.0.1 8 host UNKNOWN
4026532403 1 lo 00:00:00:00:00:00 False ::1 128 host UNKNOWN
(layer_name) >>>

First we need to import the Volatility class that contains the plugin we want to invoke. The basic syntax here is “from volatility3.plugins.<os> import <class>“. “<os>” will be “windows“, “mac“, or “linux“. Since we’re running a Linux plugin, “<class>” will be the word that appears after “linux.” in the plugin name. For example, if were trying to run “linux.elfs.Elfs“, then “<class>” is “elfs“.

We use the dpo() method to actually run the plugin and get the output. If we were invoking the plugin on the command line, we would specify “linux.ip.Addr” as the plugin name. But here in Linux volshell, we can leave off the “linux.“. After the plugin name always specify “kernel = self.config['kernel']” to satisfy the dpo() method’s syntax.

If you want to run another plugin, just repeat the pattern. Import the appropriate Volatility class and run dpo() as before:

(layer_name) >>> from volatility3.plugins.linux import pstree
(layer_name) >>> dpo(pstree.PsTree, kernel = self.config['kernel'])

OFFSET (V) PID TID PPID COMM

0x8c7cc0281980 1 1 0 systemd
* 0x8c7cc0d79980 310 310 1 systemd-journal
* 0x8c7cc8268000 357 357 1 systemd-timesyn
* 0x8c7cc81a6600 365 365 1 systemd-udevd
* 0x8c7cc67e0000 687 687 1 avahi-daemon
** 0x8c7cc9986600 718 718 687 avahi-daemon
...

What’s great about this approach is that the memory image was already parsed when volshell started up. So each plugin runs very quickly.

Changing Output Modes

In many cases, it’s better for my workflow to get the plugin output in JSON format rather than the standard text output. I’d be embarrassed to admit how long the following little recipe took me to figure out, so let’s just get to the code:

(layer_name) >>> from volatility3.cli import text_renderer
(layer_name) >>> from volatility3.plugins.linux import psaux
(layer_name) >>> treegrid = gt(psaux.PsAux, kernel = self.config['kernel'])
(layer_name) >>> treegrid.populate()
(layer_name) >>> rt(treegrid,text_renderer.JsonLinesRenderer())

{"ARGS": "/sbin/init", "COMM": "systemd", "PID": 1, "PPID": 0, "__children": []}
{"ARGS": "[kthreadd]", "COMM": "kthreadd", "PID": 2, "PPID": 0, "__children": []}
{"ARGS": "[pool_workqueue_]", "COMM": "pool_workqueue_", "PID": 3, "PPID": 2, "__children": []}
{"ARGS": "[kworker/R-kvfre]", "COMM": "kworker/R-kvfre", "PID": 4, "PPID": 2, "__children": []}
{"ARGS": "[kworker/R-rcu_g]", "COMM": "kworker/R-rcu_g", "PID": 5, "PPID": 2, "__children": []}
...

First we’re importing the text_renderer class from volatility3.cli. This class contains methods for outputting various text formats, like JsonLinesRenderer() for single-line JSON format. Other options include JsonRenderer() for “pretty-printed” JSON output, or CSVRenderer() for comma-separated values formatting.

Next we import the class for Volatility plugin we want to invoke, just as before. But rather than calling dpo(), we create a new treegrid object with generate_treegrid() (abbreviated “gt()“). The arguments to gt() are the same as those for dpo().

gt() merely creates the treegrid object. We still have to call the treegrid.populate() method to load data into the object. Once we have populated the treegrid with data, we can invoke render_treegrid() (“rt()“) to output the data with our chosen text renderer.

Stumbling Towards Automation

Clearly this approach requires a lot of redundant typing. Automating the task of running multiple plugins through volshell is clearly the next step. My ptt.sh script has an initial attempt at this. At some point, I’d like to turn this idea into a standalone script outside of ptt.sh.

Leave a comment