11 minutes
Tutorial - Volatility plugins & malware analysis
The benefits of analyzing malware in live memory are well known. What we’ll see here is how to leverage the power of the Volatility framework to automate the task of extracting a malware’s configuration file.
Prerequisites
- A working installation of Volatility 2.5, Yara 3.5.0, and python-yara
- Basic knowledge of Python and Volatility
- Basic knowledge of how malware behaves in a system
Useful links
- Volatility and its wiki (very well documented)
- Yara (on GitHub) and especially its documentation
- Cisco Talos blogpost on Locky configuration files
How plugins work
The Calculate function
This is the main plugin core. The goal of the function is to perform all the
heavy lifting and to provide structured output to a render
function (such as
render_text
)
The render function
This takes the output provided by the calculate function and renders it according to its type.
render_text
will render it as textrender_table
will make a tablerender_custom_format
will render the output using a custom output format
output format can be called with --output [text|table|custom_format]
A note on “list” vs. “scan” plugins
Volatility has two main approaches to plugins, which are sometimes reflected in
their names. “list” plugins will try to navigate through Windows Kernel
structures to retrieve information like processes (locate and walk the linked
list of _EPROCESS
structures in memory), OS handles (locating and listing the
handle table, dereferencing any pointers found, etc). They more or less behave
like the Windows API would if requested to, for example, list processes.
That makes “list” plugins pretty fast, but just as vulnerable as the Windows API
to manipulation by malware. For instance, if malware uses DKOM to unlink a
process from the _EPROCESS
linked list, it won’t show up in the Task Manager
and neither will it in the pslist.
“scan” plugins, on the other hand, will take an approach similar to carving the
memory for things that might make sense when dereferenced as specific
structures. psscan
for instance will read the memory and try to make out
_EPROCESS
objects out of it (it uses pool-tag scanning, which is basically
searching for 4-byte strings that indicate the presence of a structure of
interest). The advantage is that it can dig up processes that have exited, and
even if malware tampers with the _EPROCESS
linked list, the plugin will still
find the structure lying around in memory (since it still needs to exist for the
process to run). The downfall is that “scan” plugins are a bit slower than
“list” plugins, and can sometimes yield false-positives (a process that exited
too long ago and had parts of its structure overwritten by other operations).
Yara signatures FTW
Scanning the memory for structures can take some time, especially now that
memory dumps can regularly be over 8 GB in size, especially when the structures
you’re looking for are small or don’t have lots of constraints. A good approach
for searching for specific patterns in memory are Yara rules, which can be
leveraged with the yarascan
plugin. Besides being faster, it has the added
bonus of being able to be re-used as Yara rules outside Volatility!
Hands-on: Building a Locky configuration extractor
Implementation strategy
Based on available information, the implementation strategy for your plugin may change. Unfortunately, there is no silver bullet for locating interesting stuff in memory. You’ll need to have a basic understanding of how your sample works and what it does in memory; depending on your reverse engineering skills this might be more or less easy.
As most forensic investigations, it’s important to have anchor or pivot points. In the case of most plugins bundled with Volatility, these anchor points are the Kernel. Since malware does not register its crown jewels in Kernel entries, you’ll have to work through the sample in order to find how it references interesting data, and try to find the point where it references it in a way where it is as generic as possible.
In the case of Locky, our analysis tells us that one of the places it accesses its configuration looks like this:
This looks like a good place to start.
- Based on the opcodes relevant to our code, use
yarascan
to find the interesting process
$ vol.py -f affid_3_dga_87233.mem --profile=Win7SP1x64 yarascan -Y "{A1 ?? ?? ?? ?? 8B 40 08 85 C0}"
Volatility Foundation Volatility Framework 2.5
Rule: r1
Owner: Process svchost.exe Pid 2308
[snip]
Rule: r1
Owner: Process svchost.exe Pid 2308
[snip]
Rule: r1
Owner: Process rundll32.exe Pid 1456
0x73e24eac a1 70 23 e4 73 8b 40 08 85 c0 74 0d 69 c0 e8 03 .p#.s.@...t.i...
0x73e24ebc 00 00 50 ff 15 28 71 e3 73 8d 45 b4 50 e8 04 20 ..P..(q.s.E.P...
0x73e24ecc 00 00 59 50 bb 74 10 e4 73 8b c3 c6 45 fc 01 e8 ..YP.t..s...E...
0x73e24edc f3 15 00 00 6a 01 33 ff 8d 75 b4 c6 45 fc 00 e8 ....j.3..u..E...
0x73e24eec ec 1a 00 00 e8 51 fe ff ff 84 c0 0f 85 9b 04 00 .....Q..........
0x73e24efc 00 83 3d 88 10 e4 73 10 a1 74 10 e4 73 73 02 8b ..=...s..t..ss..
[snip]
- Start
volshell
and get to the point where we’re visualizing the assembly code.
In [1]: cc(pid=1456)
Current context: rundll32.exe @ 0xfffffa8003b55060, pid=1456, ppid=2220 DTB=0x74f39000
In [2]: db(0x73e24eac)
0x73e24eac a1 70 23 e4 73 8b 40 08 85 c0 74 0d 69 c0 e8 03 .p#.s.@...t.i...
0x73e24ebc 00 00 50 ff 15 28 71 e3 73 8d 45 b4 50 e8 04 20 ..P..(q.s.E.P...
0x73e24ecc 00 00 59 50 bb 74 10 e4 73 8b c3 c6 45 fc 01 e8 ..YP.t..s...E...
0x73e24edc f3 15 00 00 6a 01 33 ff 8d 75 b4 c6 45 fc 00 e8 ....j.3..u..E...
0x73e24eec ec 1a 00 00 e8 51 fe ff ff 84 c0 0f 85 9b 04 00 .....Q..........
0x73e24efc 00 83 3d 88 10 e4 73 10 a1 74 10 e4 73 73 02 8b ..=...s..t..ss..
0x73e24f0c c3 50 ff 15 44 71 e3 73 66 85 c0 0f 85 7b 04 00 .P..Dq.sf....{..
0x73e24f1c 00 83 3d 88 10 e4 73 10 72 06 8b 1d 74 10 e4 73 ..=...s.r...t..s
Extracting relevant information from memory
We found the instance of our code in memory. Time to access the relevant data! We’re going to need to read raw bytes from memory and also be able to dereference pointers. The bytes we’re after are the address of the pointer to the configuration file, the ones which were left as wildcards in the Yara rule.
0x73e24eac a1 [70 23 e4 73] 8b 40 08 85 c0 74 0d 69 c0 e8 03 .p#.s.@...t.i...
This is 0x73e42370
in little endian. What’s the value of this variable?
In [4]: db(0x73e42370, 4)
0x73e42370 00 00 16 00 ....
In little endian again, this means that our configuration file is located at
0x00160000
. This nice, round, and page-aligned number also happens to be it’s
own memory segment. Check the output of vaddump
, you’ll see something like
rundll32.exe.99155060.0x0000000000160000-0x0000000000166fff.dmp
which actually
contains all of our configuration file.
What’s at address 0x00160000
?
In [5]: db(0x00160000)
0x00160000 01 00 00 00 c1 54 01 00 19 00 00 00 00 00 01 2f .....T........./
0x00160010 61 70 61 63 68 65 5f 68 61 6e 64 6c 65 72 2e 70 apache_handler.p
0x00160020 68 70 00 00 00 00 00 00 00 00 00 00 00 00 00 00 hp..............
0x00160030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 34 ...............4
0x00160040 36 2e 38 2e 34 34 2e 31 30 35 2c 39 31 2e 32 31 6.8.44.105,91.21
0x00160050 39 2e 32 38 2e 37 36 2c 31 38 38 2e 31 32 30 2e 9.28.76,188.120.
0x00160060 32 33 36 2e 32 31 2c 32 31 37 2e 31 32 2e 32 32 236.21,217.12.22
0x00160070 33 2e 37 38 2c 34 36 2e 31 38 33 2e 32 32 31 2e 3.78,46.183.221.
That’s starting to look like something!
Since it can become quite tedious to copy and paste values from vollshell
, so
we’re going to use two simple functions to read bytes from memory and
dereference pointers. Copy and paste the following lines into the prompt:
a = self._proc.get_process_address_space()
def read_bytes(aspace, a, length=4):
return a.read(address, length)
def deref(address, a, length=4):
return struct.unpack("<I", a.read(address, length))[0]
a = self._proc.get_process_address_space()
sets an object to the process' address space (so that we can reference memory using the processes virtual memory addressing)read_bytes
just reads bytes at a given address and returns it as a binary bufferderef
will read 4 bytes at a given address and return the pointer value at that address
See for yourself:
In [23]: config_ptr = deref(0x73e24eac + 1, a)
In [24]: hex(config_ptr)
Out[24]: '0x73e42370'
In [25]: conf_object = deref(config_ptr, a)
In [26]: db(conf_object)
0x00160000 01 00 00 00 c1 54 01 00 19 00 00 00 00 00 01 2f .....T........./
0x00160010 61 70 61 63 68 65 5f 68 61 6e 64 6c 65 72 2e 70 apache_handler.p
0x00160020 68 70 00 00 00 00 00 00 00 00 00 00 00 00 00 00 hp..............
0x00160030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 34 ...............4
0x00160040 36 2e 38 2e 34 34 2e 31 30 35 2c 39 31 2e 32 31 6.8.44.105,91.21
0x00160050 39 2e 32 38 2e 37 36 2c 31 38 38 2e 31 32 30 2e 9.28.76,188.120.
0x00160060 32 33 36 2e 32 31 2c 32 31 37 2e 31 32 2e 32 32 236.21,217.12.22
0x00160070 33 2e 37 38 2c 34 36 2e 31 38 33 2e 32 32 31 2e 3.78,46.183.221.
Thanks to Michael Ligh for pointing this out: there is a helper Volatility class for dereferencing pointers as structures.
For example, if you wanted to read an address and dereference it as a pointer to a
_LockyConfig
, you could do this:ptr = obj.Object("address", offset = offset, vm = a) config = ptr.dereference_as("_LockyConfig")
The size of an “address” will be automatically read from the profile (so 4 bytes on 32-bit and 8 bytes on 64-bit). You can also define pointers to values in the structure definitions (
vtypes
).
Building your plugin
So now that we understand how to go from a raw memory dump to the interesting data, let’s try to automate it! Here’s what our plugin will start looking like the contents in volatility-locky.py.00
Baby steps
Make sure that volatility loads it:
$ vol.py --plugins=./plugin/ -f affid_3_dga_87233.mem -h | grep locky
Volatility Foundation Volatility Framework 2.5
lockyconfig Searches for Locky configs in memory
Let’s make it a little more useful. The idea is to:
- iterate though all processes - use the
tasks.pslist(addr_space)
generator which yieldstask
objects - for each process, iterate through its VADs - use
task.get_vads()
generator, which yields(vad, address_space)
tuples - reach each VAD’s content and match it against our Yara rule - read the
address_space with
process_space.zread(vad.Start, vad.Length)
Check out volatility-locky.py.01_partial for hints and Yara-related commands, and volatility-locky.py.01 for the final solution.
Notice we’re not using render_text
yet; we’ll get back to this a little later.
Dereferencing memory inside plugins
Remember when we used volshell to derefence memory? Let’s try to automate this so we don’t have to copy and paste the commands for each candidate (ie. match) we find.
See volatility-locky.py.02_partial for hints and volatility-locky.py.02 for working code.
Using vtypes to convert objects
Now that we have the address for our configuration file, we can easily parse it
and use the render_output
function to give the user some feedback. Before
that, let’s explore a cool feature of Volatiliy : vtypes. vtypes are
structures that can be “applied” to memory, yielding objects that can then be
easily manipulated. The advantage of using vtypes over standard string slicing
to parse structures is manyfold:
- It’s auto-documenting
- It’s easier to maintain should the structure change in the future
- It’s easier for a third party to understand what’s going on
- No need for special code for strings, integers (unpacking), binary code, etc.
vtypes look like C structures. The Cisco Talos blog has conveniently translated Locky structures into C:
typedef struct _LockyConfigHeader {
DWORD affilID;
DWORD DGASeed;
DWORD Delay;
BYTE PersistSvchost;
BYTE PersistRegistry;
BYTE IgnoreRussian;
} LOCKY_CONFIG_HEADER;
typedef struct _LockyConfig {
LOCKY_CONFIG_HEADER Header;
CHAR CallbackPath[48];
CHAR C2Servers[4096];
DWORD RsaKeyID;
DWORD RsaKeySizeBytes;
PUBLICKEYSTRUC RsaKeyStruct;
RSAPUBKEY RsaKeyHdr;
BYTE RsaKeyData[1080];
CHAR RansomNote[0x1000];
CHAR HtmlRansom[0x3000];
} LOCKY_CONFIG;
The good news is that vtypes are pretty easy to build. To make things easier, load the VAD dump corresponding to the configuration file in a hex editor (you’ll be playing around with offsets in that file, which correspond to the offsets you will be defining in the vtype).
A volatility vtype looks like this:
locky_config = {
"_LOCKY_CONFIG": [<TOTAL_STRUCTURE_SIZE>, {
'<MEMBER_NAME>': [<MEMBER_OFFSET>, <MEMBER_TYPE>],
'<MEMBER_NAME>': [<MEMBER_OFFSET>, <MEMBER_TYPE>],
'<MEMBER_NAME>': [<MEMBER_OFFSET>, <MEMBER_TYPE>],
[...],
}]
}
- MEMBER_NAME: the name of the structure member; it will be accessed using
<object>.<member_name>
in your code - MEMBER_OFFSET: the offset from the start of the structure (ie. the cumulative size of previous members)
- MEMBER_TYPE: how volatility should interpret this member:
['int']
,['char']
,['String', dict(length=0x30)]
,…['int']
- your typical integer (4 bytes long)['char']
- a DWORD (4 bytes long)['String', dict(length=0x30)]
- a string. The dict after it specifies the length of the string (n bytes long, as defined)['array', 1080, ['unsigned char']]
- an array ofunsigned chars
.
Check out volatility-locky.py.03_partial for sample code, and fill in the vtype. Check out volatility-locky.py.03 for a complete vtype.
Rendering the output
Time to use render text and take those nasty print statements out of the
calculate
function! You need to make a minor adjustment to calculate
: is has
to yield
any data that will be passed on to the render
function (thus
becoming a generator).
Check out files volatility-locky.py.04_partial and volatility-locky.py.04 for solutions.