Move all core plugins over to framework/plugins.

This should have no impact functionality-wise.
The statistics plugin was left out a) as an example and b) because it
was committed by mistake in the first place and was never meant to be a
real plugin.
This commit is contained in:
Mike Auty
2018-12-16 13:40:15 +00:00
parent 9824538bd9
commit 35ad2325a8
38 changed files with 9 additions and 0 deletions
@@ -0,0 +1,56 @@
from typing import List
import volatility.framework.constants as constants
import volatility.framework.interfaces.plugins as interfaces_plugins
from volatility.framework import exceptions, renderers, interfaces
from volatility.framework.configuration import requirements
from volatility.framework.objects import utility
from volatility.plugins.windows import pslist
class CmdLine(interfaces_plugins.PluginInterface):
"""Lists process command line arguments"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS")]
def _generator(self, procs):
for proc in procs:
process_name = utility.array_to_string(proc.ImageFileName)
# TODO: what kind of exceptions could this raise and what should we do?
proc_layer_name = proc.add_process_layer()
try:
peb = self._context.object(self.config["nt_symbols"] + constants.BANG + "_PEB",
layer_name = proc_layer_name,
offset = proc.Peb)
result_text = peb.ProcessParameters.CommandLine.get_string()
except exceptions.SwappedInvalidAddressException as exp:
result_text = "Required memory at {0:#x} is inaccessible (swapped)".format(exp.invalid_address)
except exceptions.PagedInvalidAddressException as exp:
result_text = "Required memory at {0:#x} is not valid (process exited?)".format(exp.invalid_address)
yield (0, (proc.UniqueProcessId,
process_name,
result_text))
def run(self):
filter_func = pslist.PsList.create_filter([self.config.get('pid', None)])
return renderers.TreeGrid([("PID", int),
("Process", str),
("Args", str)],
self._generator(pslist.PsList.list_processes(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'],
filter_func = filter_func)))
@@ -0,0 +1,104 @@
import logging
import ntpath
from typing import List
import volatility.framework.constants as constants
import volatility.framework.interfaces.plugins as interfaces_plugins
import volatility.plugins.windows.pslist as pslist
import volatility.plugins.windows.vadinfo as vadinfo
from volatility.framework import interfaces
from volatility.framework import renderers
from volatility.framework.configuration import requirements
from volatility.framework.objects import utility
from volatility.framework.symbols.windows.pe import PEIntermedSymbols
vollog = logging.getLogger(__name__)
class DllDump(interfaces_plugins.PluginInterface):
"""Dumps process memory ranges as DLLs"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS"),
# 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 _generator(self, procs):
pe_table_name = PEIntermedSymbols.create(self.context,
self.config_path,
"windows",
"pe")
filter_func = lambda _: False
if self.config.get('address', None) is not None:
filter_func = lambda x: x.get_start() not in [self.config['address']]
for proc in procs:
process_name = utility.array_to_string(proc.ImageFileName)
# TODO: what kind of exceptions could this raise and what should we do?
proc_layer_name = proc.add_process_layer()
for vad in vadinfo.VadInfo.list_vads(proc, filter_func = filter_func):
# this parameter is inherited from the VadInfo plugin. if a user specifies
# an address, then it bypasses the DLL identification heuristics
if self.config.get("address", None) is None:
# rather than relying on the PEB for DLLs, which can be swapped,
# it requires special handling on wow64 processes, and its
# unreliable from an integrity standpoint, let's use the VADs instead
protection_string = vad.get_protection(vadinfo.VadInfo.protect_values(self.context,
self.config['primary'],
self.config['nt_symbols']),
vadinfo.winnt_protections)
# DLLs are write copy...
if protection_string != "PAGE_EXECUTE_WRITECOPY":
continue
# DLLs have mapped files...
if isinstance(vad.get_file_name(), interfaces.renderers.BaseAbsentValue):
continue
try:
filedata = interfaces_plugins.FileInterface(
"pid.{0}.{1}.{2:#x}.dmp".format(proc.UniqueProcessId,
ntpath.basename(vad.get_file_name()),
vad.get_start()))
dos_header = self.context.object(pe_table_name + constants.BANG +
"_IMAGE_DOS_HEADER", offset = vad.get_start(),
layer_name = proc_layer_name)
for offset, data in dos_header.reconstruct():
filedata.data.seek(offset)
filedata.data.write(data)
self.produce_file(filedata)
result_text = "Stored {}".format(filedata.preferred_filename)
except Exception:
result_text = "Unable to dump PE at {0:#x}".format(vad.get_start())
yield (0, (proc.UniqueProcessId,
process_name,
result_text))
def run(self):
filter_func = pslist.PsList.create_filter([self.config.get('pid', None)])
return renderers.TreeGrid([("PID", int),
("Process", str),
("Result", str)],
self._generator(pslist.PsList.list_processes(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'],
filter_func = filter_func)))
@@ -0,0 +1,54 @@
from typing import List
import volatility.framework.interfaces.plugins as interfaces_plugins
from volatility.framework import exceptions, renderers, interfaces
from volatility.framework.configuration import requirements
from volatility.framework.renderers import format_hints
from volatility.plugins.windows import pslist
class DllList(interfaces_plugins.PluginInterface):
"""Lists the loaded modules in a particular windows memory image"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS")]
def _generator(self, procs):
for proc in procs:
for entry in proc.load_order_modules():
BaseDllName = FullDllName = renderers.UnreadableValue()
try:
BaseDllName = entry.BaseDllName.get_string()
# We assume that if the BaseDllName points to an invalid buffer, so will FullDllName
FullDllName = entry.FullDllName.get_string()
except exceptions.InvalidAddressException:
pass
yield (0, (proc.UniqueProcessId,
proc.ImageFileName.cast("string", max_length = proc.ImageFileName.vol.count,
errors = 'replace'),
format_hints.Hex(entry.DllBase), format_hints.Hex(entry.SizeOfImage),
BaseDllName, FullDllName))
def run(self):
filter_func = pslist.PsList.create_filter([self.config.get('pid', None)])
return renderers.TreeGrid([("PID", int),
("Process", str),
("Base", format_hints.Hex),
("Size", format_hints.Hex),
("Name", str),
("Path", str)],
self._generator(pslist.PsList.list_processes(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'],
filter_func = filter_func)))
@@ -0,0 +1,319 @@
import logging
from typing import Optional
import volatility.framework.interfaces.plugins as interfaces_plugins
import volatility.plugins.windows.pslist as pslist
from volatility.framework import constants, exceptions, renderers, interfaces
from volatility.framework.configuration import requirements
from volatility.framework.objects import utility
from volatility.framework.renderers import format_hints
vollog = logging.getLogger(__name__)
try:
import capstone
has_capstone = True
except ImportError:
has_capstone = False
class Handles(interfaces_plugins.PluginInterface):
"""Lists process open handles"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._sar_value = None
self._type_map = None
self._cookie = None
self._level_mask = 7
@classmethod
def get_requirements(cls):
# Since we're calling the plugin, make sure we have the plugin's requirements
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS")]
def _decode_pointer(self, value, magic):
"""Windows encodes pointers to objects and decodes them on the fly
before using them. This function mimics the decoding routine so we
can generate the proper pointer values as well."""
value = value & 0xFFFFFFFFFFFFFFF8
value = value >> magic
# if (value & (1 << 47)):
# value = value | 0xFFFF000000000000
return value
def _get_item(self, handle_table_entry, handle_value):
"""Given a handle table entry (_HANDLE_TABLE_ENTRY) structure from
a process' handle table, determine where the corresponding object's
_OBJECT_HEADER can be found."""
virtual = self.config["primary"]
try:
# before windows 7
if not self.context.memory[virtual].is_valid(handle_table_entry.Object):
return None
fast_ref = handle_table_entry.Object.cast(self.config["nt_symbols"] + constants.BANG + "_EX_FAST_REF")
object_header = fast_ref.dereference().cast(self.config["nt_symbols"] + constants.BANG + "_OBJECT_HEADER")
object_header.GrantedAccess = handle_table_entry.GrantedAccess
except AttributeError:
# starting with windows 8
if handle_table_entry.LowValue == 0:
return None
magic = self.find_sar_value()
# is this the right thing to raise here?
if magic == None:
raise AttributeError("Unable to find the SAR value for decoding handle table pointers")
offset = self._decode_pointer(handle_table_entry.LowValue, magic)
# print("LowValue: {0:#x} Magic: {1:#x} Offset: {2:#x}".format(handle_table_entry.InfoTable, magic, offset))
object_header = self.context.object(self.config["nt_symbols"] + constants.BANG + "_OBJECT_HEADER", virtual,
offset = offset)
object_header.GrantedAccess = handle_table_entry.GrantedAccessBits
object_header.HandleValue = handle_value
return object_header
def find_sar_value(self):
"""Locate ObpCaptureHandleInformationEx if it exists in the
sample. Once found, parse it for the SAR value that we need
to decode pointers in the _HANDLE_TABLE_ENTRY which allows us
to find the associated _OBJECT_HEADER."""
if self._sar_value is None:
if not has_capstone:
return None
virtual_layer_name = self.config['primary']
kvo = self.context.memory[virtual_layer_name].config['kernel_virtual_offset']
ntkrnlmp = self.context.module(self.config["nt_symbols"], layer_name = virtual_layer_name, offset = kvo)
try:
func_addr = ntkrnlmp.get_symbol("ObpCaptureHandleInformationEx").address
except exceptions.SymbolError:
return None
data = self.context.memory.read(virtual_layer_name, kvo + func_addr, 0x200)
if data == None:
return None
md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64)
for (address, size, mnemonic, op_str) in md.disasm_lite(data, kvo + func_addr):
# print("{} {} {} {}".format(address, size, mnemonic, op_str))
if mnemonic.startswith("sar"):
# if we don't want to parse op strings, we can disasm the
# single sar instruction again, but we use disasm_lite for speed
self._sar_value = int(op_str.split(",")[1].strip(), 16)
break
return self._sar_value
@classmethod
def list_objects(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str) -> dict:
"""List the executive object types (_OBJECT_TYPE) using the
ObTypeIndexTable or ObpObjectTypes symbol (differs per OS).
This method will be necessary for determining what type of
object we have given an object header.
Note: The object type index map was hard coded into profiles
in vol2, but we generate it dynamically now."""
type_map = {}
kvo = context.memory[layer_name].config['kernel_virtual_offset']
ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo)
try:
table_addr = ntkrnlmp.get_symbol("ObTypeIndexTable").address
except exceptions.SymbolError:
table_addr = ntkrnlmp.get_symbol("ObpObjectTypes").address
ptrs = ntkrnlmp.object(type_name = "array", offset = kvo + table_addr,
subtype = ntkrnlmp.get_type("pointer"),
count = 100)
for i, ptr in enumerate(ptrs):
# the first entry in the table is always null. break the
# loop when we encounter the first null entry after that
if i > 0 and ptr == 0:
break
objt = ptr.dereference().cast(symbol_table + constants.BANG + "_OBJECT_TYPE")
try:
type_name = objt.Name.String
except exceptions.PagedInvalidAddressException:
vollog.log(constants.LOGLEVEL_VVV,
"Cannot access _OBJECT_HEADER.Name at {0:#x}".format(objt.Name.vol.offset))
continue
type_map[i] = type_name
return type_map
@classmethod
def find_cookie(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str) -> Optional[interfaces.objects.ObjectInterface]:
"""Find the ObHeaderCookie value (if it exists)"""
try:
offset = context.symbol_space.get_symbol(
symbol_table + constants.BANG + "ObHeaderCookie").address
except exceptions.SymbolError:
return None
kvo = context.memory[layer_name].config['kernel_virtual_offset']
return context.object(symbol_table + constants.BANG + "unsigned int",
layer_name, offset = kvo + offset)
def _make_handle_array(self, offset, level, depth = 0):
"""Parse a process' handle table and yield valid handle table
entries, going as deep into the table "levels" as necessary."""
virtual = self.config["primary"]
kvo = self.context.memory[virtual].config['kernel_virtual_offset']
ntkrnlmp = self.context.module(self.config["nt_symbols"], layer_name = virtual, offset = kvo)
if level > 0:
subtype = ntkrnlmp.get_type("pointer")
count = 0x1000 / subtype.size
else:
subtype = ntkrnlmp.get_type("_HANDLE_TABLE_ENTRY")
count = 0x1000 / subtype.size
if not self.context.memory[virtual].is_valid(offset):
return
table = ntkrnlmp.object(type_name = "array", offset = offset,
subtype = subtype, count = int(count))
layer_object = self.context.memory[virtual]
masked_offset = (offset & layer_object.maximum_address)
for entry in table:
if level > 0:
for x in self._make_handle_array(entry, level - 1, depth):
yield x
depth += 1
else:
handle_multiplier = 4
handle_level_base = depth * count * handle_multiplier
handle_value = ((entry.vol.offset - masked_offset) /
(subtype.size / handle_multiplier)) + handle_level_base
item = self._get_item(entry, handle_value)
if item == None:
continue
try:
if item.TypeIndex != 0x0:
yield item
except AttributeError:
if item.Type.Name:
yield item
except exceptions.PagedInvalidAddressException:
continue
def handles(self, handle_table):
try:
TableCode = handle_table.TableCode & ~self._level_mask
table_levels = handle_table.TableCode & self._level_mask
except exceptions.PagedInvalidAddressException:
vollog.log(constants.LOGLEVEL_VVV, "Handle table parsing was aborted due to an invalid address exception")
return
for handle_table_entry in self._make_handle_array(TableCode, table_levels):
yield handle_table_entry
def _generator(self, procs):
type_map = self.list_objects(context = self.context,
layer_name = self.config["primary"],
symbol_table = self.config["nt_symbols"])
cookie = self.find_cookie(context = self.context,
layer_name = self.config["primary"],
symbol_table = self.config["nt_symbols"])
for proc in procs:
try:
object_table = proc.ObjectTable
except exceptions.PagedInvalidAddressException:
vollog.log(constants.LOGLEVEL_VVV,
"Cannot access _EPROCESS.ObjectType at {0:#x}".format(proc.ObjectTable.vol.offset))
continue
process_name = utility.array_to_string(proc.ImageFileName)
for entry in self.handles(object_table):
try:
obj_type = entry.get_object_type(type_map, cookie)
if obj_type == None:
continue
if obj_type == "File":
item = entry.Body.cast(self.config["nt_symbols"] + constants.BANG + "_FILE_OBJECT")
obj_name = item.file_name_with_device()
elif obj_type == "Process":
item = entry.Body.cast(self.config["nt_symbols"] + constants.BANG + "_EPROCESS")
obj_name = "{} Pid {}".format(utility.array_to_string(proc.ImageFileName),
item.UniqueProcessId)
elif obj_type == "Thread":
item = entry.Body.cast(self.config["nt_symbols"] + constants.BANG + "_ETHREAD")
obj_name = "Tid {} Pid {}".format(item.Cid.UniqueThread, item.Cid.UniqueProcess)
elif obj_type == "Key":
item = entry.Body.cast(self.config["nt_symbols"] + constants.BANG + "_CM_KEY_BODY")
obj_name = item.get_full_key_name()
else:
try:
obj_name = entry.NameInfo.Name.String
except exceptions.InvalidAddressException:
obj_name = ""
except (exceptions.InvalidAddressException):
vollog.log(constants.LOGLEVEL_VVV,
"Cannot access _OBJECT_HEADER at {0:#x}".format(entry.vol.offset))
continue
yield (0, (proc.UniqueProcessId,
process_name,
format_hints.Hex(entry.HandleValue),
obj_type,
format_hints.Hex(entry.GrantedAccess),
obj_name))
def run(self):
filter_func = pslist.PsList.create_filter([self.config.get('pid', None)])
return renderers.TreeGrid([("PID", int),
("Process", str),
("HandleValue", format_hints.Hex),
("Type", str),
("GrantedAccess", format_hints.Hex),
("Name", str)],
self._generator(pslist.PsList.list_processes(self.context,
self.config['primary'],
self.config['nt_symbols'],
filter_func = filter_func)))
@@ -0,0 +1,139 @@
import time
from typing import List
import volatility.framework.interfaces.plugins as plugins
from volatility.framework import constants, interfaces, layers
from volatility.framework.configuration import requirements
from volatility.framework.renderers import TreeGrid
from volatility.framework.symbols.windows.kdbg import KdbgIntermedSymbols
from volatility.framework.symbols.windows.pe import PEIntermedSymbols
class Info(plugins.PluginInterface):
"""Show OS & kernel details of the memory sample being analyzed"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS")]
def get_depends(self, layer_name: str, index: int = 0):
"""List the dependencies of a given layer.
Args:
layer_name: the name of the starting layer
index: the index/order of the layer
"""
layer = self.context.memory[layer_name]
yield index, layer
try:
for depends in layer.dependencies:
for j, dep in self.get_depends(depends, index + 1):
yield j, self.context.memory[dep.name]
except AttributeError:
# FileLayer won't have dependencies
pass
def _generator(self):
virtual_layer_name = self.config["primary"]
virtual_layer = self.context.memory[virtual_layer_name]
if not isinstance(virtual_layer, layers.intel.Intel):
raise TypeError("Virtual Layer is not an intel layer")
native_types = self.context.symbol_space[self.config["nt_symbols"]].natives
kdbg_table_name = KdbgIntermedSymbols.create(self.context,
self.config_path,
"windows",
"kdbg",
native_types = native_types)
pe_table_name = PEIntermedSymbols.create(self.context,
self.config_path,
"windows",
"pe")
kvo = virtual_layer.config["kernel_virtual_offset"]
ntkrnlmp = self.context.module(self.config["nt_symbols"],
layer_name = virtual_layer_name, offset = kvo)
kdbg_offset = ntkrnlmp.get_symbol("KdDebuggerDataBlock").address
kdbg = self.context.object(kdbg_table_name + constants.BANG +
"_KDDEBUGGER_DATA64", offset = kvo + kdbg_offset,
layer_name = virtual_layer_name)
yield (0, ("Memory Location", self.config["primary.memory_layer.location"]))
yield (0, ("Kernel Base", hex(self.config["primary.kernel_virtual_offset"])))
yield (0, ("DTB", hex(self.config["primary.page_map_offset"])))
yield (0, ("Symbols", self.config["nt_symbols.isf_url"]))
for i, layer in self.get_depends("primary"):
yield (0, (layer.name, "{} {}".format(i, layer.__class__.__name__)))
if kdbg.Header.OwnerTag == 0x4742444B:
yield (0, ("KdDebuggerDataBlock", hex(kdbg.vol.offset)))
yield (0, ("NTBuildLab", kdbg.get_build_lab()))
yield (0, ("CSDVersion", str(kdbg.get_csdversion())))
vers_offset = ntkrnlmp.get_symbol("KdVersionBlock").address
vers = ntkrnlmp.object(type_name = "_DBGKD_GET_VERSION64",
layer_name = virtual_layer_name,
offset = kvo + vers_offset)
yield (0, ("KdVersionBlock", hex(vers.vol.offset)))
yield (0, ("Major/Minor", "{0}.{1}".format(vers.MajorVersion, vers.MinorVersion)))
yield (0, ("MachineType", str(vers.MachineType)))
cpu_count_offset = ntkrnlmp.get_symbol("KeNumberProcessors").address
cpu_count = ntkrnlmp.object(type_name = "unsigned int",
layer_name = virtual_layer_name,
offset = kvo + cpu_count_offset)
yield (0, ("KeNumberProcessors", str(cpu_count)))
# this is a hard-coded address in the Windows OS
if virtual_layer.bits_per_register == 32:
kuser_addr = 0xFFDF0000
else:
kuser_addr = 0xFFFFF78000000000
kuser = ntkrnlmp.object(type_name = "_KUSER_SHARED_DATA",
layer_name = virtual_layer_name,
offset = kuser_addr)
yield (0, ("SystemTime", str(kuser.SystemTime.get_time())))
yield (0, ("NtSystemRoot", str(kuser.NtSystemRoot.cast("string",
encoding = "utf-16",
errors = "replace",
max_length = 260))))
yield (0, ("NtProductType", str(kuser.NtProductType.description)))
yield (0, ("NtMajorVersion", str(kuser.NtMajorVersion)))
yield (0, ("NtMinorVersion", str(kuser.NtMinorVersion)))
# yield (0, ("KdDebuggerEnabled", "True" if ord(kuser.KdDebuggerEnabled) else "False"))
# yield (0, ("SafeBootMode", "True" if ord(kuser.SafeBootMode) else "False"))
dos_header = self.context.object(pe_table_name + constants.BANG +
"_IMAGE_DOS_HEADER", offset = kvo,
layer_name = virtual_layer_name)
nt_header = dos_header.get_nt_header()
yield (0, ("PE MajorOperatingSystemVersion", str(nt_header.OptionalHeader.MajorOperatingSystemVersion)))
yield (0, ("PE MinorOperatingSystemVersion", str(nt_header.OptionalHeader.MinorOperatingSystemVersion)))
yield (0, ("PE Machine", str(nt_header.FileHeader.Machine)))
yield (0, ("PE TimeDateStamp", time.asctime(time.gmtime(nt_header.FileHeader.TimeDateStamp))))
def run(self):
return TreeGrid([("Variable", str),
("Value", str)],
self._generator())
@@ -0,0 +1,129 @@
import volatility.plugins.windows.pslist as pslist
import volatility.plugins.windows.vadinfo as vadinfo
from volatility.framework import interfaces, symbols
from volatility.framework import renderers
from volatility.framework.configuration import requirements
from volatility.framework.objects import utility
from volatility.framework.renderers import format_hints
class Malfind(interfaces.plugins.PluginInterface):
"""Lists process memory ranges that potentially contain injected code"""
@classmethod
def get_requirements(cls):
# Since we're calling the plugin, make sure we have the plugin's requirements
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS")]
@classmethod
def is_vad_empty(self, proc_layer, vad):
"""Check if a VAD region is either entirely unavailable
due to paging, entirely consisting of zeros, or a
combination of the two. This helps ignore false positives
whose VAD flags match task._injection_filter requirements
but there's no data and thus not worth reporting it.
Args:
proc_layer: the process layer
vad: the MMVAD structure to test
"""
CHUNK_SIZE = 0x1000
all_zero_page = "\x00" * CHUNK_SIZE
offset = 0
vad_length = vad.get_end() - vad.get_start()
while offset < vad_length:
next_addr = vad.get_start() + offset
if proc_layer.is_valid(next_addr) and proc_layer.read(next_addr, CHUNK_SIZE) != all_zero_page:
return False
offset += CHUNK_SIZE
return True
@classmethod
def list_injections(cls,
context: interfaces.context.ContextInterface,
symbol_table: str,
proc: interfaces.objects.ObjectInterface):
"""Generate memory regions for a process that may contain
injected code.
Args:
proc: an _EPROCESS instance
"""
proc_layer_name = proc.add_process_layer()
proc_layer = context.memory[proc_layer_name]
for vad in proc.get_vad_root().traverse():
protection_string = vad.get_protection(vadinfo.VadInfo.protect_values(context,
proc_layer_name,
symbol_table),
vadinfo.winnt_protections)
write_exec = "EXECUTE" in protection_string and "WRITE" in protection_string
# the write/exec check applies to everything
if not write_exec:
continue
if (vad.get_private_memory() == 1 and vad.get_tag() == "VadS") or (
vad.get_private_memory() == 0 and protection_string != "PAGE_EXECUTE_WRITECOPY"):
if cls.is_vad_empty(proc_layer, vad):
continue
data = proc_layer.read(vad.get_start(), 64, pad = True)
yield vad, data
def _generator(self, procs):
# determine if we're on a 32 or 64 bit kernel
is_32bit_arch = not symbols.symbol_table_is_64bit(self.context, self.config["nt_symbols"])
for proc in procs:
process_name = utility.array_to_string(proc.ImageFileName)
for vad, data in self.list_injections(self.context, self.config["nt_symbols"], proc):
# if we're on a 64 bit kernel, we may still need 32 bit disasm due to wow64
if is_32bit_arch or proc.get_is_wow64():
architecture = "intel"
else:
architecture = "intel64"
disasm = interfaces.renderers.Disassembly(data, vad.get_start(), architecture)
yield (0, (proc.UniqueProcessId,
process_name,
format_hints.Hex(vad.get_start()),
format_hints.Hex(vad.get_end()),
vad.get_tag(),
vad.get_protection(vadinfo.VadInfo.protect_values(self.context,
proc.vol.layer_name,
self.config["nt_symbols"]),
vadinfo.winnt_protections),
vad.get_commit_charge(),
vad.get_private_memory(),
format_hints.HexBytes(data),
disasm))
def run(self):
filter_func = pslist.PsList.create_filter([self.config.get('pid', None)])
return renderers.TreeGrid([("PID", int),
("Process", str),
("Start VPN", format_hints.Hex),
("End VPN", format_hints.Hex),
("Tag", str),
("Protection", str),
("CommitCharge", int),
("PrivateMemory", int),
("Hexdump", format_hints.HexBytes),
("Disasm", interfaces.renderers.Disassembly)],
self._generator(pslist.PsList.list_processes(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'],
filter_func = filter_func)))
@@ -0,0 +1,148 @@
import logging
from typing import List, Generator, Iterable
import volatility.framework.constants as constants
import volatility.framework.exceptions as exceptions
import volatility.framework.interfaces.plugins as interfaces_plugins
import volatility.framework.renderers as renderers
import volatility.plugins.windows.modules as modules
import volatility.plugins.windows.pslist as pslist
from volatility.framework import interfaces
from volatility.framework.configuration import requirements
from volatility.framework.renderers import format_hints
from volatility.framework.symbols.windows.pe import PEIntermedSymbols
vollog = logging.getLogger(__name__)
class ModDump(interfaces_plugins.PluginInterface):
"""Dumps kernel modules"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Reuse the requirements from the plugins we use
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS")]
@classmethod
def get_session_layers(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
pids: List[int] = None) -> Generator[str, None, None]:
"""Build a cache of possible virtual layers, in priority starting with
the primary/kernel layer. Then keep one layer per session by cycling
through the process list.
Returns:
<list> of layer names
"""
seen_ids = [] # type: List[interfaces.objects.ObjectInterface]
filter_func = pslist.PsList.create_filter(pids or [])
for proc in pslist.PsList.list_processes(context = context,
layer_name = layer_name,
symbol_table = symbol_table,
filter_func = filter_func):
proc_layer_name = proc.add_process_layer()
try:
# create the session space object in the process' own layer.
# not all processes have a valid session pointer.
session_space = context.object(symbol_table + constants.BANG + "_MM_SESSION_SPACE",
layer_name = layer_name,
offset = proc.Session)
if session_space.SessionId in seen_ids:
continue
except exceptions.InvalidAddressException:
vollog.log(constants.LOGLEVEL_VVV,
"Process {} does not have a valid Session".format(proc.UniqueProcessId))
continue
# save the layer if we haven't seen the session yet
seen_ids.append(session_space.SessionId)
yield proc_layer_name
@classmethod
def find_session_layer(cls,
context: interfaces.context.ContextInterface,
session_layers: Iterable[str],
base_address: int):
"""Given a base address and a list of layer names, find a
layer that can access the specified address.
Args:
session_layers: <list> of layer names
base_address: <int> the base address
Returns:
layer name (or None)
"""
for layer_name in session_layers:
if context.memory[layer_name].is_valid(base_address):
return layer_name
return None
def _generator(self, mods):
session_layers = list(self.get_session_layers(self.context,
self.config['primary'],
self.config['nt_symbols']))
pe_table_name = PEIntermedSymbols.create(self.context,
self.config_path,
"windows",
"pe")
for mod in mods:
try:
BaseDllName = mod.BaseDllName.get_string()
except exceptions.InvalidAddressException:
BaseDllName = renderers.UnreadableValue()
session_layer_name = self.find_session_layer(self.context, session_layers, mod.DllBase)
if session_layer_name is None:
result_text = "Cannot find a viable session layer for {0:#x}".format(mod.DllBase)
else:
try:
dos_header = self.context.object(pe_table_name + constants.BANG +
"_IMAGE_DOS_HEADER", offset = mod.DllBase,
layer_name = session_layer_name)
filedata = interfaces_plugins.FileInterface(
"module.{0:#x}.dmp".format(mod.DllBase))
for offset, data in dos_header.reconstruct():
filedata.data.seek(offset)
filedata.data.write(data)
self.produce_file(filedata)
result_text = "Stored {}".format(filedata.preferred_filename)
except ValueError:
result_text = "PE parsing error"
except exceptions.SwappedInvalidAddressException as exp:
result_text = "Required memory at {0:#x} is inaccessible (swapped)".format(exp.invalid_address)
except exceptions.InvalidAddressException as exp:
result_text = "Required memory at {0:#x} is not valid".format(exp.invalid_address)
yield (0, (format_hints.Hex(mod.DllBase),
BaseDllName,
result_text))
def run(self):
return renderers.TreeGrid([("Base", format_hints.Hex),
("Name", str),
("Result", str)],
self._generator(
modules.Modules.list_modules(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'])))
@@ -0,0 +1,71 @@
import volatility.framework.interfaces.plugins as plugins
from volatility.framework import constants
from volatility.framework import exceptions, interfaces
from volatility.framework import renderers
from volatility.framework.configuration import requirements
from volatility.framework.renderers import format_hints
class Modules(plugins.PluginInterface):
"""Lists the loaded kernel modules"""
@classmethod
def get_requirements(cls):
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS")]
def _generator(self):
for mod in self.list_modules(self.context, self.config['primary'], self.config['nt_symbols']):
try:
BaseDllName = mod.BaseDllName.get_string()
except exceptions.InvalidAddressException:
BaseDllName = ""
try:
FullDllName = mod.FullDllName.get_string()
except exceptions.InvalidAddressException:
FullDllName = ""
yield (0, (format_hints.Hex(mod.vol.offset),
format_hints.Hex(mod.DllBase),
format_hints.Hex(mod.SizeOfImage),
BaseDllName,
FullDllName,
))
@classmethod
def list_modules(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str):
"""Lists all the modules in the primary layer"""
kvo = context.memory[layer_name].config['kernel_virtual_offset']
ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo)
try:
# use this type if its available (starting with windows 10)
ldr_entry_type = ntkrnlmp.get_type("_KLDR_DATA_TABLE_ENTRY")
except exceptions.SymbolError:
ldr_entry_type = ntkrnlmp.get_type("_LDR_DATA_TABLE_ENTRY")
type_name = ldr_entry_type.type_name.split(constants.BANG)[1]
list_head = ntkrnlmp.get_symbol("PsLoadedModuleList").address
list_entry = ntkrnlmp.object(type_name = "_LIST_ENTRY", offset = kvo + list_head)
reloff = ldr_entry_type.relative_child_offset("InLoadOrderLinks")
module = ntkrnlmp.object(type_name = type_name, offset = list_entry.vol.offset - reloff)
for mod in module.InLoadOrderLinks:
yield mod
def run(self):
return renderers.TreeGrid([("Offset", format_hints.Hex),
("Base", format_hints.Hex),
("Size", format_hints.Hex),
("Name", str),
("Path", str)],
self._generator())
@@ -0,0 +1,250 @@
import enum
import logging
from typing import Optional, Tuple, List, Generator
import volatility.plugins.windows.handles as handles
from volatility.framework import constants, interfaces, renderers, validity, exceptions, symbols
from volatility.framework.configuration import requirements
from volatility.framework.interfaces import plugins, configuration
from volatility.framework.layers import scanners
from volatility.framework.renderers import format_hints
from volatility.framework.symbols import intermed
from volatility.framework.symbols.windows import extensions
vollog = logging.getLogger(__name__)
class PoolType(enum.IntEnum):
"""Class to maintain the different possible PoolTypes
The values must be integer powers of 2"""
PAGED = 1
NONPAGED = 2
FREE = 4
class PoolHeaderSymbolTable(intermed.IntermediateSymbolTable):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self.set_type_class('_POOL_HEADER', extensions._POOL_HEADER)
class PoolConstraint(validity.ValidityRoutines):
"""Class to maintain tag/size/index/type information about Pool header tags"""
def __init__(self,
tag: bytes,
type_name: str,
object_type: Optional[str] = None,
page_type: Optional[PoolType] = None,
size: Optional[Tuple[Optional[int], Optional[int]]] = None,
index: Optional[Tuple[Optional[int], Optional[int]]] = None,
alignment: Optional[int] = 1) -> None:
self.tag = self._check_type(tag, bytes)
self.type_name = type_name
self.object_type = object_type
self.page_type = page_type
self.size = size
self.index = index
self.alignment = alignment
class PoolScanner(plugins.PluginInterface):
"""A generic pool scanner plugin"""
@classmethod
def get_requirements(cls):
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS")]
def _generator(self):
constraints = [
# atom tables
PoolConstraint(b'AtmT',
type_name = self.config["nt_symbols"] + constants.BANG + "_RTL_ATOM_TABLE",
size = (200, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
# processes on windows before windows 8
PoolConstraint(b'Pro\xe3',
type_name = self.config["nt_symbols"] + constants.BANG + "_EPROCESS",
object_type = "Process",
size = (600, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
# processes on windows starting with windows 8
PoolConstraint(b'Proc',
type_name = self.config["nt_symbols"] + constants.BANG + "_EPROCESS",
object_type = "Process",
size = (600, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
# files on windows before windows 8
PoolConstraint(b'Fil\xe5',
type_name = self.config["nt_symbols"] + constants.BANG + "_FILE_OBJECT",
object_type = "File",
size = (150, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
# files on windows starting with windows 8
PoolConstraint(b'File',
type_name = self.config["nt_symbols"] + constants.BANG + "_FILE_OBJECT",
object_type = "File",
size = (150, None),
page_type = PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE),
]
# get the object type map
type_map = handles.Handles.list_objects(context = self.context,
layer_name = self.config["primary"],
symbol_table = self.config["nt_symbols"])
cookie = handles.Handles.find_cookie(context = self.context,
layer_name = self.config["primary"],
symbol_table = self.config["nt_symbols"])
# FIXME: replace these lambdas with real functions
is_windows_10 = lambda: False
is_windows_8_or_later = lambda: False
# FIXME: scanning the primary layer seems very slow (10min on 512mb grrcon)
# start off with the primary virtual layer
scan_layer = self.config['primary']
# switch to a non-virtual layer if necessary
if not is_windows_10():
scan_layer = self.context.memory[scan_layer].config['memory_layer']
for constraint, header in self.pool_scan(self._context,
scan_layer,
self.config['nt_symbols'],
constraints,
alignment = 8):
mem_object = header.get_object(type_name = constraint.type_name,
type_map = type_map,
use_top_down = is_windows_8_or_later(),
object_type = constraint.object_type,
native_layer_name = 'primary',
cookie = cookie)
if mem_object is None:
vollog.log(constants.LOGLEVEL_VVV, "Cannot create an instance of {}".format(constraint.type_name))
continue
# generate some type-specific info for sanity checking
if constraint.object_type == "Process":
name = mem_object.ImageFileName.cast("string",
max_length = mem_object.ImageFileName.vol.count,
errors = "replace")
elif constraint.object_type == "File":
try:
name = mem_object.FileName.String
except exceptions.PagedInvalidAddressException:
vollog.log(constants.LOGLEVEL_VVV, "Skipping file at {0:#x}".format(mem_object.vol.offset))
continue
else:
name = renderers.NotApplicableValue()
yield (0, (constraint.type_name,
format_hints.Hex(header.vol.offset),
header.vol.layer_name,
name))
@classmethod
def pool_scan(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
pool_constraints: List[PoolConstraint],
alignment: int = 8,
progress_callback: Optional[validity.ProgressCallback] = None) \
-> Generator[Tuple[PoolConstraint, interfaces.objects.ObjectInterface], None, None]:
"""Returns the _POOL_HEADER object (based on the symbol_table template) after scanning through layer_name
returning all headers that match any of the constraints provided. Only one constraint can be provided per tag"""
# Setup the pattern
constraint_lookup = {} # type: Dict[bytes, List[PoolConstraint]]
for constraint in pool_constraints:
temp_list = constraint_lookup.get(constraint.tag, [])
temp_list.append(constraint)
constraint_lookup[constraint.tag] = temp_list
# Setup the pool header and offset differential
try:
module = context.module(symbol_table, layer_name, offset = 0)
header_type = module.get_type('_POOL_HEADER')
except exceptions.SymbolError:
# We have to manually load a symbol table
if symbols.symbol_table_is_64bit(context, symbol_table):
# FIXME: Do proper test for is_win_7
is_win_7 = False
if is_win_7:
pool_header_json_filename = "poolheader-x64-win7"
else:
pool_header_json_filename = "poolheader-x64"
else:
pool_header_json_filename = "poolheader-x86"
new_table_name = PoolHeaderSymbolTable.create(context = context,
config_path = configuration.path_join(
context.symbol_space[symbol_table].config_path,
"poolheader"
),
sub_path = "windows",
filename = pool_header_json_filename,
table_mapping = {'nt_symbols': symbol_table})
module = context.module(new_table_name, layer_name, offset = 0)
header_type = module.get_type('_POOL_HEADER')
header_offset = header_type.relative_child_offset('PoolTag')
# Run the scan locating the offsets of a particular tag
layer = context.memory[layer_name]
scanner = scanners.MultiStringScanner([c for c in constraint_lookup.keys()])
for offset, pattern in layer.scan(context, scanner, progress_callback = progress_callback):
for constraint in constraint_lookup[pattern]:
header = module.object(type_name = "_POOL_HEADER", offset = offset - header_offset)
# Size check
try:
if constraint.size is not None:
if constraint.size[0]:
if (alignment * header.BlockSize) < constraint.size[0]:
continue
if constraint.size[1]:
if (alignment * header.BlockSize) > constraint.size[1]:
continue
# Type check
if constraint.page_type is not None:
checks_pass = False
if (constraint.page_type & PoolType.FREE) and header.PoolType == 0:
checks_pass = True
elif (
constraint.page_type & PoolType.PAGED) and header.PoolType % 2 == 0 and header.PoolType > 0:
checks_pass = True
elif (constraint.page_type & PoolType.NONPAGED) and header.PoolType % 2 == 1:
checks_pass = True
if not checks_pass:
continue
if constraint.index is not None:
if constraint.index[0]:
if header.index < constraint.index[0]:
continue
if constraint.index[1]:
if header.index > constraint.index[1]:
continue
except exceptions.InvalidAddressException:
# The tested object's header doesn't point to valid addresses, ignore it
continue
# We found one that passed!
yield (constraint, header)
def run(self) -> renderers.TreeGrid:
return renderers.TreeGrid([("Tag", str),
("Offset", format_hints.Hex),
("Layer", str),
("Name", str)],
self._generator())
@@ -0,0 +1,81 @@
import logging
from typing import List
import volatility.framework.constants as constants
import volatility.framework.exceptions as exceptions
import volatility.framework.interfaces.plugins as interfaces_plugins
import volatility.framework.renderers as renderers
import volatility.plugins.windows.pslist as pslist
from volatility.framework import interfaces
from volatility.framework.configuration import requirements
from volatility.framework.objects import utility
from volatility.framework.symbols.windows.pe import PEIntermedSymbols
vollog = logging.getLogger(__name__)
class ProcDump(interfaces_plugins.PluginInterface):
"""Dumps process executable images"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS")]
def _generator(self, procs):
pe_table_name = PEIntermedSymbols.create(self.context,
self.config_path,
"windows",
"pe")
for proc in procs:
process_name = utility.array_to_string(proc.ImageFileName)
# TODO: what kind of exceptions could this raise and what should we do?
proc_layer_name = proc.add_process_layer()
try:
peb = self._context.object(self.config["nt_symbols"] + constants.BANG + "_PEB",
layer_name = proc_layer_name,
offset = proc.Peb)
dos_header = self.context.object(pe_table_name + constants.BANG +
"_IMAGE_DOS_HEADER", offset = peb.ImageBaseAddress,
layer_name = proc_layer_name)
filedata = interfaces_plugins.FileInterface(
"pid.{0}.{1:#x}.dmp".format(proc.UniqueProcessId, peb.ImageBaseAddress))
for offset, data in dos_header.reconstruct():
filedata.data.seek(offset)
filedata.data.write(data)
self.produce_file(filedata)
result_text = "Stored {}".format(filedata.preferred_filename)
except ValueError:
result_text = "PE parsing error"
except exceptions.SwappedInvalidAddressException as exp:
result_text = "Required memory at {0:#x} is inaccessible (swapped)".format(exp.invalid_address)
except exceptions.PagedInvalidAddressException as exp:
result_text = "Required memory at {0:#x} is not valid (process exited?)".format(exp.invalid_address)
yield (0, (proc.UniqueProcessId,
process_name,
result_text))
def run(self):
filter_func = pslist.PsList.create_filter([self.config.get('pid', None)])
return renderers.TreeGrid([("PID", int),
("Process", str),
("Result", str)],
self._generator(pslist.PsList.list_processes(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'],
filter_func = filter_func)))
@@ -0,0 +1,124 @@
import datetime
from typing import Callable, Iterable, List
import volatility.framework.interfaces.plugins as plugins
from volatility.framework import renderers, interfaces, layers
from volatility.framework.configuration import requirements
from volatility.framework.renderers import format_hints
from volatility.plugins import timeliner
class PsList(plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Lists the processes present in a particular windows memory image"""
PHYSICAL_DEFAULT = False
@classmethod
def get_requirements(cls):
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS"),
# TODO: Convert this to a ListRequirement so that people can filter on sets of pids
requirements.IntRequirement(name = 'pid',
description = "Process ID to include (all other processes are excluded)",
optional = True),
requirements.BooleanRequirement(name = 'physical',
description = 'Display physical offsets instead of virtual',
default = cls.PHYSICAL_DEFAULT,
optional = True)]
@classmethod
def create_filter(cls, pid_list: List[int] = None) -> Callable[[int], bool]:
filter_func = lambda _: False
# FIXME: mypy #4973 or #2608
pid_list = pid_list or []
filter_list = [x for x in pid_list if x is not None]
if filter_list:
filter_func = lambda x: x not in filter_list
return filter_func
@classmethod
def list_processes(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
filter_func: Callable[[int], bool] = lambda _: False) -> \
Iterable[interfaces.objects.ObjectInterface]:
"""Lists all the processes in the primary layer that are in the pid config option"""
# We only use the object factory to demonstrate how to use one
kvo = context.memory[layer_name].config['kernel_virtual_offset']
ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo)
ps_aph_offset = ntkrnlmp.get_symbol("PsActiveProcessHead").address
list_entry = ntkrnlmp.object(type_name = "_LIST_ENTRY", offset = kvo + ps_aph_offset)
# This is example code to demonstrate how to use symbol_space directly, rather than through a module:
#
# ```
# reloff = self.context.symbol_space.get_type(
# self.config['nt_symbols'] + constants.BANG + "_EPROCESS").relative_child_offset(
# "ActiveProcessLinks")
# ```
#
# Note: "nt_symbols!_EPROCESS" could have been used, but would rely on the "nt_symbols" symbol table not already
# having been present. Strictly, the value of the requirement should be joined with the BANG character
# defined in the constants file
reloff = ntkrnlmp.get_type("_EPROCESS").relative_child_offset("ActiveProcessLinks")
eproc = ntkrnlmp.object(type_name = "_EPROCESS", offset = list_entry.vol.offset - reloff)
for proc in eproc.ActiveProcessLinks:
if not filter_func(proc):
yield proc
def _generator(self):
for proc in self.list_processes(self.context,
self.config['primary'],
self.config['nt_symbols'],
filter_func = self.create_filter([self.config.get('pid', None)])):
if not self.config.get('physical', self.PHYSICAL_DEFAULT):
offset = proc.vol.offset
else:
layer_name = self.config['primary']
memory = self.context.memory[layer_name]
if not isinstance(memory, layers.intel.Intel):
raise TypeError("Primary layer is not an intel layer")
(_, offset, _, _) = list(memory.mapping(offset = proc.vol.offset, length = 0))[0]
yield (0, (proc.UniqueProcessId,
proc.InheritedFromUniqueProcessId,
proc.ImageFileName.cast("string",
max_length = proc.ImageFileName.vol.count,
errors = 'replace'),
format_hints.Hex(offset),
proc.ActiveThreads,
proc.get_handle_count(),
proc.get_session_id(),
proc.get_is_wow64(),
proc.get_create_time(),
proc.get_exit_time()))
def generate_timeline(self):
for row in self._generator():
_depth, row_data = row
description = "Process: {} ({})".format(row_data[2], row_data[3])
yield (description, timeliner.TimeLinerType.CREATED, row_data[8])
yield (description, timeliner.TimeLinerType.MODIFIED, row_data[9])
def run(self):
offsettype = "(V)" if not self.config.get('physical', self.PHYSICAL_DEFAULT) else "(P)"
return renderers.TreeGrid([("PID", int),
("PPID", int),
("ImageFileName", str),
("Offset{0}".format(offsettype), format_hints.Hex),
("Threads", int),
("Handles", int),
("SessionId", int),
("Wow64", bool),
("CreateTime", datetime.datetime),
("ExitTime", datetime.datetime)],
self._generator())
@@ -0,0 +1,67 @@
from volatility.framework import objects
from volatility.framework.renderers import format_hints
from volatility.plugins.windows import pslist
class PsTree(pslist.PsList):
"""Plugin for listing processes in a tree based on their parent process ID """
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._processes = {}
self._levels = {}
self._children = {}
def find_level(self, pid: objects.Pointer) -> None:
"""Finds how deep the pid is in the processes list"""
seen = set([])
seen.add(pid)
level = 0
proc = self._processes.get(pid, None)
while proc is not None and proc.InheritedFromUniqueProcessId not in seen:
child_list = self._children.get(proc.InheritedFromUniqueProcessId, set([]))
child_list.add(proc.UniqueProcessId)
self._children[proc.InheritedFromUniqueProcessId] = child_list
proc = self._processes.get(proc.InheritedFromUniqueProcessId, None)
level += 1
self._levels[pid] = level
def _generator(self):
"""Generates the Tree of processes"""
for proc in self.list_processes(self.context, self.config['primary'], self.config['nt_symbols']):
if not self.config.get('physical', self.PHYSICAL_DEFAULT):
offset = proc.vol.offset
else:
layer_name = self.config['primary']
memory = self.context.memory[layer_name]
(_, offset, _, _) = list(memory.mapping(offset = proc.vol.offset, length = 0))[0]
self._processes[proc.UniqueProcessId] = proc
# Build the child/level maps
for pid in self._processes:
self.find_level(pid)
def yield_processes(pid):
proc = self._processes[pid]
row = (proc.UniqueProcessId,
proc.InheritedFromUniqueProcessId,
proc.ImageFileName.cast("string",
max_length = proc.ImageFileName.vol.count,
errors = 'replace'),
format_hints.Hex(offset),
proc.ActiveThreads,
proc.get_handle_count(),
proc.get_session_id(),
proc.get_is_wow64(),
proc.get_create_time(),
proc.get_exit_time())
yield (self._levels[pid] - 1, row)
for child_pid in self._children.get(pid, []):
yield from yield_processes(child_pid)
for pid in self._levels:
if self._levels[pid] == 1:
yield from yield_processes(pid)
@@ -0,0 +1,56 @@
from typing import Iterator, List, Tuple
import volatility.framework.interfaces.plugins as plugins
from volatility.framework import renderers, interfaces
from volatility.framework.configuration import requirements
from volatility.framework.renderers import format_hints
class HiveList(plugins.PluginInterface):
"""Lists the registry hives present in a particular memory image"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS"),
requirements.StringRequirement(name = 'filter',
description = "String to filter hive names returned",
optional = True,
default = None)]
def _generator(self) -> Iterator[Tuple[int, Tuple[int, str]]]:
for hive in self.list_hives(context = self.context,
layer_name = self.config["primary"],
symbol_table = self.config["nt_symbols"],
filter_string = self.config.get('filter', None)):
yield (0, (format_hints.Hex(hive.vol.offset),
hive.get_name() or ""))
@classmethod
def list_hives(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
filter_string: None = None) -> Iterator[interfaces.objects.ObjectInterface]:
"""Lists all the hives in the primary layer"""
# We only use the object factory to demonstrate how to use one
kvo = context.memory[layer_name].config['kernel_virtual_offset']
ntkrnlmp = context.module(symbol_table, layer_name = layer_name, offset = kvo)
list_head = ntkrnlmp.get_symbol("CmpHiveListHead").address
list_entry = ntkrnlmp.object(type_name = "_LIST_ENTRY", offset = kvo + list_head)
reloff = ntkrnlmp.get_type("_CMHIVE").relative_child_offset("HiveList")
cmhive = ntkrnlmp.object(type_name = "_CMHIVE", offset = list_entry.vol.offset - reloff)
for hive in cmhive.HiveList:
if filter_string is None or filter_string.lower() in str(hive.get_name() or "").lower():
yield hive
def run(self) -> renderers.TreeGrid:
return renderers.TreeGrid([("Offset", format_hints.Hex),
("FileFullPath", str)],
self._generator())
@@ -0,0 +1,136 @@
import datetime
import logging
from typing import Generator, Sequence
import volatility.framework.interfaces.plugins as plugins
from volatility.framework import objects, renderers, exceptions
from volatility.framework.configuration import requirements
from volatility.framework.layers.registry import RegistryHive
from volatility.framework.renderers import TreeGrid, conversion, format_hints
from volatility.framework.symbols.windows.extensions.registry import RegValueTypes
vollog = logging.getLogger(__name__)
class PrintKey(plugins.PluginInterface):
"""Lists the registry keys under a hive or specific key value"""
@classmethod
def get_requirements(cls):
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols",
description = "Windows OS"),
requirements.IntRequirement(name = 'offset',
description = "Hive Offset",
default = None,
optional = True),
requirements.StringRequirement(name = 'key',
description = "Key to start from",
default = None,
optional = True),
requirements.BooleanRequirement(name = 'recurse',
description = 'Recurses through keys',
default = False,
optional = True)]
def hive_walker(self,
hive: RegistryHive,
node_path: Sequence[objects.Struct] = None,
key_path: str = None) -> Generator:
"""Walks through a set of nodes from a given node (last one in node_path).
Avoids loops by not traversing into nodes already present in the node_path
"""
if not node_path:
node_path = [hive.get_node(hive.root_cell_offset)]
if not isinstance(node_path, list) or len(node_path) < 1:
vollog.warning("Hive walker was not passed a valid node_path (or None)")
return
node = node_path[-1]
key_path = key_path or node.get_key_path()
last_write_time = conversion.wintime_to_datetime(node.LastWriteTime.QuadPart)
for key_node in node.get_subkeys():
result = (key_path.count("\\"),
(last_write_time,
renderers.format_hints.Hex(hive.hive_offset),
"Key",
key_path,
key_node.get_name(),
"",
key_node.get_volatile()))
yield result
for value_node in node.get_values():
result = (key_path.count("\\"),
(last_write_time,
renderers.format_hints.Hex(hive.hive_offset),
RegValueTypes.get(value_node.Type).name,
key_path,
value_node.get_name(),
str(value_node.decode_data()),
node.get_volatile()))
yield result
if self.config.get('recurse', None):
for sub_node in node.get_subkeys():
if sub_node.vol.offset not in [x.vol.offset for x in node_path]:
yield from self.hive_walker(hive, node_path + [sub_node], key_path + "\\" + sub_node.get_name())
def registry_walker(self):
"""Walks through a registry, hive by hive"""
if self.config.get('offset', None) is None:
try:
import volatility.plugins.windows.registry.hivelist as hivelist
hive_offsets = [hive.vol.offset for hive in hivelist.HiveList.list_hives(self.context,
self.config['primary'],
self.config['nt_symbols'])]
except ImportError:
vollog.warning("Unable to import windows.hivelist plugin, please provide a hive offset")
raise ValueError("Unable to import windows.hivelist plugin, please provide a hive offset")
else:
hive_offsets = [self.config['offset']]
for hive_offset in hive_offsets:
# Construct the hive
reg_config_path = self.make_subconfig(hive_offset = hive_offset,
base_layer = self.config['primary'],
nt_symbols = self.config['nt_symbols'])
hive = RegistryHive(self.context, reg_config_path, name = 'hive' + hex(hive_offset))
try:
self.context.memory.add_layer(hive)
# Walk it
if 'key' in self.config:
node_path = hive.get_key(self.config['key'], return_list = True)
else:
node_path = [hive.get_node(hive.root_cell_offset)]
yield from self.hive_walker(hive, node_path)
except (exceptions.PagedInvalidAddressException, KeyError) as excp:
if type(excp) == KeyError:
vollog.debug(
"Key '{}' not found in Hive at offset {}.".format(self.config['key'], hex(hive_offset)))
else:
vollog.debug("Invalid address identified in Hive: {}".format(hex(excp.invalid_address)))
result = (0,
(renderers.UnreadableValue(),
format_hints.Hex(hive.hive_offset),
"Key",
self.config.get('key', "ROOT"),
renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue()))
yield result
def run(self):
return TreeGrid(columns = [('Last Write Time', datetime.datetime),
('Hive Offset', format_hints.Hex),
('Type', str),
('Key', str),
('Name', str),
('Data', str),
('Volatile', bool)],
generator = self.registry_walker())
@@ -0,0 +1,103 @@
{
"{0139D44E-6AFE-49F2-8690-3DAFCAE6FFB8}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs",
"{054FAE61-4DD8-4787-80B6-090220C4B700}": "GameExplorer",
"{0762D272-C50A-4BB0-A382-697DCD729B80}": "%SystemDrive%\\Users",
"{0AC0837C-BBF8-452A-850D-79D08E667CA7}": "(My) Computer",
"{0F214138-B1D3-4a90-BBA9-27CBC0C5389A}": "Sync Setup",
"{15CA69B3-30EE-49C1-ACE1-6B5EC372AFB5}": "%PUBLIC%\\Music\\Sample Playlists",
"{1777F761-68AD-4D8A-87BD-30B759FA33DD}": "%USERPROFILE%\\Favorites",
"{18989B1D-99B5-455B-841C-AB7C74E4DDFC}": "%USERPROFILE%\\Videos",
"{190337d1-b8ca-4121-a639-6d472d16972a}": "Search Results",
"{1A6FDBA2-F42D-4358-A798-B74D745926C5}": "%PUBLIC%\\RecordedTV.library-ms",
"{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}": "%windir%\\system32",
"{1B3EA5DC-B587-4786-B4EF-BD1DC332AEAE}": "%APPDATA%\\Microsoft\\Windows\\Libraries",
"{2112AB0A-C86A-4FFE-A368-0DE96E47012E}": "%APPDATA%\\Microsoft\\Windows\\Libraries\\Music.library-ms",
"{2400183A-6185-49FB-A2D8-4A392A602BA3}": "%PUBLIC%\\Videos",
"{289a9a43-be44-4057-a41b-587a76d7e7f9}": "Sync Results",
"{2A00375E-224C-49DE-B8D1-440DF7EF3DDC}": "%windir%\\resources\\0409 (code page)",
"{2B0F765D-C0E9-4171-908E-08A611B84FF6}": "%APPDATA%\\Microsoft\\Windows\\Cookies",
"{2C36C0AA-5812-4b87-BFD0-4CD0DFB19B39}": "%LOCALAPPDATA%\\Microsoft\\Windows Photo Gallery\\Original Images",
"{3214FAB5-9757-4298-BB61-92A9DEAA44FF}": "%PUBLIC%\\Music",
"{33E28130-4E1E-4676-835A-98395C3BC3BB}": "%USERPROFILE%\\Pictures",
"{352481E8-33BE-4251-BA85-6007CAEDCF9D}": "%LOCALAPPDATA%\\Microsoft\\Windows\\Temporary Internet Files",
"{374DE290-123F-4565-9164-39C4925E467B}": "%USERPROFILE%\\Downloads",
"{3D644C9B-1FB8-4f30-9B45-F670235F79C0}": "%PUBLIC%\\Downloads",
"{3EB685DB-65F9-4CF6-A03A-E3EF65729F3D}": "%APPDATA% (%USERPROFILE%\\AppData\\Roaming)",
"{43668BF8-C14E-49B2-97C9-747784D784B7}": "Sync Center",
"{48DAF80B-E6CF-4F4E-B800-0E69D84EE384}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Libraries",
"{491E922F-5643-4AF4-A7EB-4E7A138D8174}": "%APPDATA%\\Microsoft\\Windows\\Libraries\\Videos.library-ms",
"{4BD8D571-6D19-48D3-BE97-422220080E43}": "%USERPROFILE%\\Music",
"{4C5C32FF-BB9D-43b0-B5B4-2D72E54EAAA4}": "%USERPROFILE%\\Saved Games",
"{4D9F7874-4E0C-4904-967B-40B0D20C3E4B}": "The Internet",
"{4bfefb45-347d-4006-a5be-ac0cb0567192}": "Conflicts",
"{52528A6B-B9E3-4ADD-B60D-588C2DBA842D}": "Homegroup",
"{52a4f021-7b75-48a9-9f6b-4b87a210bc8f}": "%APPDATA%\\Microsoft\\Internet Explorer\\Quick Launch",
"{56784854-C6CB-462b-8169-88E350ACB882}": "%USERPROFILE%\\Contacts",
"{5CD7AEE2-2219-4A67-B85D-6C9CE15660CB}": "%LOCALAPPDATA%\\Programs",
"{5CE4A5E9-E4EB-479D-B89F-130C02886155}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\DeviceMetadataStore",
"{5E6C858F-0E22-4760-9AFE-EA3317B67173}": "%USERPROFILE% (%SystemDrive%\\Users\\%USERNAME%)",
"{625B53C3-AB48-4EC1-BA1F-A1EF4146FC19}": "%APPDATA%\\Microsoft\\Windows\\Start Menu",
"{62AB5D82-FDC1-4DC3-A9DD-070D1D495D97}": "%ALLUSERSPROFILE% (%ProgramData%, %SystemDrive%\\ProgramData)",
"{6365D5A7-0F0D-45E5-87F6-0DA56B6A4F7D}": "%ProgramFiles%\\Common Files",
"{69D2CF90-FC33-4FB7-9A0C-EBB0F0FCB43C}": "%USERPROFILE%\\Pictures\\Slide Shows",
"{6D809377-6AF0-444b-8957-A3773F02200E}": "%ProgramFiles%",
"{6F0CD92B-2E97-45D1-88FF-B0D186B8DEDD}": "Network Connections",
"{724EF170-A42D-4FEF-9F26-B60E846FBA4F}": "%APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Administrative Tools",
"{76FC4E2D-D6AD-4519-A663-37BD56068185}": "Printers",
"{7B0DB17D-9CD2-4A93-9733-46CC89022E7C}": "%APPDATA%\\Microsoft\\Windows\\Libraries\\Documents.library-ms",
"{7B396E54-9EC5-4300-BE0A-2482EBAE1A26}": "%ProgramFiles%\\Windows Sidebar\\Gadgets",
"{7C5A40EF-A0FB-4BFC-874A-C0F2E0B9FA8E}": "%ProgramFiles%",
"{7d1d3a04-debb-4115-95cf-2f29da2920da}": "%USERPROFILE%\\Searches",
"{82A5EA35-D9CD-47C5-9629-E15D2F714E6E}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs\\StartUp",
"{82A74AEB-AEB4-465C-A014-D097EE346D63}": "Control Panel",
"{859EAD94-2E85-48AD-A71A-0969CB56A6CD}": "%PUBLIC%\\Videos\\Sample Videos",
"{8983036C-27C0-404B-8F08-102D10DCFD74}": "%APPDATA%\\Microsoft\\Windows\\SendTo",
"{8AD10C31-2ADB-4296-A8F7-E4701232C972}": "%windir%\\Resources",
"{905e63b6-c1bf-494e-b29c-65b732d3d21a}": "%ProgramFiles%",
"{9274BD8D-CFD1-41C3-B35E-B13F55A758F4}": "%APPDATA%\\Microsoft\\Windows\\Printer Shortcuts",
"{98ec0e18-2098-4d44-8644-66979315a281}": "Microsoft Office Outlook",
"{9E3995AB-1F9C-4F13-B827-48B24B6C7174}": "%APPDATA%\\Microsoft\\Internet Explorer\\Quick Launch\\User Pinned",
"{9E52AB10-F80D-49DF-ACB8-4330F5687855}": "%LOCALAPPDATA%\\Microsoft\\Windows\\Burn\\Burn",
"{A302545D-DEFF-464b-ABE8-61C8648D939B}": "Libraries",
"{A4115719-D62E-491D-AA7C-E74B8BE3B067}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu",
"{A520A1A4-1780-4FF6-BD18-167343C5AF16}": "%USERPROFILE%\\AppData\\LocalLow",
"{A63293E8-664E-48DB-A079-DF759E0509F7}": "%APPDATA%\\Microsoft\\Windows\\Templates",
"{A75D362E-50FC-4fb7-AC2C-A8BEAA314493}": "%LOCALAPPDATA%\\Microsoft\\Windows Sidebar\\Gadgets",
"{A77F5D77-2E2B-44C3-A6A2-ABA601054A51}": "%APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs",
"{A990AE9F-A03B-4E80-94BC-9912D7504104}": "%APPDATA%\\Microsoft\\Windows\\Libraries\\Pictures.library-ms",
"{AE50C081-EBD2-438A-8655-8A092E34987A}": "%APPDATA%\\Microsoft\\Windows\\Recent",
"{B250C668-F57D-4EE1-A63C-290EE7D1AA1F}": "%PUBLIC%\\Music\\Sample Music",
"{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}": "Desktop",
"{B6EBFB86-6907-413C-9AF7-4FC2ABF07CC5}": "%PUBLIC%\\Pictures",
"{B7534046-3ECB-4C18-BE4E-64CD4CB7D6AC}": "Recycle Bin",
"{B94237E7-57AC-4347-9151-B08C6C32D1F7}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Templates",
"{B97D20BB-F46A-4C97-BA10-5E3608430854}": "%APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\StartUp",
"{BCB5256F-79F6-4CEE-B725-DC34E402FD46}": "%APPDATA%\\Microsoft\\Internet Explorer\\Quick Launch\\User Pinned\\ImplicitAppShortcuts",
"{BCBD3057-CA5C-4622-B42D-BC56DB0AE516}": "%LOCALAPPDATA%\\Programs\\Common",
"{C1BAE2D0-10DF-4334-BEDD-7AA20B227A9D}": "%ALLUSERSPROFILE%\\OEM Links",
"{C4900540-2379-4C75-844B-64E6FAF8716B}": "%PUBLIC%\\Pictures\\Sample Pictures",
"{C4AA340D-F20F-4863-AFEF-F87EF2E6BA25}": "%PUBLIC%\\Desktop",
"{C5ABBF53-E17F-4121-8900-86626FC2C973}": "%APPDATA%\\Microsoft\\Windows\\Network Shortcuts",
"{C870044B-F49E-4126-A9C3-B52A1FF411E8}": "%LOCALAPPDATA%\\Microsoft\\Windows\\Ringtones",
"{CAC52C1A-B53D-4edc-92D7-6B2E8AC19434}": "Games",
"{D0384E7D-BAC3-4797-8F14-CBA229B392B5}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs\\Administrative Tools",
"{D20BEEC4-5CA8-4905-AE3B-BF251EA09B53}": "Network",
"{D65231B0-B2F1-4857-A4CE-A8E7C6EA7D27}": "%windir%\\system32",
"{D9DC8A3B-B784-432E-A781-5A1130A75963}": "%LOCALAPPDATA%\\Microsoft\\Windows\\History",
"{DE92C1C7-837F-4F69-A3BB-86E631204A23}": "%USERPROFILE%\\Music\\Playlists",
"{DE974D24-D9C6-4D3E-BF91-F4455120B917}": "%ProgramFiles%\\Common Files",
"{DEBF2536-E1A8-4c59-B6A2-414586476AEA}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\GameExplorer",
"{DFDF76A2-C82A-4D63-906A-5644AC457385}": "%PUBLIC% (%SystemDrive%\\Users\\Public)",
"{E555AB60-153B-4D17-9F04-A5FE99FC15EC}": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Ringtones",
"{ED4824AF-DCE4-45A8-81E2-FC7965083634}": "%PUBLIC%\\Documents",
"{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}": "%LOCALAPPDATA% (%USERPROFILE%\\AppData\\Local)",
"{F38BF404-1D43-42F2-9305-67DE0B28FC23}": "%windir%",
"{F7F1ED05-9F6D-47A2-AAAE-29D317C6F066}": "%ProgramFiles%\\Common Files",
"{FD228CB7-AE11-4AE3-864C-16F3910AB8FE}": "%windir%\\Fonts",
"{a305ce99-f527-492b-8b1a-7e76fa98d6e4}": "Installed Updates",
"{bfb9d5e0-c6a9-404c-b2b2-ae6db6af4968}": "%USERPROFILE%\\Links",
"{de61d971-5ebc-4f02-a3a9-6c82895e5c04}": "Add or Remove Programs (Control Panel)",
"{df7266ac-9274-4867-8d55-3bd661de872d}": "Programs and Features",
"{ee32e446-31ca-4aba-814f-a5ebd2fd6d5e}": "Offline Files",
"{f3ce0f7c-4901-4acc-8648-d5d44b04ef8f}": "The user's full name"
}
@@ -0,0 +1,277 @@
import codecs
import datetime
import json
import logging
import os
from typing import List
from volatility.framework import exceptions, renderers, constants, interfaces
from volatility.framework.configuration import requirements
from volatility.framework.layers.physical import BufferDataLayer
from volatility.framework.layers.registry import RegistryHive
from volatility.framework.renderers import format_hints, conversion
from volatility.framework.symbols import intermed
vollog = logging.getLogger(__name__)
class UserAssist(interfaces.plugins.PluginInterface):
"""Print userassist registry keys and information"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._userassist_size = 0
self._userassist_type_name = "_VOL_USERASSIST_TYPES_7"
self._reg_table_name = None
self._win7 = None
# taken from http://msdn.microsoft.com/en-us/library/dd378457%28v=vs.85%29.aspx
self._folder_guids = json.load(open(os.path.join(os.path.dirname(__file__), "userassist.json"), "rb"))
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols",
description = "Windows OS"),
requirements.IntRequirement(name = 'offset',
description = "Hive Offset",
default = None,
optional = True)]
def parse_userassist_data(self, reg_val):
"""Reads the raw data of a _CM_KEY_VALUE and returns a dict of userassist fields"""
item = {
"id": renderers.UnparsableValue(),
"count": renderers.UnparsableValue(),
"focus": renderers.UnparsableValue(),
"time": renderers.UnparsableValue(),
"lastupdated": renderers.UnparsableValue(),
"rawdata": renderers.UnparsableValue(),
}
userassist_data = reg_val.decode_data()
if userassist_data is None:
return item
item["rawdata"] = userassist_data
if self._win7 is None:
# if OS is still unknown at this point, return the default item which just has the rawdata
return item
if len(userassist_data) < self._userassist_size:
return item
userassist_layer_name = self.context.memory.free_layer_name("userassist_buffer")
buffer = BufferDataLayer(self.context, self._config_path, userassist_layer_name, userassist_data)
self.context.add_layer(buffer)
userassist_obj = self.context.object(
symbol = self._reg_table_name + constants.BANG + self._userassist_type_name,
layer_name = userassist_layer_name,
offset = 0)
if self._win7:
item["id"] = renderers.NotApplicableValue()
item["count"] = int(userassist_obj.Count)
seconds = (userassist_obj.FocusTime + 500) / 1000.0
time = datetime.timedelta(seconds = seconds) if seconds > 0 else userassist_obj.FocusTime
item["focus"] = int(userassist_obj.FocusCount)
item["time"] = str(time)
else:
item["id"] = int(userassist_obj.ID)
item["count"] = int(userassist_obj.CountStartingAtFive
if userassist_obj.CountStartingAtFive < 5
else userassist_obj.CountStartingAtFive - 5)
item["focus"] = renderers.NotApplicableValue()
item["time"] = renderers.NotApplicableValue()
item["lastupdated"] = conversion.wintime_to_datetime(userassist_obj.LastUpdated.QuadPart)
return item
def _determine_userassist_type(self) -> None:
"""Determine the userassist type and size depending on the OS version"""
if self._win7 is True:
self._userassist_type_name = "_VOL_USERASSIST_TYPES_7"
elif self._win7 is False:
self._userassist_type_name = "_VOL_USERASSIST_TYPES_XP"
self._userassist_size = self.context.symbol_space.get_type(
self._reg_table_name + constants.BANG + self._userassist_type_name).size
def _win7_or_later(self) -> bool:
# TODO: change this if there is a better way of determining the OS version
# _KUSER_SHARED_DATA.CookiePad is in Windows 6.1 (Win7) and later
return self.context.symbol_space.get_type(
self.config['nt_symbols'] + constants.BANG + "_KUSER_SHARED_DATA").has_member('CookiePad')
def list_userassist(self, hive: RegistryHive):
"""Generate userassist data for a registry hive."""
hive_name = hive.hive.cast(self.config["nt_symbols"] + constants.BANG + "_CMHIVE").get_name()
if self._win7 is None:
try:
self._win7 = self._win7_or_later()
except exceptions.SymbolError:
# self._win7 will be None and only registry value rawdata will be output
pass
self._determine_userassist_type()
userassist_node_path = hive.get_key("software\\microsoft\\windows\\currentversion\\explorer\\userassist",
return_list = True)
if not userassist_node_path:
vollog.warning("list_userassist did not find a valid node_path (or None)")
return
userassist_node = userassist_node_path[-1]
# iterate through the GUIDs under the userassist key
for guidkey in userassist_node.get_subkeys():
# each guid key should have a Count key in it
for countkey in guidkey.get_subkeys():
countkey_path = countkey.get_key_path()
countkey_last_write_time = conversion.wintime_to_datetime(countkey.LastWriteTime.QuadPart)
# output the parent Count key
result = (0,
(renderers.format_hints.Hex(hive.hive_offset),
hive_name,
countkey_path,
countkey_last_write_time,
"Key",
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue())) # type: Tuple[int, Tuple[format_hints.Hex, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any, Any]]
yield result
# output any subkeys under Count
for subkey in countkey.get_subkeys():
subkey_name = subkey.get_name()
result = (1, (renderers.format_hints.Hex(hive.hive_offset),
hive_name,
countkey_path,
countkey_last_write_time,
"Subkey",
subkey_name,
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
renderers.NotApplicableValue(),))
yield result
# output any values under Count
for value in countkey.get_values():
value_name = value.get_name()
try:
value_name = codecs.encode(value_name, "rot_13")
except UnicodeDecodeError:
pass
if self._win7:
guid = value_name.split("\\")[0]
if guid in self._folder_guids:
value_name = value_name.replace(guid, self._folder_guids[guid])
userassist_data_dict = self.parse_userassist_data(value)
result = (1, (renderers.format_hints.Hex(hive.hive_offset),
hive_name,
countkey_path,
countkey_last_write_time,
"Value",
value_name,
userassist_data_dict["id"],
userassist_data_dict["count"],
userassist_data_dict["focus"],
userassist_data_dict["time"],
userassist_data_dict["lastupdated"],
format_hints.HexBytes(userassist_data_dict["rawdata"]),))
yield result
def _generator(self):
# get all the user hive offsets or use the one specified
if self.config.get('offset', None) is None:
try:
import volatility.plugins.windows.registry.hivelist as hivelist
hive_offsets = [hive.vol.offset for hive in
hivelist.HiveList.list_hives(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'],
filter_string = "ntuser.dat")]
except ImportError:
vollog.warning("Unable to import windows.hivelist plugin, please provide a hive offset")
raise ValueError("Unable to import windows.hivelist plugin, please provide a hive offset")
else:
hive_offsets = [self.config['offset']]
self._reg_table_name = intermed.IntermediateSymbolTable.create(self.context,
self._config_path,
'windows',
'registry')
for hive_offset in hive_offsets:
# Construct the hive
reg_config_path = self.make_subconfig(hive_offset = hive_offset,
base_layer = self.config['primary'],
nt_symbols = self.config['nt_symbols'])
hive_name = None
try:
hive = RegistryHive(self.context, reg_config_path, name = 'hive' + hex(hive_offset))
hive_name = hive.hive.cast(self.config["nt_symbols"] + constants.BANG + "_CMHIVE").get_name()
self.context.memory.add_layer(hive)
yield from self.list_userassist(hive)
continue
except exceptions.PagedInvalidAddressException as excp:
vollog.debug("Invalid address identified in Hive: {}".format(hex(excp.invalid_address)))
except KeyError:
vollog.debug("Key '{}' not found in Hive at offset {}.".format(
"software\\microsoft\\windows\\currentversion\\explorer\\userassist", hex(hive_offset)))
# yield UnreadableValues when an exception occurs for a given hive_offset
result = (0,
(renderers.format_hints.Hex(hive_offset),
hive_name if hive_name else renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue(),
renderers.UnreadableValue()))
yield result
def run(self):
return renderers.TreeGrid([("Hive Offset", renderers.format_hints.Hex),
("Hive Name", str),
("Path", str),
("Last Write Time", datetime.datetime),
("Type", str),
("Name", str),
("ID", int),
("Count", int),
("Focus Count", int),
("Time Focused", str),
("Last Updated", datetime.datetime),
("Raw Data", format_hints.HexBytes)],
self._generator())
@@ -0,0 +1,117 @@
import os
from typing import Any, Iterator, List, Tuple
from volatility.framework import constants, interfaces
from volatility.framework import contexts
from volatility.framework import exceptions, symbols
from volatility.framework import renderers
from volatility.framework.configuration import requirements
from volatility.framework.constants import windows as windows_constants
from volatility.framework.interfaces import plugins
from volatility.framework.renderers import format_hints
from volatility.plugins.windows import modules
class SSDT(plugins.PluginInterface):
"""Lists the system call table"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS")]
def _generator(self, mods: Iterator[Any]) -> Iterator[Tuple[int, Tuple[int, int, str, str]]]:
layer_name = self.config['primary']
context_modules = []
for mod in mods:
try:
module_name_with_ext = mod.BaseDllName.get_string()
except exceptions.InvalidAddressException:
# there's no use for a module with no name?
continue
module_name = os.path.splitext(module_name_with_ext)[0]
if module_name in windows_constants.KERNEL_MODULE_NAMES:
symbol_table_name = self.config["nt_symbols"]
else:
symbol_table_name = None
context_module = contexts.SizedModule(self._context,
module_name,
layer_name,
mod.DllBase,
mod.SizeOfImage,
symbol_table_name)
context_modules.append(context_module)
collection = contexts.ModuleCollection(context_modules)
kvo = self.context.memory[layer_name].config['kernel_virtual_offset']
ntkrnlmp = self.context.module(self.config["nt_symbols"], layer_name = layer_name, offset = kvo)
# this is just one way to enumerate the native (NT) service table.
# to do the same thing for the Win32K service table, we would need Win32K.sys symbol support
## we could also find nt!KeServiceDescriptorTable (NT) and KeServiceDescriptorTableShadow (NT, Win32K)
service_table_address = ntkrnlmp.get_symbol("KiServiceTable").address
service_limit_address = ntkrnlmp.get_symbol("KiServiceLimit").address
service_limit = ntkrnlmp.object(type_name = "int", offset = kvo + service_limit_address)
# on 32-bit systems the table indexes are 32-bits and contain pointers (unsigned)
# on 64-bit systems the indexes are also 32-bits but they're offsets from the
# base address of the table and can be negative, so we need a signed data type
is_kernel_64 = symbols.symbol_table_is_64bit(self.context, self.config["nt_symbols"])
if is_kernel_64:
array_subtype = "long"
def kvo_calulator(func):
return kvo + service_table_address + (func >> 4)
find_address = kvo_calulator
else:
array_subtype = "unsigned long"
def passthrough(func):
return func
find_address = passthrough
functions = ntkrnlmp.object(type_name = "array", offset = kvo + service_table_address,
subtype = ntkrnlmp.get_type(array_subtype),
count = service_limit)
for idx, function in enumerate(functions):
function = find_address(function)
module_symbols = collection.get_module_symbols_by_absolute_location(function)
for module_name, symbol_generator in module_symbols:
symbols_found = False
for symbol in symbol_generator:
symbols_found = True
yield (0, (idx,
format_hints.Hex(function),
module_name,
symbol.split(constants.BANG)[1]))
if not symbols_found:
yield (0, (idx,
format_hints.Hex(function),
module_name,
renderers.NotAvailableValue()))
def run(self) -> renderers.TreeGrid:
return renderers.TreeGrid([("Index", int),
("Address", format_hints.Hex),
("Module", str),
("Symbol", str)],
self._generator(modules.Modules.list_modules(self.context,
self.config['primary'],
self.config['nt_symbols'])))
@@ -0,0 +1,92 @@
import logging
import re
from typing import Dict, Generator, List, Set, Tuple
from volatility.framework import interfaces, renderers, layers
from volatility.framework.configuration import requirements
from volatility.framework.layers import intel
from volatility.framework.renderers import format_hints
from volatility.plugins.windows import pslist
vollog = logging.getLogger(__name__)
class Strings(interfaces.plugins.PluginInterface):
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS"),
requirements.URIRequirement(name = "strings_file", description = "Strings file")]
# TODO: Make URLRequirement that can accept a file address which the framework can open
def run(self):
return renderers.TreeGrid([("String", str),
("Physical Address", format_hints.Hex),
("Result", str)],
self._generator())
def _generator(self) -> Generator[Tuple, None, None]:
"""Generates results from a strings file"""
revmap = self.generate_mapping(self.config['primary'])
accessor = layers.ResourceAccessor()
for line in accessor.open(self.config['strings_file'], "rb").readlines():
try:
offset, string = self._parse_line(line)
try:
revmap_list = [name + ":" + hex(offset) for (name, offset) in revmap[offset >> 12]]
except (IndexError, KeyError):
revmap_list = ["FREE MEMORY"]
yield (0, (str(string, 'latin-1'), format_hints.Hex(offset), ", ".join(revmap_list)))
except ValueError:
vollog.error("Strings file is in the wrong format")
return
@staticmethod
def _parse_line(line: bytes) -> Tuple[int, bytes]:
"""Parses a single line from a strings file"""
pattern = re.compile(rb"(?:\W*)([0-9]+)(?:\W*)(\w[\w\W]+)")
match = pattern.search(line)
if not match:
raise ValueError("Strings file contains invalid strings line")
offset, string = match.group(1, 2)
return int(offset), string
def generate_mapping(self, layer_name: str) -> Dict[int, Set[Tuple[str, int]]]:
"""Creates a reverse mapping between virtual addresses and physical addresses"""
layer = self._context.memory[layer_name]
reverse_map = dict() # type: Dict[int, Set[Tuple[str, int]]]
if isinstance(layer, intel.Intel):
# We don't care about errors, we just wanted chunks that map correctly
for mapval in layer.mapping(0x0, layer.maximum_address, ignore_errors = True):
vpage, kpage, page_size, maplayer = mapval
for val in range(kpage, kpage + page_size, 0x1000):
cur_set = reverse_map.get(kpage >> 12, set())
cur_set.add(("kernel", vpage))
reverse_map[kpage >> 12] = cur_set
self._progress_callback((vpage * 100) / layer.maximum_address, "Creating reverse kernel map")
# TODO: Include kernel modules
for process in pslist.PsList.list_processes(self.context,
self.config['primary'],
self.config['nt_symbols']):
proc_layer_name = process.add_process_layer()
proc_layer = self.context.memory[proc_layer_name]
if isinstance(proc_layer, interfaces.layers.TranslationLayerInterface):
for mapval in proc_layer.mapping(0x0, proc_layer.maximum_address, ignore_errors = True):
kpage, vpage, page_size, maplayer = mapval
for val in range(kpage, kpage + page_size, 0x1000):
cur_set = reverse_map.get(kpage >> 12, set())
cur_set.add(("Process {}".format(process.UniqueProcessId), vpage))
reverse_map[kpage >> 12] = cur_set
# FIXME: make the progress for all processes, rather than per-process
self._progress_callback((vpage * 100) / layer.maximum_address,
"Creating mapping for task {}".format(process.UniqueProcessId))
return reverse_map
@@ -0,0 +1,81 @@
import logging
from typing import List
import volatility.framework.interfaces.plugins as interfaces_plugins
import volatility.plugins.windows.pslist as pslist
import volatility.plugins.windows.vadinfo as vadinfo
from volatility.framework import renderers, interfaces, exceptions
from volatility.framework.configuration import requirements
from volatility.framework.objects import utility
vollog = logging.getLogger(__name__)
class VadDump(interfaces_plugins.PluginInterface):
"""Dumps process memory ranges"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS"),
# 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 _generator(self, procs):
filter_func = lambda _: False
if self.config.get('address', None) is not None:
filter_func = lambda x: x.get_start() not in [self.config['address']]
chunk_size = 1024 * 1024 * 10
for proc in procs:
process_name = utility.array_to_string(proc.ImageFileName)
# TODO: what kind of exceptions could this raise and what should we do?
proc_layer_name = proc.add_process_layer()
proc_layer = self.context.memory[proc_layer_name]
for vad in vadinfo.VadInfo.list_vads(proc, filter_func = filter_func):
try:
filedata = interfaces_plugins.FileInterface(
"pid.{0}.vad.{1:#x}-{2:#x}.dmp".format(proc.UniqueProcessId,
vad.get_start(),
vad.get_end()))
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
filedata.data.write(data)
offset += to_read
self.produce_file(filedata)
result_text = "Stored {}".format(filedata.preferred_filename)
except exceptions.InvalidAddressException:
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):
filter_func = pslist.PsList.create_filter([self.config.get('pid', None)])
return renderers.TreeGrid([("PID", int),
("Process", str),
("Result", str)],
self._generator(pslist.PsList.list_processes(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'],
filter_func = filter_func)))
@@ -0,0 +1,125 @@
import logging
from typing import Callable, Generator, Iterable
import volatility.framework.interfaces.plugins as interfaces_plugins
import volatility.plugins.windows.pslist as pslist
from volatility.framework import renderers, interfaces
from volatility.framework.configuration import requirements
from volatility.framework.objects import utility
from volatility.framework.renderers import format_hints
vollog = logging.getLogger(__name__)
# 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, *args, **kwargs):
super().__init__(*args, **kwargs)
self._protect_values = None
@classmethod
def get_requirements(cls):
# Since we're calling the plugin, make sure we have the plugin's requirements
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS"),
# 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)]
@classmethod
def protect_values(cls,
context: interfaces.context.ContextInterface,
virtual_layer: str,
nt_symbols: str) -> Iterable[int]:
"""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."""
kvo = context.memory[virtual_layer].config["kernel_virtual_offset"]
ntkrnlmp = context.module(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)
return values # type: ignore
@classmethod
def list_vads(cls, proc: interfaces.objects.ObjectInterface,
filter_func: Callable[[int], bool] = lambda _: False) -> \
Generator[interfaces.objects.ObjectInterface, None, None]:
for vad in proc.get_vad_root().traverse():
if not filter_func(vad):
yield vad
def _generator(self, procs):
def passthrough(_):
return False
filter_func = passthrough
if self.config.get('address', None) is not None:
def filter_function(x):
return x.get_start() not in [self.config['address']]
filter_func = filter_function
for proc in procs:
process_name = utility.array_to_string(proc.ImageFileName)
for vad in self.list_vads(proc, filter_func = filter_func):
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.get_tag(),
vad.get_protection(self.protect_values(self.context,
self.config['primary'],
self.config['nt_symbols']), winnt_protections),
vad.get_commit_charge(),
vad.get_private_memory(),
format_hints.Hex(vad.get_parent()),
vad.get_file_name()))
def run(self):
filter_func = pslist.PsList.create_filter([self.config.get('pid', None)])
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(pslist.PsList.list_processes(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'],
filter_func = filter_func)))
@@ -0,0 +1,84 @@
import logging
from typing import Any, Iterable, List, Tuple
from volatility.framework import interfaces, layers, renderers
from volatility.framework.configuration import requirements
from volatility.framework.renderers import format_hints
from volatility.framework.symbols.windows import extensions
from volatility.plugins import yarascan
from volatility.plugins.windows import pslist
vollog = logging.getLogger(__name__)
try:
import yara
except ImportError:
vollog.info("Python Yara module not found, plugin (and dependent plugins) not available")
class VadYaraScan(interfaces.plugins.PluginInterface):
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [requirements.TranslationLayerRequirement(name = 'primary',
description = "Primary kernel address space",
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS"),
requirements.BooleanRequirement(name = "wide",
description = "Match wide (unicode) strings",
default = False,
optional = True),
requirements.StringRequirement(name = "yara_rules",
description = "Yara rules (as a string)",
optional = True),
requirements.URIRequirement(name = "yara_file",
description = "Yara rules (as a file)",
optional = True),
requirements.IntRequirement(name = "max_size",
default = 0x40000000,
description = "Set the maximum size (default is 1GB)",
optional = True)
]
def _generator(self):
layer = self.context.memory[self.config['primary']]
rules = None
if self.config.get('yara_rules', None) is not None:
rule = self.config['yara_rules']
if rule[0] not in ["{", "/"]:
rule = '"{}"'.format(rule)
if self.config.get('case', False):
rule += " nocase"
if self.config.get('wide', False):
rule += " wide ascii"
rules = yara.compile(sources = {'n': 'rule r1 {{strings: $a = {} condition: $a}}'.format(rule)})
elif self.config.get('yara_file', None) is not None:
rules = yara.compile(file = layers.ResourceAccessor().open(self.config['yara_file'], "rb"))
else:
vollog.error("No yara rules, nor yara rules file were specified")
filter_func = pslist.PsList.create_filter([self.config.get('pid', None)])
for task in pslist.PsList.list_processes(context = self.context,
layer_name = self.config['primary'],
symbol_table = self.config['nt_symbols'],
filter_func = filter_func):
for offset, name in layer.scan(context = self.context,
scanner = yarascan.YaraScanner(rules = rules),
sections = self.get_vad_maps(task)):
yield format_hints.Hex(offset), name
def get_vad_maps(self, task: Any) -> Iterable[Tuple[int, int]]:
task = self._check_type(task, extensions._EPROCESS)
vad_root = task.get_vad_root()
for vad in vad_root.traverse():
end = vad.get_end()
start = vad.get_start()
yield (start, end - start)
def run(self):
return renderers.TreeGrid([('Offset', format_hints.Hex),
('Rule', str)], self._generator())
@@ -0,0 +1,172 @@
import io
import logging
from typing import Generator, List, Tuple
import volatility.framework.interfaces.plugins as interfaces_plugins
import volatility.plugins.windows.moddump as moddump
import volatility.plugins.windows.modules as modules
from volatility.framework import exceptions, renderers, constants, interfaces
from volatility.framework.configuration import requirements
from volatility.framework.renderers import format_hints
from volatility.framework.symbols.windows.pe import PEIntermedSymbols
from volatility.plugins.windows import pslist
vollog = logging.getLogger(__name__)
try:
import pefile
except ImportError:
vollog.info("Python pefile module not found, plugin (and dependent plugins) not available")
raise
class VerInfo(interfaces_plugins.PluginInterface):
"""Lists version information from PE files"""
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
## TODO: we might add a regex option on the name later, but otherwise we're good
## TODO: and we don't want any CLI options from pslist, modules, or moddump
return [requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolRequirement(name = "nt_symbols", description = "Windows OS"), ]
@classmethod
def get_version_information(cls,
context: interfaces.context.ContextInterface,
pe_table_name: str,
layer_name: str,
base_address: int) -> Tuple[int, int, int, int]:
"""Get File and Product version information from PE files
Args:
context: volatility context on which to operate
pe_table_name: name of the PE table
layer_name: name of the layer containing the PE file
base_address: base address of the PE (where MZ is found)
"""
if layer_name is None:
raise ValueError("Layer must be a string not None")
pe_data = io.BytesIO()
dos_header = context.object(pe_table_name + constants.BANG +
"_IMAGE_DOS_HEADER", offset = base_address,
layer_name = layer_name)
for offset, data in dos_header.reconstruct():
pe_data.seek(offset)
pe_data.write(data)
pe = pefile.PE(data = pe_data.getvalue(), fast_load = True)
pe.parse_data_directories([pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_RESOURCE"]])
major = pe.VS_FIXEDFILEINFO.ProductVersionMS >> 16
minor = pe.VS_FIXEDFILEINFO.ProductVersionMS & 0xFFFF
product = pe.VS_FIXEDFILEINFO.ProductVersionLS >> 16
build = pe.VS_FIXEDFILEINFO.ProductVersionLS & 0xFFFF
pe_data.close()
return major, minor, product, build
def _generator(self,
procs: Generator[interfaces.objects.ObjectInterface, None, None],
mods: Generator[interfaces.objects.ObjectInterface, None, None],
session_layers: Generator[str, None, None]):
"""Generates a list of PE file version info for processes, dlls, and modules.
Args:
procs: <generator> of processes
mods: <generator> of modules
session_layers: <generator> of layers in the session to be checked
"""
pe_table_name = PEIntermedSymbols.create(self.context,
self.config_path,
"windows",
"pe")
for mod in mods:
try:
BaseDllName = mod.BaseDllName.get_string()
except exceptions.InvalidAddressException:
BaseDllName = renderers.UnreadableValue()
session_layer_name = moddump.ModDump.find_session_layer(self.context, session_layers, mod.DllBase)
(major, minor, product, build) = [
renderers.NotAvailableValue()] * 4 # type: Tuple[Union[int, interfaces.renderers.BaseAbsentValue],Union[int, interfaces.renderers.BaseAbsentValue],Union[int, interfaces.renderers.BaseAbsentValue],Union[int, interfaces.renderers.BaseAbsentValue]]
try:
(major, minor, product, build) = self.get_version_information(self._context,
pe_table_name,
session_layer_name,
mod.DllBase)
except (exceptions.InvalidAddressException, ValueError, AttributeError):
(major, minor, product, build) = [renderers.UnreadableValue()] * 4
# the pid and process are not applicable for kernel modules
yield (0, (renderers.NotApplicableValue(),
renderers.NotApplicableValue(),
format_hints.Hex(mod.DllBase),
BaseDllName,
major,
minor,
product,
build))
# now go through the process and dll lists
for proc in procs:
proc_layer_name = proc.add_process_layer()
for entry in proc.load_order_modules():
try:
BaseDllName = entry.BaseDllName.get_string()
except exceptions.InvalidAddressException:
BaseDllName = renderers.UnreadableValue()
session_layer_name = moddump.ModDump.find_session_layer(self.context, session_layers, mod.DllBase)
(major, minor, product, build) = [renderers.NotAvailableValue()] * 4
try:
(major, minor, product, build) = self.get_version_information(self._context,
pe_table_name,
proc_layer_name,
entry.DllBase)
except (exceptions.InvalidAddressException, ValueError, AttributeError):
(major, minor, product, build) = [renderers.UnreadableValue()] * 4
yield (0, (proc.UniqueProcessId,
proc.ImageFileName.cast("string",
max_length = proc.ImageFileName.vol.count,
errors = "replace"),
format_hints.Hex(entry.DllBase),
BaseDllName,
major,
minor,
product,
build))
def run(self):
procs = pslist.PsList.list_processes(self.context,
self.config["primary"],
self.config["nt_symbols"])
mods = modules.Modules.list_modules(self.context,
self.config["primary"],
self.config["nt_symbols"])
# populate the session layers for kernel modules
session_layers = moddump.ModDump.get_session_layers(self.context,
self.config['primary'],
self.config['nt_symbols'])
return renderers.TreeGrid([("PID", int),
("Process", str),
("Base", format_hints.Hex),
("Name", str),
("Major", int),
("Minor", int),
("Product", int),
("Build", int)],
self._generator(procs, mods, session_layers))