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

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 text
  • render_table will make a table
  • render_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:

Interesting assemly code

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 buffer

  • deref 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 yields task 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 of unsigned 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.