Merge branch 'volatilityfoundation:develop' into feature/mermaid

This commit is contained in:
Donghyun Kim
2022-06-21 02:13:53 +09:00
committed by GitHub
27 changed files with 360 additions and 61 deletions
+23 -1
View File
@@ -9,7 +9,11 @@ Synopsis
**volatility** [-h] [-c CONFIG] [--parallelism [{processes,threads,off}]]
[-e EXTEND] [-p PLUGIN_DIRS] [-s SYMBOL_DIRS] [-v] [-l LOG]
[-o OUTPUT_DIR] [-q] [-r RENDERER] [-f FILE]
[--write-config] [--single-location SINGLE_LOCATION]
[--write-config] [--save-config SAVE_CONFIG]
[--clear-cache] [--cache-path CACHE_PATH]
[--offline]
[--single-location SINGLE_LOCATION]
[--stackers [STACKERS ...]]
[--single-swap-locations SINGLE_SWAP_LOCATIONS]
<plugin> ...
@@ -98,6 +102,10 @@ Options
attempt to build upon, and can be considered the input for the program.
--write-config
*Deprecated*
Use of `--write-config` has been deprecated, replaced by `--save-config`
--save-config
This flag specifies that volatility should write or overwrite a file
called config.json in the current directory. The file will contain
the necessary JSON configuration to recreate the environment that the
@@ -105,11 +113,25 @@ Options
other plugins, but there's no guarantee that plugins use the same
configuration options.
--clear-cache
Clears out all short-term cached items.
--cache-path
Change the default path used to store the cache.
--offline
Do not search online for additional JSON files.
Run offline mode (defaults to false) and for
remote windows symbol tables, linux/mac banner repositories.
--single-location SINGLE_LOCATION
This specifies a URL which will be downloaded if necessary, and built
upon by the automagic and, since most plugins require a single memory
image, can be considered the input for the program.
--stackers STACKERS
Creates the list of stackers to use based on the config option.
--single-swap-locations SINGLE_SWAP_LOCATIONS
A comma-separated list of swap files to be considered as part of the
memory image specified by the single-location or file parameters.
+1 -1
View File
@@ -26,7 +26,7 @@ except ImportError:
# Volatility must be findable in sys.path in order for collect_submodules to work
# This adds the current working directory, which should usually do the trick
sys.path.append(os.getcwd())
sys.path.append(os.path.dirname(os.path.abspath(SPEC)))
vol_analysis = Analysis(['vol.py'],
pathex = [],
+3 -3
View File
@@ -37,9 +37,9 @@ class WarningFindSpec(abc.MetaPathFinder):
first."""
if fullname.startswith("volatility3.framework.plugins."):
warning = "Please do not use the volatility3.framework.plugins namespace directly, only use volatility3.plugins"
# Pyinstaller uses walk_packages to import, but needs to read the modules to figure out dependencies
# As such, we only print the warning when directly imported rather than from within walk_packages
if inspect.stack()[-2].function != 'walk_packages':
# Pyinstaller uses walk_packages/_collect_submodules to import, but needs to read the modules to figure out dependencies
# As such, we only print the warning when directly imported rather than from within walk_packages/_collect_submodules
if inspect.stack()[-2].function in ['walk_packages', '_collect_submodules']:
raise Warning(warning)
+1
View File
@@ -332,6 +332,7 @@ class CommandLine:
parser.error(f"Cannot write configuration: file {args.save_config} already exists")
with open(args.save_config, "w") as f:
json.dump(dict(constructed.build_configuration()), f, sort_keys = True, indent = 2)
f.write("\n")
except exceptions.UnsatisfiedException as excp:
self.process_unsatisfied_exceptions(excp)
parser.exit(1, f"Unable to validate the plugin requirements: {[x for x in excp.unsatisfied]}\n")
+1 -1
View File
@@ -246,6 +246,7 @@ class VolShell(cli.CommandLine):
parser.error(f"Cannot write configuration: file {args.save_config} already exists")
with open(args.save_config, "w") as f:
json.dump(dict(constructed.build_configuration()), f, sort_keys = True, indent = 2)
f.write("\n")
except exceptions.UnsatisfiedException as excp:
self.process_unsatisfied_exceptions(excp)
parser.exit(1, f"Unable to validate the plugin requirements: {[x for x in excp.unsatisfied]}\n")
@@ -256,7 +257,6 @@ class VolShell(cli.CommandLine):
constructed.run()
except exceptions.VolatilityException as excp:
self.process_exceptions(excp)
parser.exit(1, f"Unable to validate the plugin requirements: {[x for x in excp.unsatisfied]}\n")
def main():
+1 -1
View File
@@ -321,7 +321,7 @@ class SizedModule(Module):
The mapping should be sorted and should be quicker than reading
the data We turn it into JSON to make a common string and use a
quick hash, because collissions are unlikely
quick hash, because collisions are unlikely
"""
layer = self._context.layers[self.layer_name]
if not isinstance(layer, interfaces.layers.TranslationLayerInterface):
@@ -523,7 +523,7 @@ class ConstructableRequirementInterface(RequirementInterface):
must happen after the class configuration value has been provided).
These values are then provided to the object's constructor by name
as arguments (as well as the standard `context` and `config_path`
arguments.
arguments).
"""
def __init__(self, *args, **kwargs) -> None:
+2 -2
View File
@@ -307,7 +307,7 @@ class DataLayerInterface(interfaces.configuration.ConfigurableInterface, metacla
while length > 0:
chunk_size = min(length, scanner.chunk_size + scanner.overlap)
yield [(layer_name, mapped_offset, chunk_size)], offset + chunk_size
# It we've got more than the scanner's chunk_size, only move up by the chunk_size
# If we've got more than the scanner's chunk_size, only move up by the chunk_size
if chunk_size > scanner.chunk_size:
chunk_size -= scanner.overlap
length -= chunk_size
@@ -517,7 +517,7 @@ class TranslationLayerInterface(DataLayerInterface, metaclass = ABCMeta):
yield output, chunk_position
output = []
chunk_position = chunk_start
# Take from chunk_position as far as far as the block can go,
# Take from chunk_position as far as the block can go,
# or as much left of a scanner chunk as we can
chunk_size = min(block_end - chunk_position,
scanner.chunk_size + scanner.overlap - (chunk_position - chunk_start))
+1 -1
View File
@@ -169,7 +169,7 @@ class BaseSymbolTableInterface:
def optional_set_type_class(self, name: str, clazz: Type[objects.ObjectInterface]) -> bool:
"""Calls the set_type_class function but does not throw an exception.
Returns whether setting the type class was successfull.
Returns whether setting the type class was successful.
Args:
name: The name of the type to override the class for
clazz: The actual class to override for the provided type name
+6 -4
View File
@@ -88,6 +88,7 @@ class FileLayer(interfaces.layers.DataLayerInterface):
self._accessor = resources.ResourceAccessor()
self._file_: Optional[IO[Any]] = None
self._size: Optional[int] = None
self._maximum_address: Optional[int] = None
# Construct the lock now (shared if made before threading) in case we ever need it
self._lock: Union[DummyLock, threading.Lock] = DummyLock()
if constants.PARALLELISM == constants.Parallelism.Threading:
@@ -113,14 +114,15 @@ class FileLayer(interfaces.layers.DataLayerInterface):
def maximum_address(self) -> int:
"""Returns the largest available address in the space."""
# Zero based, so we return the size of the file minus 1
if self._size:
return self._size
if self._maximum_address:
return self._maximum_address
with self._lock:
orig = self._file.tell()
self._file.seek(0, 2)
self._size = self._file.tell()
self._file.seek(orig)
return self._size
self._maximum_address = self._size - 1
return self._maximum_address
@property
def minimum_address(self) -> int:
@@ -189,7 +191,7 @@ class FileLayer(interfaces.layers.DataLayerInterface):
"""Closes the file handle."""
self._file.close()
def __del__(self) -> None:
def __exit__(self) -> None:
self.destroy()
@classmethod
+122 -10
View File
@@ -3,12 +3,17 @@
#
import functools
import json
from typing import Optional, Dict, Any, Tuple, List, Set
import logging
import re
import struct
from typing import Any, Dict, List, Optional, Set, Tuple
from volatility3.framework import interfaces, exceptions, constants
from volatility3.framework.layers import segmented
from volatility3.framework import constants, exceptions, interfaces
from volatility3.framework.layers import scanners, segmented
from volatility3.framework.symbols import intermed
vollog = logging.getLogger(__name__)
class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer):
"""A Qemu suspend-to-disk translation layer."""
@@ -32,6 +37,34 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer):
SEGMENT_FLAG_XBZRLE = 0x40
SEGMENT_FLAG_HOOK = 0x80
# See https://qemu.readthedocs.io/en/latest/devel/memory.html for more info
#
# At least the following values could occur for devices using > 3-4 GB RAM:
# +--------------------------------+--------------------------------+------------+-------------+
# | Architecture | Reference Code | Hole Start | Hole End |
# +--------------------------------+--------------------------------+------------+-------------+
# | PC i440FX + PIIX "New Default" | qemu/hw/i386/pc_piix.c:98 | 0xc0000000 | 0x100000000 |
# | PC i440FX + PIIX "Old Default" | qemu/hw/i386/pc_piix.c:98 | 0xe0000000 | 0x100000000 |
# | PC Q35 + ICH9 | qemu/hw/i386/pc_q35.c:141 | 0x80000000 | 0x100000000 |
# | MicroVM | qemu/hw/i386/microvm.c:291 | 0xc0000000 | 0x100000000 |
# | Xen | qemu/hw/i386/xen/xen-hvm.c:248 | 0xf0000000 | 0x100000000 |
# +--------------------------------+--------------------------------+------------+-------------+
#
# For now, we assume that the parameter max-ram-below-4g is not set, since this parameter influences the size
# and location of the memory gap. Deviating hole sizes could eventually be detected for Linux by e.g. scanning
# for dmesg entries with a regex like rb'\[mem (0x[0-9a-f]{4,10})-0x[0-9a-f]{4,10}\] available for PCI devices'
distro_re = r"(\w+[\d{1,2}\.]*)"
pci_hole_table = {re.compile(r"^pc-i440fx-([23456789]|\d\d+)\.\d$"): (0xe0000000, 0xc0000000, 0x100000000),
re.compile(r"^pc-i440fx-[01]\.\d$"): (0xe0000000, 0xe0000000, 0x100000000),
re.compile(r"^pc-q35-\d\.\d$"): (0xb0000000, 0x80000000, 0x100000000),
re.compile(r"^microvm$"): (0xc0000000, 0xc0000000, 0x100000000),
re.compile(r"^xen$"): (0xf0000000, 0xf0000000, 0x100000000),
re.compile(r"^pc-i440fx-" + distro_re + r"$"): (0xe0000000, 0xc0000000, 0x100000000),
re.compile(r"^pc-q35-" + distro_re + r"$"): (0xb0000000, 0x80000000, 0x100000000),
}
def __init__(self,
context: interfaces.context.ContextInterface,
config_path: str,
@@ -39,8 +72,12 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer):
metadata: Optional[Dict[str, Any]] = None) -> None:
self._qemu_table_name = intermed.IntermediateSymbolTable.create(context, config_path, 'generic', 'qemu')
self._configuration = None
self._architecture = None
self._compressed: Set[int] = set()
self._current_segment_name = b''
self._pci_hole_start = 0
self._pci_hole_end = 0
self._pci_hole_minimum = 0
super().__init__(context = context, config_path = config_path, name = name, metadata = metadata)
@classmethod
@@ -50,6 +87,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer):
raise exceptions.LayerException(name, 'No QEMU magic bytes')
if header[4:] != b'\x00\x00\x00\x03':
raise exceptions.LayerException(name, 'Unsupported QEMU version found')
vollog.debug("QEVM header found")
def _read_configuration(self, base_layer: interfaces.layers.DataLayerInterface, name: str) -> Any:
"""Reads the JSON configuration from the end of the file"""
@@ -73,12 +111,13 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer):
done = None
segments = []
size_array = {}
base_layer = self.context.layers[self._base_layer]
while not done:
addr = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned long long',
offset = index,
layer_name = self._base_layer)
# Use struct.unpack here for performance improvements
addr = struct.unpack('>Q', base_layer.read(index, 8))[0]
# Flags are stored in the n least significant bits, where n equals the bit-length of pagesize
flags = addr & (page_size - 1)
# addr equals the highest multiple of pagesize <= offset
@@ -86,19 +125,29 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer):
addr = addr ^ (addr & (page_size - 1))
index += 8
if addr >= self._pci_hole_start:
addr += self._pci_hole_end - self._pci_hole_start
if flags & self.SEGMENT_FLAG_MEM_SIZE:
namelen = self._context.object(self._qemu_table_name + constants.BANG + 'unsigned char',
offset = index,
layer_name = self._base_layer)
while namelen != 0:
# if base_layer.read(index + 1, namelen) == b'pc.ram':
# total_size = self._context.object(self._qemu_table_name + constants.BANG + 'unsigned long long',
# offset = index + 1 + namelen,
# layer_name = self._base_layer)
total_size = self._context.object(self._qemu_table_name + constants.BANG + 'unsigned long long',
offset = index + 1 + namelen,
layer_name = self._base_layer)
size_array[base_layer.read(index + 1, namelen)] = total_size
index += 1 + namelen + 8
namelen = self._context.object(self._qemu_table_name + constants.BANG + 'unsigned char',
offset = index,
layer_name = self._base_layer)
highest_possible_maximum = max([x[0] for x in self.pci_hole_table.values()]) + 1
if size_array.get(b'pc.ram', highest_possible_maximum) < self._pci_hole_minimum:
# Turns off the pci_hole if it's not supposed to be there
vollog.debug(
f"QEVM turning off PCI hole due to small image size: 0x{size_array.get(b'pc.ram'):x} < 0x{self._pci_hole_minimum:x}")
self._pci_hole_start, self._pci_hole_end = 0, 0
if flags & (self.SEGMENT_FLAG_COMPRESS | self.SEGMENT_FLAG_PAGE):
if not (flags & self.SEGMENT_FLAG_CONTINUE):
namelen = self._context.object(self._qemu_table_name + constants.BANG + 'unsigned char',
@@ -130,7 +179,26 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer):
index = 8
section_info = dict()
current_section_id = -1
arch_detected = False
while section_byte != self.QEVM_EOF and index <= base_layer.maximum_address:
if index > 20 and not arch_detected:
# We're past where the QEVM_CONFIGURATION might be, so set the values
# If no architecture has been set, try to determine it using fallback mechanisms
if not self._architecture:
self._architecture = self._fallback_determine_architecture()
if self._architecture is None:
vollog.log(constants.LOGLEVEL_VV, f"QEVM architecture could not be determined")
# Once all segments have been read, determine the PCI hole if any
for regex in self.pci_hole_table:
if regex.match(self._architecture):
self._pci_hole_minimum, self._pci_hole_start, self._pci_hole_end = self.pci_hole_table[regex]
vollog.log(constants.LOGLEVEL_VVVV, f"QEVM architecture detected as: {self._architecture}")
break
else:
vollog.log(constants.LOGLEVEL_VVVV, f"QEVM unknown architecture found: {self._architecture}")
arch_detected = True
section_byte = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned char',
offset = index,
layer_name = self._base_layer)
@@ -139,6 +207,9 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer):
section_len = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned long',
offset = index,
layer_name = self._base_layer)
self._architecture = self.context.object(self._qemu_table_name + constants.BANG + 'string',
offset = index + 4, layer_name = self._base_layer,
max_length = section_len)
index += 4 + section_len
elif section_byte == self.QEVM_SECTION_START or section_byte == self.QEVM_SECTION_FULL:
section_id = self.context.object(self._qemu_table_name + constants.BANG + 'unsigned long',
@@ -189,6 +260,47 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer):
else:
raise exceptions.LayerException(self._name, f'QEMU unknown section encountered: {section_byte}')
def _fallback_determine_architecture(self) -> str:
architecture_pattern = rb'pc-(i440fx|q35)-(\d{1,2}\.\d{1,2}|\w+[\d{1,2}\.]*)'
default_suffix = "-2.0"
base_layer = self.context.layers[self._base_layer]
vollog.log(constants.LOGLEVEL_VVVV, "QEVM fallback architecture detection used")
res = scanners.RegExScanner(architecture_pattern)
for offset in base_layer.scan(context = self.context, scanner = res):
line = base_layer.read(offset, 64)
regex_results = re.search(architecture_pattern, line)
architecture = regex_results.group().decode()
return architecture
# If that does not work, look in configuration JSON for devices specific to a certain architecture
architecture = None
for device in self._configuration.get('devices', []):
device_name = device.get('vmsd_name', '').lower()
if 'i440fx' in device_name or 'piix' in device_name:
architecture = 'pc-i440fx' + default_suffix
break
elif 'ich9' in device_name:
architecture = 'pc-q35' + default_suffix
break
if architecture:
vollog.log(constants.LOGLEVEL_VVV, f'Architecture version unknown, default used: {default_suffix}')
return architecture
# Still haven't found architecture, switch to fallback-method
architecture_pattern = rb'Standard PC \((i440FX|Q35)'
res = scanners.RegExScanner(architecture_pattern)
for offset in base_layer.scan(context = self.context, scanner = res):
line = base_layer.read(offset, 64)
regex_results = re.search(architecture_pattern, line)
architecture = "pc-" + regex_results.groups()[0].decode().lower() + default_suffix
vollog.log(constants.LOGLEVEL_VVV, f'Architecture version unknown, default used: {default_suffix}')
return architecture
vollog.warning("Could not determine QEMU target architecture!")
return None
def extract_data(self, index, name, version_id):
if name == 'ram':
if version_id != 4:
@@ -171,6 +171,8 @@ class ResourceAccessor(object):
cache_file.write(block)
block = fp.read(block_size)
cache_file.close()
else:
vollog.debug(f"Using already cached file at: {temp_filename}")
# Re-open the cache with a different mode
# Since we don't want people thinking they're able to save to the cache file,
# open it in read mode only and allow breakages to happen if they wanted to write
+1 -1
View File
@@ -63,7 +63,7 @@ class ObjectTemplate(interfaces.objects.Template):
object_info: interfaces.objects.ObjectInformation) -> interfaces.objects.ObjectInterface:
"""Constructs the object.
Returns: an object adhereing to the :class:`~volatility3.framework.interfaces.objects.ObjectInterface`
Returns: an object adhering to the :class:`~volatility3.framework.interfaces.objects.ObjectInterface`
"""
arguments: Dict[str, Any] = {}
for arg in self.vol:
@@ -80,7 +80,7 @@ class Check_syscall(plugins.PluginInterface):
def _get_table_info_disassembly(self, ptr_sz, vmlinux):
"""Find the size of the system call table by disassembling functions
that immediately reference it in their first isntruction This is in the
that immediately reference it in their first instruction This is in the
form 'cmp reg,NR_syscalls'."""
table_size = 0
@@ -1,7 +1,6 @@
# This file is Copyright 2021 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
# Author: Gustavo Moreira
import logging
from collections import namedtuple
@@ -0,0 +1,111 @@
# This file is Copyright 2022 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from typing import Optional
from volatility3.framework.configuration import requirements
from volatility3.framework import symbols, exceptions, renderers, interfaces
from volatility3.framework.objects import utility
from volatility3.plugins.linux import pslist
from volatility3.framework.interfaces import plugins
class PsAux(plugins.PluginInterface):
""" Lists processes with their command line arguments """
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
# Since we're calling the plugin, make sure we have the plugin's requirements
return [
requirements.ModuleRequirement(name = 'kernel', description = 'Linux kernel',
architectures = ["Intel32", "Intel64"]),
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)),
requirements.ListRequirement(name = 'pid',
description = 'Filter on specific process IDs',
element_type = int,
optional = True)
]
def _get_command_line_args(self, task: interfaces.objects.ObjectInterface,
name: str) -> Optional[str]:
"""
Reads the command line arguments of a process
These are stored on the userland stack
Kernel threads re-use the process data structure, but do not have a valid 'mm' pointer
Parameters:
task: task_struct object of the process
name: string name of the process (from task.comm)
"""
# kernel threads never have an mm as they do not have userland mappings
try:
mm = task.mm
except exceptions.InvalidAddressException:
mm = None
if mm:
proc_layer_name = task.add_process_layer()
if proc_layer_name is None:
return renderers.UnreadableValue()
proc_layer = self.context.layers[proc_layer_name]
# read argv from userland
start = task.mm.arg_start
# get the size of the arguments with sanity checking
size_to_read = task.mm.arg_end - task.mm.arg_start
if not (0 < size_to_read <= 4096):
return renderers.UnreadableValue()
# attempt to read it all as partial values are invalid and misleading
try:
argv = proc_layer.read(start, size_to_read)
except exceptions.InvalidAddressException:
return renderers.UnreadableValue()
# the arguments are null byte terminated, replace the nulls with spaces
s = argv.decode().split('\x00')
args = " ".join(s)
else:
# kernel thread
# [ ] mimics ps on a live system
# also helps identify malware masquerading as a kernel thread, which is fairly common
args = "[" + name + "]"
# remove trailing space, if present
if len(args) > 1 and args[-1] == " ":
args = args[:-1]
return args
def _generator(self, tasks):
""" Generates a listing of processes along with command line arguments """
# walk the process list and report the arguments
for task in tasks:
pid = task.pid
try:
ppid = task.parent.pid
except exceptions.InvalidAddressException:
ppid = 0
name = utility.array_to_string(task.comm)
args = self._get_command_line_args(task, name)
yield (0, (pid, ppid, name, args))
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None))
return renderers.TreeGrid([("PID", int), ("PPID", int), ("COMM", str), ("ARGS", str)],
self._generator(
pslist.PsList.list_tasks(self.context,
self.config['kernel'],
filter_func = filter_func)))
@@ -19,7 +19,7 @@ class PsTree(pslist.PsList):
"""Finds how deep the PID is in the tasks hierarchy.
Args:
pid: PID to find the level in the hierachy
pid: PID to find the level in the hierarchy
"""
seen = set([pid])
level = 0
@@ -1,4 +1,4 @@
# This file is opyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0
# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
+1 -1
View File
@@ -74,7 +74,7 @@ class Kevents(interfaces.plugins.PluginInterface):
@classmethod
def _walk_klist_array(cls, kernel, fdp, array_pointer_member, array_size_member):
"""
Convience wrapper for walking an array of lists of kernel events
Convenience wrapper for walking an array of lists of kernel events
Handles invalid address references
"""
try:
+3 -3
View File
@@ -101,7 +101,7 @@ class Timeliner(interfaces.plugins.PluginInterface):
return [sortable(timestamp) for timestamp in data[2:]]
def _generator(self, runable_plugins: List[TimeLinerInterface]) -> Optional[Iterable[Tuple[int, Tuple]]]:
def _generator(self, runnable_plugins: List[TimeLinerInterface]) -> Optional[Iterable[Tuple[int, Tuple]]]:
"""Takes a timeline, sorts it and output the data from each relevant
row from each plugin."""
# Generate the results for each plugin
@@ -115,9 +115,9 @@ class Timeliner(interfaces.plugins.PluginInterface):
file_data = None
fp = None
for plugin in runable_plugins:
for plugin in runnable_plugins:
plugin_name = plugin.__class__.__name__
self._progress_callback((runable_plugins.index(plugin) * 100) // len(runable_plugins),
self._progress_callback((runnable_plugins.index(plugin) * 100) // len(runnable_plugins),
f"Running plugin {plugin_name}...")
try:
vollog.log(logging.INFO, f"Running {plugin_name}")
@@ -25,7 +25,7 @@ class ModScan(interfaces.plugins.PluginInterface):
return [
requirements.ModuleRequirement(name = 'kernel', description = 'Windows kernel',
architectures = ["Intel32", "Intel64"]),
requirements.VersionRequirement(name = 'poolerscanner',
requirements.VersionRequirement(name = 'poolscanner',
component = poolscanner.PoolScanner,
version = (1, 0, 0)),
requirements.VersionRequirement(name = 'pslist', component = pslist.PsList, version = (2, 0, 0)),
@@ -9,7 +9,7 @@
# For a thorough walkthrough on how the R&D was performed to develop this plugin,
# please see our blogpost here:
#
# <insert blog URL once published>
# https://volatility-labs.blogspot.com/2021/10/memory-forensics-r-illustrated.html
import io
import logging
@@ -95,10 +95,10 @@ class SSDT(plugins.PluginInterface):
if is_kernel_64:
array_subtype = "long"
def kvo_calulator(func: int) -> int:
def kvo_calculator(func: int) -> int:
return kvo + service_table_address + (func >> 4)
find_address = kvo_calulator
find_address = kvo_calculator
else:
array_subtype = "unsigned long"
@@ -1,10 +1,10 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from typing import List, Tuple, Iterator
from typing import Iterator, List, Tuple
from volatility3 import framework
from volatility3.framework import exceptions, constants, interfaces, objects
from volatility3.framework import constants, exceptions, interfaces, objects
from volatility3.framework.objects import utility
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.linux import extensions
@@ -29,7 +29,9 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable):
self.set_type_class('files_struct', extensions.files_struct)
self.set_type_class('vfsmount', extensions.vfsmount)
self.set_type_class('kobject', extensions.kobject)
self.set_type_class('mnt_namespace', extensions.mnt_namespace)
if 'mnt_namespace' in self.types:
self.set_type_class('mnt_namespace', extensions.mnt_namespace)
if 'module' in self.types:
self.set_type_class('module', extensions.module)
@@ -267,4 +269,4 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
while list_start:
list_struct = vmlinux.object(object_type = struct_name, offset = list_start.vol.offset)
yield list_struct
list_start = getattr(list_struct, list_member)
list_start = getattr(list_struct, list_member)
+1 -1
View File
@@ -38,4 +38,4 @@ class WindowsMetadata(interfaces.symbols.MetadataInterface):
class LinuxMetadata(interfaces.symbols.MetadataInterface):
"""Class to handle the etadata from a Linux symbol table."""
"""Class to handle the metadata from a Linux symbol table."""
@@ -746,7 +746,10 @@ class LIST_ENTRY(objects.StructType, collections.abc.Iterable):
trans_layer = self._context.layers[layer]
try:
trans_layer.is_valid(self.vol.offset)
is_valid = trans_layer.is_valid(self.vol.offset)
if not is_valid:
return
link = getattr(self, direction).dereference()
except exceptions.InvalidAddressException:
return
@@ -761,9 +764,7 @@ class LIST_ENTRY(objects.StructType, collections.abc.Iterable):
while link.vol.offset not in seen:
obj_offset = link.vol.offset - relative_offset
try:
trans_layer.is_valid(obj_offset)
except exceptions.InvalidAddressException:
if not trans_layer.is_valid(obj_offset):
return
obj = self._context.object(symbol_type,
@@ -10,10 +10,10 @@ import os
import re
import struct
from typing import Any, Dict, Generator, List, Optional, Tuple, Union
from urllib import request, parse
from urllib import parse, request
from volatility3 import symbols
from volatility3.framework import constants, interfaces, exceptions
from volatility3.framework import constants, contexts, exceptions, interfaces
from volatility3.framework.configuration.requirements import SymbolTableRequirement
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.windows import pdbconv
@@ -24,7 +24,7 @@ vollog = logging.getLogger(__name__)
class PDBUtility(interfaces.configuration.VersionableInterface):
"""Class to handle and manage all getting symbols based on MZ header"""
_version = (1, 0, 0)
_version = (1, 0, 1)
_required_framework_version = (2, 0, 0)
@classmethod
@@ -131,14 +131,14 @@ class PDBUtility(interfaces.configuration.VersionableInterface):
# Check it is actually the MZ header
if mz_sig != b"MZ":
return None
nt_header_start, = struct.unpack("<I", layer.read(offset + 0x3C, 4))
pe_sig = layer.read(offset + nt_header_start, 2)
# Check it is actually the Nt Headers
if pe_sig != b"PE":
return None
optional_header_size, = struct.unpack('<H', layer.read(offset + nt_header_start + 0x14, 2))
# Just enough to tell us the max size
pe_header = layer.read(offset, nt_header_start + 0x16 + optional_header_size)
@@ -146,7 +146,7 @@ class PDBUtility(interfaces.configuration.VersionableInterface):
max_size = pe_data.OPTIONAL_HEADER.SizeOfImage
# Proper data
virtual_data = layer.read(offset, max_size, pad=True)
virtual_data = layer.read(offset, max_size, pad = True)
pe_data = pefile.PE(data = virtual_data)
# De-virtualize the memory
@@ -291,7 +291,7 @@ class PDBUtility(interfaces.configuration.VersionableInterface):
@classmethod
def symbol_table_from_pdb(cls, context: interfaces.context.ContextInterface, config_path: str, layer_name: str,
pdb_name: str, module_offset: int, module_size: int) -> str:
pdb_name: str, module_offset: int = None, module_size: int = None) -> str:
"""Creates symbol table for a module in the specified layer_name.
Searches the memory section of the loaded module for its PDB GUID
@@ -307,6 +307,19 @@ class PDBUtility(interfaces.configuration.VersionableInterface):
Returns:
The name of the constructed and loaded symbol table
"""
_, symbol_table_name = cls._modtable_from_pdb(context, config_path, layer_name, pdb_name, module_offset,
module_size)
return symbol_table_name
@classmethod
def _modtable_from_pdb(cls, context: interfaces.context.ContextInterface, config_path: str, layer_name: str,
pdb_name: str, module_offset: int = None, module_size: int = None,
create_module: bool = False) -> Tuple[Optional[str], Optional[str]]:
if module_offset is None:
module_offset = context.layers[layer_name].minimum_address
if module_size is None:
module_size = context.layers[layer_name].maximum_address - module_offset
guids = list(
cls.pdbname_scan(context,
@@ -323,12 +336,46 @@ class PDBUtility(interfaces.configuration.VersionableInterface):
vollog.debug(f"Found {guid['pdb_name']}: {guid['GUID']}-{guid['age']}")
return cls.load_windows_symbol_table(context,
guid["GUID"],
guid["age"],
guid["pdb_name"],
"volatility3.framework.symbols.intermed.IntermediateSymbolTable",
config_path = config_path)
module_name = guid["pdb_name"].strip('.pdb')
symbol_table_name = cls.load_windows_symbol_table(context,
guid["GUID"],
guid["age"],
guid["pdb_name"],
"volatility3.framework.symbols.intermed.IntermediateSymbolTable",
config_path = config_path)
new_module_name = None
if create_module:
new_module = contexts.Module.create(context, module_name, layer_name, offset = guid['mz_offset'],
symbol_table_name = symbol_table_name)
new_module_name = new_module.name
return new_module_name, symbol_table_name
@classmethod
def module_from_pdb(cls, context: interfaces.context.ContextInterface, config_path: str, layer_name: str,
pdb_name: str, module_offset: int = None, module_size: int = None) -> str:
"""Creates a module in the specified layer_name based on a pdb name.
Searches the memory section of the loaded module for its PDB GUID
and loads the associated symbol table into the symbol space.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
config_path: The config path where to find symbol files
layer_name: The name of the layer on which to operate
module_offset: This memory dump's module image offset
module_size: The size of the module for this dump
Returns:
The name of the constructed and loaded symbol table
"""
module_name, _ = cls._modtable_from_pdb(context, config_path, layer_name, pdb_name, module_offset,
module_size, create_module = True)
return module_name
class PdbSignatureScanner(interfaces.layers.ScannerInterface):