mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-08-23 22:52:23 +02:00
Initial versions of vadinfo and vaddump
This commit is contained in:
@@ -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]:
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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()))
|
||||
@@ -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()))
|
||||
Reference in New Issue
Block a user