Merge pull request #1935 from volatilityfoundation/release/v2.27.0

Release/v2.27.0
This commit is contained in:
ikelos
2026-01-29 21:28:51 +00:00
committed by GitHub
114 changed files with 3351 additions and 729 deletions
+1 -1
View File
@@ -28,7 +28,7 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install pyinstaller
pip install -e .[full,cloud]
pip install -e .[full,cloud,arrow]
- name: Pyinstall executable
run: |
+5 -1
View File
@@ -66,13 +66,17 @@ pip install -e ".[dev]"
Symbol table packs for the various operating systems are available for download at:
<https://downloads.volatilityfoundation.org/volatility3/symbols/windows.zip>
<https://downloads.volatilityfoundation.org/volatility3/symbols/mac.zip>
<https://downloads.volatilityfoundation.org/volatility3/symbols/linux.zip>
The hashes to verify whether any of the symbol pack files have downloaded successfully or have changed can be found at:
<https://downloads.volatilityfoundation.org/volatility3/symbols/SHA256SUMS>
<https://downloads.volatilityfoundation.org/volatility3/symbols/SHA1SUMS>
<https://downloads.volatilityfoundation.org/volatility3/symbols/MD5SUMS>
Symbol tables zip files must be placed, as named, into the `volatility3/symbols` directory (or just the symbols directory next to the executable file).
@@ -92,7 +96,7 @@ The latest generated copy of the documentation can be found at: <https://volatil
## Licensing and Copyright
Copyright (C) 2007-2025 Volatility Foundation
Copyright (C) 2007-2026 Volatility Foundation
All Rights Reserved
-2
View File
@@ -14,7 +14,6 @@ vollog = logging.getLogger(__name__)
class BannerCacheGenerator:
def __init__(self, path: str, url_prefix: str):
self._path = path
self._url_prefix = url_prefix
@@ -79,7 +78,6 @@ class BannerCacheGenerator:
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--path", default=os.path.dirname(__file__))
parser.add_argument(
-1
View File
@@ -208,7 +208,6 @@ class Volatility3PyPyTest(VolatilityTest):
class VolatilityTester:
def __init__(
self,
images: List[VolatilityImage],
-1
View File
@@ -22,7 +22,6 @@ if __name__ == "__main__":
class PDBRetreiver:
def retreive_pdb(self, guid: str, file_name: str) -> Optional[str]:
logger.info("Download PDB file...")
file_name = ".".join(file_name.split(".")[:-1] + ["pdb"])
-1
View File
@@ -13,7 +13,6 @@ DWARF2JSON = "./dwarf2json"
class Downloader:
def __init__(self, url_lists: List[List[str]]) -> None:
self.url_lists = url_lists
+3 -4
View File
@@ -1,4 +1,4 @@
# This file is Copyright 2022 Volatility Foundation and licensed under the Volatility Software License 1.0
# This file is Copyright 2026 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
#
@@ -16,11 +16,10 @@
import os
import sys
from importlib.util import find_spec
import sphinx.ext.apidoc
from importlib.util import find_spec
def setup(app):
volatility_directory = os.path.abspath(
@@ -167,7 +166,7 @@ master_doc = "index"
# General information about the project.
project = "Volatility 3"
copyright = "2012-2025, Volatility Foundation"
copyright = "2012-2026, Volatility Foundation"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
+3 -1
View File
@@ -42,6 +42,8 @@ dev = [
"types-jsonschema>=4.23.0,<5",
]
arrow = ["pyarrow>=17.0.0"]
test = [
"volatility3[dev]",
"pytest>=8.3.3,<9",
@@ -52,7 +54,7 @@ test = [
docs = [
"volatility3[dev]",
"sphinx>=4.0.0,<9",
"sphinx-autodoc-typehints>=3.0.0,<4",
"sphinx-autodoc-typehints>=3.0.0,<4; python_version >= '3.11'",
"sphinx-rtd-theme>=3.0.1,<4",
]
File diff suppressed because it is too large Load Diff
+4 -3
View File
@@ -4,6 +4,7 @@ import json
import os
import shutil
import tempfile
from test import WindowsSamples, test_volatility
@@ -437,7 +438,7 @@ class TestWindowsVadyarascan:
class TestWindowsAmcache:
def test_windows_generic_amcache(self, volatility, python, image):
rc, out, _err = test_volatility.runvol_plugin(
"windows.amcache.Amcache",
"windows.registry.amcache.Amcache",
image,
volatility,
python,
@@ -492,7 +493,7 @@ class TestWindowsBigPools:
# class TestWindowsCachedump:
# def test_windows_generic_cachedump(self, volatility, python, image):
# rc, out, _err = test_volatility.runvol_plugin(
# "windows.cachedump.Cachedump",
# "windows.registry.cachedump.Cachedump",
# image,
# volatility,
# python,
@@ -820,7 +821,7 @@ class TestWindowsLsadump:
def test_windows_specific_lsadump(self, volatility, python):
image = WindowsSamples.WINDOWSXP_GENERIC.value.path
rc, out, _err = test_volatility.runvol_plugin(
"windows.lsadump.Lsadump",
"windows.registry.lsadump.Lsadump",
image,
volatility,
python,
View File
+151
View File
@@ -0,0 +1,151 @@
import io
import pytest
from abc import ABC, abstractmethod
from test import test_volatility
HAS_PYARROW = False
try:
import pyarrow as pa
import pyarrow.parquet as pq
import pyarrow.compute as pc
HAS_PYARROW = True
except ImportError:
# The user doesn't have pyarrow installed, but HAS_PYARROW will be false so just continue
pass
@pytest.mark.skipif(not HAS_PYARROW, reason="pyarrow not installed")
class TestArrowRendererBase(ABC):
"""Base class for testing Arrow-based renderers.
Re-implements Windows and Linux plugin tests using PyArrow operations
instead of text-based assertions.
"""
renderer_format = None # Override in subclasses
@abstractmethod
def _get_table_from_output(self, output_bytes) -> "pa.Table":
"""Parse output bytes into Arrow table. Override in subclasses."""
def test_windows_generic_pslist(self, volatility, python, image):
rc, out, _err = test_volatility.runvol_plugin(
"windows.pslist.PsList",
image,
volatility,
python,
globalargs=("-r", self.renderer_format),
)
assert rc == 0
table = self._get_table_from_output(out)
assert table.num_rows > 10
assert (
table.filter(
pc.match_substring(
pc.utf8_lower(table.column("ImageFileName")), "system"
)
).num_rows
> 0
)
assert (
table.filter(
pc.match_substring(
pc.utf8_lower(table.column("ImageFileName")), "csrss.exe"
)
).num_rows
> 0
)
assert (
table.filter(
pc.match_substring(
pc.utf8_lower(table.column("ImageFileName")), "svchost.exe"
)
).num_rows
> 0
)
assert (
table.filter(pc.greater(table.column("PID"), 0)).num_rows == table.num_rows
)
def test_linux_generic_pslist(self, volatility, python, image):
rc, out, _err = test_volatility.runvol_plugin(
"linux.pslist.PsList",
image,
volatility,
python,
globalargs=("-r", self.renderer_format),
)
assert rc == 0
table = self._get_table_from_output(out)
assert table.num_rows > 10
init_rows = table.filter(
pc.match_substring(pc.utf8_lower(table.column("COMM")), "init")
)
systemd_rows = table.filter(
pc.match_substring(pc.utf8_lower(table.column("COMM")), "systemd")
)
assert (init_rows.num_rows > 0) or (systemd_rows.num_rows > 0)
assert (
table.filter(
pc.match_substring(pc.utf8_lower(table.column("COMM")), "watchdog")
).num_rows
> 0
)
assert (
table.filter(pc.greater(table.column("PID"), 0)).num_rows == table.num_rows
)
def test_windows_generic_handles(self, volatility, python, image):
rc, out, _err = test_volatility.runvol_plugin(
"windows.handles.Handles",
image,
volatility,
python,
globalargs=("-r", self.renderer_format),
pluginargs=("--pid", "4"),
)
assert rc == 0
table = self._get_table_from_output(out)
assert table.num_rows > 500
assert (
table.filter(
pc.match_substring(
pc.utf8_lower(table.column("Name")), "machine\\system"
)
).num_rows
> 0
)
def test_linux_generic_lsof(self, volatility, python, image):
rc, out, _err = test_volatility.runvol_plugin(
"linux.lsof.Lsof",
image,
volatility,
python,
globalargs=("-r", self.renderer_format),
)
assert rc == 0
table = self._get_table_from_output(out)
assert table.num_rows > 35
class TestParquetRenderer(TestArrowRendererBase):
renderer_format = "parquet"
def _get_table_from_output(self, output_bytes):
return pq.read_table(io.BytesIO(output_bytes))
class TestArrowRenderer(TestArrowRendererBase):
renderer_format = "arrow"
def _get_table_from_output(self, output_bytes):
return pa.ipc.open_stream(io.BytesIO(output_bytes)).read_all()
+5 -5
View File
@@ -82,7 +82,6 @@ class CodeViolation(metaclass=abc.ABCMeta):
class UnrequiredVersionableUsage(CodeViolation):
def __init__(
self,
module: types.ModuleType,
@@ -107,7 +106,6 @@ class UnrequiredVersionableUsage(CodeViolation):
class DirectVolatilityImportUsage(CodeViolation):
def __init__(
self,
module: types.ModuleType,
@@ -174,8 +172,11 @@ class ModuleVisitor(NodeVisitor):
"""
if (
node.module
and node.module.startswith("volatility3.") # Give a pass to volatility3 module
and node.module != "volatility3.framework.constants._version" # make an exception for this
and node.module.startswith(
"volatility3."
) # Give a pass to volatility3 module
and node.module
!= "volatility3.framework.constants._version" # make an exception for this
):
for name in node.names:
try:
@@ -204,7 +205,6 @@ class ModuleVisitor(NodeVisitor):
def enter_ImportFrom(self, node: ast.ImportFrom):
self._check_vol3_import_from(node)
def enter_ClassDef(self, node: ast.ClassDef) -> Any:
logger.debug("Entering class %s", node.name)
clazz = None
+1
View File
@@ -2,6 +2,7 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
"""Volatility 3 - An open-source memory forensics framework"""
import inspect
import sys
from importlib import abc
+31 -24
View File
@@ -10,6 +10,7 @@ User interfaces make use of the framework to:
* run the plugin
* display the results
"""
import argparse
import inspect
import io
@@ -106,13 +107,6 @@ class CommandLine:
volatility3.framework.require_interface_version(2, 0, 0)
renderers = dict(
[
(x.name.lower(), x)
for x in framework.class_subclasses(text_renderer.CLIRenderer)
]
)
# Load up system defaults
delayed_logs, default_config = self.load_system_defaults("vol.json")
@@ -193,14 +187,6 @@ class CommandLine:
default=False,
action="store_true",
)
parser.add_argument(
"-r",
"--renderer",
metavar="RENDERER",
help=f"Determines how to render the output ({', '.join(list(renderers))})",
default="quick",
choices=list(renderers),
)
parser.add_argument(
"-f",
"--file",
@@ -270,11 +256,6 @@ class CommandLine:
known_args = [arg for arg in sys.argv if arg != "--help" and arg != "-h"]
partial_args, _ = parser.parse_known_args(known_args)
banner_output = sys.stdout
if renderers[partial_args.renderer].structured_output:
banner_output = sys.stderr
banner_output.write(f"Volatility 3 Framework {constants.PACKAGE_VERSION}\n")
### Start up logging
if partial_args.log:
file_logger = logging.FileHandler(partial_args.log)
@@ -346,6 +327,24 @@ class CommandLine:
plugin_list = framework.list_plugins()
# Discover renderers after plugin directories are loaded
# This allows custom renderers to be found in plugin directories
renderers = dict(
[
(x.name.lower(), x)
for x in framework.class_subclasses(text_renderer.CLIRenderer)
]
)
parser.add_argument(
"-r",
"--renderer",
metavar="RENDERER",
help=f"Determines how to render the output ({', '.join(list(renderers))})",
default="quick",
choices=list(renderers),
)
seen_automagics = set()
chosen_configurables_list = {}
for amagic in automagics:
@@ -392,6 +391,13 @@ class CommandLine:
# before all the plugins have been added
argcomplete.autocomplete(parser)
args = parser.parse_args()
# Display banner - redirect to stderr if using structured output
banner_output = sys.stdout
if renderers[args.renderer].structured_output:
banner_output = sys.stderr
banner_output.write(f"Volatility 3 Framework {constants.PACKAGE_VERSION}\n")
if args.plugin is None:
parser.error(
f"Please select a plugin to run (see '{self.CLI_NAME} --help' for options"
@@ -453,8 +459,9 @@ class CommandLine:
raise ValueError(
"Invalid extension (extensions must be of the format \"conf.path.value='value'\")"
)
address, value = extension[: extension.find("=")], json.loads(
extension[extension.find("=") + 1 :]
address, value = (
extension[: extension.find("=")],
json.loads(extension[extension.find("=") + 1 :]),
)
ctx.config[address] = value
@@ -569,7 +576,7 @@ class CommandLine:
delayed_logs.append(
(
logging.DEBUG,
f"Loaded configuration: {json.dumps(result, indent = 2, sort_keys = True)}",
f"Loaded configuration: {json.dumps(result, indent=2, sort_keys=True)}",
)
)
return delayed_logs, result
@@ -758,7 +765,7 @@ class CommandLine:
constants.LOGLEVEL_VVVV,
]
):
logging.addLevelName(level_value, f"DETAIL {level+1}")
logging.addLevelName(level_value, f"DETAIL {level + 1}")
def file_handler_class_factory(self, direct=True):
output_dir = self.output_dir
-2
View File
@@ -278,7 +278,6 @@ class CLIRenderer(interfaces.renderers.Renderer):
class QuickTextRenderer(CLIRenderer):
name = "quick"
def get_render_options(self):
@@ -348,7 +347,6 @@ class NoneRenderer(CLIRenderer):
class CSVRenderer(CLIRenderer):
name = "csv"
structured_output = True
+3 -2
View File
@@ -344,8 +344,9 @@ class VolShell(cli.CommandLine):
raise ValueError(
"Invalid extension (extensions must be of the format \"conf.path.value='value'\")"
)
address, value = extension[: extension.find("=")], json.loads(
extension[extension.find("=") + 1 :]
address, value = (
extension[: extension.find("=")],
json.loads(extension[extension.find("=") + 1 :]),
)
ctx.config[address] = value
+3 -3
View File
@@ -469,7 +469,7 @@ class Volshell(interfaces.plugins.PluginInterface):
and dereference_count < MAX_DEREFERENCE_COUNT
):
# before defreerencing the pointer, show it's information
print(f'{" " * dereference_count}{self._display_simple_type(volobject)}')
print(f"{' ' * dereference_count}{self._display_simple_type(volobject)}")
# check that we can follow the pointer before dereferencing and do not
# attempt to follow null pointers.
@@ -486,7 +486,7 @@ class Volshell(interfaces.plugins.PluginInterface):
if hasattr(volobject.vol, "members"):
# display the header for this object, if the original object was just a type string, display the type information
struct_header = f'{" " * dereference_count}{volobject.vol.type_name} ({volobject.vol.size} bytes)'
struct_header = f"{' ' * dereference_count}{volobject.vol.type_name} ({volobject.vol.size} bytes)"
if isinstance(object, str) and offset is None:
suffix = ":"
else:
@@ -523,7 +523,7 @@ class Volshell(interfaces.plugins.PluginInterface):
len_typename = len(member_type_name)
if len(member_type_name) > MAX_TYPENAME_DISPLAY_LENGTH:
len_typename = MAX_TYPENAME_DISPLAY_LENGTH
member_type_name = f"{member_type_name[:len_typename - 3]}..."
member_type_name = f"{member_type_name[: len_typename - 3]}..."
if isinstance(volobject, interfaces.objects.ObjectInterface):
# We're an instance, so also display the data
+1
View File
@@ -2,6 +2,7 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
"""Volatility 3 framework."""
# Check the python version to ensure it's suitable
import glob
import sys
+2 -1
View File
@@ -7,6 +7,7 @@ from loaded PE files.
This module contains a standalone scanner, and also a :class:`~volatility3.framework.interfaces.layers.ScannerInterface`
based scanner for use within the framework by calling :func:`~volatility3.framework.interfaces.layers.DataLayerInterface.scan`.
"""
import contextlib
import logging
import math
@@ -449,7 +450,7 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
while (kernel_base + 0x2000000) > kernel_hint:
for i in range(0, 0x200000, 0x1000):
valid_kernel = self.check_kernel_offset(
context, vlayer, kernel_base, progress_callback
context, vlayer, kernel_base + i, progress_callback
)
if valid_kernel:
return valid_kernel
+3 -2
View File
@@ -153,8 +153,9 @@ class LayerStacker(interfaces.automagic.AutomagicInterface):
constructor(context, config_path, requirement)
# Stash the changed config items
self._cached = context.config.get(path, None), context.config.branch(
path
self._cached = (
context.config.get(path, None),
context.config.branch(path),
)
vollog.debug(
f"physical_layer maximum_address: {physical_layer.maximum_address}"
@@ -26,6 +26,7 @@ The self-referential indices for older versions of windows are listed below:
| x64 | 0x1ED |
+--------------+-------+
"""
import logging
import struct
from typing import Generator, Iterable, List, Optional, Tuple, Type
@@ -8,6 +8,7 @@ These requirement types allow plugins to request simple information
types (such as strings, integers, etc) as well as indicating what they
expect to be in the context (such as particular layers or symboltables).
"""
import abc
import logging
import os
+2 -2
View File
@@ -1,7 +1,7 @@
# We use the SemVer 2.0.0 versioning scheme
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
VERSION_MINOR = 26 # Number of changes that only add to the interface
VERSION_PATCH = 2 # Number of changes that do not change the interface
VERSION_MINOR = 27 # Number of changes that only add to the interface
VERSION_PATCH = 0 # Number of changes that do not change the interface
VERSION_SUFFIX = ""
PACKAGE_VERSION = (
@@ -5,6 +5,7 @@
Linux-specific values that aren't found in debug symbols
"""
import enum
from dataclasses import dataclass
@@ -8,6 +8,7 @@ This has been made an object to allow quick swapping and changing of
contexts, to allow a plugin to act on multiple different contexts
without them interfering with each other.
"""
import functools
import hashlib
import logging
+1 -1
View File
@@ -79,7 +79,7 @@ def deprecated_method(
"This is a bug, the deprecated call needs to be removed and the caller needs to update their code to use the new method.",
)
deprecation_msg = f"Method \"{deprecated_func.__module__ + '.' + deprecated_func.__qualname__}\" is deprecated and will be removed in the first release after {removal_date}, use \"{replacement.__module__ + '.' + replacement.__qualname__}\" instead. {additional_information}"
deprecation_msg = f'Method "{deprecated_func.__module__ + "." + deprecated_func.__qualname__}" is deprecated and will be removed in the first release after {removal_date}, use "{replacement.__module__ + "." + replacement.__qualname__}" instead. {additional_information}'
warnings.warn(deprecation_msg, FutureWarning)
# Return the wrapped function with its original arguments
return deprecated_func(*args, **kwargs)
+2 -1
View File
@@ -8,6 +8,7 @@ space or symbol tables, and by layers when an address is invalid. The
:class:`PagedInvalidAddressException` contains information about the
size of the invalid page.
"""
from typing import Callable, Dict, Optional, Tuple
from volatility3.framework import interfaces
@@ -161,4 +162,4 @@ class VersionMismatchException(VolatilityException):
self.failure_reason = failure_reason
def __str__(self):
return f"{self.source_component.__module__+ '.' + self.source_component.__qualname__}: Version {self.target_version} dependency on {self.target_component.__module__+ '.' + self.target_component.__name__} {self.target_component.version} unmet."
return f"{self.source_component.__module__ + '.' + self.source_component.__qualname__}: Version {self.target_version} dependency on {self.target_component.__module__ + '.' + self.target_component.__name__} {self.target_component.version} unmet."
@@ -7,6 +7,7 @@ runs.
Automagic objects attempt to automatically fill configuration values
that a user has not filled.
"""
import logging
from abc import ABCMeta
from typing import Any, List, Optional, Tuple, Type, Union
@@ -11,6 +11,7 @@ convenience functions, most notably the object constructor function,
`object`, which will construct a symbol on a layer at a particular
offset.
"""
import collections
import copy
from abc import ABCMeta, abstractmethod
@@ -6,6 +6,7 @@
One layer may combine other layers, map data based on the data itself,
or map a procedure (such as decryption) across another layer of data.
"""
import collections.abc
import functools
import logging
+8 -5
View File
@@ -3,12 +3,14 @@
#
"""Objects are the core of volatility, and provide pythonic access to
interpreted values of data from a layer."""
import abc
import collections
import collections.abc
import contextlib
import dataclasses
import logging
from typing import Any, Dict, List, Mapping, NamedTuple, Optional
from typing import Any, Dict, List, Mapping, Optional
from volatility3.framework import constants, interfaces
@@ -52,7 +54,8 @@ class ReadOnlyMapping(collections.abc.Mapping):
return dict(self) == dict(other)
class ObjectInformation(NamedTuple):
@dataclasses.dataclass
class ObjectInformation:
"""Contains common information useful/pertinent only to an individual
object (like an instance)
@@ -71,12 +74,12 @@ class ObjectInformation(NamedTuple):
size: Optional[int] = None
def __getitem__(self, key):
if key in self._fields:
if key in self:
return getattr(self, key)
raise KeyError(f"NamedTuple does not have a key {key}")
raise KeyError(f"No {key} present in ObjectInformation")
def __contains__(self, key):
return key in self._fields
return key in [field.name for field in dataclasses.fields(self)]
class ObjectInterface(metaclass=abc.ABCMeta):
@@ -2,6 +2,7 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
"""Symbols provide structural information about a set of bytes."""
import bisect
import collections.abc
from abc import ABC, abstractmethod
+1
View File
@@ -6,6 +6,7 @@
The user of the file doesn't have to worry about the compression,
but random access is not allowed."""
import ctypes
import logging
import struct
@@ -2,7 +2,4 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
"""Codecs used for encoding or decoding data should live here
"""
"""Codecs used for encoding or decoding data should live here"""
+14 -2
View File
@@ -315,7 +315,13 @@ class Intel(linear.LinearlyMappedLayer):
):
# The block isn't contiguous
if stashed_offset is not None:
yield stashed_offset, stashed_size, stashed_mapped_offset, stashed_mapped_size, stashed_map_layer
yield (
stashed_offset,
stashed_size,
stashed_mapped_offset,
stashed_mapped_size,
stashed_map_layer,
)
# Update all the stashed values after output
stashed_offset = offset
stashed_mapped_offset = mapped_offset
@@ -334,7 +340,13 @@ class Intel(linear.LinearlyMappedLayer):
and stashed_mapped_size is not None
and stashed_map_layer is not None
):
yield stashed_offset, stashed_size, stashed_mapped_offset, stashed_mapped_size, stashed_map_layer
yield (
stashed_offset,
stashed_size,
stashed_mapped_offset,
stashed_mapped_size,
stashed_map_layer,
)
def _mapping(
self, offset: int, length: int, ignore_errors: bool = False
+7 -3
View File
@@ -234,9 +234,13 @@ class PdbMSFStream(linear.LinearlyMappedLayer):
layer_name=self.name, invalid_address=offset + returned
)
else:
yield offset + returned, chunk_size, (
self._pages[page] * page_size
) + page_position, chunk_size, self._base_layer
yield (
offset + returned,
chunk_size,
(self._pages[page] * page_size) + page_position,
chunk_size,
self._base_layer,
)
returned += chunk_size
length -= chunk_size
+3 -2
View File
@@ -305,8 +305,9 @@ class JarHandler(VolatilityHandler):
def default_open(req: urllib.request.Request) -> Optional[Any]:
"""Handles the request if it's the jar scheme."""
if req.type == "jar":
subscheme, remainder = req.full_url.split(":")[1], ":".join(
req.full_url.split(":")[2:]
subscheme, remainder = (
req.full_url.split(":")[1],
":".join(req.full_url.split(":")[2:]),
)
if subscheme != "file":
vollog.log(
+7 -1
View File
@@ -129,7 +129,13 @@ class NonLinearlySegmentedLayer(
return None
# Crop it to the amount we need left
chunk_size = min(size, length + offset - logical_offset)
yield logical_offset, chunk_size, mapped_offset, mapped_size, self._base_layer
yield (
logical_offset,
chunk_size,
mapped_offset,
mapped_size,
self._base_layer,
)
current_offset += chunk_size
# Terminate if we've gone (or reached) our required limit
if current_offset >= offset + length:
+4 -4
View File
@@ -65,10 +65,10 @@ class VmwareLayer(segmented.SegmentedLayer):
data = meta_layer.read(0, header_size)
magic, unknown, groupCount = struct.unpack(self.header_structure, data)
if magic not in [
b"\xD0\xBE\xD2\xBE",
b"\xD1\xBA\xD1\xBA",
b"\xD2\xBE\xD2\xBE",
b"\xD3\xBE\xD3\xBE",
b"\xd0\xbe\xd2\xbe",
b"\xd1\xba\xd1\xba",
b"\xd2\xbe\xd2\xbe",
b"\xd3\xbe\xd3\xbe",
]:
raise VmwareFormatException(
self.name, f"Wrong magic bytes for Vmware layer: {repr(magic)}"
+3 -2
View File
@@ -60,8 +60,9 @@ class Banners(interfaces.plugins.PluginInterface):
not in b" #()+,;/-.0123456789:@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"
]
if not failed:
yield format_hints.Hex(offset), str(
data, encoding="latin-1", errors="?"
yield (
format_hints.Hex(offset),
str(data, encoding="latin-1", errors="?"),
)
def run(self):
+6 -3
View File
@@ -72,9 +72,12 @@ class IsfInfo(plugins.PluginInterface):
for extension in constants.ISF_EXTENSIONS:
# By ending with an extension (and therefore, not /), we should not return any directories
if name.endswith(extension):
yield "jar:file:" + str(
pathlib.Path(base_name)
) + "!" + name
yield (
"jar:file:"
+ str(pathlib.Path(base_name))
+ "!"
+ name
)
else:
for extension in constants.ISF_EXTENSIONS:
@@ -245,7 +245,6 @@ You can try using ffmpeg to decode the raw buffer. Example usage:
return fb
def _generator(self):
if not has_pil:
vollog.error(
"PIL (pillow) module is required to use this plugin. Please install it manually or through pyproject.toml."
+45 -13
View File
@@ -47,7 +47,17 @@ class Addr(plugins.PluginInterface):
prefix_len = in_ifaddr.get_prefix_len()
scope_type = in_ifaddr.get_scope_type()
ip_addr = in_ifaddr.get_address()
yield net_ns_id, iface_ifindex, iface_name, mac_addr, promisc, ip_addr, prefix_len, scope_type, operational_state
yield (
net_ns_id,
iface_ifindex,
iface_name,
mac_addr,
promisc,
ip_addr,
prefix_len,
scope_type,
operational_state,
)
# Interface IPv6 Addresses
inet6_dev = net_dev.ip6_ptr.dereference().cast("inet6_dev")
@@ -55,7 +65,17 @@ class Addr(plugins.PluginInterface):
prefix_len = inet6_ifaddr.get_prefix_len()
scope_type = inet6_ifaddr.get_scope_type()
ip6_addr = inet6_ifaddr.get_address()
yield net_ns_id, iface_ifindex, iface_name, mac_addr, promisc, ip6_addr, prefix_len, scope_type, operational_state
yield (
net_ns_id,
iface_ifindex,
iface_name,
mac_addr,
promisc,
ip6_addr,
prefix_len,
scope_type,
operational_state,
)
def _enumerate_net_namespace_list(self):
vmlinux = self.context.modules[self.config["kernel"]]
@@ -82,16 +102,19 @@ class Addr(plugins.PluginInterface):
scope_type,
operational_state,
) in self._gather_net_dev_info(net_dev):
yield 0, (
net_ns_id or renderers.NotAvailableValue(),
iface_ifindex,
iface_name,
mac_addr,
promisc,
ip6_addr,
prefix_len,
scope_type,
operational_state,
yield (
0,
(
net_ns_id or renderers.NotAvailableValue(),
iface_ifindex,
iface_name,
mac_addr,
promisc,
ip6_addr,
prefix_len,
scope_type,
operational_state,
),
)
def run(self):
@@ -150,7 +173,16 @@ class Link(plugins.PluginInterface):
]
flags_str = ",".join(flags_list)
yield net_ns_id or renderers.NotAvailableValue(), iface_name, mac_addr, operational_state, mtu, qdisc_name or renderers.NotAvailableValue(), qlen, flags_str
yield (
net_ns_id or renderers.NotAvailableValue(),
iface_name,
mac_addr,
operational_state,
mtu,
qdisc_name or renderers.NotAvailableValue(),
qlen,
flags_str,
)
def _generator(self):
vmlinux = self.context.modules[self.config["kernel"]]
+9 -6
View File
@@ -551,12 +551,15 @@ class Kmsg(interfaces.plugins.PluginInterface):
for facility, level, timestamp, caller, line in ABCKmsg.run_all(
context=self.context, config=self.config
):
yield 0, (
facility,
level,
timestamp,
caller or renderers.NotAvailableValue(),
line,
yield (
0,
(
facility,
level,
timestamp,
caller or renderers.NotAvailableValue(),
line,
),
)
def run(self):
+37 -8
View File
@@ -4,10 +4,10 @@
"""A module containing a plugin that lists loaded kernel modules."""
import logging
from typing import List, Iterable
from typing import Iterable, List
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
from volatility3.framework import interfaces, deprecation
from volatility3.framework import constants, deprecation, interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
@@ -18,27 +18,41 @@ class Lsmod(plugins.PluginInterface):
"""Lists loaded kernel modules."""
_required_framework_version = (2, 0, 0)
_version = (3, 0, 1)
_version = (3, 0, 3)
run = linux_utilities_modules.ModuleDisplayPlugin.run
_generator = linux_utilities_modules.ModuleDisplayPlugin.generator
implementation = linux_utilities_modules.Modules.list_modules
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=constants.architectures.LINUX_ARCHS,
),
requirements.VersionRequirement(
name="linux_utilities_modules",
component=linux_utilities_modules.Modules,
version=(3, 0, 0),
),
requirements.VersionRequirement(
name="linux_utilities_modules_module_display_plugin",
component=linux_utilities_modules.ModuleDisplayPlugin,
version=(1, 0, 0),
version=(2, 0, 0),
),
] + linux_utilities_modules.ModuleDisplayPlugin.get_requirements()
requirements.BooleanRequirement(
name="dump",
description="Extract listed modules",
default=False,
optional=True,
),
]
@classmethod
@deprecation.deprecated_method(
replacement=linux_utilities_modules.Modules.list_modules,
replacement_version=(3, 0, 0),
removal_date="2025-09-25",
removal_date="2026-03-25",
)
def list_modules(
cls, context: interfaces.context.ContextInterface, vmlinux_module_name: str
@@ -46,3 +60,18 @@ class Lsmod(plugins.PluginInterface):
return linux_utilities_modules.Modules.list_modules(
context, vmlinux_module_name
)
def run(self):
return renderers.TreeGrid(
linux_utilities_modules.ModuleDisplayPlugin.columns_results,
self._generator(),
)
def _generator(self):
yield from linux_utilities_modules.ModuleDisplayPlugin.generate_results(
self.context,
self.implementation,
self.config["kernel"],
self.config["dump"],
self.open,
)
+25 -6
View File
@@ -110,7 +110,7 @@ class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Lists open files for each processes."""
_required_framework_version = (2, 0, 0)
_version = (2, 0, 2)
_version = (2, 1, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -137,6 +137,12 @@ class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface):
element_type=int,
optional=True,
),
requirements.BooleanRequirement(
name="files_only",
description="Include only file descriptors of type file",
optional=True,
default=False,
),
]
@classmethod
@@ -145,6 +151,7 @@ class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface):
context: interfaces.context.ContextInterface,
vmlinux_module_name: str,
filter_func: Callable[[int], bool] = lambda _: False,
include_files_only: bool = False,
) -> Iterable[FDInternal]:
"""Enumerates open file descriptors in tasks
@@ -167,16 +174,20 @@ class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface):
linuxutils_symbol_table = task.vol.type_name.split(constants.BANG)[0]
fd_generator = linux.LinuxUtilities.files_descriptors_for_process(
context, linuxutils_symbol_table, task
context, linuxutils_symbol_table, task, files_only=include_files_only
)
for fd_fields in fd_generator:
yield FDInternal(task=task, fd_fields=fd_fields)
def _generator(self, pids, vmlinux_module_name):
def _generator(self, pids, vmlinux_module_name, include_files_only):
filter_func = pslist.PsList.create_pid_filter(pids)
for fd_internal in self.list_fds(
self.context, vmlinux_module_name, filter_func=filter_func
self.context,
vmlinux_module_name,
filter_func=filter_func,
include_files_only=include_files_only,
):
fd_user = fd_internal.to_user()
yield (0, dataclasses.astuple(fd_user))
@@ -184,6 +195,7 @@ class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface):
def run(self):
pids = self.config.get("pid", None)
vmlinux_module_name = self.config["kernel"]
include_files_only = self.config.get("files_only")
tree_grid_args = [
("PID", int),
@@ -201,7 +213,10 @@ class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface):
("Size", int),
]
return renderers.TreeGrid(
tree_grid_args, self._generator(pids, vmlinux_module_name)
tree_grid_args,
self._generator(
pids, vmlinux_module_name, include_files_only=include_files_only
),
)
def generate_timeline(self):
@@ -220,5 +235,9 @@ class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface):
)
yield description, timeliner.TimeLinerType.CHANGED, fd_user.change_time
yield description, timeliner.TimeLinerType.MODIFIED, fd_user.modification_time
yield (
description,
timeliner.TimeLinerType.MODIFIED,
fd_user.modification_time,
)
yield description, timeliner.TimeLinerType.ACCESSED, fd_user.access_time
@@ -3,6 +3,7 @@
#
"""A module containing a plugin that verifies the operation function
pointers of network protocols."""
import logging
from typing import List, Tuple, Generator
@@ -3,19 +3,18 @@
#
import logging
from typing import List, Dict, Generator
from typing import Dict, Generator, List
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
from volatility3.framework import interfaces, deprecation
from volatility3.framework import constants, deprecation, interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
from volatility3.framework.symbols.linux import extensions
from volatility3.framework.interfaces import plugins
vollog = logging.getLogger(__name__)
class Check_modules(plugins.PluginInterface):
class Check_modules(interfaces.plugins.PluginInterface):
"""Compares module list to sysfs info, if available"""
_version = (3, 0, 1)
@@ -23,7 +22,7 @@ class Check_modules(plugins.PluginInterface):
@classmethod
def compare_kset_and_lsmod(
cls, context: str, vmlinux_name: str
cls, context: interfaces.context.ContextInterface, vmlinux_name: str
) -> Generator[extensions.module, None, None]:
kset_modules = linux_utilities_modules.Modules.get_kset_modules(
context=context, vmlinux_name=vmlinux_name
@@ -39,13 +38,16 @@ class Check_modules(plugins.PluginInterface):
for mod_name in set(kset_modules.keys()).difference(lsmod_modules):
yield kset_modules[mod_name]
run = linux_utilities_modules.ModuleDisplayPlugin.run
_generator = linux_utilities_modules.ModuleDisplayPlugin.generator
implementation = compare_kset_and_lsmod
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=constants.architectures.LINUX_ARCHS,
),
requirements.VersionRequirement(
name="modules",
component=linux_utilities_modules.Modules,
@@ -54,17 +56,38 @@ class Check_modules(plugins.PluginInterface):
requirements.VersionRequirement(
name="linux_utilities_modules_module_display_plugin",
component=linux_utilities_modules.ModuleDisplayPlugin,
version=(1, 0, 0),
version=(2, 0, 0),
),
] + linux_utilities_modules.ModuleDisplayPlugin.get_requirements()
requirements.BooleanRequirement(
name="dump",
description="Extract listed modules",
default=False,
optional=True,
),
]
@classmethod
@deprecation.deprecated_method(
replacement=linux_utilities_modules.Modules.get_kset_modules,
removal_date="2025-09-25",
removal_date="2026-03-25",
replacement_version=(3, 0, 0),
)
def get_kset_modules(
cls, context: interfaces.context.ContextInterface, vmlinux_name: str
) -> Dict[str, extensions.module]:
return linux_utilities_modules.Modules.get_kset_modules(context, vmlinux_name)
def run(self):
return renderers.TreeGrid(
linux_utilities_modules.ModuleDisplayPlugin.columns_results,
self._generator(),
)
def _generator(self):
yield from linux_utilities_modules.ModuleDisplayPlugin.generate_results(
self.context,
self.implementation,
self.config["kernel"],
self.config["dump"],
self.open,
)
@@ -2,6 +2,7 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
"""A module containing a plugin that checks the system call table for hooks."""
import contextlib
import logging
from typing import List
@@ -2,14 +2,21 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import List, Set, Tuple, Iterable
from typing import Generator, Iterable, List, Set, Tuple
from volatility3.framework import (
constants,
deprecation,
exceptions,
interfaces,
renderers,
)
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.symbols.linux import extensions
from volatility3.framework.symbols.linux.utilities import (
modules as linux_utilities_modules,
)
from volatility3.framework import interfaces, exceptions, deprecation
from volatility3.framework.configuration import requirements
from volatility3.framework.symbols.linux import extensions
from volatility3.framework.interfaces import plugins
vollog = logging.getLogger(__name__)
@@ -18,12 +25,12 @@ class Hidden_modules(plugins.PluginInterface):
"""Carves memory to find hidden kernel modules"""
_required_framework_version = (2, 25, 0)
_version = (3, 0, 2)
_version = (3, 0, 3)
@classmethod
def find_hidden_modules(
cls, context, vmlinux_module_name: str
) -> extensions.module:
) -> Generator[extensions.module, None, None]:
if context.symbol_space.verify_table_versions(
"dwarf2json", lambda version, _: (not version) or version < (0, 8, 0)
):
@@ -81,29 +88,38 @@ class Hidden_modules(plugins.PluginInterface):
vmlinux_module_name, known_module_addresses, modules_memory_boundaries
)
run = linux_utilities_modules.ModuleDisplayPlugin.run
_generator = linux_utilities_modules.ModuleDisplayPlugin.generator
implementation = find_hidden_modules
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=constants.architectures.LINUX_ARCHS,
),
requirements.VersionRequirement(
name="linux_utilities_modules_module_display_plugin",
component=linux_utilities_modules.ModuleDisplayPlugin,
version=(1, 0, 0),
version=(2, 0, 0),
),
requirements.VersionRequirement(
name="linux_utilities_modules",
component=linux_utilities_modules.Modules,
version=(3, 0, 1),
),
] + linux_utilities_modules.ModuleDisplayPlugin.get_requirements()
requirements.BooleanRequirement(
name="dump",
description="Extract listed modules",
default=False,
optional=True,
),
]
@staticmethod
@deprecation.deprecated_method(
replacement=linux_utilities_modules.Modules.get_modules_memory_boundaries,
removal_date="2025-09-25",
removal_date="2026-03-25",
replacement_version=(3, 0, 0),
)
def get_modules_memory_boundaries(
@@ -116,7 +132,7 @@ class Hidden_modules(plugins.PluginInterface):
@deprecation.deprecated_method(
replacement=linux_utilities_modules.Modules.get_module_address_alignment,
removal_date="2025-09-25",
removal_date="2026-03-25",
replacement_version=(3, 0, 0),
)
@classmethod
@@ -144,13 +160,13 @@ class Hidden_modules(plugins.PluginInterface):
@deprecation.deprecated_method(
replacement=linux_utilities_modules.Modules.get_hidden_modules,
removal_date="2025-09-25",
removal_date="2026-03-25",
replacement_version=(3, 0, 0),
)
@staticmethod
@deprecation.deprecated_method(
replacement=linux_utilities_modules.Modules.validate_alignment_patterns,
removal_date="2025-09-25",
removal_date="2026-03-25",
replacement_version=(3, 0, 0),
)
def _validate_alignment_patterns(
@@ -195,3 +211,18 @@ class Hidden_modules(plugins.PluginInterface):
)
}
return known_module_addresses
def run(self):
return renderers.TreeGrid(
linux_utilities_modules.ModuleDisplayPlugin.columns_results,
self._generator(),
)
def _generator(self):
yield from linux_utilities_modules.ModuleDisplayPlugin.generate_results(
self.context,
self.implementation,
self.config["kernel"],
self.config["dump"],
self.open,
)
@@ -18,7 +18,7 @@ class Malfind(interfaces.plugins.PluginInterface):
"""Lists process memory ranges that potentially contain injected code."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 3)
_version = (1, 0, 4)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -37,6 +37,18 @@ class Malfind(interfaces.plugins.PluginInterface):
element_type=int,
optional=True,
),
requirements.IntRequirement(
name="dump-size",
description="Amount of bytes to dump for each dirty region/page found - Default 64 bytes",
optional=True,
default=64,
),
requirements.BooleanRequirement(
name="dump-page",
description="Dump each dirty page and content - Default off",
optional=True,
default=False,
),
]
def _list_injections(
@@ -51,14 +63,36 @@ class Malfind(interfaces.plugins.PluginInterface):
proc_layer = self.context.layers[proc_layer_name]
dump_size = self.config["dump-size"]
# Dumping page defaults to off, as in case a whole r-xp region is dirty
# this would likely dump 1000's of pages which might not always be wise nor necessary
dump_page = self.config["dump-page"]
for vma in task.mm.get_vma_iter():
vma_name = vma.get_name(self.context, task)
vollog.debug(
f"Injections : processing PID {task.pid} : VMA {vma_name} : {hex(vma.vm_start)}-{hex(vma.vm_end)}"
)
# If is_suspicious returns true, this means at least one page
# in the region is dirty. If dump_page is true, then we dump
# all dirty pages
if vma.is_suspicious(proc_layer) and vma_name != "[vdso]":
data = proc_layer.read(vma.vm_start, 64, pad=True)
yield vma, vma_name, data
malicious_pages = vma.get_malicious_pages(proc_layer)
offset = 0
if dump_page:
# Dumping each dirty page
for page_addr in malicious_pages:
offset = page_addr - vma.vm_start
data = proc_layer.read(page_addr, dump_size, pad=True)
yield vma, f"{vma_name}, page address: {page_addr:#x}, offset: {offset:#x}", data, offset
else:
# Original behaviour - Dump the start of the region (not necessarily matching the dirty page)
data = proc_layer.read(vma.vm_start, dump_size, pad=True)
yield vma, vma_name, data, offset
def _generator(self, tasks):
# determine if we're on a 32 or 64 bit kernel
@@ -70,13 +104,15 @@ class Malfind(interfaces.plugins.PluginInterface):
for task in tasks:
process_name = utility.array_to_string(task.comm)
for vma, vma_name, data in self._list_injections(task):
for vma, vma_name, data, offset in self._list_injections(task):
if is_32bit_arch:
architecture = "intel"
else:
architecture = "intel64"
disasm = renderers.Disassembly(data, vma.vm_start, architecture)
disasm = renderers.Disassembly(
data, vma.vm_start + offset, architecture
)
yield (
0,
@@ -2,15 +2,14 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import List, Dict, Iterator
from typing import Dict, Iterator, List
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
from volatility3.framework import interfaces, deprecation, renderers
from volatility3.framework import deprecation, interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.constants import architectures
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols.linux import extensions
from volatility3.framework.constants import architectures
from volatility3.framework.symbols.linux.utilities import tainting
vollog = logging.getLogger(__name__)
@@ -66,7 +65,7 @@ spot modules presence and taints."""
@deprecation.deprecated_method(
replacement=linux_utilities_modules.Modules.flatten_run_modules_results,
replacement_version=(3, 0, 0),
removal_date="2025-09-25",
removal_date="2026-03-25",
)
def flatten_run_modules_results(
cls, run_results: Dict[str, List[extensions.module]], deduplicate: bool = True
@@ -89,7 +88,7 @@ spot modules presence and taints."""
@deprecation.deprecated_method(
replacement=linux_utilities_modules.Modules.run_modules_scanners,
replacement_version=(3, 0, 0),
removal_date="2025-09-25",
removal_date="2026-03-25",
)
def run_modules_scanners(
cls,
@@ -1,22 +1,22 @@
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from dataclasses import dataclass, field
from abc import ABC, abstractmethod
import logging
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Iterator, List, Optional, Tuple
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
from typing import Iterator, List, Tuple, Optional
from volatility3 import framework
from volatility3.framework import (
constants,
deprecation,
exceptions,
interfaces,
renderers,
exceptions,
deprecation,
)
from volatility3.framework.renderers import format_hints
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols.linux import network
vollog = logging.getLogger(__name__)
@@ -223,7 +223,16 @@ class AbstractNetfilter(ABC):
)
hooked = module_info is None
yield netns, proto_name, hook_name, priority, hook_ops_hook, module_info, symbol_name, hooked
yield (
netns,
proto_name,
hook_name,
priority,
hook_ops_hook,
module_info,
symbol_name,
hooked,
)
@classmethod
@abstractmethod
@@ -304,7 +313,7 @@ class AbstractNetfilter(ABC):
return ("IPV4", "ARP", "BRIDGE", "IPV6", "DECNET")
@deprecation.method_being_removed(
removal_date="2025-09-25",
removal_date="2026-03-25",
message="Callers to this method should adapt `linux_utilities_modules.Modules.run_module_scanners`",
)
def get_module_name_for_address(self, addr) -> str:
@@ -100,11 +100,14 @@ class Tty_Check(plugins.PluginInterface):
else:
module_name = renderers.NotAvailableValue()
yield 0, (
name,
format_hints.Hex(recv_buf),
module_name,
symbol_name or renderers.NotAvailableValue(),
yield (
0,
(
name,
format_hints.Hex(recv_buf),
module_name,
symbol_name or renderers.NotAvailableValue(),
),
)
def run(self):
@@ -75,10 +75,13 @@ class ModuleExtract(interfaces.plugins.PluginInterface):
with self.open(file_name) as file_handle:
file_handle.write(elf_data)
yield 0, (
format_hints.Hex(base_address),
len(elf_data),
file_handle.preferred_filename,
yield (
0,
(
format_hints.Hex(base_address),
len(elf_data),
file_handle.preferred_filename,
),
)
def run(self):
@@ -386,7 +386,11 @@ class Files(plugins.PluginInterface, timeliner.TimeLinerInterface):
inode_out = inode_in.to_user(vmlinux_layer)
description = f"Cached Inode for {inode_out.path}"
yield description, timeliner.TimeLinerType.ACCESSED, inode_out.access_time
yield description, timeliner.TimeLinerType.MODIFIED, inode_out.modification_time
yield (
description,
timeliner.TimeLinerType.MODIFIED,
inode_out.modification_time,
)
yield description, timeliner.TimeLinerType.CHANGED, inode_out.change_time
@classmethod
@@ -813,7 +817,6 @@ class RecoverFs(plugins.PluginInterface):
visited_paths = seen_prefixes = set()
for inode_in in inodes_iter:
# Code is slightly duplicated here with the if-block below.
# However this prevents unneeded tar manipulation if fifo
# or sock inodes come through for example.
+15 -12
View File
@@ -225,18 +225,21 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
task_euid = self._format_cred(task_fields.euid)
task_egid = self._format_cred(task_fields.egid)
yield 0, (
format_hints.Hex(task_fields.offset),
task_fields.user_pid,
task_fields.user_tid,
task_fields.user_ppid,
task_fields.name,
task_uid,
task_gid,
task_euid,
task_egid,
task_fields.creation_time or renderers.NotAvailableValue(),
file_output,
yield (
0,
(
format_hints.Hex(task_fields.offset),
task_fields.user_pid,
task_fields.user_tid,
task_fields.user_ppid,
task_fields.name,
task_uid,
task_gid,
task_euid,
task_egid,
task_fields.creation_time or renderers.NotAvailableValue(),
file_output,
),
)
@classmethod
@@ -70,7 +70,6 @@ class PerfEvents(plugins.PluginInterface):
for task in pslist.PsList.list_tasks(
context, vmlinux_module_name, include_threads=True
):
# walk the list of perf_event entries for this process
for event in task.perf_event_list.to_list(
vmlinux.symbol_table_name + constants.BANG + "perf_event", "owner_entry"
@@ -64,7 +64,6 @@ class VmaRegExScan(plugins.PluginInterface):
vollog.debug(f"RegEx Pattern: {regex_pattern}")
for task in tasks:
if not task.mm:
continue
name = utility.array_to_string(task.comm)
@@ -106,12 +105,15 @@ class VmaRegExScan(plugins.PluginInterface):
bytes_result = result_data
user_pid = task.tgid
yield 0, (
user_pid,
name,
format_hints.Hex(offset),
text_result,
bytes_result,
yield (
0,
(
user_pid,
name,
format_hints.Hex(offset),
text_result,
bytes_result,
),
)
def run(self):
@@ -103,12 +103,15 @@ class VmaYaraScan(interfaces.plugins.PluginInterface):
layer_name=proc_layer.name,
length=len(value),
)
yield 0, (
format_hints.Hex(offset),
task.tgid,
rule_name,
name,
layer_data,
yield (
0,
(
format_hints.Hex(offset),
task.tgid,
rule_name,
name,
layer_data,
),
)
@classmethod
@@ -3,6 +3,7 @@
#
"""A module containing a collection of plugins that produce data typically
found in Mac's lsmod command."""
from typing import Set
from volatility3.framework import renderers, interfaces, exceptions
@@ -3,6 +3,7 @@
#
"""A module containing a collection of plugins that produce data typically
found in Mac's mount command."""
from volatility3.framework import renderers, interfaces
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
@@ -2,6 +2,7 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
"""In-memory artifacts from OSX systems."""
from typing import Iterator, Tuple, Any, Generator, List
from volatility3.framework import exceptions, renderers, interfaces
@@ -0,0 +1,218 @@
# 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
#
import datetime
import logging
import sys
from typing import (
Any,
Dict,
List,
Optional,
Tuple,
TextIO,
)
from volatility3.framework import interfaces, renderers
from volatility3.framework.renderers import format_hints
from volatility3.cli import text_renderer
vollog = logging.getLogger(__name__)
ARROW_PRESENT = False
try:
import pyarrow as pa
import pyarrow.parquet as pq
ARROW_PRESENT = True
except ImportError:
vollog.debug("Arrow/Parquet libraries not found")
class ArrowRenderer(text_renderer.CLIRenderer):
"""Renderer that outputs Arrow IPC format data."""
name = "arrow"
structured_output = True
_version = (1, 0, 0)
def __init__(
self, options: Optional[List[interfaces.renderers.RenderOption]] = None
) -> None:
super().__init__(options)
if not ARROW_PRESENT:
raise RuntimeError("Arrow output format requires the pyarrow package")
self._to_arrow_type = {
renderers.Disassembly: pa.utf8,
bool: pa.bool_,
int: pa.int64,
float: pa.float64,
str: pa.utf8,
datetime.datetime: lambda: pa.timestamp("ms"),
format_hints.Bin: pa.uint64,
format_hints.Hex: pa.uint64,
format_hints.MultiTypeData: pa.utf8,
format_hints.HexBytes: pa.binary,
renderers.LayerData: pa.binary,
bytes: pa.binary,
}
# indicates if the output from the plugin is nested, e.g., pstree
# which would then need to be flattened
self._is_tree_result = False
self._node_id_counter = 0
def get_render_options(self) -> List[interfaces.renderers.RenderOption]:
return []
def to_arrow_schema(self, grid: interfaces.renderers.TreeGrid) -> "pa.Schema":
fields = []
for column in grid.columns:
arrow_type = self._to_arrow_type[column.type]
fields.append(pa.field(column.name, arrow_type()))
# if the output is nested, e.g., windows.pstree
if self._is_tree_result:
fields.append(pa.field("_vol_id", pa.uint64()))
fields.append(pa.field("_vol_parent_id", pa.uint64()))
return pa.schema(fields)
def _flatten_tree_structure(self, nested: List[Dict]) -> List[Dict]:
"""
Flattens a list of nested dicts using the `__children` key.
Each node gets a `_vol_id` and a `_vol_parent_id` to preserve
the original tree structure in a flat format suitable for tabular output.
Args:
nested: A list of dicts with optional `__children` lists (tree nodes).
Returns:
A flat list of dicts with `_vol_id` and `_vol_parent_id`.
"""
rows = []
self._node_id_counter = 0
def _process_node(node: Dict, parent_id: Optional[int]):
current_id = self._node_id_counter
self._node_id_counter += 1
entry = {k: v for k, v in node.items() if k != "__children"}
entry["_vol_id"] = current_id
entry["_vol_parent_id"] = parent_id
rows.append(entry)
for child in node.get("__children", []):
_process_node(child, current_id)
for root in nested:
_process_node(root, None)
return rows
def output_result(self, schema: "pa.Schema", outfd: TextIO, result):
"""Outputs the JSON data to a file in a particular format"""
if self._is_tree_result:
result = self._flatten_tree_structure(result)
t = pa.Table.from_pylist(result, schema=schema)
self.write_table(t, outfd)
def write_table(self, t: "pa.Table", outfd: TextIO) -> None:
buf = pa.BufferOutputStream()
writer = pa.ipc.new_stream(buf, t.schema)
writer.write_table(t)
writer.close()
# Get the buffer bytes and write to output
buf_bytes = buf.getvalue().to_pybytes()
outfd.buffer.write(buf_bytes)
def render(self, grid: interfaces.renderers.TreeGrid):
outfd = sys.stdout
final_output: Tuple[
Dict[str, List[interfaces.renderers.TreeNode]],
List[interfaces.renderers.TreeNode],
] = ({}, [])
ignore_columns = self.ignored_columns(grid)
def visitor(
node: interfaces.renderers.TreeNode,
accumulator: Tuple[Dict[str, Dict[str, Any]], List[Dict[str, Any]]],
) -> Tuple[Dict[str, Dict[str, Any]], List[Dict[str, Any]]]:
# Nodes always have a path value, giving them a path_depth of at least 1, we use max just in case
acc_map, final_tree = accumulator
node_dict: Dict[str, Any] = {"__children": []}
line = []
for column_index, column in enumerate(grid.columns):
if column in ignore_columns:
continue
data = list(node.values)[column_index]
if isinstance(data, interfaces.renderers.BaseAbsentValue):
data = None
if isinstance(data, renderers.Disassembly):
data = text_renderer.display_disassembly(data)
if isinstance(data, renderers.LayerData):
data = text_renderer.LayerDataRenderer().render_bytes(data)[0]
node_dict[column.name] = data
line.append(data)
if self.filter and self.filter.filter(line):
return accumulator
if node.parent:
acc_map[node.parent.path]["__children"].append(node_dict)
self._is_tree_result = True
else:
final_tree.append(node_dict)
acc_map[node.path] = node_dict
return (acc_map, final_tree)
if not grid.populated:
grid.populate(visitor, final_output)
else:
grid.visit(node=None, function=visitor, initial_accumulator=final_output)
schema = self.to_arrow_schema(grid)
self.output_result(schema, outfd, final_output[1])
class ParquetRenderer(ArrowRenderer):
"""Renderer that outputs Parquet format data."""
name = "parquet"
structured_output = True
_version = (1, 0, 0)
def get_render_options(self) -> List[interfaces.renderers.RenderOption]:
return []
def write_table(self, table: "pa.Table", outfd: TextIO) -> None:
"""
Writes a table to stdout using the Parquet format.
Args:
t: The Arrow table to write
outfd: The output file descriptor
Returns:
Nothing
"""
# Write DataFrame to a temporary file-like object
buf = pa.BufferOutputStream()
pq.write_table(table, buf, compression="snappy")
# Get the buffer as a bytes object
buf_bytes = buf.getvalue().to_pybytes()
outfd.buffer.write(buf_bytes)
@@ -2,7 +2,8 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from volatility3.framework import interfaces, deprecation
from volatility3.framework import deprecation, interfaces
from volatility3.plugins.windows.registry import amcache
vollog = logging.getLogger(__name__)
@@ -12,7 +13,7 @@ class Amcache(
interfaces.plugins.PluginInterface,
deprecation.PluginRenameClass,
replacement_class=amcache.Amcache,
removal_date="2025-09-25",
removal_date="2026-09-25",
):
"""Extract information on executed applications from the AmCache (deprecated)."""
@@ -2,7 +2,8 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from volatility3.framework import interfaces, deprecation
from volatility3.framework import deprecation, interfaces
from volatility3.plugins.windows.registry import cachedump
vollog = logging.getLogger(__name__)
@@ -12,7 +13,7 @@ class Cachedump(
interfaces.plugins.PluginInterface,
deprecation.PluginRenameClass,
replacement_class=cachedump.Cachedump,
removal_date="2025-09-25",
removal_date="2026-09-25",
):
"""Dumps lsa secrets from memory (deprecated)"""
@@ -78,7 +78,6 @@ class Callbacks(interfaces.plugins.PluginInterface):
def _create_default_scan_constraints(
context: interfaces.context.ContextInterface, symbol_table: str
) -> List[poolscanner.PoolConstraint]:
shutdown_packet_size = context.symbol_space.get_type(
symbol_table + constants.BANG + "_SHUTDOWN_PACKET"
).size
@@ -590,7 +589,11 @@ class Callbacks(interfaces.plugins.PluginInterface):
except exceptions.InvalidAddressException:
component = renderers.UnreadableValue()
yield "KeBugCheckReasonCallbackListHead", callback.CallbackRoutine, component
yield (
"KeBugCheckReasonCallbackListHead",
callback.CallbackRoutine,
component,
)
@classmethod
def list_bugcheck_callbacks(
@@ -77,6 +77,11 @@ class DeskScan(desktops.Desktops):
continue
for _thread, process_name, process_pid in desktop.get_threads():
yield format_hints.Hex(
desktop.vol.offset
), winsta_name, session_id, desktop_name, process_name, process_pid
yield (
format_hints.Hex(desktop.vol.offset),
winsta_name,
session_id,
desktop_name,
process_name,
process_pid,
)
@@ -63,9 +63,14 @@ class Desktops(interfaces.plugins.PluginInterface):
for desktop, desktop_name in winsta.desktops(kernel.symbol_table_name):
# for each desktop, walk its threads
for _thread, process_name, process_pid in desktop.get_threads():
yield format_hints.Hex(
desktop.vol.offset
), station_name, session_id, desktop_name, process_name, process_pid
yield (
format_hints.Hex(desktop.vol.offset),
station_name,
session_id,
desktop_name,
process_name,
process_pid,
)
def _generator(self):
kernel_name = self.config["kernel"]
@@ -22,7 +22,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Lists the loaded DLLs in a particular windows memory image."""
_required_framework_version = (2, 0, 0)
_version = (3, 0, 0)
_version = (3, 0, 1)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -173,6 +173,10 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
except exceptions.InvalidAddressException:
size_of_image = renderers.NotAvailableValue()
LoadCount = entry.get_load_count()
if LoadCount is None:
LoadCount = renderers.NotAvailableValue()
yield (
0,
(
@@ -186,6 +190,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
size_of_image,
BaseDllName,
FullDllName,
LoadCount,
DllLoadTime,
file_output,
),
@@ -232,6 +237,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
("Size", format_hints.Hex),
("Name", str),
("Path", str),
("LoadCount", int),
("LoadTime", datetime.datetime),
("File output", str),
],
@@ -2,7 +2,8 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from volatility3.framework import interfaces, deprecation
from volatility3.framework import deprecation, interfaces
from volatility3.plugins.windows.registry import hashdump
vollog = logging.getLogger(__name__)
@@ -12,7 +13,7 @@ class Hashdump(
interfaces.plugins.PluginInterface,
deprecation.PluginRenameClass,
replacement_class=hashdump.Hashdump,
removal_date="2025-09-25",
removal_date="2026-09-25",
):
"""Dumps user hashes from memory (deprecated)"""
@@ -96,7 +96,6 @@ class KPCRs(interfaces.plugins.PluginInterface):
yield kpcr, kpcr.member(kpcr_member)
def _generator(self) -> Iterator[Tuple]:
for kpcr, current_prcb in self.list_kpcrs(self.context, self.config["kernel"]):
yield (
0,
@@ -2,7 +2,8 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from volatility3.framework import interfaces, deprecation
from volatility3.framework import deprecation, interfaces
from volatility3.plugins.windows.registry import lsadump
vollog = logging.getLogger(__name__)
@@ -12,7 +13,7 @@ class Lsadump(
interfaces.plugins.PluginInterface,
deprecation.PluginRenameClass,
replacement_class=lsadump.Lsadump,
removal_date="2025-09-25",
removal_date="2026-09-25",
):
"""Dumps lsa secrets from memory (deprecated)"""
@@ -451,12 +451,15 @@ class DirectSystemCalls(interfaces.plugins.PluginInterface):
address, disasm_bytes = syscall_info
yield 0, (
proc_name,
proc.UniqueProcessId,
vad_path,
format_hints.Hex(address),
disasm_bytes,
yield (
0,
(
proc_name,
proc.UniqueProcessId,
vad_path,
format_hints.Hex(address),
disasm_bytes,
),
)
def run(self) -> renderers.TreeGrid:
@@ -198,10 +198,13 @@ class HollowProcesses(interfaces.plugins.PluginInterface):
for check in checks:
for note in check(proc, vads, dlls):
yield 0, (
pid,
proc_name,
note,
yield (
0,
(
pid,
proc_name,
note,
),
)
def run(self):
@@ -92,8 +92,11 @@ class Malfind(interfaces.plugins.PluginInterface):
for vad, data_object in cls.list_injection_sites(
context, kernel_layer_name, symbol_table, proc
):
yield vad, data_object.context.layers[data_object.layer_name].read(
data_object.offset, data_object.length
yield (
vad,
data_object.context.layers[data_object.layer_name].read(
data_object.offset, data_object.length
),
)
@classmethod
@@ -0,0 +1,233 @@
import logging
from typing import List, Union, Tuple
from volatility3.framework import interfaces, renderers, exceptions
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
from volatility3.plugins.windows import pslist
vollog = logging.getLogger(__name__)
# https://www.ired.team/offensive-security/defense-evasion/masquerading-processes-in-userland-through-_peb
# https://github.com/FuzzySecurity/PowerShell-Suite/blob/master/Masquerade-PEB.ps1
class PebMasquerade(interfaces.plugins.PluginInterface):
"""Detects potential process name spoofing by comparing EPROCESS and PEB data."""
_version = (1, 0, 0)
_required_framework_version = (2, 27, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.ListRequirement(
name="pid",
element_type=int,
description="Process ID to include (all other processes are excluded)",
optional=True,
),
]
@classmethod
def get_process_names(cls, proc: interfaces.objects.ObjectInterface) -> Tuple[
Union[str, renderers.NotAvailableValue],
Union[str, renderers.NotAvailableValue],
Union[str, renderers.NotAvailableValue],
Union[str, renderers.NotAvailableValue],
]:
"""Extract process names and related information from various sources (EPROCESS and PEB).
Args:
proc: The process object
Returns:
tuple: (eprocess_imagefilename, eprocess_seaudit_imagefilename, peb_imagefilepath, peb_cmdline)
"""
eprocess_imagefilename = renderers.NotAvailableValue()
eprocess_seaudit_imagefilename = renderers.NotAvailableValue()
peb_imagefilepath = renderers.NotAvailableValue()
peb_cmdline = renderers.NotAvailableValue()
try:
eprocess_imagefilename = utility.array_to_string(proc.ImageFileName)
except (AttributeError, exceptions.InvalidAddressException):
vollog.debug(
"Unable to read EPROCESS.ImageFileName for PID %d", proc.UniqueProcessId
)
except Exception as e:
vollog.warning(
"Error reading EPROCESS.ImageFileName for PID %d: %s",
proc.UniqueProcessId,
str(e),
)
try:
audit = proc.SeAuditProcessCreationInfo.ImageFileName.Name
audit_string = audit.get_string()
if audit_string:
eprocess_seaudit_imagefilename = audit_string
except exceptions.InvalidAddressException:
vollog.debug(
"Unable to read SeAuditProcessCreationInfo.ImageFileName for PID %d",
proc.UniqueProcessId,
)
except AttributeError:
vollog.debug(
"SeAuditProcessCreationInfo structure not available for PID %d",
proc.UniqueProcessId,
)
except Exception as e:
vollog.warning(
"Error reading SeAuditProcessCreationInfo for PID %d: %s",
proc.UniqueProcessId,
str(e),
)
try:
peb = proc.get_peb()
if peb and peb.ProcessParameters:
# Get ImagePathName
try:
image_path_str = peb.ProcessParameters.ImagePathName.get_string()
if image_path_str:
peb_imagefilepath = image_path_str
except (AttributeError, exceptions.InvalidAddressException):
vollog.debug(
"Unable to read PEB.ImagePathName for PID %d",
proc.UniqueProcessId,
)
except Exception as e:
vollog.warning(
"Error reading PEB.ImagePathName for PID %d: %s",
proc.UniqueProcessId,
str(e),
)
try:
cmdline_str = peb.ProcessParameters.CommandLine.get_string()
if cmdline_str:
peb_cmdline = cmdline_str
except (AttributeError, exceptions.InvalidAddressException):
vollog.debug(
"Unable to read PEB.ProcessParameters.CommandLine for PID %d",
proc.UniqueProcessId,
)
except Exception as e:
vollog.warning(
"Error reading PEB.ProcessParameters.CommandLine for PID %d: %s",
proc.UniqueProcessId,
str(e),
)
except (AttributeError, exceptions.InvalidAddressException):
# Important for cases where PEB does not exist or is inaccessible (e.g SYSTEM process)
vollog.debug("Unable to access PEB for PID %d", proc.UniqueProcessId)
except Exception as e:
vollog.warning(
"Error accessing PEB for PID %d: %s", proc.UniqueProcessId, str(e)
)
return (
eprocess_imagefilename,
eprocess_seaudit_imagefilename,
peb_imagefilepath,
peb_cmdline,
)
def _generator(self, pids, context, kernel_module_name):
pid_filter = pslist.PsList.create_pid_filter(pids)
for proc in pslist.PsList.list_processes(
context=context,
kernel_module_name=kernel_module_name,
filter_func=pid_filter,
):
proc_id = proc.UniqueProcessId
try:
peb = proc.get_peb()
except (exceptions.InvalidAddressException, AttributeError):
vollog.debug(
"Unable to access PEB for PID %d, skipping process", proc_id
)
peb_imagefilepath_length_check = False
peb_cmdline_length_check = False
(
eprocess_imagefilename,
eprocess_seaudit_imagefilename,
peb_imagefilepath,
peb_cmdline,
) = PebMasquerade.get_process_names(proc)
if isinstance(peb_imagefilepath, str) and peb:
try:
# Length values are of type USHORT
peb_imagefilepath_length = (
peb.ProcessParameters.ImagePathName.Length // 2
)
peb_imagefilepath_maxlength = (
peb.ProcessParameters.ImagePathName.MaximumLength // 2 - 1
)
if (peb_imagefilepath_length != len(peb_imagefilepath)) or (
peb_imagefilepath_maxlength != len(peb_imagefilepath)
):
peb_imagefilepath_length_check = True
except Exception as e:
vollog.warning(
"PEB.ImagePathName Length comparison error for PID %d: %s",
proc_id,
str(e),
)
if isinstance(peb_cmdline, str) and peb:
try:
# Length values are of type USHORT
peb_cmdline_length = peb.ProcessParameters.CommandLine.Length // 2
peb_cmdline_maxlength = (
peb.ProcessParameters.CommandLine.MaximumLength // 2 - 1
)
if (peb_cmdline_length != len(peb_cmdline)) or (
peb_cmdline_maxlength != len(peb_cmdline)
):
peb_cmdline_length_check = True
except Exception as e:
vollog.warning(
"PEB.CommandLine Length comparison error for PID %d: %s",
proc_id,
str(e),
)
yield (
0,
(
proc_id,
eprocess_imagefilename,
eprocess_seaudit_imagefilename,
peb_imagefilepath,
peb_cmdline_length_check,
peb_imagefilepath_length_check,
),
)
def run(self):
pids = self.config.get("pid", None)
context = self.context
kernel_module_name = self.config["kernel"]
return renderers.TreeGrid(
[
("PID", int),
("EPROCESS_ImageFileName", str),
("EPROCESS_SeAudit_ImageFileName", str),
("PEB_ImageFilePath", str),
("PEB_ImageFilePath_Spoofed", bool),
("PEB_CommandLine_Spoofed", bool),
],
self._generator(pids, context, kernel_module_name),
)
@@ -149,9 +149,12 @@ class ProcessGhosting(interfaces.plugins.PluginInterface):
for file_object_address, delete_pending, delete_on_close in cls._vad_checks(
control_area, path
):
yield format_hints.Hex(
file_object_address
), delete_pending, delete_on_close, vad_base
yield (
format_hints.Hex(file_object_address),
delete_pending,
delete_on_close,
vad_base,
)
def _generator(self, procs):
kernel = self.context.modules[self.config["kernel"]]
@@ -187,14 +190,17 @@ class ProcessGhosting(interfaces.plugins.PluginInterface):
else:
path = renderers.NotAvailableValue()
yield 0, (
pid,
process_name,
format_hints.Hex(base_address),
format_hints.Hex(file_object_address),
delete_pending or renderers.NotApplicableValue(),
delete_on_close or renderers.NotApplicableValue(),
path,
yield (
0,
(
pid,
process_name,
format_hints.Hex(base_address),
format_hints.Hex(file_object_address),
delete_pending or renderers.NotApplicableValue(),
delete_on_close or renderers.NotApplicableValue(),
path,
),
)
def run(self):
@@ -648,12 +648,15 @@ class Skeleton_Key_Check(interfaces.plugins.PluginInterface):
csystem, cryptdll_base, cryptdll_size
)
yield 0, (
lsass_proc.UniqueProcessId,
"lsass.exe",
skeleton_key_present,
format_hints.Hex(csystem.Initialize),
format_hints.Hex(csystem.Decrypt),
yield (
0,
(
lsass_proc.UniqueProcessId,
"lsass.exe",
skeleton_key_present,
format_hints.Hex(csystem.Initialize),
format_hints.Hex(csystem.Decrypt),
),
)
def _lsass_proc_filter(self, proc):
@@ -196,14 +196,17 @@ class SuspiciousThreads(interfaces.plugins.PluginInterface):
for vad_path, note in self._check_thread_address(
exe_path, ranges, address
):
yield 0, (
proc_name,
pid,
tid,
context,
format_hints.Hex(address),
vad_path,
note,
yield (
0,
(
proc_name,
pid,
tid,
context,
format_hints.Hex(address),
vad_path,
note,
),
)
def run(self):
@@ -132,19 +132,22 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
# There should only be one STANDARD_INFORMATION attribute, but we
# do this just in case.
for std_information in mft_record.standard_information_entries():
yield 0, cls.MFTScanResult(
format_hints.Hex(std_information.vol.offset),
str(mft_record.get_signature()),
mft_record.RecordNumber,
mft_record.LinkCount,
mft_flag,
renderers.NotApplicableValue(),
"STANDARD_INFORMATION",
conversion.wintime_to_datetime(std_information.CreationTime),
conversion.wintime_to_datetime(std_information.ModifiedTime),
conversion.wintime_to_datetime(std_information.UpdatedTime),
conversion.wintime_to_datetime(std_information.AccessedTime),
renderers.NotApplicableValue(),
yield (
0,
cls.MFTScanResult(
format_hints.Hex(std_information.vol.offset),
str(mft_record.get_signature()),
mft_record.RecordNumber,
mft_record.LinkCount,
mft_flag,
renderers.NotApplicableValue(),
"STANDARD_INFORMATION",
conversion.wintime_to_datetime(std_information.CreationTime),
conversion.wintime_to_datetime(std_information.ModifiedTime),
conversion.wintime_to_datetime(std_information.UpdatedTime),
conversion.wintime_to_datetime(std_information.AccessedTime),
renderers.NotApplicableValue(),
),
)
except exceptions.InvalidAddressException:
pass
@@ -163,26 +166,28 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
# File Name Attribute
try:
for filename_info in mft_record.filename_entries():
# If we don't have a valid enum, coerce to hex so we can keep the record
try:
permissions = filename_info.Flags.lookup()
except ValueError:
permissions = hex(filename_info.Flags)
yield 1, cls.MFTScanResult(
format_hints.Hex(filename_info.vol.offset),
str(mft_record.get_signature()),
mft_record.RecordNumber,
mft_record.LinkCount,
mft_flag,
permissions,
"FILE_NAME",
conversion.wintime_to_datetime(filename_info.CreationTime),
conversion.wintime_to_datetime(filename_info.ModifiedTime),
conversion.wintime_to_datetime(filename_info.UpdatedTime),
conversion.wintime_to_datetime(filename_info.AccessedTime),
filename_info.get_full_name(),
yield (
1,
cls.MFTScanResult(
format_hints.Hex(filename_info.vol.offset),
str(mft_record.get_signature()),
mft_record.RecordNumber,
mft_record.LinkCount,
mft_flag,
permissions,
"FILE_NAME",
conversion.wintime_to_datetime(filename_info.CreationTime),
conversion.wintime_to_datetime(filename_info.ModifiedTime),
conversion.wintime_to_datetime(filename_info.UpdatedTime),
conversion.wintime_to_datetime(filename_info.AccessedTime),
filename_info.get_full_name(),
),
)
except exceptions.InvalidAddressException:
return
@@ -214,22 +219,25 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
# but in this case memory usage is so extreme due to the number of
# records that it becomes necessary. The rich types are still
# exposed through classmethods.
yield level, (
record.offset,
record.record_type,
int(record.record_number),
int(record.link_count),
record.mft_type,
record.permissions,
record.attribute_type,
record.created,
record.modified,
record.updated,
record.accessed,
yield (
level,
(
str(record.filename)
if isinstance(record.filename, objects.String)
else record.filename
record.offset,
record.record_type,
int(record.record_number),
int(record.link_count),
record.mft_type,
record.permissions,
record.attribute_type,
record.created,
record.modified,
record.updated,
record.accessed,
(
str(record.filename)
if isinstance(record.filename, objects.String)
else record.filename
),
),
)
@@ -344,22 +352,25 @@ class ADS(interfaces.plugins.PluginInterface):
# but in this case memory usage is so extreme due to the number of
# records that it becomes necessary. The rich types are still
# exposed through classmethods.
yield 0, (
record.offset,
str(record.signature),
int(record.record_number),
record.attribute_type,
yield (
0,
(
str(record.filename)
if isinstance(record.filename, objects.String)
else record.filename
record.offset,
str(record.signature),
int(record.record_number),
record.attribute_type,
(
str(record.filename)
if isinstance(record.filename, objects.String)
else record.filename
),
(
str(record.stream_name)
if isinstance(record.stream_name, objects.String)
else record.stream_name
),
record.content,
),
(
str(record.stream_name)
if isinstance(record.stream_name, objects.String)
else record.stream_name
),
record.content,
)
def run(self):
@@ -454,13 +465,16 @@ class ResidentData(interfaces.plugins.PluginInterface):
# but in this case memory usage is so extreme due to the number of
# records that it becomes necessary. The rich types are still
# exposed through classmethods.
yield 0, (
resident_data_entry.offset,
str(resident_data_entry.signature),
int(resident_data_entry.record_number),
resident_data_entry.attribute_type,
str(resident_data_entry.filename),
resident_data_entry.content,
yield (
0,
(
resident_data_entry.offset,
str(resident_data_entry.signature),
int(resident_data_entry.record_number),
resident_data_entry.attribute_type,
str(resident_data_entry.filename),
resident_data_entry.content,
),
)
def run(self):
@@ -119,13 +119,16 @@ class Modules(interfaces.plugins.PluginInterface):
if self.config["dump"]:
file_output = self.dump_module(session_layers, pe_table_name, mod)
yield 0, (
format_hints.Hex(mod.vol.offset),
format_hints.Hex(mod.DllBase),
format_hints.Hex(mod.SizeOfImage),
BaseDllName,
FullDllName,
file_output,
yield (
0,
(
format_hints.Hex(mod.vol.offset),
format_hints.Hex(mod.DllBase),
format_hints.Hex(mod.SizeOfImage),
BaseDllName,
FullDllName,
file_output,
),
)
@classmethod
@@ -436,7 +436,6 @@ class PoolScanner(plugins.PluginInterface):
constraints,
alignment=alignment,
):
mem_objects = header.get_object(
constraint=constraint,
use_top_down=is_windows_8_or_later,
@@ -246,13 +246,29 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
) -> Iterator[Tuple[str, timeliner.TimeLinerType, datetime.datetime]]:
for _, entry in self._generator():
if isinstance(entry.last_modify_time, datetime.datetime):
yield f"Amcache: {entry.entry_type} {entry.path} registry key modified", timeliner.TimeLinerType.MODIFIED, entry.last_modify_time
yield (
f"Amcache: {entry.entry_type} {entry.path} registry key modified",
timeliner.TimeLinerType.MODIFIED,
entry.last_modify_time,
)
if isinstance(entry.last_modify_time_2, datetime.datetime):
yield f"Amcache: {entry.entry_type} {entry.path} STANDARD_INFORMATION create time", timeliner.TimeLinerType.CREATED, entry.last_modify_time_2
yield (
f"Amcache: {entry.entry_type} {entry.path} STANDARD_INFORMATION create time",
timeliner.TimeLinerType.CREATED,
entry.last_modify_time_2,
)
if isinstance(entry.install_time, datetime.datetime):
yield f"Amcache: {entry.entry_type} {entry.path} installed", timeliner.TimeLinerType.CREATED, entry.install_time
yield (
f"Amcache: {entry.entry_type} {entry.path} installed",
timeliner.TimeLinerType.CREATED,
entry.install_time,
)
if isinstance(entry.compile_time, datetime.datetime):
yield f"Amcache: {entry.entry_type} {entry.path} compiled (PE metadata)", timeliner.TimeLinerType.MODIFIED, entry.compile_time
yield (
f"Amcache: {entry.entry_type} {entry.path} compiled (PE metadata)",
timeliner.TimeLinerType.MODIFIED,
entry.compile_time,
)
@classmethod
def get_amcache_hive(
@@ -319,20 +335,23 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
vollog.debug(f"Found sha1hash {sha1_hash}")
product_name = _get_string_value(values, val_enum.Product.value)
yield program_id, _AmcacheEntry(
AmcacheEntryType.File.name,
path=path,
company=company,
last_modify_time=last_mod_time,
last_modify_time_2=last_mod_time_2,
install_time=install_time,
compile_time=compile_time,
sha1_hash=(
sha1_hash.lstrip("0000")
if isinstance(sha1_hash, str)
else sha1_hash
yield (
program_id,
_AmcacheEntry(
AmcacheEntryType.File.name,
path=path,
company=company,
last_modify_time=last_mod_time,
last_modify_time_2=last_mod_time_2,
install_time=install_time,
compile_time=compile_time,
sha1_hash=(
sha1_hash.lstrip("0000")
if isinstance(sha1_hash, str)
else sha1_hash
),
product_name=product_name,
),
product_name=product_name,
)
@classmethod
@@ -365,15 +384,18 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
)
version = _get_string_value(values, val_enum.Version.value)
yield program_id, _AmcacheEntry(
AmcacheEntryType.Program.name,
company=company,
last_modify_time=conversion.wintime_to_datetime(
program_key.LastWriteTime.QuadPart
yield (
program_id,
_AmcacheEntry(
AmcacheEntryType.Program.name,
company=company,
last_modify_time=conversion.wintime_to_datetime(
program_key.LastWriteTime.QuadPart
),
install_time=install_time,
product_name=product,
product_version=version,
),
install_time=install_time,
product_name=product,
product_version=version,
)
@classmethod
@@ -411,14 +433,17 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
product: str = name if isinstance(name, str) else "UNKNOWN" # type: ignore
yield program_id.strip().strip("\u0000"), _AmcacheEntry(
AmcacheEntryType.Program.name,
path=path,
last_modify_time=last_mod,
install_time=install_date,
product_name=product,
company=publisher,
product_version=version,
yield (
program_id.strip().strip("\u0000"),
_AmcacheEntry(
AmcacheEntryType.Program.name,
path=path,
last_modify_time=last_mod,
install_time=install_date,
product_name=product,
company=publisher,
product_version=version,
),
)
@classmethod
@@ -456,19 +481,22 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
prod_ver = _get_string_value(values, val_enum.ProductVersion.value)
program_id = _get_string_value(values, val_enum.ProgramID.value)
yield program_id, _AmcacheEntry(
AmcacheEntryType.File.name,
path=path,
company=publisher,
last_modify_time=last_mod,
compile_time=linkdate,
sha1_hash=(
sha1_hash.lstrip("0000")
if isinstance(sha1_hash, str)
else sha1_hash
yield (
program_id,
_AmcacheEntry(
AmcacheEntryType.File.name,
path=path,
company=publisher,
last_modify_time=last_mod,
compile_time=linkdate,
sha1_hash=(
sha1_hash.lstrip("0000")
if isinstance(sha1_hash, str)
else sha1_hash
),
product_name=prod_name,
product_version=prod_ver,
),
product_name=prod_name,
product_version=prod_ver,
)
@classmethod
@@ -485,7 +513,6 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
wanted_values = [key.value for key in val_enum]
for binary_key in driver_binary_key.get_subkeys():
values = {
str(value.get_name()): value
for value in binary_key.get_values()
@@ -636,7 +663,6 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
yield 0, empty_program
def run(self):
return renderers.TreeGrid(
[
("EntryType", str),
@@ -185,7 +185,6 @@ NULL = "\u0000"
class _ScheduledTasksReader(io.BytesIO):
def read_task_scheduler_time(self) -> Optional[datetime.datetime]:
_ = bool(self.read_aligned_u1()) # is_localized
filetime = self.decode_filetime()
@@ -393,7 +392,6 @@ class TaskAction:
num_attachment_filenames = reader.read_u4()
if num_attachment_filenames is not None:
attachment_filenames = [
reader.read_bstring() for _ in range(num_attachment_filenames)
]
@@ -1138,11 +1136,23 @@ class ScheduledTasks(interfaces.plugins.PluginInterface, timeliner.TimeLinerInte
) -> Iterator[Tuple[str, timeliner.TimeLinerType, datetime.datetime]]:
for _, task in self._generator():
if isinstance(task.last_run_time, datetime.datetime):
yield f"ScheduledTasks: task action {task.action_description} with trigger {task.trigger_description} ran", timeliner.TimeLinerType.ACCESSED, task.last_run_time
yield (
f"ScheduledTasks: task action {task.action_description} with trigger {task.trigger_description} ran",
timeliner.TimeLinerType.ACCESSED,
task.last_run_time,
)
if isinstance(task.last_successful_run_time, datetime.datetime):
yield f"ScheduledTasks: task action {task.action_description} with trigger {task.trigger_description} ran successfully", timeliner.TimeLinerType.ACCESSED, task.last_successful_run_time
yield (
f"ScheduledTasks: task action {task.action_description} with trigger {task.trigger_description} ran successfully",
timeliner.TimeLinerType.ACCESSED,
task.last_successful_run_time,
)
if isinstance(task.creation_time, datetime.datetime):
yield f"ScheduledTasks: Creation Time for task {task.guid} with trigger {task.trigger_description or '<UNKNOWN>'}", timeliner.TimeLinerType.CREATED, task.creation_time
yield (
f"ScheduledTasks: Creation Time for task {task.guid} with trigger {task.trigger_description or '<UNKNOWN>'}",
timeliner.TimeLinerType.CREATED,
task.creation_time,
)
@classmethod
def get_software_hive(
@@ -1203,7 +1213,6 @@ class ScheduledTasks(interfaces.plugins.PluginInterface, timeliner.TimeLinerInte
def parse_dynamic_info_value(
cls, dyn_info_value: reg_extensions.CM_KEY_VALUE
) -> Optional[DynamicInfo]:
try:
data = dyn_info_value.decode_data()
except exceptions.InvalidAddressException:
@@ -1318,7 +1327,6 @@ class ScheduledTasks(interfaces.plugins.PluginInterface, timeliner.TimeLinerInte
all_actions = action_set.actions or [None] if action_set is not None else [None]
for action, trigger in itertools.product(all_actions, all_triggers):
if action is not None:
if action.action_type in (
ActionType.Exe,
@@ -2,7 +2,8 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from volatility3.framework import interfaces, deprecation
from volatility3.framework import deprecation, interfaces
from volatility3.plugins.windows.registry import scheduled_tasks
vollog = logging.getLogger(__name__)
@@ -12,7 +13,7 @@ class ScheduledTasks(
interfaces.plugins.PluginInterface,
deprecation.PluginRenameClass,
replacement_class=scheduled_tasks.ScheduledTasks,
removal_date="2025-09-25",
removal_date="2026-09-25",
):
"""Decodes scheduled task information from the Windows registry, including
information about triggers, actions, run times, and creation times (deprecated)."""
@@ -95,13 +95,16 @@ class Sessions(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
# Group and yield each row
for rows in sessions.values():
for row in rows:
yield 0, (
row.get("session_id"),
row.get("session_type"),
row.get("process_id"),
row.get("process_name"),
row.get("user_name"),
row.get("process_start"),
yield (
0,
(
row.get("session_id"),
row.get("session_type"),
row.get("process_id"),
row.get("process_name"),
row.get("user_name"),
row.get("process_start"),
),
)
def generate_timeline(self):
@@ -51,9 +51,17 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
) -> Iterator[Tuple[str, timeliner.TimeLinerType, datetime]]:
for _, (_, last_modified, last_update, _, _, file_path) in self._generator():
if isinstance(last_update, datetime):
yield f"Shimcache: File {file_path} executed", timeliner.TimeLinerType.ACCESSED, last_update
yield (
f"Shimcache: File {file_path} executed",
timeliner.TimeLinerType.ACCESSED,
last_update,
)
if isinstance(last_modified, datetime):
yield f"Shimcache: File {file_path} modified", timeliner.TimeLinerType.MODIFIED, last_modified
yield (
f"Shimcache: File {file_path} modified",
timeliner.TimeLinerType.MODIFIED,
last_modified,
)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -161,7 +169,7 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
continue
try:
if proc_layer.read(vad.get_start(), 4) != b"\xEF\xBE\xAD\xDE":
if proc_layer.read(vad.get_start(), 4) != b"\xef\xbe\xad\xde":
if pid == 624:
vollog.debug("VAD magic bytes don't match DEADBEEF")
continue
@@ -150,7 +150,6 @@ class SvcScan(interfaces.plugins.PluginInterface):
def _get_service_key(
context, config_path: str, kernel_module_name: str
) -> Optional[objects.StructType]:
for hive in hivelist.HiveList.list_hives(
context=context,
base_config_path=interfaces.configuration.path_join(
@@ -167,16 +167,19 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
info = self.gather_thread_info(ethread, vads_cache)
if info:
yield 0, (
format_hints.Hex(info.offset),
info.pid,
info.tid,
format_hints.Hex(info.start_addr),
info.start_path or renderers.NotAvailableValue(),
format_hints.Hex(info.win32_start_addr),
info.win32_start_path or renderers.NotAvailableValue(),
info.create_time,
info.exit_time,
yield (
0,
(
format_hints.Hex(info.offset),
info.pid,
info.tid,
format_hints.Hex(info.start_addr),
info.start_path or renderers.NotAvailableValue(),
format_hints.Hex(info.win32_start_addr),
info.win32_start_path or renderers.NotAvailableValue(),
info.create_time,
info.exit_time,
),
)
def generate_timeline(self):
@@ -62,7 +62,6 @@ class VadRegExScan(plugins.PluginInterface):
vollog.debug(f"RegEx Pattern: {regex_pattern}")
for proc in procs:
# attempt to create a process layer for each proc
proc_layer_name = proc.add_process_layer()
if not proc_layer_name:
@@ -106,12 +105,15 @@ class VadRegExScan(plugins.PluginInterface):
max_length=proc.ImageFileName.vol.count,
errors="replace",
)
yield 0, (
proc_id,
process_name,
format_hints.Hex(offset),
text_result,
bytes_result,
yield (
0,
(
proc_id,
process_name,
format_hints.Hex(offset),
text_result,
bytes_result,
),
)
def run(self):
@@ -4,6 +4,7 @@
import logging
from typing import Iterable, List, Tuple
import datetime
from volatility3.framework import interfaces, renderers
from volatility3.framework.configuration import requirements
@@ -18,7 +19,7 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
"""Scans all the Virtual Address Descriptor memory maps using yara."""
_required_framework_version = (2, 22, 0)
_version = (1, 1, 3)
_version = (1, 1, 4)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -99,12 +100,24 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
layer_name=layer.name,
length=len(value),
)
yield 0, (
format_hints.Hex(offset),
task.UniqueProcessId,
rule_name,
name,
layer_data,
yield (
0,
(
format_hints.Hex(offset),
task.UniqueProcessId,
task.get_create_time(),
task.InheritedFromUniqueProcessId,
task.ImageFileName.cast(
"string",
max_length=task.ImageFileName.vol.count,
errors="replace",
),
task.get_session_id(),
task.ActiveThreads,
rule_name,
name,
layer_data,
),
)
@classmethod
@@ -130,6 +143,11 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
[
("Offset", format_hints.Hex),
("PID", int),
("CreateTime", datetime.datetime),
("PPID", int),
("ImageFileName", str),
("SessionId", int),
("Threads", int),
("Rule", str),
("Component", str),
("Value", renderers.LayerData),
@@ -112,15 +112,18 @@ class Windows(interfaces.plugins.PluginInterface):
)
continue
yield 0, (
format_hints.Hex(window.vol.offset),
station_name,
sess_id,
desktop_name,
window_name or renderers.NotAvailableValue(),
window_proc,
process_name,
process_pid,
yield (
0,
(
format_hints.Hex(window.vol.offset),
station_name,
sess_id,
desktop_name,
window_name or renderers.NotAvailableValue(),
window_proc,
process_name,
process_pid,
),
)
def run(self):
@@ -6,6 +6,7 @@
Renderers display the unified output format in some manner (be it text
or file or graphical output
"""
import collections
import collections.abc
import dataclasses
@@ -8,6 +8,7 @@ These hints allow a plugin to indicate how they would like data from a particula
Text renderers should attempt to honour all hints provided in this module where possible
"""
from typing import Type, Union
from volatility3.framework import interfaces
+4 -3
View File
@@ -210,9 +210,10 @@ class SymbolSpace(interfaces.symbols.SymbolSpaceInterface):
replacements = set()
# Whole Symbols that still need traversing
while traverse_list:
template_traverse_list, traverse_list = [
self._resolved[traverse_list[0]]
], traverse_list[1:]
template_traverse_list, traverse_list = (
[self._resolved[traverse_list[0]]],
traverse_list[1:],
)
# Traverse a single symbol looking for any ReferenceTemplate objects
while template_traverse_list:
traverser, template_traverse_list = (
+6 -3
View File
@@ -246,9 +246,12 @@ class IntermediateSymbolTable(interfaces.symbols.SymbolTableInterface):
if name.endswith(zip_match + extension) or (
zip_match == "*" and name.endswith(extension)
):
yield "jar:file:" + str(
pathlib.Path(zip_path)
) + "!" + name
yield (
"jar:file:"
+ str(pathlib.Path(zip_path))
+ "!"
+ name
)
@classmethod
def create(
@@ -99,8 +99,10 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable):
class LinuxUtilities(interfaces.configuration.VersionableInterface):
"""Class with multiple useful linux functions."""
_version = (2, 3, 1)
_version = (2, 4, 0)
_required_framework_version = (2, 0, 0)
deleted = "(deleted)"
smear = "<potentially smeared>"
framework.require_interface_version(*_required_framework_version)
@@ -168,6 +170,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
# vfsmnt can be the vfsmount object itself (>=3.3) or a vfsmount * (<3.3)
return ""
inode = dentry.d_inode
path_reversed = []
smeared = False
while (
@@ -191,6 +194,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
parent = dentry.d_parent
dname = dentry.d_name.name_as_str()
# empty dentry names are most likely
# the result of smearing
if not dname:
@@ -203,7 +207,10 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
# if there is smear the missing dname will be empty. e.g. if the normal
# path would be /foo/bar/baz, but bar is missing due to smear the results
# returned here will show /foo//baz. Note the // for the missing dname.
return f"<potentially smeared> {path}"
return f"{LinuxUtilities.smear} {path}"
if inode and inode.is_readable() and inode.is_valid() and inode.i_nlink == 0:
path = f" {path} {LinuxUtilities.deleted}"
return path
@classmethod
@@ -301,7 +308,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
return f"{pre_name}:[{inode.i_ino:d}]"
@classmethod
def path_for_file(cls, context, task, filp) -> str:
def path_for_file(cls, context, task, filp, files_only=False) -> str:
"""Returns a file (or sock pipe) pathname relative to the task's root directory.
A 'file' structure doesn't have enough information to properly restore its
@@ -340,7 +347,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
except exceptions.InvalidAddressException:
dname_is_valid = False
if dname_is_valid:
if dname_is_valid and not files_only:
ret = LinuxUtilities._get_new_sock_pipe_path(context, task, filp)
else:
ret = LinuxUtilities._get_path_file(task, filp)
@@ -353,6 +360,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
context: interfaces.context.ContextInterface,
symbol_table: str,
task: interfaces.objects.ObjectInterface,
files_only: bool = False,
):
try:
files = task.files
@@ -376,7 +384,9 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
for fd_num, filp in enumerate(fds):
if filp and filp.is_readable():
full_path = LinuxUtilities.path_for_file(context, task, filp)
full_path = LinuxUtilities.path_for_file(
context, task, filp, files_only
)
yield fd_num, filp, full_path
@@ -36,7 +36,6 @@ vollog = logging.getLogger(__name__)
class module(generic.GenericIntelProcess):
def is_valid(self):
"""Determine whether it is a valid module object by verifying the self-referential
in module_kobject. This also confirms that the module is actively allocated and
@@ -991,7 +990,6 @@ class maple_tree(objects.StructType):
class mm_struct(objects.StructType):
# TODO: As of version 3.0.0 this method should be removed
def get_mmap_iter(self) -> Iterable[interfaces.objects.ObjectInterface]:
"""
@@ -1273,6 +1271,42 @@ class vm_area_struct(objects.StructType):
except exceptions.InvalidAddressException:
return None
def get_malicious_pages(self, proclayer) -> List[int]:
"""Identifies and returns a list of potentially malicious memory pages.
A page is considered malicious if it is:
- Executable (protection flags match 'r-x')
- Dirty (modified since process start, according to proclayer.is_dirty())
Args:
proclayer: The process's memory layer
Returns:
List[int]: A list of virtual addresses for pages flagged as potentially malicious.
"""
malicious_pages = []
flags_str = self.get_protection()
if (
proclayer
and "r-x" in flags_str
and self.vm_file.dereference().vol.offset != 0
):
for i in range(self.vm_start, self.vm_end, proclayer.page_size):
try:
if proclayer.is_dirty(i):
vollog.debug(f"Found malicious (dirty+exec) page at {hex(i)} !")
malicious_pages.append(i)
except (
exceptions.PagedInvalidAddressException,
exceptions.InvalidAddressException,
) as excp:
vollog.debug(f"Unable to translate address {hex(i)} : {excp}")
# Abort as it is likely that other addresses in the same range will also fail
break
return malicious_pages
# used by malfind
def is_suspicious(self, proclayer=None):
ret = False
@@ -1288,7 +1322,7 @@ class vm_area_struct(objects.StructType):
try:
if proclayer.is_dirty(i):
vollog.warning(
f"Found malicious (dirty+exec) page at {hex(i)} !"
f"Found malicious page(s) inside (dirty+exec) region {hex(self.vm_start)} !"
)
# We do not attempt to find other dirty+exec pages once we have found one
ret = True
@@ -1971,8 +2005,9 @@ class mnt_namespace(objects.StructType):
self._context, self
)
for node in self.mounts.get_nodes():
# See kernel's node_to_mount()
mnt = linux.LinuxUtilities.container_of(
node, "mount", "mnt_list", vmlinux
node, "mount", "mnt_node", vmlinux
)
yield mnt
else:
@@ -3048,7 +3083,6 @@ class latch_tree_root(objects.StructType):
class kernel_symbol(objects.StructType):
def _offset_to_ptr(self, off) -> int:
layer = self._context.layers[self.vol.layer_name]
long_mask = (1 << layer.bits_per_register) - 1

Some files were not shown because too many files have changed in this diff Show More