From 8282df5893f24bb527fdbca3d08bf2aa557e0551 Mon Sep 17 00:00:00 2001 From: Michael Ligh Date: Sat, 17 Mar 2018 09:04:43 -0500 Subject: [PATCH] Initial versions of vadinfo and vaddump --- .../framework/symbols/windows/__init__.py | 17 ++ .../symbols/windows/extensions/__init__.py | 242 +++++++++++++++++- volatility/plugins/windows/vaddump.py | 75 ++++++ volatility/plugins/windows/vadinfo.py | 105 ++++++++ 4 files changed, 438 insertions(+), 1 deletion(-) create mode 100644 volatility/plugins/windows/vaddump.py create mode 100644 volatility/plugins/windows/vadinfo.py diff --git a/volatility/framework/symbols/windows/__init__.py b/volatility/framework/symbols/windows/__init__.py index 0409c4920..1e87b6728 100644 --- a/volatility/framework/symbols/windows/__init__.py +++ b/volatility/framework/symbols/windows/__init__.py @@ -31,6 +31,23 @@ class WindowsKernelIntermedSymbols(intermed.IntermediateSymbolTable): self.set_type_class('_CM_KEY_NODE', registry._CM_KEY_NODE) self.set_type_class('_CM_KEY_VALUE', registry._CM_KEY_VALUE) self.set_type_class('_HMAP_ENTRY', registry._HMAP_ENTRY) + self.set_type_class('_MMVAD_SHORT', extensions._MMVAD_SHORT) + self.set_type_class('_MMVAD', extensions._MMVAD) + + try: + self.set_type_class('_MMADDRESS_NODE', extensions._MMVAD_SHORT) + except ValueError: + pass + + try: + self.set_type_class('_MM_AVL_NODE', extensions._MMVAD_SHORT) + except ValueError: + pass + + try: + self.set_type_class('_RTL_BALANCED_NODE', extensions._MMVAD_SHORT) + except ValueError: + pass @classmethod def get_requirements(cls) -> typing.List[interfaces.configuration.RequirementInterface]: diff --git a/volatility/framework/symbols/windows/extensions/__init__.py b/volatility/framework/symbols/windows/extensions/__init__.py index f67ea1395..5aba07a04 100644 --- a/volatility/framework/symbols/windows/extensions/__init__.py +++ b/volatility/framework/symbols/windows/extensions/__init__.py @@ -11,6 +11,232 @@ vollog = logging.getLogger(__name__) # Keep these in a basic module, to prevent import cycles when symbol providers require them +class _MMVAD_SHORT(objects.Struct): + + def traverse(self, visited = None, depth = 0): + """Traverse the VAD tree, determining each underlying VAD node type by looking + up the tag in memory behind the structure (essentially the pool tag).""" + + if depth > 100: + vollog.log(constants.LOGLEVEL_VVV, "Vad tree is too deep, something went wrong!") + raise RuntimeError("Vad tree is too deep") + + if visited == None: + visited = set() + + vad_address = self.vol.offset + + if vad_address in visited: + vollog.log(constants.LOGLEVEL_VVV, "VAD node already seen!") + return + + visited.add(vad_address) + memory = self._context.memory[self.vol.layer_name] + + # the offset is different on 32 and 64 bits + symbol_table_name = self.vol.type_name.split(constants.BANG)[0] + if self._context.symbol_space.get_type(symbol_table_name + constants.BANG + "pointer").size == 4: + vad_address -= 4 + else: + vad_address -= 12 + + try: + tag = memory.read(vad_address, 4, pad = False).decode() + + if tag in ["VadS", "VadF"]: + target = "_MMVAD_SHORT" + elif tag.startswith("Vad"): + target = "_MMVAD" + elif depth == 0: + # the root node at depth 0 is allowed to not have a tag + # but we still want to continue and access its right & left child + target = None + else: + # any node other than the root that doesn't have a recognized tag + # is just garbage and we skip the node entirely + return + + if target: + vad_object = self.cast(target) + + setattr(vad_object, "Tag", tag) + yield vad_object + + except exceptions.InvalidAddressException: + return + except UnicodeDecodeError: + pass + + for vad_node in self.get_left_child().dereference().traverse(visited, depth + 1): + yield vad_node + + for vad_node in self.get_right_child().dereference().traverse(visited, depth + 1): + yield vad_node + + def get_right_child(self): + """Get the right child member""" + + if hasattr(self, "RightChild"): + return self.RightChild + + elif hasattr(self, "Right"): + return self.Right + + raise AttributeError("Unable to find the right child member") + + def get_left_child(self): + """Get the left child member""" + + if hasattr(self, "LeftChild"): + return self.LeftChild + + elif hasattr(self, "Left"): + return self.Left + + raise AttributeError("Unable to find the left child member") + + def get_parent(self): + """Get the VAD's parent member""" + + # this is for xp and 2003 + if hasattr(self, "Parent"): + return self.Parent + + # this is for vista through windows 7 + elif hasattr(self, "u1") and hasattr(self.u1, "Parent"): + return self.u1.Parent & ~0x3 + + # this is for windows 8 and 10 + elif hasattr(self, "VadNode"): + + if hasattr(self.VadNode, "u1"): + return self.VadNode.u1.Parent & ~0x3 + + elif hasattr(self.VadNode, "ParentValue"): + return self.VadNode.ParentValue & ~0x3 + + # also for windows 8 and 10 + elif hasattr(self, "Core"): + + if hasattr(self.Core.VadNode, "u1"): + return self.Core.VadNode.u1.Parent & ~0x3 + + elif hasattr(self.Core.VadNode, "ParentValue"): + return self.Core.VadNode.ParentValue & ~0x3 + + raise AttributeError("Unable to find the parent member") + + def get_start(self): + """Get the VAD's starting virtual address""" + + if hasattr(self, "StartingVpn"): + + if hasattr(self, "StartingVpnHigh"): + return (self.StartingVpn << 12) | (self.StartingVpnHigh << 44) + else: + return self.StartingVpn << 12 + + elif hasattr(self, "Core"): + + if hasattr(self.Core, "StartingVpnHigh"): + return (self.Core.StartingVpn << 12) | (self.Core.StartingVpnHigh << 44) + else: + return self.Core.StartingVpn << 12 + + raise AttributeError("Unable to find the starting VPN member") + + def get_end(self): + """Get the VAD's ending virtual address""" + + if hasattr(self, "EndingVpn"): + + if hasattr(self, "EndingVpnHigh"): + return (self.EndingVpn << 12) | (self.EndingVpnHigh << 44) + else: + return ((self.EndingVpn + 1) << 12) - 1 + + elif hasattr(self, "Core"): + + if hasattr(self.Core, "EndingVpnHigh"): + return (self.Core.EndingVpn << 12) | (self.Core.EndingVpnHigh << 44) + else: + return ((self.Core.EndingVpn + 1) << 12) - 1 + + raise AttributeError("Unable to find the ending VPN member") + + def get_commit_charge(self): + """Get the VAD's commit charge (number of committed pages)""" + + if hasattr(self, "u1") and hasattr(self.u1, "VadFlags1"): + return self.u1.VadFlags1.CommitCharge + + if hasattr(self, "u") and hasattr(self.u, "VadFlags"): + return self.u.VadFlags.CommitCharge + + elif hasattr(self, "Core"): + return self.Core.u1.VadFlags1.CommitCharge + + raise AttributeError("Unable to find the commit charge member") + + def get_private_memory(self): + """Get the VAD's private memory setting""" + + if hasattr(self, "u1") and hasattr(self.u1, "VadFlags1"): + return self.u1.VadFlags1.PrivateMemory + + if hasattr(self, "u") and hasattr(self.u, "VadFlags"): + return self.u.VadFlags.PrivateMemory + + elif hasattr(self, "Core"): + return self.Core.u1.VadFlags1.PrivateMemory + + raise AttributeError("Unable to find the private memory member") + + def get_protection(self, protect_values, winnt_protections): + """Get the VAD's protection constants as a string""" + + protect = None + + if hasattr(self, "u"): + protect = self.u.VadFlags.Protection + + elif hasattr(self, "Core"): + protect = self.Core.u.VadFlags.Protection + + value = protect_values[protect] + names = [] + + for name, mask in winnt_protections.items(): + if value & mask != 0: + names.append(name) + + return "|".join(names) + + def get_file_name(self): + """Only long(er) vads have mapped files""" + return "" # TODO: followup after decision around returning None + +class _MMVAD(_MMVAD_SHORT): + + def get_file_name(self): + """Get the name of the file mapped into the memory range (if any)""" + + file_name = "" # TODO: followup after decision around returning None + + try: + # this is for xp and 2003 + if hasattr(self, "ControlArea"): + file_name = self.ControlArea.FilePointer.FileName.get_string() + + # this is for vista through windows 7 + else: + file_name = self.Subsection.ControlArea.FilePointer.dereference().cast("_FILE_OBJECT").FileName.get_string() + + except exceptions.PagedInvalidAddressException: + pass + + return file_name + class _EX_FAST_REF(objects.Struct): """This is a standard Windows structure that stores a pointer to an object but also leverages the least significant bits to encode additional @@ -72,7 +298,7 @@ class _DEVICE_OBJECT(objects.Struct, ExecutiveObject): class _FILE_OBJECT(objects.Struct, ExecutiveObject): def file_name_with_device(self) -> str: - name = "" + name = "" # TODO: followup after decision around returning None if self._context.memory[self.vol.layer_name].is_valid(self.DeviceObject): name = "\\Device\\{}".format(self.DeviceObject.get_device_name()) @@ -236,6 +462,20 @@ class _EPROCESS(generic.GenericIntelProcess): return False return value != 0 and value != None + + def get_vad_root(self): + + # windows 8 and 2012 (_MM_AVL_TABLE) + if hasattr(self.VadRoot, "BalancedRoot"): + return self.VadRoot.BalancedRoot + + # windows 8.1 and windows 10 (_RTL_AVL_TREE) + elif hasattr(self.VadRoot, "Root"): + return self.VadRoot.Root.dereference() # .cast("_MMVAD") + + else: + # windows xp and 2003 + return self.VadRoot.dereference().cast("_MMVAD") class _LIST_ENTRY(objects.Struct, collections.abc.Iterable): diff --git a/volatility/plugins/windows/vaddump.py b/volatility/plugins/windows/vaddump.py new file mode 100644 index 000000000..04534caa3 --- /dev/null +++ b/volatility/plugins/windows/vaddump.py @@ -0,0 +1,75 @@ +import os +import volatility.framework.interfaces.plugins as interfaces_plugins +import volatility.plugins.windows.vadinfo as vadinfo +import volatility.plugins.windows.pslist as pslist +from volatility.framework import renderers +from volatility.framework.objects import utility +from volatility.framework.configuration import requirements +import logging + +vollog = logging.getLogger() + +class VadDump(interfaces_plugins.PluginInterface): + """Dumps process memory ranges""" + + @classmethod + def get_requirements(cls): + # Since we're calling the plugin, make sure we have the plugin's requirements + return vadinfo.VadInfo.get_requirements() + [requirements.StringRequirement(name = "outdir", + description = "Output directory", + default = None, + optional = False)] + + def _generator(self, procs): + + plugin = vadinfo.VadInfo(self.context, "plugins.VadDump") + chunk_size = 1024 * 1024 * 10 + + for proc in procs: + process_name = utility.array_to_string(proc.ImageFileName) + + # what kind of exceptions could this raise? + proc_layer_name = proc.add_process_layer(self.context) + proc_layer = self.context.memory[proc_layer_name] + + for vad in plugin.list_vads(proc): + try: + file_name = os.path.join(self.config["outdir"], + "pid.{0}.vad.{1:#x}-{2:#x}.dmp".format(proc.UniqueProcessId, vad.get_start(), vad.get_end())) + + with open(file_name, "wb") as handle: + offset = vad.get_start() + out_of_range = vad.get_start() + vad.get_end() + while offset < out_of_range: + to_read = min(chunk_size, out_of_range - offset) + data = proc_layer.read(offset, to_read, pad = True) + if not data: + break + handle.write(data) + offset += to_read + + result_text = "Saved to {}".format(os.path.basename(file_name)) + except: + result_text = "Unable to dump {0:#x} - {1:#x}".format(vad.get_start(), vad.get_end()) + + yield (0, (proc.UniqueProcessId, + process_name, + result_text)) + + def run(self): + + try: + # the optional=False requirement should make sure this always exists + os.makedirs(self.config["outdir"]) + except FileExistsError: + pass + except OSError: + # is this what we want to raise here? + raise OSError("Cannot create the desired output directory!") + + plugin = pslist.PsList(self.context, "plugins.VadDump") + + return renderers.TreeGrid([("PID", int), + ("Process", str), + ("Result", str)], + self._generator(plugin.list_processes())) diff --git a/volatility/plugins/windows/vadinfo.py b/volatility/plugins/windows/vadinfo.py new file mode 100644 index 000000000..80410cadb --- /dev/null +++ b/volatility/plugins/windows/vadinfo.py @@ -0,0 +1,105 @@ +import volatility.framework.interfaces.plugins as interfaces_plugins +import volatility.plugins.windows.pslist as pslist +from volatility.framework import renderers +from volatility.framework.renderers import format_hints +from volatility.framework.objects import utility +from volatility.framework.configuration import requirements +import logging + +vollog = logging.getLogger() + +# these are from WinNT.h +winnt_protections = { + "PAGE_NOACCESS": 0x01, + "PAGE_READONLY": 0x02, + "PAGE_READWRITE": 0x04, + "PAGE_WRITECOPY": 0x08, + "PAGE_EXECUTE": 0x10, + "PAGE_EXECUTE_READ": 0x20, + "PAGE_EXECUTE_READWRITE": 0x40, + "PAGE_EXECUTE_WRITECOPY": 0x80, + "PAGE_GUARD": 0x100, + "PAGE_NOCACHE": 0x200, + "PAGE_WRITECOMBINE": 0x400, + "PAGE_TARGETS_INVALID": 0x40000000, +} + +class VadInfo(interfaces_plugins.PluginInterface): + """Lists process memory ranges""" + + def __init__(self, context, config_path): + super().__init__(context, config_path) + self._protect_values = None + + @classmethod + def get_requirements(cls): + # Since we're calling the plugin, make sure we have the plugin's requirements + return pslist.PsList.get_requirements() + [ + # TODO: Convert this to a ListRequirement so that people can filter on sets of ranges + requirements.IntRequirement(name='address', + description="Process virtual memory address to include "\ + "(all other address ranges are excluded). This must be "\ + "a base address, not an address within the desired range.", + optional=True)] + + def protect_values(self): + """Look up the array of memory protection constants from the memory sample. + These don't change often, but if they do in the future, then finding them + # dynamically versus hard-coding here will ensure we parse them properly.""" + + if self._protect_values == None: + virtual_layer = self.config["primary"] + kvo = self.context.memory[virtual_layer].config["kernel_virtual_offset"] + ntkrnlmp = self.context.module(self.config["nt_symbols"], layer_name=virtual_layer, offset=kvo) + addr = ntkrnlmp.get_symbol("MmProtectToValue").address + values = ntkrnlmp.object(type_name="array", offset=kvo + addr, + subtype=ntkrnlmp.get_type("int"), + count=32) + self._protect_values = values + + return self._protect_values + + def list_vads(self, proc): + + filter = lambda _: False + if self.config.get('address', None) is not None: + filter = lambda x: x.get_start() not in [self.config['address']] + + for vad in proc.get_vad_root().traverse(): + if not filter(vad): + yield vad + + def _generator(self, procs): + + for proc in procs: + process_name = utility.array_to_string(proc.ImageFileName) + + for vad in self.list_vads(proc): + yield (0, (proc.UniqueProcessId, + process_name, + format_hints.Hex(vad.vol.offset), + format_hints.Hex(vad.get_start()), + format_hints.Hex(vad.get_end()), + vad.Tag, + vad.get_protection(self.protect_values(), winnt_protections), + vad.get_commit_charge(), + vad.get_private_memory(), + format_hints.Hex(vad.get_parent()), + vad.get_file_name())) + + def run(self): + + plugin = pslist.PsList(self.context, "plugins.VadInfo") + + return renderers.TreeGrid([("PID", int), + ("Process", str), + ("Offset", format_hints.Hex), + ("Start VPN", format_hints.Hex), + ("End VPN", format_hints.Hex), + ("Tag", str), + ("Protection", str), + ("CommitCharge", int), + ("PrivateMemory", int), + ("Parent", format_hints.Hex), + ("File", str)], + self._generator(plugin.list_processes()))