Awesome
Volatility Plugin Tutorial
Introduction
Developing a plugin for Volatility is way easier that it might appear. The biggest obstacle, in my opinion, is knowing how or where to start. There're plenty of sources where you can learn about Volatility (The Art of Memory Forensics book, /r/memoryforensics, Volatility Labs blog, etc.) but very few - almost none - where you can learn how to develop a plugin. Also, this tutorial inspired me to make a more complex one, and here we are!
I'll be working on a x64 Ubuntu 16.04 machine.
Objectives
In this guide you will learn the following:
- Download and run Volatility from source.
- Get a memory dump from Oracle's VirtualBox VM.
- Understand what exactly a Volatility plugin is.
- Write a working Volatility plugin.
Before we begin
Be sure to have Oracle's VirtualBox installed. You won't need the Extension Pack but it's advisable to have it. Also, If you don't have a licensed Windows copy go get a Microsoft Edge and IE test free copy. Yes, for real.
Guide
Download Volatility
The latest Volatility stable version cat be found at The Volatility Foundation GitHub. You can just clone the repository via:
$ git clone https://github.com/volatilityfoundation/volatility.git
Now cd
to volatility's folder and execute a couple or two... wait! We don't have a memory dump to analyze yet. Let's fix that!
Get a memory dump
You can get a memory dump in multiple ways, we'll leverage virtualization and use VirtualBox to easily get a VM's memory dump. A more extensive version of this process can be found here, but, long story short:
- Boot up a virtual machine in VirtualBox and note it's name.
$ vboxmanage debugvm <VM-NAME> dumpvmcore --filename vm.elf
- That's it.
Well, this is not a memory dump per se. It's a .elf
that also contains other data from VirtualBox we don't need. Guess what? Volatility doesn't care. If you really want a raw memory dump go check out the more extensive version.
Analyze memory dump
Now we get to look into the memory dump. We have all we need so cd
to volatility's folder and execute pslist
command like this:
$ python vol.py -f <vm-dump> --profile=<vm-profile> pslist
Let's elaborate.
- vm-dump: This is the .elf - or .raw - file that contains the VM's memory dump. It's imperative that you provide this argument, otherwise, volatility won't run.
- vm-profile: The profile tells volatility from which machine the memory dump came from. If you don't provide this information, volatility will try to guess - not a good idea btw. A list of valid profiles can be found here or you can run
$ python vol.py --info
. I recommend the latter. - pslist: This is the command we invoke.
Since I extracted a Windows 7 SP1 x64 memory dump, I would run:
$ python vol.py -f /home/user/vmcore.elf --profile=Win7SP1x64 pslist
Volatility Foundation Volatility Framework 2.6
Offset(V) Name PID PPID Thds Hnds Sess Wow64 Start Exit
------------------ -------------------- ------ ------ ------ -------- ------ ------ ------------------------------ ------------------------------
0xffffc480b4258040 System 4 0 107 0 ------ 0 2017-07-13 18:18:39 UTC+0000
0xffffc480b45a6040 smss.exe 296 4 4 0 ------ 0 2017-07-13 18:18:39 UTC+0000
0xffffc480b5b1e7c0 csrss.exe 384 368 11 0 0 0 2017-07-13 18:18:42 UTC+0000
0xffffc480b62d3080 smss.exe 448 296 0 -------- 1 0 2017-07-13 18:18:42 UTC+0000 2017-07-13 18:18:42 UTC+0000
0xffffc480b62d6080 wininit.exe 456 368 4 0 0 0 2017-07-13 18:18:42 UTC+0000
0xffffc480b62d9580 csrss.exe 464 448 12 0 1 0 2017-07-13 18:18:42 UTC+0000
0xffffc480b630d080 winlogon.exe 516 448 5 0 1 0 2017-07-13 18:18:42 UTC+0000
...
pslist
is such a trivial command, it simply lists all running processes. But don't be fooled, volatility has quite a set of useful commands which can be found here.
What's a Volatility plugin?
You want to develop a plugin for volatility, but, do you know what a plugin is? The first thing to know here is: EVERYTHING IS A PLUGIN. Yes, there're plugins developed by regular people - you and me - and the ones developed by The Volatility Foundation, that are included in Volatility. The so-called volatility commands are nothing more than plugins, pslist
is a plugin. The plugin you'll create will be invoked almost the same way we used pslist
.
Create the plugin
Now that we know what we're going to do, time to learn how to make it. We'll start by creating a folder for our plugin:
cd ~
mkdir myPlugin
cd myPlugin
Now we create a file for the plugin. Note that the name you give this file will be the name of the command you'll have to give volatility later to run your plugin. I'll call it myplugin
.
vim myplugin.py
Ok, I was kidding, I'm not using vim for this. Any text editor will do: gedit, ATOM, VS Code, Sublime Text, etc.
The basics
We're going to do this the right way: Write the minimum for the plugin to run and build up from it. This would be the simplest working plugin:
import volatility.plugins.common as common
class MyPlugin(common.AbstractWindowsCommand):
def render_text(self, outfd, data):
print "Hello world!"
Let me elaborate:
class MyPlugin(common.AbstractWindowsCommand)
: This is the class that Volatility instantiates when you run the plugin. It must inherit from one of thecommon.Command
subclases, I've chosencommon.AbstractWindowsCommand
because my plugin will be Windows-oriented.def render_text(self, outfd, data)
: This is the called function for presenting the results in plain text format. outfd is the file descriptor to which Volatility will write, by default it'sstdout
but you can give it with--output-file
. We didn't do any work so, after (not) doing all the things my plugin does, it's called and printsHello world!
tostdout
. You can tell the plugin to present the results in plain text, dot, JSON or even a formated HTML document but we'll get to that later.
Now we can run it with:
$ python vol.py --plugins=<myplugin-dir> -f <vm-dump> --profile=<vm-profile> myplugin
Note that the --plugins
argument must be passed right after vol.py
, doing it elsewhere wont' work. Also, the path provided must be absolute:
- This is good:
python vol.py --plugins=/home/user/volpydev/myplugin -f <vmcore> myplugin
- This is not:
python vol.py -f <vmcore> --plugins=/home/user/volpydev/myplugin myplugin
- This isn't either:
python vol.py --plugins=volpydev/myplugin -f <vmcore> myplugin
The not-so-basics
Now we're going to make something with that memory dump and output something more useful than Hello world!
. Take this code:
import volatility.plugins.common as common
import volatility.utils as utils
import volatility.win32 as win32
class MyPlugin(common.AbstractWindowsCommand):
''' My plugin '''
def calculate(self):
addr_space = utils.load_as(self._config)
tasks = win32.tasks.pslist(addr_space)
return tasks
def render_text(self, outfd, data):
for task in data:
outfd.write("{!s}\n".format(str(task)))
Important things here:
''' My plugin'''
: This docstring will be written when calling your plugin with-h
(or--help
)def calculate(self)
: Here is where we do the real work, the returned data will be passed asdata
torender_text
. What we are doing is getting a list with all the running processes in the given memory dump's address space and return it.render_text
will iterate over that list and print whateverstr(task)
returns (It happens to be the virtual offset of the process' _EPROCESS internal structure, but that's not important right now).self._config
: This is the configuration object built for a single specific run of Volatility, it identifies the given memory dump's address space. It also contains other data such as passed arguments.
Now you can run it and see what it prints:
$ python vol.py --plugins=<myplugin-dir> -f <vmcore> --profile=<vmcore-profile> myplugin
Volatility Foundation Volatility Framework 2.6
2223228800
2238952424
2246946864
2238545192
2223723360
2238307624
2247151664
2247164976
...
This looks good, but we can do better! Let's print something that we can understand, take this render_text
function:
def render_text(self, outfd, data):
for task in data:
outfd.write("{0} {1}\n".format(task.UniqueProcessId, task.ImageFileName))
This will print every process's PID next to it's name. Go check it!
$ python vol.py --plugins=<myplugin-dir> -f <vmcore> --profile=<vmcore-profile> myplugin
Volatility Foundation Volatility Framework 2.6
4 System
276 smss.exe
352 csrss.exe
388 wininit.exe
396 csrss.exe
436 winlogon.exe
480 services.exe
488 lsass.exe
496 lsm.exe
604 svchost.exe
...
Now we're talking! This is great but, what if you want the results in greptext for an easier processing? Or even an sqlite3 database? We'll need the unified output for that, let's find out how!
Unified Output
Here we learn how to print the right way. render_text()
works for a quick fix but, if we want our plugin to offer the best we need unifed_output()
. This will centralize all the presenting results stuff into one function - actually two. However, if you want to do something "special" when printing on any specific format you can always define render_x()
- where x is any of the available formats - and do it as you like. Defining render_x()
actually overrides the default printing function. We've already done that, remember?
As I said, it's not just one function, but two: unified_output()
and generator()
. The latter will provide unified_output()
a generator with the data and unified_output()
will return a TreeGrid
object with that data and it's structure. An example is worth a thousand words, so:
def generator(self, data):
for task in data:
yield (0, [
int(task.UniqueProcessId),
str(task.ImageFileName)
])
def unified_output(self, data):
tree = [
("PID", int),
("Name", str)
]
return TreeGrid(tree, self.generator(data))
As you can see, TreeGrid
takes a tuple array and the generator returned by generator()
. This tuple contains the name and data type of each column. generator()
will return a generator with the process's PID and name. Note that the types of the data given to yield
in generator()
and the types in the tuple list must coincide. Let's see the result:
import volatility.plugins.common as common
import volatility.utils as utils
import volatility.win32 as win32
from volatility.renderers import TreeGrid
class MyPlugin(common.AbstractWindowsCommand):
''' My plugin '''
def calculate(self):
addr_space = utils.load_as(self._config)
tasks = win32.tasks.pslist(addr_space)
return tasks
def generator(self, data):
for task in data:
yield (0, [
int(task.UniqueProcessId),
str(task.ImageFileName)
])
def unified_output(self, data):
tree = [
("PID", int),
("Name", str)
]
return TreeGrid(tree, self.generator(data))
Now execute it and check that the results... are exactly the same as before. But hey! now we can get the results in other formats too. Try json for example (It'll output a minified JSON, I've beautified it):
$ python vol.py --plugins=<myplugin-dir> -f <vmcore> --profile=<vmcore-profile> myplugin --output=json
Volatility Foundation Volatility Framework 2.6
{
"rows":[
[
4,
"System"
],
[
276,
"smss.exe"
],
...
],
"columns":[
"PID",
"Name"
]
}
Other interesting things
Now we're doing useful work and using unified output, what else can we do? I'll teach you a couple interesting things but the best way to learn is by doing!
Parameters
A nice way to give your plugin some life is by taking parameters. We're gonna use the __init__()
function for this. For example, we want to give our plugin a prefix for all processes' names. Why? Why not! Here's how we do it:
def __init__(self, config, *args, **kwargs):
common.AbstractWindowsCommand.__init__(self, config, *args, **kwargs)
self._config.add_option('PREFIX', short_option = 'P', default = None, help = 'Prefix all names.',
action = 'store')
def calculate(self):
addr_space = utils.load_as(self._config)
tasks = win32.tasks.pslist(addr_space)
prefix = self._config.PREFIX
if prefix:
for task in tasks:
task.ImageFileName = str(prefix) + task.ImageFileName
return tasks
This how we define a new parameter:
'PREFIX'
: This is the parameter name, this way we'll be able to give the prefix via--prefix <prefix>
.short_option = 'P'
: Why write--prefix <prefix>
when we can do it shorter:-P <prefix>
.default = None
: If no PREFIX parameter given, self._config.PREFIX will be whatever we put here. In this case:None
help = 'Prefix all names'
: This is what will be written about this parameter when running the plugin with-h
- or--help
-.action = 'store'
: This indicates that the parameter takes a value behind, the prefix in this case. Instead ofstore
you can usestore_true
(giving the parameter will make it store True, or False) orappend
(multiple parameters will stack: -P hehe -P you ) too.
Use other plugins from inside yours
Please, don't reinvent the wheel. Volatility comes with a lot of plugins that might do part of the work your plugin needs, you simply need to call that other plugin from yours. Let's say you need a the Portable Executable that's in a process' address space, procdump
can do it for you:
def build_conf(self):
# Create conf obj
procdump_conf = conf.ConfObject()
# Define conf
procdump_conf.readonly = {}
procdump_conf.PROFILE = self._config.PROFILE
procdump_conf.LOCATION = self._config.LOCATION
procdump_conf.DUMP_DIR = tempfile.mkdtemp()
return procdump_conf
def calculate(self):
...
procdump_conf = self.build_dump_conf()
# Run ProcDump
p = procdump.ProcDump(procdump_conf)
p.execute()
# It's done, get rid of it
del p
...
You need to build a ConfObject
, just like the one that Volatility gives you in self._config
. Give that configuration object to the plugin you want to run, execute it and remember to delete it afterwards - you no longer need it.
Other community plugins
A nice way to learn and see what others can come up with wile developing a plugin is navigation through the Community Plugins GitHub.