Merge pull request #1156 from volatilityfoundation/release/v2.7.0

Release/v2.7.0
This commit is contained in:
ikelos
2024-05-29 20:24:06 +01:00
committed by GitHub
55 changed files with 4442 additions and 318 deletions
+15
View File
@@ -143,3 +143,18 @@ Options
`hivescan` would match `windows.registry.hivescan.HiveScan`, but
`pslist` is ambiguous because it could match `windows.pslist` or
`linux.pslist`.
Overriding options
------------------
The default values for the command line interface are defined by constants within the code,
but can be overridden by creating a JSON file (`%APPDATA%/volatility3/vol.json` for Windows
systems, or `~/.config/volatility3/vol.json` or `volshell.json` for all others).
The format of this file is a JSON dictionary, containing the options above and their value.
It should be noted that the ordering is (`<` means is overridden by):
`in-built default value < config file value < command line parameter`
It should also be noted that boolean flags (such as `offline`) that are overridden as true will
not be unset by not specifying the command line flag.
+4 -17
View File
@@ -1,22 +1,9 @@
# The following packages are required for core functionality.
pefile>=2023.2.7
# The following packages are optional.
# If certain packages are not necessary, place a comment (#) at the start of the line.
# This is required for the yara plugins
yara-python>=3.8.0
# This is required for several plugins that perform malware analysis and disassemble code.
# It can also improve accuracy of Windows 8 and later memory samples.
capstone>=3.0.5
# This is required by plugins that decrypt passwords, password hashes, etc.
pycryptodome
-r requirements.txt
# This can improve error messages regarding improperly configured ISF files,
# but is only recommended for development
jsonschema>=2.3.0
# This is required for memory acquisition via leechcore/pcileech.
leechcorepyc>=2.4.0
# Used to build executable file
pyinstaller>=6.5.0
pyinstaller-hooks-contrib>=2024.3
+2 -2
View File
@@ -1,5 +1,5 @@
# The following packages are required for core functionality.
pefile>=2023.2.7
# Include the minimal requirements
-r requirements-minimal.txt
# The following packages are optional.
# If certain packages are not necessary, place a comment (#) at the start of the line.
+37
View File
@@ -6,6 +6,7 @@
#
import os
import re
import subprocess
import sys
import shutil
@@ -189,6 +190,16 @@ def test_windows_svcscan(image, volatility, python):
assert rc == 0
def test_windows_thrdscan(image, volatility, python):
rc, out, err = runvol_plugin("windows.thrdscan.ThrdScan", image, volatility, python)
# find pid 4 (of system process) which starts with lowest tids
assert out.find(b"\t4\t8") != -1
assert out.find(b"\t4\t12") != -1
assert out.find(b"\t4\t16") != -1
#assert out.find(b"this raieses AssertionError") != -1
assert rc == 0
def test_windows_privileges(image, volatility, python):
rc, out, err = runvol_plugin(
"windows.privileges.Privs", image, volatility, python, pluginargs=["--pid", "4"]
@@ -331,6 +342,32 @@ def test_linux_tty_check(image, volatility, python):
assert rc == 0
def test_linux_library_list(image, volatility, python):
rc, out, err = runvol_plugin(
"linux.library_list.LibraryList", image, volatility, python
)
assert re.search(
rb"NetworkManager\s2363\s0x7f52cdda0000\s/lib/x86_64-linux-gnu/libnss_files.so.2",
out,
)
assert re.search(
rb"gnome-settings-\s3807\s0x7f7e660b5000\s/lib/x86_64-linux-gnu/libbz2.so.1.0",
out,
)
assert re.search(
rb"gdu-notificatio\s3878\s0x7f25ce33e000\s/usr/lib/x86_64-linux-gnu/libXau.so.6",
out,
)
assert re.search(
rb"bash\s8600\s0x7fe78a85f000\s/lib/x86_64-linux-gnu/libnss_files.so.2",
out,
)
assert out.count(b"\n") >= 2677
assert rc == 0
# MAC
+101 -20
View File
@@ -19,9 +19,10 @@ import os
import sys
import tempfile
import traceback
from typing import Any, Dict, Type, Union
from typing import Any, Dict, List, Tuple, Type, Union
from urllib import parse, request
from volatility3.cli import text_filter
import volatility3.plugins
import volatility3.symbols
from volatility3 import framework
@@ -105,6 +106,9 @@ class CommandLine:
]
)
# Load up system defaults
delayed_logs, default_config = self.load_system_defaults("vol.json")
parser = volargparse.HelpfulArgParser(
add_help=False,
prog=self.CLI_NAME,
@@ -230,6 +234,14 @@ class CommandLine:
default=False,
action="store_true",
)
parser.add_argument(
"--filters",
help="List of filters to apply to the output (in the form of [+-]columname,pattern[!])",
default=[],
action="append",
)
parser.set_defaults(**default_config)
# We have to filter out help, otherwise parse_known_args will trigger the help message before having
# processed the plugin choice or had the plugin subparser added.
@@ -241,6 +253,30 @@ class CommandLine:
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)
file_logger.setLevel(1)
file_formatter = logging.Formatter(
datefmt="%y-%m-%d %H:%M:%S",
fmt="%(asctime)s %(name)-12s %(levelname)-8s %(message)s",
)
file_logger.setFormatter(file_formatter)
rootlog.addHandler(file_logger)
vollog.info("Logging started")
self.order_extra_verbose_levels()
if partial_args.verbosity < 3:
if partial_args.verbosity < 1:
sys.tracebacklimit = None
console.setLevel(logging.WARNING - (partial_args.verbosity * 10))
else:
console.setLevel(logging.DEBUG - (partial_args.verbosity - 2))
for level, msg in delayed_logs:
vollog.log(level, msg)
### Alter constants if necessary
if partial_args.plugin_dirs:
volatility3.plugins.__path__ = [
os.path.abspath(p) for p in partial_args.plugin_dirs.split(";")
@@ -254,23 +290,6 @@ class CommandLine:
if partial_args.cache_path:
constants.CACHE_PATH = partial_args.cache_path
if partial_args.log:
file_logger = logging.FileHandler(partial_args.log)
file_logger.setLevel(1)
file_formatter = logging.Formatter(
datefmt="%y-%m-%d %H:%M:%S",
fmt="%(asctime)s %(name)-12s %(levelname)-8s %(message)s",
)
file_logger.setFormatter(file_formatter)
rootlog.addHandler(file_logger)
vollog.info("Logging started")
if partial_args.verbosity < 3:
if partial_args.verbosity < 1:
sys.tracebacklimit = None
console.setLevel(30 - (partial_args.verbosity * 10))
else:
console.setLevel(10 - (partial_args.verbosity - 2))
vollog.info(f"Volatility plugins path: {volatility3.plugins.__path__}")
vollog.info(f"Volatility symbols path: {volatility3.symbols.__path__}")
@@ -444,7 +463,10 @@ class CommandLine:
try:
# Construct and run the plugin
if constructed:
renderers[args.renderer]().render(constructed.run())
grid = constructed.run()
renderer = renderers[args.renderer]()
renderer.filter = text_filter.CLIFilter(grid, args.filters)
renderer.render(grid)
except exceptions.VolatilityException as excp:
self.process_exceptions(excp)
@@ -463,6 +485,50 @@ class CommandLine:
)
return requirements.URIRequirement.location_from_file(filename)
def load_system_defaults(
self, filename: str
) -> Tuple[List[Tuple[int, str]], Dict[str, Any]]:
"""Modify the main configuration based on the default configuration override"""
# Build the config path
default_config_path = os.path.join(
os.path.expanduser("~"), ".config", "volatility3", filename
)
if sys.platform == "win32":
default_config_path = os.path.join(
os.environ.get("APPDATA", os.path.expanduser("~")),
"volatility3",
filename,
)
delayed_logs = []
# Process it if the files exist
if os.path.exists(default_config_path):
with open(default_config_path, "rb") as config_json:
result = json.load(config_json)
if not isinstance(result, dict):
delayed_logs.append(
(
logging.INFO,
f"Default configuration file {default_config_path} does not contain a dictionary",
)
)
else:
delayed_logs.append(
(
logging.INFO,
f"Loading default configuration options from {default_config_path}",
)
)
delayed_logs.append(
(
logging.DEBUG,
f"Loaded configuration: {json.dumps(result, indent = 2, sort_keys = True)}",
)
)
return delayed_logs, result
return delayed_logs, {}
def process_exceptions(self, excp):
"""Provide useful feedback if an exception occurs during a run of a plugin."""
# Ensure there's nothing in the cache
@@ -631,6 +697,17 @@ class CommandLine:
)
context.config[extended_path] = value
def order_extra_verbose_levels(self):
for level, level_value in enumerate(
[
constants.LOGLEVEL_V,
constants.LOGLEVEL_VV,
constants.LOGLEVEL_VVV,
constants.LOGLEVEL_VVVV,
]
):
logging.addLevelName(level_value, f"DETAIL {level+1}")
def file_handler_class_factory(self, direct=True):
output_dir = self.output_dir
@@ -763,7 +840,11 @@ class CommandLine:
requirement,
volatility3.framework.configuration.requirements.ListRequirement,
):
additional["type"] = requirement.element_type
# Allow a list of integers, specified with the convenient 0x hexadecimal format
if requirement.element_type == int:
additional["type"] = lambda x: int(x, 0)
else:
additional["type"] = requirement.element_type
nargs = "*" if requirement.optional else "+"
additional["nargs"] = nargs
elif isinstance(
+98
View File
@@ -0,0 +1,98 @@
import logging
from typing import Any, List, Optional
from volatility3.framework import constants, interfaces
import re
vollog = logging.getLogger(__name__)
class CLIFilter:
def __init__(self, treegrid, filters: List[str]):
self._filters = self._prepare(treegrid, filters)
def _prepare(self, treegrid: interfaces.renderers.TreeGrid, filters: List[str]):
"""Runs through the filter strings and creates the necessary filter objects"""
output = []
for filter in filters:
exclude = False
regex = False
pattern = None
column_name = None
if filter.startswith("-"):
exclude = True
filter = filter[1:]
elif filter.startswith("+"):
filter = filter[1:]
components = filter.split(",")
if len(components) < 2:
pattern = components[0]
else:
column_name = components[0]
pattern = ",".join(components[1:])
if pattern and pattern.endswith("!"):
regex = True
pattern = pattern[:-1]
column_num = None
if column_name:
for num, column in enumerate(treegrid.columns):
if column_name.lower() in column.name.lower():
column_num = num
break
if pattern:
output.append(ColumnFilter(column_num, pattern, regex, exclude))
vollog.log(constants.LOGLEVEL_VVV, "Filters:\n" + repr(output))
return output
def filter(
self,
row: List[Any],
) -> bool:
"""Filters the row based on each of the column_filters"""
if not self._filters:
return False
found = any([column_filter.found(row) for column_filter in self._filters])
return not found
class ColumnFilter:
def __init__(
self,
column_num: Optional[int],
pattern: str,
regex: bool = False,
exclude: bool = False,
) -> None:
self.column_num = column_num
self.pattern = pattern
self.exclude = exclude
self.regex = regex
def find(self, item) -> bool:
"""Identifies whether an item is found in the appropriate column"""
try:
if self.regex:
return re.search(self.pattern, f"{item}")
return self.pattern in f"{item}"
except IOError:
return False
def found(self, row: List[Any]) -> bool:
"""Determines whether a row should be filtered
If the classes exclude value is false, and the necessary pattern is found, the row is not filtered,
otherwise it is filtered.
"""
if self.column_num is None:
found = any([self.find(x) for x in row])
else:
found = self.find(row[self.column_num])
if self.exclude:
return not found
return found
def __repr__(self) -> str:
"""Returns a display of a column filter"""
return f"ColumnFilter(column={self.column_num},exclude={self.exclude},regex={self.regex},pattern={self.pattern})"
+9
View File
@@ -10,6 +10,7 @@ import string
import sys
from functools import wraps
from typing import Any, Callable, Dict, List, Tuple
from volatility3.cli import text_filter
from volatility3.framework import interfaces, renderers
from volatility3.framework.renderers import format_hints
@@ -134,6 +135,7 @@ class CLIRenderer(interfaces.renderers.Renderer):
name = "unnamed"
structured_output = False
filter: text_filter.CLIFilter = None
class QuickTextRenderer(CLIRenderer):
@@ -172,6 +174,9 @@ class QuickTextRenderer(CLIRenderer):
outfd.write("\n{}\n".format("\t".join(line)))
def visitor(node: interfaces.renderers.TreeNode, accumulator):
if self.filter and self.filter.filter(node.values):
return accumulator
accumulator.write("\n")
# Nodes always have a path value, giving them a path_depth of at least 1, we use max just in case
accumulator.write(
@@ -306,6 +311,10 @@ class PrettyTextRenderer(CLIRenderer):
max_column_widths[tree_indent_column] = max(
max_column_widths.get(tree_indent_column, 0), node.path_depth
)
if self.filter and self.filter.filter(node.values):
return accumulator
line = {}
for column_index in range(len(grid.columns)):
column = grid.columns[column_index]
+40 -17
View File
@@ -22,12 +22,14 @@ from volatility3.framework import (
)
# Make sure we log everything
rootlog = logging.getLogger()
vollog = logging.getLogger()
vollog.setLevel(0)
# Trim the console down by default
console = logging.StreamHandler()
console.setLevel(logging.WARNING)
formatter = logging.Formatter("%(levelname)-8s %(name)-12s: %(message)s")
# Trim the console down by default
console.setFormatter(formatter)
vollog.addHandler(console)
@@ -53,6 +55,9 @@ class VolShell(cli.CommandLine):
framework.require_interface_version(2, 0, 0)
# Load up system defaults
delayed_logs, default_config = self.load_system_defaults("volshell.json")
parser = argparse.ArgumentParser(
prog=self.CLI_NAME,
description="A tool for interactivate forensic analysis of memory images",
@@ -146,6 +151,12 @@ class VolShell(cli.CommandLine):
default=constants.CACHE_PATH,
type=str,
)
parser.add_argument(
"--offline",
help="Do not search online for additional JSON files",
default=False,
action="store_true",
)
# Volshell specific flags
os_specific = parser.add_mutually_exclusive_group(required=False)
@@ -167,10 +178,35 @@ class VolShell(cli.CommandLine):
"-m", "--mac", default=False, action="store_true", help="Run a Mac volshell"
)
parser.set_defaults(**default_config)
# We have to filter out help, otherwise parse_known_args will trigger the help message before having
# processed the plugin choice or had the plugin subparser added.
known_args = [arg for arg in sys.argv if arg != "--help" and arg != "-h"]
partial_args, _ = parser.parse_known_args(known_args)
### Start up logging
if partial_args.log:
file_logger = logging.FileHandler(partial_args.log)
file_logger.setLevel(0)
file_formatter = logging.Formatter(
datefmt="%y-%m-%d %H:%M:%S",
fmt="%(asctime)s %(name)-12s %(levelname)-8s %(message)s",
)
file_logger.setFormatter(file_formatter)
vollog.addHandler(file_logger)
vollog.info("Logging started")
self.order_extra_verbose_levels()
if partial_args.verbosity < 3:
console.setLevel(logging.WARNING - (partial_args.verbosity * 10))
else:
console.setLevel(logging.DEBUG - (partial_args.verbosity - 2))
for level, msg in delayed_logs:
vollog.log(level, msg)
### Alter constants if necessary
if partial_args.plugin_dirs:
volatility3.plugins.__path__ = [
os.path.abspath(p) for p in partial_args.plugin_dirs.split(";")
@@ -187,25 +223,12 @@ class VolShell(cli.CommandLine):
vollog.info(f"Volatility plugins path: {volatility3.plugins.__path__}")
vollog.info(f"Volatility symbols path: {volatility3.symbols.__path__}")
if partial_args.log:
file_logger = logging.FileHandler(partial_args.log)
file_logger.setLevel(0)
file_formatter = logging.Formatter(
datefmt="%y-%m-%d %H:%M:%S",
fmt="%(asctime)s %(name)-12s %(levelname)-8s %(message)s",
)
file_logger.setFormatter(file_formatter)
vollog.addHandler(file_logger)
vollog.info("Logging started")
if partial_args.verbosity < 3:
console.setLevel(30 - (partial_args.verbosity * 10))
else:
console.setLevel(10 - (partial_args.verbosity - 2))
if partial_args.clear_cache:
framework.clear_cache()
if partial_args.offline:
constants.OFFLINE = partial_args.offline
# Do the initialization
ctx = contexts.Context() # Construct a blank context
failures = framework.import_files(
+10 -6
View File
@@ -44,8 +44,8 @@ BANG = "!"
# We use the SemVer 2.0.0 versioning scheme
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
VERSION_MINOR = 5 # Number of changes that only add to the interface
VERSION_PATCH = 2 # Number of changes that do not change the interface
VERSION_MINOR = 7 # Number of changes that only add to the interface
VERSION_PATCH = 0 # Number of changes that do not change the interface
VERSION_SUFFIX = ""
# TODO: At version 2.0.0, remove the symbol_shift feature
@@ -59,14 +59,18 @@ PACKAGE_VERSION = (
AUTOMAGIC_CONFIG_PATH = "automagic"
"""The root section within the context configuration for automagic values"""
LOGLEVEL_INFO = 20
"""Logging level for information data, showed when use the requests any logging: -v"""
LOGLEVEL_DEBUG = 10
"""Logging level for debugging data, showed when the user requests more logging detail: -vv"""
LOGLEVEL_V = 9
"""Logging level for a single -v"""
"""Logging level for the lowest "extra" level of logging: -vvv"""
LOGLEVEL_VV = 8
"""Logging level for -vv"""
"""Logging level for two levels of detail: -vvvv"""
LOGLEVEL_VVV = 7
"""Logging level for -vvv"""
"""Logging level for three levels of detail: -vvvvv"""
LOGLEVEL_VVVV = 6
"""Logging level for -vvvv"""
"""Logging level for four levels of detail: -vvvvvv"""
CACHE_PATH = os.path.join(os.path.expanduser("~"), ".cache", "volatility3")
"""Default path to store cached data"""
@@ -5,11 +5,10 @@
Linux-specific values that aren't found in debug symbols
"""
from enum import IntEnum
KERNEL_NAME = "__kernel__"
# arch/x86/include/asm/page_types.h
PAGE_SHIFT = 12
"""The value hard coded from the Linux Kernel (hence not extracted from the layer itself)"""
# include/linux/sched.h
@@ -281,3 +280,25 @@ CAPABILITIES = (
)
ELF_MAX_EXTRACTION_SIZE = 1024 * 1024 * 1024 * 4 - 1
class ELF_IDENT(IntEnum):
"""ELF header e_ident indexes"""
EI_MAG0 = 0
EI_MAG1 = 1
EI_MAG2 = 2
EI_MAG3 = 3
EI_CLASS = 4
EI_DATA = 5
EI_VERSION = 6
EI_OSABI = 7
EI_PAD = 8
class ELF_CLASS(IntEnum):
"""ELF header class types"""
ELFCLASSNONE = 0
ELFCLASS32 = 1
ELFCLASS64 = 2
+1 -1
View File
@@ -87,7 +87,7 @@ class ContextInterface(metaclass=ABCMeta):
offset: int,
native_layer_name: str = None,
**arguments,
):
) -> "interfaces.objects.ObjectInterface":
"""Object factory, takes a context, symbol, offset and optional
layer_name.
+1 -1
View File
@@ -60,7 +60,7 @@ class FileHandlerInterface(io.RawIOBase):
@staticmethod
def sanitize_filename(filename: str) -> str:
"""Sanititizes the filename to ensure only a specific whitelist of characters is allowed through"""
allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.- ()[]\{\}!$%^:#~?<>,|"
allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.- ()[]{}!$%^:#~?<>,|"
result = ""
for char in filename:
if char in allowed:
@@ -20,7 +20,6 @@ try:
except ImportError:
HAS_GCSFS = False
from volatility3.framework import exceptions
from volatility3.framework.layers import resources
vollog = logging.getLogger(__file__)
+5 -1
View File
@@ -261,11 +261,15 @@ class WindowsCrashDumpStacker(interfaces.automagic.StackerLayerInterface):
progress_callback: constants.ProgressCallback = None,
) -> Optional[interfaces.layers.DataLayerInterface]:
for layer in [WindowsCrashDump32Layer, WindowsCrashDump64Layer]:
with contextlib.suppress(WindowsCrashDumpFormatException):
try:
layer.check_header(context.layers[layer_name])
new_name = context.layers.free_layer_name(layer.__name__)
context.config[
interfaces.configuration.path_join(new_name, "base_layer")
] = layer_name
return layer(context, new_name, new_name)
except WindowsCrashDumpFormatException as excp:
vollog.log(
constants.LOGLEVEL_VVVV, f"Exception reading crashdump: {excp}"
)
return None
+13 -2
View File
@@ -6,9 +6,11 @@ import struct
from typing import Optional
from volatility3.framework import exceptions, interfaces, constants
from volatility3.framework.constants.linux import ELF_CLASS
from volatility3.framework.layers import segmented
from volatility3.framework.symbols import intermed
vollog = logging.getLogger(__name__)
@@ -21,7 +23,7 @@ class Elf64Layer(segmented.SegmentedLayer):
_header_struct = struct.Struct("<IBBB")
MAGIC = 0x464C457F # "\x7fELF"
ELF_CLASS = 2
ELF_CLASS = ELF_CLASS.ELFCLASS64
def __init__(
self, context: interfaces.context.ContextInterface, config_path: str, name: str
@@ -50,8 +52,17 @@ class Elf64Layer(segmented.SegmentedLayer):
offset=ehdr.e_phoff + (pindex * ehdr.e_phentsize),
)
# We only want PT_TYPES with valid sizes
try:
ptype = phdr.p_type.description
except ValueError:
vollog.log(
constants.LOGLEVEL_VVVV,
f"Skipping unknown ELF program header type: {phdr.p_type}",
)
continue
if (
phdr.p_type.lookup() == "PT_LOAD"
ptype == "PT_LOAD"
and phdr.p_filesz == phdr.p_memsz
and phdr.p_filesz > 0
):
+12
View File
@@ -67,6 +67,12 @@ class Intel(linear.LinearlyMappedLayer):
math.ceil(math.log2(struct.calcsize(self._entry_format)))
)
@classproperty
@functools.lru_cache()
def page_shift(cls) -> int:
"""Page shift for the intel memory layers."""
return cls._page_size_in_bits
@classproperty
@functools.lru_cache()
def page_size(cls) -> int:
@@ -76,6 +82,12 @@ class Intel(linear.LinearlyMappedLayer):
"""
return 1 << cls._page_size_in_bits
@classproperty
@functools.lru_cache()
def page_mask(cls) -> int:
"""Page mask for the intel memory layers."""
return ~(cls.page_size - 1)
@classproperty
@functools.lru_cache()
def bits_per_register(cls) -> int:
+4 -5
View File
@@ -5,6 +5,7 @@ from typing import Optional
from volatility3.framework import constants, interfaces, exceptions
from volatility3.framework.layers import elf
from volatility3.framework.symbols import intermed
from volatility3.framework.constants.linux import ELF_CLASS
vollog = logging.getLogger(__name__)
@@ -14,7 +15,7 @@ class XenCoreDumpLayer(elf.Elf64Layer):
_header_struct = struct.Struct("<IBBB")
MAGIC = 0x464C457F # "\x7fELF"
ELF_CLASS = 2
ELF_CLASS = ELF_CLASS.ELFCLASS64
def __init__(
self, context: interfaces.context.ContextInterface, config_path: str, name: str
@@ -115,12 +116,10 @@ class XenCoreDumpLayer(elf.Elf64Layer):
)
)
elif p2m_data and pfn_data:
raise elf.ElfFormatException(
self.name, f"Both P2M and PFN in Xen Core Dump"
)
raise elf.ElfFormatException(self.name, "Both P2M and PFN in Xen Core Dump")
else:
raise elf.ElfFormatException(
self.name, f"Neither P2M nor PFN in Xen Core Dump"
self.name, "Neither P2M nor PFN in Xen Core Dump"
)
if len(segments) == 0:
+20 -15
View File
@@ -14,8 +14,10 @@ from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.linux.extensions import elf
from volatility3.framework.constants.linux import ELF_MAX_EXTRACTION_SIZE
from volatility3.plugins.linux import pslist
vollog = logging.getLogger(__name__)
@@ -23,7 +25,7 @@ class Elfs(plugins.PluginInterface):
"""Lists all memory mapped ELF files for all processes."""
_required_framework_version = (2, 0, 0)
_version = (2, 0, 0)
_version = (2, 0, 1)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -82,12 +84,20 @@ class Elfs(plugins.PluginInterface):
)
if not elf_object.is_valid():
vollog.debug("ELF object to be dumped is not valid")
return None
sections = {}
# TODO: Apply more effort to reconstruct ELF, e.g.: https://github.com/enbarberis/core2ELF64 ?
for phdr in elf_object.get_program_headers():
if phdr.p_type != 1: # PT_LOAD = 1
try:
if phdr.p_type.description != "PT_LOAD":
continue
except ValueError:
vollog.log(
constants.LOGLEVEL_VVVV,
f"Skipping unknown ELF program header type: {phdr.p_type}",
)
continue
start = phdr.p_vaddr
@@ -95,18 +105,18 @@ class Elfs(plugins.PluginInterface):
end = start + size
# Use complete memory pages for dumping
# If start isn't a multiple of 4096, stick to the highest multiple < start
# If end isn't a multiple of 4096, stick to the lowest multiple > end
if start % 4096:
start = start & ~0xFFF
# If start isn't a multiple of a page, stick to the highest multiple < start
# If end isn't a multiple of a page, stick to the lowest multiple > end
if start % proc_layer.page_size:
start = start & proc_layer.page_mask
if end % 4096:
end = (end & ~0xFFF) + 4096
if end % proc_layer.page_size:
end = (end & proc_layer.page_mask) + proc_layer.page_size
real_size = end - start
# Check if ELF has a legitimate size
if real_size < 0 or real_size > constants.linux.ELF_MAX_EXTRACTION_SIZE:
if real_size < 0 or real_size > ELF_MAX_EXTRACTION_SIZE:
raise ValueError(f"The claimed size of the ELF is invalid: {real_size}")
sections[start] = real_size
@@ -140,12 +150,7 @@ class Elfs(plugins.PluginInterface):
for vma in task.mm.get_vma_iter():
hdr = proc_layer.read(vma.vm_start, 4, pad=True)
if not (
hdr[0] == 0x7F
and hdr[1] == 0x45
and hdr[2] == 0x4C
and hdr[3] == 0x46
):
if hdr != b"\x7fELF":
continue
path = vma.get_name(self.context, task)
+161 -66
View File
@@ -1,6 +1,7 @@
# This file is Copyright 2021 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import re
import logging
from abc import ABC, abstractmethod
from enum import Enum
@@ -9,12 +10,11 @@ from typing import Generator, Iterator, List, Tuple
from volatility3.framework import (
class_subclasses,
constants,
contexts,
exceptions,
interfaces,
renderers,
)
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.objects import utility
vollog = logging.getLogger(__name__)
@@ -82,7 +82,7 @@ class ABCKmsg(ABC):
config: Core configuration
Yields:
kmsg records
The kmsg records. Same as run()
"""
vmlinux = context.modules[config["kernel"]]
@@ -102,18 +102,26 @@ class ABCKmsg(ABC):
subclass.__name__,
)
kmsg_inst = subclass(context=context, config=config)
# More than one class could be executed for an specific kernel
# version i.e. Netfilter Ingress hooks
# We expect just one implementation to be executed for an specific kernel
yield from kmsg_inst.run()
# So far, it only allows a single implementation to be executed for each
# specific kernel.
break
if kmsg_inst is None:
vollog.error("Unsupported Netfilter kernel implementation")
vollog.error("Unsupported kernel ring buffer implementation")
@abstractmethod
def run(self) -> Iterator[Tuple[str, str, str, str, str]]:
"""Walks through the specific kernel implementation."""
"""Walks through the specific kernel implementation.
Returns:
tuple:
facility [str]: The log facility: kern, user, etc. See FACILITIES
level [str]: The log level: info, debug, etc. See LEVELS
timestamp [str]: The message timestamp. See nsec_to_sec_str()
caller [str]: The caller ID: CPU(1) or Task(1234). See get_caller()
line [str]: The log message.
"""
@classmethod
@abstractmethod
@@ -123,7 +131,8 @@ class ABCKmsg(ABC):
The first class returning True will be instantiated and called via the
run() method.
:return: True is the kernel being analysed fulfill the class requirements.
Returns:
bool: True if the kernel being analyzed fulfill the class requirements.
"""
def get_string(self, addr: int, length: int) -> str:
@@ -143,7 +152,7 @@ class ABCKmsg(ABC):
return "%lu.%06lu" % (nsec / 1000000000, (nsec % 1000000000) / 1000)
def get_timestamp_in_sec_str(self, obj) -> str:
# obj could be printk_log or printk_info
# obj could be log, printk_log or printk_info
return self.nsec_to_sec_str(obj.ts_nsec)
def get_caller(self, obj):
@@ -153,7 +162,7 @@ class ABCKmsg(ABC):
if obj.has_member("caller_id"):
return self.get_caller_text(obj.caller_id)
else:
return ""
return renderers.NotAvailableValue()
def get_caller_text(self, caller_id):
caller_name = "CPU" if caller_id & 0x80000000 else "Task"
@@ -161,7 +170,7 @@ class ABCKmsg(ABC):
return caller
def get_prefix(self, obj) -> Tuple[int, int, str, str]:
# obj could be printk_log or printk_info
# obj could be log, printk_log or printk_info
return (
obj.facility,
obj.level,
@@ -186,39 +195,92 @@ class ABCKmsg(ABC):
return str(facility)
class KmsgLegacy(ABCKmsg):
"""Linux kernels prior to v5.10, the ringbuffer is initially kept in
__log_buf, and log_buf is a pointer to the former. __log_buf is declared as
a char array but it actually contains an array of printk_log structs.
The length of this array is defined in the kernel KConfig configuration via
the CONFIG_LOG_BUF_SHIFT value as a power of 2.
This can also be modified by the log_buf_len kernel boot parameter.
In SMP systems with more than 64 CPUs this ringbuffer size is dynamically
allocated according the number of CPUs based on the value of
CONFIG_LOG_CPU_MAX_BUF_SHIFT, and the log_buf pointer is updated
consequently to the new buffer.
In that case, the original static buffer in __log_buf is unused.
class Kmsg_pre_3_5(ABCKmsg):
"""The kernel ring buffer (log_buf) is a char array that sequentially stores
log lines, each separated by newline (LF) characters. i.e:
<6>[ 9565.250411] line1!\\n<6>[ 9565.250412] line2\\n...
"""
@classmethod
def symtab_checks(cls, vmlinux) -> bool:
return vmlinux.has_type("printk_log")
return (
vmlinux.has_symbol("log_end")
and not vmlinux.has_symbol("log_first_idx")
and not (
vmlinux.has_type("log")
and vmlinux.get_type("log").has_member("ts_nsec")
)
)
def get_text_from_printk_log(self, msg) -> str:
msg_offset = msg.vol.offset + self.vmlinux.get_type("printk_log").size
def run(self) -> Iterator[Tuple[str, str, str, str, str]]:
log_buf_ptr = self.vmlinux.object_from_symbol(symbol_name="log_buf")
log_buf_len = self.vmlinux.object_from_symbol(symbol_name="log_buf_len")
log_buf = utility.pointer_to_string(log_buf_ptr, count=log_buf_len)
log_end = self.vmlinux.object_from_symbol(symbol_name="log_end")
if log_end > log_buf_len:
start = log_end - log_buf_len
first_half = log_buf[start:]
second_half = log_buf[:start]
log_buf = first_half + second_half
log_buf_lines = log_buf.splitlines()
for log_buf_line in log_buf_lines:
m = re.match(r"<(\d+)>\[\s*(\d+\.\d+)\]\s(.*?)$", log_buf_line)
if not m:
# If there was a wrap-around in the ring buffer, it will find
# remnants at the top. As those remnants do not conform to the
# expected line format, they are discarded
continue
level_facility_str, timestamp_str, line = m.groups()
level_facility = int(level_facility_str)
# The lower 3 bit are the log level, the rest are the log facility
level = level_facility & 7
facility = level_facility >> 3
level_txt = self.get_level_text(level)
facility_txt = self.get_facility_text(facility)
caller = renderers.NotAvailableValue()
yield facility_txt, level_txt, timestamp_str, caller, line
class Kmsg_3_5_to_3_11(ABCKmsg):
"""While 'log_buf' is declared as a pointer and '__log_buf' as a char array,
it essentially holds an array of 'log' structs.
"""
@classmethod
def symtab_checks(cls, vmlinux) -> bool:
return (
vmlinux.has_type("log")
and vmlinux.get_type("log").has_member("ts_nsec")
and vmlinux.has_symbol("log_first_idx")
)
def _get_log_struct_name(self):
return "log"
def get_text_from_log(self, msg) -> str:
log_struct_name = self._get_log_struct_name()
log_struct_size = self.vmlinux.get_type(log_struct_name).size
msg_offset = msg.vol.offset + log_struct_size
return self.get_string(msg_offset, msg.text_len)
def get_log_lines(self, msg) -> Generator[str, None, None]:
if msg.text_len > 0:
text = self.get_text_from_printk_log(msg)
text = self.get_text_from_log(msg)
yield from text.splitlines()
def get_dict_lines(self, msg) -> Generator[str, None, None]:
if msg.dict_len == 0:
return None
dict_offset = (
msg.vol.offset + self.vmlinux.get_type("printk_log").size + msg.text_len
)
log_struct_name = self._get_log_struct_name()
log_struct_size = self.vmlinux.get_type(log_struct_name).size
dict_offset = msg.vol.offset + log_struct_size + msg.text_len
dict_data = self._context.layers[self.layer_name].read(
dict_offset, msg.dict_len
)
@@ -226,29 +288,41 @@ class KmsgLegacy(ABCKmsg):
yield " " + chunk.decode()
def run(self) -> Iterator[Tuple[str, str, str, str, str]]:
log_buf_ptr = self.vmlinux.object_from_symbol(symbol_name="log_buf")
if log_buf_ptr == 0:
# This is weird, let's fallback to check the static ringbuffer.
log_buf_ptr = self.vmlinux.object_from_symbol(
symbol_name="__log_buf"
).vol.offset
if log_buf_ptr == 0:
raise ValueError("Log buffer is not available")
# First, the ring buffer size is determined in the kernel configuration
# by CONFIG_LOG_BUF_SHIFT. This static buffer is held in the '__log_buf'
# global variable, with 'log_buf' serving as a pointer to it.
# The user can also update this size using 'log_buf_len' in the
# kernel boot parameters. Additionally, in SMP systems with over 64 CPUs,
# the ring buffer size dynamically allocates based on the number of CPUs,
# following CONFIG_LOG_CPU_MAX_BUF_SHIFT.
# In the last two cases mentioned above, the 'log_buf' pointer is
# updated to this new buffer. The original static buffer in '__log_buf'
# remains unused. Therefore, it is crucial to read from 'log_buf' rather
# than '__log_buf'.
log_buf_ptr = self.vmlinux.object_from_symbol("log_buf")
log_buf_len = self.vmlinux.object_from_symbol("log_buf_len")
log_first_idx = int(self.vmlinux.object_from_symbol("log_first_idx"))
log_next_idx = int(self.vmlinux.object_from_symbol("log_next_idx"))
log_struct_name = self._get_log_struct_name()
log_first_idx = int(
self.vmlinux.object_from_symbol(symbol_name="log_first_idx")
)
cur_idx = log_first_idx
end_idx = None # We don't need log_next_idx here. See below msg.len == 0
while cur_idx != end_idx:
end_idx = log_first_idx
if log_first_idx < log_next_idx:
end_idx = log_next_idx
else:
end_idx = log_buf_len
while cur_idx < end_idx:
msg_offset = log_buf_ptr + cur_idx # type: ignore
msg = self.vmlinux.object(object_type="printk_log", offset=msg_offset)
msg = self.vmlinux.object(object_type=log_struct_name, offset=msg_offset)
if msg.len == 0:
# As per kernel/printk/printk.c:
# As per kernel/printk.c:
# A length == 0 for the next message indicates a wrap-around to
# the beginning of the buffer.
cur_idx = 0
end_idx = log_next_idx
else:
facility, level, timestamp, caller = self.get_prefix(msg)
level_txt = self.get_level_text(level)
@@ -262,39 +336,53 @@ class KmsgLegacy(ABCKmsg):
cur_idx += msg.len
class KmsgFiveTen(ABCKmsg):
"""In 5.10 the kernel ringbuffer implementation changed.
class Kmsg_3_11_to_5_10(Kmsg_3_5_to_3_11):
"""Starting from version 3.11, the struct 'log' was renamed to 'printk_log'.
While 'log_buf' is declared as a pointer and '__log_buf' as a char array,
it essentially holds an array of 'printk_log' structs.
"""
@classmethod
def symtab_checks(cls, vmlinux) -> bool:
return vmlinux.has_type("printk_log")
def _get_log_struct_name(self):
return "printk_log"
class Kmsg_5_10_to_(ABCKmsg):
"""In 5.10 the kernel ring buffer implementation changed.
Previously only one process should read /proc/kmsg and it is permanently
open and periodically read by the syslog daemon.
A high level structure 'printk_ringbuffer' was added to represent the printk
ringbuffer which actually contains two ringbuffers. The descriptor ring
ring buffer which actually contains two ring buffers. The descriptor ring
'desc_ring' contains the records' metadata, text offsets and states.
The data block ring 'text_data_ring' contains the records' text strings.
A pointer to the high level structure is kept in the prb pointer which is
initialized to a static ringbuffer.
initialized to a static ring buffer.
.. code-block:: c
static struct printk_ringbuffer *prb = &printk_rb_static;
In SMP systems with more than 64 CPUs this ringbuffer size is dynamically
In SMP systems with more than 64 CPUs this ring buffer size is dynamically
allocated according the number of CPUs based on the value of
CONFIG_LOG_CPU_MAX_BUF_SHIFT. The prb pointer is updated consequently to
this dynamic ringbuffer in setup_log_buf().
this dynamic ring buffer in setup_log_buf().
.. code-block:: c
prb = &printk_rb_dynamic;
Behind scenes, log_buf is still used as external buffer.
When the static printk_ringbuffer struct is initialized, _DEFINE_PRINTKRB
sets text_data_ring.data pointer to the address in log_buf which points to
the static buffer __log_buff.
If a dynamic ringbuffer takes place, setup_log_buf() sets
text_data_ring.data of printk_rb_dynamic to the new allocated external
buffer via the prb_init function.
In that case, the original external static buffer in __log_buf and
printk_rb_static are unused.
Behind scenes, 'log_buf' is still used as external buffer.
When the static 'printk_ringbuffer' struct is initialized, _DEFINE_PRINTKRB
sets text_data_ring.data pointer to the address in 'log_buf' which points
to the static buffer '__log_buf'.
If a dynamic ring buffer takes place, setup_log_buf() sets
text_data_ring.data of 'printk_rb_dynamic' to the new allocated external
buffer via the 'prb_init' function.
In that case, the original external static buffer in '__log_buf' and
'printk_rb_static' are unused.
.. code-block:: c
@@ -352,7 +440,7 @@ class KmsgFiveTen(ABCKmsg):
def run(self) -> Iterator[Tuple[str, str, str, str, str]]:
# static struct printk_ringbuffer *prb = &printk_rb_static;
ringbuffers = self.vmlinux.object_from_symbol(symbol_name="prb").dereference()
ringbuffers = self.vmlinux.object_from_symbol("prb").dereference()
desc_ring = ringbuffers.desc_ring
text_data_ring = ringbuffers.text_data_ring
@@ -407,12 +495,12 @@ class KmsgFiveTen(ABCKmsg):
cur_id &= desc_id_mask
class Kmsg(plugins.PluginInterface):
class Kmsg(interfaces.plugins.PluginInterface):
"""Kernel log buffer reader"""
_required_framework_version = (2, 0, 0)
_required_framework_version = (2, 6, 0)
_version = (1, 0, 1)
_version = (1, 0, 2)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -429,6 +517,13 @@ class Kmsg(plugins.PluginInterface):
yield (0, values)
def run(self):
if not self.context.symbol_space.verify_table_versions(
"dwarf2json", lambda version, _: (not version) or version > (0, 4, 1)
):
raise exceptions.SymbolSpaceError(
"Invalid symbol table, please ensure the ISF table produced by dwarf2json was produced using a version > 0.4.1"
)
return renderers.TreeGrid(
[
("facility", str),
@@ -0,0 +1,169 @@
# 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
#
import logging
from typing import Iterable, Tuple
from volatility3.framework import interfaces, renderers, constants, exceptions
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints
from volatility3.framework.objects import utility
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.linux.extensions import elf
from volatility3.plugins.linux import pslist
vollog = logging.getLogger(__name__)
class LibraryList(interfaces.plugins.PluginInterface):
"""Enumerate libraries loaded into processes"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls):
return [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 2, 0)
),
requirements.ListRequirement(
name="pids",
description="Filter on specific process IDs",
element_type=int,
optional=True,
),
]
def _get_libdl_libraries(
self, proc_layer_name: str, vma_start: int
) -> interfaces.objects.ObjectInterface:
"""Get the ELF link map objects for the given VMA address
Args:
proc_layer_name (str): Name of the process layer
vma_start (int): VMA start address
Yields:
ELF link map objects for the given VMA address
"""
elf_table_name = intermed.IntermediateSymbolTable.create(
self.context,
self.config_path,
"linux",
"elf",
class_types=elf.class_types,
)
elf_object = self.context.object(
elf_table_name + constants.BANG + "Elf",
offset=vma_start,
layer_name=proc_layer_name,
)
if not elf_object or not elf_object.is_valid():
return None
kernel = self.context.modules[self.config["kernel"]]
try:
for link_map in elf_object.get_link_maps(kernel.symbol_table_name):
if link_map.l_addr and link_map.l_name:
yield link_map
except exceptions.InvalidAddressException:
# Protection against memory smear in this VMA
pass
def _get_libdl_maps(
self, task: interfaces.objects.ObjectInterface, proc_layer_name: str
) -> interfaces.objects.ObjectInterface:
"""Get the ELF link maps objects for a task
Args:
task (task_struct): A reference task
proc_layer_name (str): Name of the process layer
Yields:
ELF link map objects
"""
link_map_seen = set()
for vma in task.mm.get_vma_iter():
for link_map in self._get_libdl_libraries(proc_layer_name, vma.vm_start):
if link_map.l_addr in link_map_seen:
continue
yield link_map
link_map_seen.add(link_map.l_addr)
def _get_task_libraries(
self, task: interfaces.objects.ObjectInterface
) -> Tuple[int, str]:
"""Get the task libraries from the ELF headers found within the memory maps
Args:
task (task_struct): The reference task
Yields:
Tuples with a ELF link map address and name
"""
proc_layer_name = task.add_process_layer()
if not proc_layer_name:
return
for elf_link_map in self._get_libdl_maps(task, proc_layer_name):
name = elf_link_map.get_name()
if not name:
continue
yield elf_link_map.l_addr, name
def _get_tasks_libraries(
self,
tasks: Iterable[interfaces.objects.ObjectInterface],
) -> Iterable[Tuple[str, int, int, str]]:
"""Get the task libraries from the ELF headers found within the memory maps for
all the tasks.
Args:
tasks: An iterable of tasks
Yields:
Tuples with a task name, task tgid, an ELF link map address and name
"""
for task in tasks:
task_name = utility.array_to_string(task.comm)
for linkmap_addr, linkmap_name in self._get_task_libraries(task):
yield task_name, task.tgid, linkmap_addr, linkmap_name
def _format_fields(self, fields):
task_name, task_pid, addr, name = fields
return task_name, task_pid, format_hints.Hex(addr), name
def _generator(
self, tasks: Iterable[interfaces.objects.ObjectInterface]
) -> Iterable[Tuple[int, Tuple]]:
for fields in self._get_tasks_libraries(tasks):
yield 0, self._format_fields(fields)
def run(self):
pids = self.config.get("pids")
pid_filter = pslist.PsList.create_pid_filter(pids)
tasks = pslist.PsList.list_tasks(
self.context, self.config["kernel"], filter_func=pid_filter
)
headers = [
("Name", str),
("Pid", int),
("LoadAddress", format_hints.Hex),
("Path", str),
]
return renderers.TreeGrid(headers, self._generator(tasks))
@@ -17,7 +17,7 @@ class PsList(interfaces.plugins.PluginInterface):
_required_framework_version = (2, 0, 0)
_version = (2, 2, 0)
_version = (2, 2, 1)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -83,11 +83,11 @@ class PsList(interfaces.plugins.PluginInterface):
cls, task: interfaces.objects.ObjectInterface, decorate_comm: bool = False
) -> Tuple[int, int, int, str]:
"""Extract the fields needed for the final output
Args:
task: A task object from where to get the fields.
decorate_comm: If True, it decorates the comm string of
- User threads: in curly brackets,
- Kernel threads: in square brackets
decorate_comm: If True, it decorates the comm string of user threads in curly brackets,
and of Kernel threads in square brackets.
Defaults to False.
Returns:
A tuple with the fields to show in the plugin output.
@@ -128,7 +128,7 @@ class PsList(interfaces.plugins.PluginInterface):
else:
# Find the vma that belongs to the main ELF of the process
file_output = "Error outputting file"
for v in task.mm.get_mmap_iter():
for v in task.mm.get_vma_iter():
if v.vm_start == task.mm.start_code:
file_handle = elfs.Elfs.elf_dump(
self.context,
@@ -142,6 +142,8 @@ class PsList(interfaces.plugins.PluginInterface):
file_output = str(file_handle.preferred_filename)
file_handle.close()
break
else:
file_output = "VMA start matching task start_code not found"
return file_output
def _generator(
@@ -28,7 +28,7 @@ class PsScan(interfaces.plugins.PluginInterface):
"""Scans for processes present in a particular linux image."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
_version = (1, 0, 1)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -139,7 +139,7 @@ class PsScan(interfaces.plugins.PluginInterface):
kernel_layer_name, f"Layer {kernel_layer_name} has no dependencies"
)
memory_layer_name = kernel_layer.dependencies[0]
memory_layer = context.layers[kernel_layer.dependencies[0]]
memory_layer = context.layers[memory_layer_name]
# scan the memory_layer for these needles
for address, _ in memory_layer.scan(
@@ -83,7 +83,7 @@ class SockHandlers(interfaces.configuration.VersionableInterface):
sock: Kernel generic `sock` object
Returns a tuple with:
sock: The respective kernel's \*_sock object for that socket family
sock: The respective kernel's \\*_sock object for that socket family
sock_stat: A tuple with the source and destination (address and port) along with its state string
socket_filter: A dictionary with information about the socket filter
"""
@@ -501,7 +501,7 @@ class Sockstat(plugins.PluginInterface):
family: Socket family string (AF_UNIX, AF_INET, etc)
sock_type: Socket type string (STREAM, DGRAM, etc)
protocol: Protocol string (UDP, TCP, etc)
sock_fields: A tuple with the \*_sock object, the sock stats and the extended info dictionary
sock_fields: A tuple with the \\*_sock object, the sock stats and the extended info dictionary
"""
vmlinux = context.modules[symbol_table]
@@ -0,0 +1,79 @@
# 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
#
import logging
from volatility3.framework import interfaces, renderers, exceptions
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
vollog = logging.getLogger(__name__)
class Dmesg(interfaces.plugins.PluginInterface):
"""Prints the kernel log buffer."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls):
return [
requirements.ModuleRequirement(
name="kernel",
description="Kernel module for the OS",
architectures=["Intel32", "Intel64"],
),
]
@classmethod
def get_kernel_log_buffer(
cls, context: interfaces.context.ContextInterface, kernel_module_name: str
):
"""
Online documentation :
- https://github.com/apple-open-source/macos/blob/master/xnu/bsd/sys/msgbuf.h
- https://github.com/apple-open-source/macos/blob/ea4cd5a06831aca49e33df829d2976d6de5316ec/xnu/bsd/kern/subr_log.c#L751
Volatility 2 plugin :
- https://github.com/volatilityfoundation/volatility/blob/master/volatility/plugins/mac/dmesg.py
"""
kernel = context.modules[kernel_module_name]
if not kernel.has_symbol("msgbufp"):
raise exceptions.SymbolError(
"msgbufp",
kernel.symbol_table_name,
'The provided symbol table does not include the "msgbufp" symbol. This means you are either analyzing an unsupported kernel version or that your symbol table is corrupt.',
)
msgbufp = kernel.object_from_symbol(symbol_name="msgbufp")
msg_size = msgbufp.msg_size # max buffer size
msg_bufx = msgbufp.msg_bufx # write index of the msg_bufc circular buffer
msg_bufc = msgbufp.msg_bufc
# msg_bufc is circular, meaning that if its size exceeds msg_size,
# msg_bufx will point to the beginning of the buffer and start overwriting.
msg_bufc_data: str = utility.pointer_to_string(msg_bufc, msg_size)
# Avoid OOB reads
msg_bufx = msg_bufx if msg_bufx <= msg_size else 0
# We directly take into account the case where the write buffer did a loop,
# as older messages will start at msg_bufx offset (not overwritten yet).
dmesg = msg_bufc_data[msg_bufx:]
dmesg += msg_bufc_data[:msg_bufx]
# Yield each line
for dmesg_line in dmesg.splitlines():
yield (dmesg_line,)
def _generator(self):
for value in self.get_kernel_log_buffer(
context=self.context, kernel_module_name=self.config["kernel"]
):
yield (0, value)
def run(self):
return renderers.TreeGrid(
[
("line", str),
],
self._generator(),
)
+166 -4
View File
@@ -2,17 +2,23 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from volatility3.framework import renderers, interfaces
from volatility3.framework import renderers, interfaces, exceptions
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.plugins.mac import pslist
from typing import Callable, Generator, Type, Optional
import logging
vollog = logging.getLogger(__name__)
class Maps(interfaces.plugins.PluginInterface):
"""Lists process memory ranges that potentially contain injected code."""
_required_framework_version = (2, 0, 0)
_version = (1, 1, 0)
MAXSIZE_DEFAULT = 1024 * 1024 * 1024 # 1 Gb
@classmethod
def get_requirements(cls):
@@ -31,14 +37,152 @@ class Maps(interfaces.plugins.PluginInterface):
element_type=int,
optional=True,
),
requirements.BooleanRequirement(
name="dump",
description="Extract listed memory segments",
default=False,
optional=True,
),
requirements.ListRequirement(
name="address",
description="Process virtual memory addresses to include "
"(all other VMA sections are excluded). This can be any "
"virtual address within the VMA section. Virtual addresses "
"must be separated by a space.",
element_type=int,
optional=True,
),
requirements.IntRequirement(
name="maxsize",
description="Maximum size for dumped VMA sections "
"(all the bigger sections will be ignored)",
default=cls.MAXSIZE_DEFAULT,
optional=True,
),
]
@classmethod
def list_vmas(
cls,
task: interfaces.objects.ObjectInterface,
filter_func: Callable[
[interfaces.objects.ObjectInterface], bool
] = lambda _: True,
) -> Generator[interfaces.objects.ObjectInterface, None, None]:
"""Lists the Virtual Memory Areas of a specific process.
Args:
task: task object from which to list the vma
filter_func: Function to take a vma and return False if it should be filtered out
Returns:
Yields vmas based on the task and filtered based on the filter function
"""
for vma in task.get_map_iter():
if filter_func(vma):
yield vma
else:
vollog.debug(
f"Excluded vma at offset {vma.vol.offset:#x} for pid {task.p_pid} due to filter_func"
)
@classmethod
def vma_dump(
cls,
context: interfaces.context.ContextInterface,
task: interfaces.objects.ObjectInterface,
vm_start: int,
vm_end: int,
open_method: Type[interfaces.plugins.FileHandlerInterface],
maxsize: int = MAXSIZE_DEFAULT,
) -> Optional[interfaces.plugins.FileHandlerInterface]:
"""Extracts the complete data for VMA as a FileInterface.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
task: an task_struct instance
vm_start: The start virtual address from the vma to dump
vm_end: The end virtual address from the vma to dump
open_method: class to provide context manager for opening the file
maxsize: Max size of VMA section (default MAXSIZE_DEFAULT)
Returns:
An open FileInterface object containing the complete data for the task or None in the case of failure
"""
pid = task.p_pid
try:
proc_layer_name = task.add_process_layer()
except exceptions.InvalidAddressException as excp:
vollog.debug(
"Process {}: invalid address {} in layer {}".format(
pid, excp.invalid_address, excp.layer_name
)
)
return None
vm_size = vm_end - vm_start
# check if vm_size is negative, this should never happen.
if vm_size < 0:
vollog.warning(
f"Skip virtual memory dump for pid {pid} between {vm_start:#x}-{vm_end:#x} as {vm_size} is negative."
)
return None
# check if vm_size is larger than the maxsize limit, and therefore is not saved out.
if maxsize <= vm_size:
vollog.warning(
f"Skip virtual memory dump for pid {pid} between {vm_start:#x}-{vm_end:#x} as {vm_size} is larger than maxsize limit of {maxsize}"
)
return None
proc_layer = context.layers[proc_layer_name]
file_name = f"pid.{pid}.vma.{vm_start:#x}-{vm_end:#x}.dmp"
try:
file_handle = open_method(file_name)
chunk_size = 1024 * 1024 * 10
offset = vm_start
while offset < vm_start + vm_size:
to_read = min(chunk_size, vm_start + vm_size - offset)
data = proc_layer.read(offset, to_read, pad=True)
file_handle.write(data)
offset += to_read
except Exception as excp:
vollog.debug(f"Unable to dump virtual memory {file_name}: {excp}")
return None
return file_handle
def _generator(self, tasks):
address_list = self.config.get("address", None)
if not address_list:
# do not filter as no address_list was supplied
vma_filter_func = lambda _: True
else:
# filter for any vm_start that matches the supplied address config
def vma_filter_function(task: interfaces.objects.ObjectInterface) -> bool:
addrs_in_vma = [
addr
for addr in address_list
if task.links.start <= addr <= task.links.end
]
# if any of the user supplied addresses would fall within this vma return true
return bool(addrs_in_vma)
vma_filter_func = vma_filter_function
for task in tasks:
process_name = utility.array_to_string(task.p_comm)
process_pid = task.p_pid
for vma in task.get_map_iter():
for vma in self.list_vmas(task, filter_func=vma_filter_func):
try:
vm_start = vma.links.start
vm_end = vma.links.end
except AttributeError:
vollog.debug(
f"Unable to find the vm_start and vm_end for vma at {vma.vol.offset:#x} for pid {process_pid}"
)
continue
path = vma.get_path(
self.context,
self.context.modules[self.config["kernel"]].symbol_table_name,
@@ -46,15 +190,32 @@ class Maps(interfaces.plugins.PluginInterface):
if path == "":
path = vma.get_special_path()
file_output = "Disabled"
if self.config["dump"]:
file_output = "Error outputting file"
file_handle = self.vma_dump(
self.context,
task,
vm_start,
vm_end,
self.open,
self.config["maxsize"],
)
if file_handle:
file_handle.close()
file_output = file_handle.preferred_filename
yield (
0,
(
process_pid,
process_name,
format_hints.Hex(vma.links.start),
format_hints.Hex(vma.links.end),
format_hints.Hex(vm_start),
format_hints.Hex(vm_end),
vma.get_perms(),
path,
file_output,
),
)
@@ -72,6 +233,7 @@ class Maps(interfaces.plugins.PluginInterface):
("End", format_hints.Hex),
("Protection", str),
("Map Name", str),
("File output", str),
],
self._generator(
list_tasks(self.context, self.config["kernel"], filter_func=filter_func)
@@ -13,7 +13,7 @@ from volatility3.framework.renderers import conversion, format_hints
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.windows.extensions import pe
from volatility3.plugins import timeliner
from volatility3.plugins.windows import info, pslist
from volatility3.plugins.windows import info, pslist, psscan
vollog = logging.getLogger(__name__)
@@ -36,6 +36,9 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="psscan", component=psscan.PsScan, version=(1, 1, 0)
),
requirements.VersionRequirement(
name="info", component=info.Info, version=(1, 0, 0)
),
@@ -45,6 +48,11 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
description="Process IDs to include (all other processes are excluded)",
optional=True,
),
requirements.IntRequirement(
name="offset",
description="Process offset in the physical address space",
optional=True,
),
requirements.BooleanRequirement(
name="dump",
description="Extract listed DLLs",
@@ -221,6 +229,25 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
kernel = self.context.modules[self.config["kernel"]]
if self.config["offset"]:
procs = psscan.PsScan.scan_processes(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
filter_func=psscan.PsScan.create_offset_filter(
self.context,
kernel.layer_name,
self.config["offset"],
),
)
else:
procs = pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
filter_func=filter_func,
)
return renderers.TreeGrid(
[
("PID", int),
@@ -232,12 +259,5 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
("LoadTime", datetime.datetime),
("File output", str),
],
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
filter_func=filter_func,
)
),
self._generator(procs=procs),
)
@@ -81,7 +81,10 @@ class DriverIrp(interfaces.plugins.PluginInterface):
address
)
module_found = False
for module_name, symbol_generator in module_symbols:
module_found = True
symbols_found = False
for symbol in symbol_generator:
@@ -111,6 +114,19 @@ class DriverIrp(interfaces.plugins.PluginInterface):
),
)
if not module_found:
yield (
0,
(
format_hints.Hex(driver.vol.offset),
driver_name,
MAJOR_FUNCTIONS[i],
format_hints.Hex(address),
renderers.NotAvailableValue(),
renderers.NotAvailableValue(),
),
)
def run(self):
return renderers.TreeGrid(
[
@@ -4,11 +4,12 @@
import logging
import ntpath
import re
from typing import List, Tuple, Type, Optional, Generator
from volatility3.framework import interfaces, renderers, exceptions, constants
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints
from volatility3.framework.renderers import format_hints, UnreadableValue
from volatility3.plugins.windows import handles
from volatility3.plugins.windows import pslist
@@ -53,6 +54,17 @@ class DumpFiles(interfaces.plugins.PluginInterface):
description="Dump a single _FILE_OBJECT at this physical address",
optional=True,
),
requirements.StringRequirement(
name="filter",
description="Dump files matching regular expression FILTER",
optional=True,
),
requirements.BooleanRequirement(
name="ignore-case",
description="Ignore case in filter match",
default=False,
optional=True,
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
),
@@ -208,6 +220,10 @@ class DumpFiles(interfaces.plugins.PluginInterface):
def _generator(self, procs: List, offsets: List):
kernel = self.context.modules[self.config["kernel"]]
file_re = None
if self.config["filter"]:
flags = re.I if self.config["ignore-case"] else 0
file_re = re.compile(self.config["filter"], flags)
if procs:
# The handles plugin doesn't expose any staticmethod/classmethod, and it also requires stashing
@@ -243,6 +259,14 @@ class DumpFiles(interfaces.plugins.PluginInterface):
obj_type = entry.get_object_type(type_map, cookie)
if obj_type == "File":
file_obj = entry.Body.cast("_FILE_OBJECT")
if file_re:
name = file_obj.file_name_with_device()
if isinstance(name, UnreadableValue):
continue
if not file_re.search(name):
continue
for result in self.process_file_object(
self.context, kernel.layer_name, self.open, file_obj
):
@@ -272,6 +296,13 @@ class DumpFiles(interfaces.plugins.PluginInterface):
if not file_obj.is_valid():
continue
if file_re:
name = file_obj.file_name_with_device()
if isinstance(name, UnreadableValue):
continue
if not file_re.search(name):
continue
for result in self.process_file_object(
self.context, kernel.layer_name, self.open, file_obj
):
@@ -315,6 +346,11 @@ class DumpFiles(interfaces.plugins.PluginInterface):
procs = list()
kernel = self.context.modules[self.config["kernel"]]
if self.config["filter"] and (
self.config["virtaddr"] or self.config["physaddr"]
):
raise ValueError("Cannot use filter flag with an address flag")
if self.config.get("virtaddr", None) is not None:
offsets.append((self.config["virtaddr"], True))
elif self.config.get("physaddr", None) is not None:
@@ -9,7 +9,7 @@ from volatility3.framework import constants, exceptions, renderers, interfaces,
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.plugins.windows import pslist
from volatility3.plugins.windows import pslist, psscan
vollog = logging.getLogger(__name__)
@@ -43,14 +43,22 @@ class Handles(interfaces.plugins.PluginInterface):
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="psscan", component=psscan.PsScan, version=(1, 1, 0)
),
requirements.ListRequirement(
name="pid",
element_type=int,
description="Process IDs to include (all other processes are excluded)",
optional=True,
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
requirements.IntRequirement(
name="offset",
description="Process offset in the physical address space",
optional=True,
),
]
@@ -416,6 +424,25 @@ class Handles(interfaces.plugins.PluginInterface):
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
kernel = self.context.modules[self.config["kernel"]]
if self.config["offset"]:
procs = psscan.PsScan.scan_processes(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
filter_func=psscan.PsScan.create_offset_filter(
self.context,
kernel.layer_name,
self.config["offset"],
),
)
else:
procs = pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
filter_func=filter_func,
)
return renderers.TreeGrid(
[
("PID", int),
@@ -426,12 +453,5 @@ class Handles(interfaces.plugins.PluginInterface):
("GrantedAccess", format_hints.Hex),
("Name", str),
],
self._generator(
pslist.PsList.list_processes(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
filter_func=filter_func,
)
),
self._generator(procs=procs),
)
@@ -0,0 +1,150 @@
# 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
import logging, io, pefile
from volatility3.framework.symbols import intermed
from volatility3.framework import renderers, interfaces, exceptions, constants
from volatility3.framework.configuration import requirements
from volatility3.plugins.windows import pslist
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols.windows.extensions import pe
vollog = logging.getLogger(__name__)
class IAT(interfaces.plugins.PluginInterface):
"""Extract Import Address Table to list API (functions) used by a program contained in external libraries"""
_required_framework_version = (2, 4, 0)
@classmethod
def get_requirements(cls):
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
),
requirements.ListRequirement(
name="pid",
element_type=int,
description="Process ID to include (all other processes are excluded)",
optional=True,
),
]
def _generator(self, procs):
kernel = self.context.modules[self.config["kernel"]]
for proc in procs:
try:
proc_id = proc.UniqueProcessId
proc_layer_name = proc.add_process_layer()
peb = self.context.object(
kernel.symbol_table_name + constants.BANG + "_PEB",
layer_name=proc_layer_name,
offset=proc.Peb,
)
if proc_layer_name is None:
raise TypeError("add_process_layer failed")
pe_table_name = intermed.IntermediateSymbolTable.create(
self.context,
self.config_path,
"windows",
"pe",
class_types=pe.class_types,
)
pe_data = io.BytesIO()
dos_header = self.context.object(
pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER",
offset=peb.ImageBaseAddress,
layer_name=proc_layer_name,
)
for offset, data in dos_header.reconstruct():
pe_data.seek(offset)
pe_data.write(data)
pe_obj = pefile.PE(data=pe_data.getvalue(), fast_load=True)
pe_obj.parse_data_directories(
[pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_IMPORT"]]
)
if hasattr(pe_obj, "DIRECTORY_ENTRY_IMPORT"):
for entry in pe_obj.DIRECTORY_ENTRY_IMPORT:
dll_entry = entry.dll
if dll_entry:
dll_entry = dll_entry.decode()
else:
dll_entry = renderers.NotAvailableValue
bound = True
# Initially set to 0 if not bound
time_date_stamp = entry.struct.TimeDateStamp
if not time_date_stamp:
bound = False
# Iterate over imported functions
for imp in entry.imports:
import_name = imp.name
if import_name:
import_name = imp.name.decode()
else:
import_name = renderers.NotAvailableValue()
function_address = (
pe_obj.OPTIONAL_HEADER.ImageBase + imp.address
)
if not function_address:
function_address = renderers.NotAvailableValue
yield (
0,
(
proc_id,
proc.ImageFileName.cast(
"string",
max_length=proc.ImageFileName.vol.count,
errors="replace",
),
dll_entry,
bound,
import_name,
format_hints.Hex(function_address),
),
)
except exceptions.InvalidAddressException as excp:
vollog.debug(
"Process {}: invalid address {} in layer {}".format(
proc_id, excp.invalid_address, excp.layer_name
)
)
continue
def run(self):
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
("PID", int),
("Name", str),
("Library", str),
("Bound", bool),
("Function", str),
("Address", format_hints.Hex),
],
self._generator(
pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
filter_func=pslist.PsList.create_pid_filter(
self.config.get("pid", None)
),
)
),
)
@@ -141,16 +141,30 @@ class Malfind(interfaces.plugins.PluginInterface):
# determine if we're on a 32 or 64 bit kernel
kernel = self.context.modules[self.config["kernel"]]
# set refined criteria to know when to add to "Notes" column
refined_criteria = {
b"MZ": "MZ header",
b"\x55\x8B": "PE header",
b"\x55\x48": "Function prologue",
b"\x55\x89": "Function prologue",
}
is_32bit_arch = not symbols.symbol_table_is_64bit(
self.context, kernel.symbol_table_name
)
for proc in procs:
# by default, "Notes" column will be set to N/A
notes = renderers.NotApplicableValue()
process_name = utility.array_to_string(proc.ImageFileName)
for vad, data in self.list_injections(
self.context, kernel.layer_name, kernel.symbol_table_name, proc
):
# Check for unique headers and update "Notes" column if criteria is met
if data[0:2] in refined_criteria:
notes = refined_criteria[data[0:2]]
# if we're on a 64 bit kernel, we may still need 32 bit disasm due to wow64
if is_32bit_arch or proc.get_is_wow64():
architecture = "intel"
@@ -196,6 +210,7 @@ class Malfind(interfaces.plugins.PluginInterface):
vad.get_commit_charge(),
vad.get_private_memory(),
file_output,
notes,
format_hints.HexBytes(data),
disasm,
),
@@ -216,6 +231,7 @@ class Malfind(interfaces.plugins.PluginInterface):
("CommitCharge", int),
("PrivateMemory", int),
("File output", str),
("Notes", str),
("Hexdump", format_hints.HexBytes),
("Disasm", interfaces.renderers.Disassembly),
],
@@ -38,7 +38,7 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
# Yara Rule to scan for MFT Header Signatures
rules = yarascan.YaraScan.process_yara_options(
{"yara_rules": "/FILE0|FILE\*|BAAD/"}
{"yara_rules": "/FILE0|FILE\\*|BAAD/"}
)
# Read in the Symbol File
@@ -53,7 +53,6 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
# get each of the individual Field Sets
mft_object = symbol_table + constants.BANG + "MFT_ENTRY"
attribute_object = symbol_table + constants.BANG + "ATTRIBUTE"
header_object = symbol_table + constants.BANG + "ATTR_HEADER"
si_object = symbol_table + constants.BANG + "STANDARD_INFORMATION_ENTRY"
fn_object = symbol_table + constants.BANG + "FILE_NAME_ENTRY"
@@ -198,7 +197,7 @@ class ADS(interfaces.plugins.PluginInterface):
# Yara Rule to scan for MFT Header Signatures
rules = yarascan.YaraScan.process_yara_options(
{"yara_rules": "/FILE0|FILE\*|BAAD/"}
{"yara_rules": "/FILE0|FILE\\*|BAAD/"}
)
# Read in the Symbol File
@@ -222,6 +222,24 @@ class PoolScanner(plugins.PluginInterface):
type_name=symbol_table + constants.BANG + "_EPROCESS",
object_type="Process",
size=(600, None),
skip_type_test=True,
page_type=PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE,
),
# threads on windows before windows8
PoolConstraint(
b"Thr\xe5", # -> “protected” allocation, MSB is set.
type_name=symbol_table + constants.BANG + "_ETHREAD",
object_type="Thread",
size=(600, None), # -> 0x0258 - size of struct in win5.1
skip_type_test=True,
page_type=PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE,
),
# threads on windows starting with windows8
PoolConstraint(
b"Thre",
type_name=symbol_table + constants.BANG + "_ETHREAD",
object_type="Thread",
size=(600, None), # -> 0x0258 - size of struct in win5.1
page_type=PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE,
),
# files on windows before windows 8
@@ -59,6 +59,75 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
),
]
@classmethod
def physical_offset_from_virtual(cls, context, layer_name, proc):
"""Calculate the physical offset from the virtual offset of a process.
Args:
context: The context containing layers and modules information.
layer_name: The name of the layer containing the process memory.
proc: The process object for which to calculate the physical offset.
Returns:
int: The physical offset of the process.
Raises:
TypeError: If the primary layer is not an Intel layer.
"""
memory = context.layers[layer_name]
if not isinstance(memory, layers.intel.Intel):
raise TypeError("Primary layer is not an intel layer")
(_, _, ph_offset, _, _) = list(
memory.mapping(offset=proc.vol.offset, length=0)
)[0]
return ph_offset
@classmethod
def create_offset_filter(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
offset: int = None,
physical: bool = True,
exclude: bool = False,
) -> Callable[[interfaces.objects.ObjectInterface], bool]:
"""A factory for producing filter functions that filter based on the physical offset of the process.
Args:
offset: A number that is the physical offset to be filtered out
exclude: Accept only tasks that are not the offset argument
Returns:
Filter function to be passed to the list of processes.
"""
filter_func = lambda _: False
if offset:
if physical:
if exclude:
filter_func = (
lambda proc: cls.physical_offset_from_virtual(
context, layer_name, proc
)
== offset
)
else:
filter_func = (
lambda proc: cls.physical_offset_from_virtual(
context, layer_name, proc
)
!= offset
)
else:
if exclude:
filter_func = lambda proc: proc.vol.offset == offset
else:
filter_func = lambda proc: proc.vol.offset != offset
return filter_func
@classmethod
def scan_processes(
cls,
@@ -5,7 +5,7 @@ import datetime
import logging
from typing import Callable, Dict, Set, Tuple
from volatility3.framework import objects, interfaces, renderers
from volatility3.framework import objects, interfaces, renderers, exceptions
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints
from volatility3.plugins.windows import pslist
@@ -132,6 +132,25 @@ class PsTree(interfaces.plugins.PluginInterface):
proc.get_exit_time(),
)
try:
audit = proc.SeAuditProcessCreationInfo.ImageFileName.Name
# If 'audit' is set to the empty string, display NotAvailableValue
row += (audit.get_string() or renderers.NotAvailableValue(),)
except exceptions.InvalidAddressException:
row += (renderers.NotAvailableValue(),)
try:
process_params = proc.get_peb().ProcessParameters
row += (
process_params.CommandLine.get_string(),
process_params.ImagePathName.get_string(),
)
except exceptions.InvalidAddressException:
row += (
renderers.NotAvailableValue(),
renderers.NotAvailableValue(),
)
yield (self._levels[pid] - 1, row)
for child_pid in self._children.get(pid, []):
yield from yield_processes(
@@ -161,6 +180,9 @@ class PsTree(interfaces.plugins.PluginInterface):
("Wow64", bool),
("CreateTime", datetime.datetime),
("ExitTime", datetime.datetime),
("Audit", str),
("Cmd", str),
("Path", str),
],
self._generator(
filter_func=pslist.PsList.create_pid_filter(
+179 -68
View File
@@ -4,25 +4,42 @@
import logging
import os
from typing import List
from typing import Dict, List, NamedTuple, Optional, Tuple, Union, cast
from volatility3.framework import interfaces, renderers, constants, symbols, exceptions
from volatility3.framework import (
constants,
exceptions,
interfaces,
objects,
renderers,
symbols,
)
from volatility3.framework.configuration import requirements
from volatility3.framework.layers import scanners
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.windows import versions
from volatility3.framework.symbols.windows.extensions import services
from volatility3.plugins.windows import poolscanner, vadyarascan, pslist
from volatility3.plugins.windows import poolscanner, pslist, vadyarascan
from volatility3.plugins.windows.registry import hivelist
vollog = logging.getLogger(__name__)
ServiceBinaryInfo = NamedTuple(
"ServiceBinaryInfo",
[
("dll", Union[str, interfaces.renderers.BaseAbsentValue]),
("binary", Union[str, interfaces.renderers.BaseAbsentValue]),
],
)
class SvcScan(interfaces.plugins.PluginInterface):
"""Scans for windows services."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -42,10 +59,16 @@ class SvcScan(interfaces.plugins.PluginInterface):
requirements.PluginRequirement(
name="vadyarascan", plugin=vadyarascan.VadYaraScan, version=(1, 0, 0)
),
requirements.PluginRequirement(
name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0)
),
]
@staticmethod
def get_record_tuple(service_record: interfaces.objects.ObjectInterface):
def get_record_tuple(
service_record: interfaces.objects.ObjectInterface,
binary_info: ServiceBinaryInfo,
):
return (
format_hints.Hex(service_record.vol.offset),
service_record.Order,
@@ -56,8 +79,32 @@ class SvcScan(interfaces.plugins.PluginInterface):
service_record.get_name(),
service_record.get_display(),
service_record.get_binary(),
binary_info.binary,
binary_info.dll,
)
# These checks must be completed from newest -> oldest OS version.
_win_version_file_map: List[Tuple[versions.OsDistinguisher, bool, str]] = [
(versions.is_win10_25398_or_later, True, "services-win10-25398-x64"),
(versions.is_win10_19041_or_later, True, "services-win10-19041-x64"),
(versions.is_win10_19041_or_later, False, "services-win10-19041-x86"),
(versions.is_win10_18362_or_later, True, "services-win10-18362-x64"),
(versions.is_win10_18362_or_later, False, "services-win10-18362-x86"),
(versions.is_win10_17763_or_later, False, "services-win10-17763-x86"),
(versions.is_win10_16299_or_later, True, "services-win10-16299-x64"),
(versions.is_win10_16299_or_later, False, "services-win10-16299-x86"),
(versions.is_win10_15063, True, "services-win10-15063-x64"),
(versions.is_win10_15063, False, "services-win10-15063-x86"),
(versions.is_win10_up_to_15063, True, "services-win8-x64"),
(versions.is_win10_up_to_15063, False, "services-win8-x86"),
(versions.is_windows_8_or_later, True, "services-win8-x64"),
(versions.is_windows_8_or_later, True, "services-win8-x86"),
(versions.is_vista_or_later, True, "services-vista-x64"),
(versions.is_vista_or_later, False, "services-vista-x86"),
(versions.is_windows_xp, False, "services-xp-x86"),
(versions.is_xp_or_2003, True, "services-xp-2003-x64"),
]
@staticmethod
def create_service_table(
context: interfaces.context.ContextInterface,
@@ -78,67 +125,14 @@ class SvcScan(interfaces.plugins.PluginInterface):
native_types = context.symbol_space[symbol_table].natives
is_64bit = symbols.symbol_table_is_64bit(context, symbol_table)
if (
versions.is_windows_xp(context=context, symbol_table=symbol_table)
and not is_64bit
):
symbol_filename = "services-xp-x86"
elif (
versions.is_xp_or_2003(context=context, symbol_table=symbol_table)
and is_64bit
):
symbol_filename = "services-xp-2003-x64"
elif (
versions.is_win10_16299_or_later(context=context, symbol_table=symbol_table)
and is_64bit
):
symbol_filename = "services-win10-16299-x64"
elif (
versions.is_win10_16299_or_later(context=context, symbol_table=symbol_table)
and not is_64bit
):
symbol_filename = "services-win10-16299-x86"
elif (
versions.is_win10_up_to_15063(context=context, symbol_table=symbol_table)
and is_64bit
):
symbol_filename = "services-win8-x64"
elif (
versions.is_win10_up_to_15063(context=context, symbol_table=symbol_table)
and not is_64bit
):
symbol_filename = "services-win8-x86"
elif (
versions.is_win10_15063(context=context, symbol_table=symbol_table)
and is_64bit
):
symbol_filename = "services-win10-15063-x64"
elif (
versions.is_win10_15063(context=context, symbol_table=symbol_table)
and not is_64bit
):
symbol_filename = "services-win10-15063-x86"
elif (
versions.is_windows_8_or_later(context=context, symbol_table=symbol_table)
and is_64bit
):
symbol_filename = "services-win8-x64"
elif (
versions.is_windows_8_or_later(context=context, symbol_table=symbol_table)
and not is_64bit
):
symbol_filename = "services-win8-x86"
elif (
versions.is_vista_or_later(context=context, symbol_table=symbol_table)
and is_64bit
):
symbol_filename = "services-vista-x64"
elif (
versions.is_vista_or_later(context=context, symbol_table=symbol_table)
and not is_64bit
):
symbol_filename = "services-vista-x86"
else:
try:
symbol_filename = next(
filename
for version_check, for_64bit, filename in SvcScan._win_version_file_map
if is_64bit == for_64bit
and version_check(context=context, symbol_table=symbol_table)
)
except StopIteration:
raise NotImplementedError("This version of Windows is not supported!")
return intermed.IntermediateSymbolTable.create(
@@ -150,6 +144,94 @@ class SvcScan(interfaces.plugins.PluginInterface):
native_types=native_types,
)
def _get_service_key(self, kernel) -> Optional[objects.StructType]:
for hive in hivelist.HiveList.list_hives(
context=self.context,
base_config_path=interfaces.configuration.path_join(
self.config_path, "hivelist"
),
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
filter_string="machine\\system",
):
# Get ControlSet\Services.
try:
return cast(
objects.StructType, hive.get_key(r"CurrentControlSet\Services")
)
except (KeyError, exceptions.InvalidAddressException):
try:
return cast(
objects.StructType, hive.get_key(r"ControlSet001\Services")
)
except (KeyError, exceptions.InvalidAddressException):
vollog.log(
constants.LOGLEVEL_VVVV,
"Could not retrieve any control set from SYSTEM hive",
)
return None
@staticmethod
def _get_service_dll(
service_key,
) -> Union[str, interfaces.renderers.BaseAbsentValue]:
try:
param_key = next(
key
for key in service_key.get_subkeys()
if key.get_name() == "Parameters"
)
return (
next(
val
for val in param_key.get_values()
if val.get_name() == "ServiceDll"
)
.decode_data()
.decode("utf-16")
.rstrip("\x00")
)
except UnicodeDecodeError:
return renderers.UnparsableValue()
except StopIteration:
return renderers.UnreadableValue()
@staticmethod
def _get_service_binary(
service_key,
) -> Union[str, interfaces.renderers.BaseAbsentValue]:
try:
return (
next(
val
for val in service_key.get_values()
if val.get_name() == "ImagePath"
)
.decode_data()
.decode("utf-16")
.rstrip("\x00")
)
except UnicodeDecodeError:
return renderers.UnparsableValue()
except StopIteration:
return renderers.UnreadableValue()
@staticmethod
def _get_service_binary_map(
services_key: interfaces.objects.ObjectInterface,
) -> Dict[str, ServiceBinaryInfo]:
services = services_key.get_subkeys()
return {
service_key.get_name(): ServiceBinaryInfo(
SvcScan._get_service_dll(service_key),
SvcScan._get_service_binary(service_key),
)
for service_key in services
}
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
@@ -157,6 +239,15 @@ class SvcScan(interfaces.plugins.PluginInterface):
self.context, kernel.symbol_table_name, self.config_path
)
# Building the dictionary ahead of time is much better for performance
# vs looking up each service's DLL individually.
services_key = self._get_service_key(kernel)
service_binary_dll_map = (
self._get_service_binary_map(services_key)
if services_key is not None
else {}
)
relative_tag_offset = self.context.symbol_space.get_type(
service_table_name + constants.BANG + "_SERVICE_RECORD"
).relative_child_offset("Tag")
@@ -209,7 +300,16 @@ class SvcScan(interfaces.plugins.PluginInterface):
if not service_record.is_valid():
continue
yield (0, self.get_record_tuple(service_record))
service_info = service_binary_dll_map.get(
service_record.get_name(),
ServiceBinaryInfo(
renderers.UnreadableValue(), renderers.UnreadableValue()
),
)
yield (
0,
self.get_record_tuple(service_record, service_info),
)
else:
service_header = self.context.object(
service_table_name + constants.BANG + "_SERVICE_HEADER",
@@ -227,7 +327,16 @@ class SvcScan(interfaces.plugins.PluginInterface):
if service_record in seen:
break
seen.append(service_record)
yield (0, self.get_record_tuple(service_record))
service_info = service_binary_dll_map.get(
service_record.get_name(),
ServiceBinaryInfo(
renderers.UnreadableValue(), renderers.UnreadableValue()
),
)
yield (
0,
self.get_record_tuple(service_record, service_info),
)
def run(self):
return renderers.TreeGrid(
@@ -241,6 +350,8 @@ class SvcScan(interfaces.plugins.PluginInterface):
("Name", str),
("Display", str),
("Binary", str),
("Binary (Registry)", str),
("Dll", str),
],
self._generator(),
)
@@ -0,0 +1,141 @@
##
## plugin for testing addition of threads scan support to poolscanner.py
##
import logging
import datetime
from typing import Iterable
from volatility3.framework import renderers, interfaces, exceptions
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints
from volatility3.plugins.windows import poolscanner
from volatility3.plugins import timeliner
vollog = logging.getLogger(__name__)
class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Scans for windows threads."""
# version 2.6.0 adds support for scanning for 'Ethread' structures by pool tags
_required_framework_version = (2, 6, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls):
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="poolscanner", plugin=poolscanner.PoolScanner, version=(1, 0, 0)
),
]
@classmethod
def scan_threads(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Scans for threads using the poolscanner module and constraints.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
Returns:
A list of _ETHREAD objects found by scanning memory for the "Thre" / "Thr\\xE5" pool signatures
"""
constraints = poolscanner.PoolScanner.builtin_constraints(
symbol_table, [b"Thr\xe5", b"Thre"]
)
for result in poolscanner.PoolScanner.generate_pool_scan(
context, layer_name, symbol_table, constraints
):
_constraint, mem_object, _header = result
yield mem_object
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
for ethread in self.scan_threads(
self.context, kernel.layer_name, kernel.symbol_table_name
):
try:
thread_offset = ethread.vol.offset
owner_proc_pid = ethread.Cid.UniqueProcess
thread_tid = ethread.Cid.UniqueThread
thread_start_addr = ethread.StartAddress
thread_create_time = (
ethread.get_create_time()
) # datetime.datetime object / volatility3.framework.renderers.UnparsableValue object
thread_exit_time = (
ethread.get_exit_time()
) # datetime.datetime object / volatility3.framework.renderers.UnparsableValue object
except (ValueError, exceptions.InvalidAddressException):
vollog.debug(
"Thread :{}, invalid address {} in layer {}".format(
thread_tid, thread_start_addr, kernel.layer_name
)
)
continue
yield (
0,
(
format_hints.Hex(thread_offset),
owner_proc_pid,
thread_tid,
format_hints.Hex(thread_start_addr),
thread_create_time,
thread_exit_time,
),
)
def generate_timeline(self):
for row in self._generator():
_depth, row_data = row
row_dict = {}
(
row_dict["Offset"],
row_dict["PID"],
row_dict["TID"],
row_dict["StartAddress"],
row_dict["CreateTime"],
row_dict["ExitTime"],
) = row_data
# Skip threads with no creation time
# - mainly system process threads
if not isinstance(row_dict["CreateTime"], datetime.datetime):
continue
description = f"Thread: Tid {row_dict['TID']} in Pid {row_dict['PID']} (Offset {row_dict['Offset']})"
# yield created time, and if there is exit time, yield it too.
yield (description, timeliner.TimeLinerType.CREATED, row_dict["CreateTime"])
if isinstance(row_dict["ExitTime"], datetime.datetime):
yield (
description,
timeliner.TimeLinerType.MODIFIED,
row_dict["ExitTime"],
)
def run(self):
return renderers.TreeGrid(
[
("Offset", format_hints.Hex),
("PID", int),
("TID", int),
("StartAddress", format_hints.Hex),
("CreateTime", datetime.datetime),
("ExitTime", datetime.datetime),
],
self._generator(),
)
@@ -0,0 +1,144 @@
# 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 typing import Iterable, Generator, List, Tuple
from volatility3.framework import constants, interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces.configuration import RequirementInterface
from volatility3.framework.interfaces.objects import ObjectInterface
from volatility3.framework.objects import Bytes, DataFormatInfo, Integer, StructType
from volatility3.framework.objects.templates import ObjectTemplate
from volatility3.framework.objects.utility import array_to_string
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.windows.extensions import pe
from volatility3.plugins.windows import modules
class Passphrase(interfaces.plugins.PluginInterface):
"""TrueCrypt Cached Passphrase Finder"""
_version = (0, 1, 0)
_required_framework_version = (2, 5, 2)
@classmethod
def get_requirements(cls) -> List[RequirementInterface]:
return [
requirements.ModuleRequirement(
"kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="modules", component=modules.Modules, version=(1, 1, 0)
),
requirements.IntRequirement(
name="min-length",
description="Minimum length of passphrases to identify",
default=5,
optional=True,
),
]
def scan_module(
self, module_base: int, layer_name: str
) -> Generator[Tuple[int, str], None, None]:
"""Scans the TrueCrypt kernel module for cached passphrases.
Args:
module_base: the module's DLL base
layer_name: the name of the layer in which the module resides
Generates:
A tuple of the offset at which a password is found, and the password
"""
pe_table_name = intermed.IntermediateSymbolTable.create(
self.context, self.config_path, "windows", "pe", class_types=pe.class_types
)
dos_header: pe.IMAGE_DOS_HEADER = self.context.object(
pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER",
layer_name,
module_base,
)
data_section: StructType = next(
sec
for sec in dos_header.get_nt_header().get_sections()
if array_to_string(sec.Name) == ".data"
)
base: int = data_section.VirtualAddress + module_base
size: int = data_section.Misc.VirtualSize
# Looking at `Length` in TrueCrypt/Common/Password.h::Password struct
DWORD_SIZE_BYTES: int = 4
format = DataFormatInfo(
length=DWORD_SIZE_BYTES, byteorder="little", signed=True
)
int32 = ObjectTemplate(
Integer, pe_table_name + constants.BANG + "int", data_format=format
)
count, not_aligned = divmod(size, DWORD_SIZE_BYTES)
if not_aligned:
raise ValueError("PE data section not DWORD-aligned!")
lengths = self.context.object(
pe_table_name + constants.BANG + "array",
layer_name,
base,
count=count,
subtype=int32,
)
min_length = self.config.get("min-length")
for length in lengths:
# TrueCrypt maximum password length is 64
# (see TrueCrypt/Common/Password.h)
if not min_length <= length <= 64:
continue
offset = length.vol["offset"] + DWORD_SIZE_BYTES
passphrase: Bytes = self.context.object(
pe_table_name + constants.BANG + "bytes",
layer_name,
offset,
length=length,
)
# TrueCrypt/Common/Password.c permits chars in the range
# [0x20, 0x7F).
if not all(0x20 <= c < 0x7F for c in passphrase):
continue
# TrueCrypt/Common/Password.h::Password struct is padded with
# 3 zero bytes to keep 64-byte alignment.
buf: Bytes = self.context.object(
pe_table_name + constants.BANG + "bytes",
layer_name,
offset + length + 1, # +1 for '\0'-terminated password string
length=3,
)
if any(buf):
continue
# Password found.
yield offset, passphrase.decode(encoding="ascii")
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
mods: Iterable[ObjectInterface] = modules.Modules.list_modules(
self.context, kernel.layer_name, kernel.symbol_table_name
)
truecrypt_module_base = next(
mod.DllBase
for mod in mods
if mod.BaseDllName.get_string().lower() == "truecrypt.sys"
)
for offset, password in self.scan_module(
truecrypt_module_base, kernel.layer_name
):
yield (0, (format_hints.Hex(offset), len(password), password))
def run(self) -> renderers.TreeGrid:
return renderers.TreeGrid(
[
("Offset", format_hints.Hex),
("Length", int),
("Password", str),
],
self._generator(),
)
+48 -1
View File
@@ -4,9 +4,20 @@
import collections
import collections.abc
import datetime
import enum
import logging
from typing import Any, Dict, Iterable, Iterator, TypeVar, List
from typing import (
Any,
Callable,
Dict,
Iterable,
Iterator,
Optional,
Tuple,
TypeVar,
List,
)
from volatility3.framework import constants, exceptions, interfaces, objects
@@ -113,6 +124,42 @@ class SymbolSpace(interfaces.symbols.SymbolSpaceInterface):
self._resolved = {}
del self._dict[key]
def verify_table_versions(
self,
producer: str,
validator: Callable[[Optional[Tuple], Optional[datetime.datetime]], bool],
tables: List[str] = None,
) -> bool:
"""Verifies the producer metadata and version of tables
Args:
producer: String name of a table producer to have validation performed
validator: callable that takes an optional version and an optional datetime that returns False if table is invalid
Returns:
False if an invalid table was found or True if no invalid table was found
"""
if tables is None:
tables = self._dict.keys()
for table_name in tables:
table = self._dict[table_name]
if not table.producer:
vollog.debug(
f"Symbol table {table_name} could not be validated because no producer metadata was found"
)
continue
if table.producer.name == producer:
# Run the verification
if not validator(
table.producer.version,
table.producer.datetime,
):
vollog.debug(f"Symbol table {table_name} does not pass validator")
return False
else:
continue
return True
### Resolution functions
class UnresolvedTemplate(objects.templates.ReferenceTemplate):
@@ -183,6 +183,7 @@ class IntermediateSymbolTable(interfaces.symbols.SymbolTableInterface):
types = _construct_delegate_function("types", True)
enumerations = _construct_delegate_function("enumerations", True)
metadata = _construct_delegate_function("metadata", True)
producer = _construct_delegate_function("producer", True)
clear_symbol_cache = _construct_delegate_function("clear_symbol_cache")
get_type = _construct_delegate_function("get_type")
get_symbol = _construct_delegate_function("get_symbol")
@@ -372,6 +373,14 @@ class ISFormatTable(interfaces.symbols.SymbolTableInterface, metaclass=ABCMeta):
table."""
return None
@property
def producer(self) -> Optional["metadata.ProducerMetadata"]:
"""Returns a metadata object containing information about the symbol
table."""
return metadata.ProducerMetadata(
self._json_object.get("metadata", {}).get("producer", {})
)
def clear_symbol_cache(self) -> None:
"""Clears the symbol cache of the symbol table."""
self._symbol_cache.clear()
+135 -7
View File
@@ -270,8 +270,8 @@
"d_tag": {
"offset": 0,
"type": {
"kind": "base",
"name": "long long"
"kind": "enum",
"name": "DtypeEnum64"
}
},
"d_ptr": {
@@ -699,8 +699,8 @@
"d_tag": {
"offset": 0,
"type": {
"kind": "base",
"name": "long"
"kind": "enum",
"name": "DtypeEnum32"
}
},
"d_ptr": {
@@ -905,11 +905,139 @@
"PT_PHDR": 6,
"PT_TLS": 7,
"PT_LOOS": 1610612736,
"PT_GNU_EH_FRAME": 1685382480,
"PT_GNU_STACK": 1685382481,
"PT_GNU_RELRO": 1685382482,
"PT_GNU_PROPERTY": 1685382483,
"PT_HIOS": 1879048191,
"PT_LOWPROC": 1879048192,
"PT_HIPROC": 2147483647
},
"size": 4
},
"DtypeEnum32": {
"base": "long",
"constants": {
"DT_NULL": 0,
"DT_NEEDED": 1,
"DT_PLTRELSZ": 2,
"DT_PLTGOT": 3,
"DT_HASH": 4,
"DT_STRTAB": 5,
"DT_SYMTAB": 6,
"DT_RELA": 7,
"DT_RELASZ": 8,
"DT_RELAENT": 9,
"DT_STRSZ": 10,
"DT_SYMENT": 11,
"DT_INIT": 12,
"DT_FINI": 13,
"DT_SONAME": 14,
"DT_RPATH": 15,
"DT_SYMBOLIC": 16,
"DT_REL": 17,
"DT_RELSZ": 18,
"DT_RELENT": 19,
"DT_PLTREL": 20,
"DT_DEBUG": 21,
"DT_TEXTREL": 22,
"DT_JMPREL": 23,
"DT_BIND_NOW": 24,
"DT_INIT_ARRAY": 25,
"DT_FINI_ARRAY": 26,
"DT_INIT_ARRAYSZ": 27,
"DT_FINI_ARRAYSZ": 28,
"DT_RUNPATH": 29,
"DT_FLAGS": 30,
"DT_ENCODING": 32,
"DT_PREINIT_ARRAYSZ": 33,
"DT_SYMTAB_SHNDX": 34,
"DT_RELRSZ": 35,
"DT_RELR": 36,
"DT_RELRENT": 37,
"DT_NUM": 38,
"OLD_DT_LOOS": 1610612736,
"DT_LOOS": 1610612749,
"DT_HIOS": 1879044096,
"DT_VALRNGLO": 1879047424,
"DT_VALRNGHI": 1879047679,
"DT_ADDRRNGLO": 1879047680,
"DT_GNU_HASH": 1879047925,
"DT_ADDRRNGHI": 1879047935,
"DT_VERSYM": 1879048176,
"DT_RELACOUNT": 1879048185,
"DT_RELCOUNT": 1879048186,
"DT_FLAGS_1": 1879048187,
"DT_VERDEF": 1879048188,
"DT_VERDEFNUM": 1879048189,
"DT_VERNEED": 1879048190,
"DT_VERNEEDNUM": 1879048191,
"DT_LOPROC": 1879048192,
"DT_HIPROC": 2147483647
},
"size": 4
},
"DtypeEnum64": {
"base": "long long",
"constants": {
"DT_NULL": 0,
"DT_NEEDED": 1,
"DT_PLTRELSZ": 2,
"DT_PLTGOT": 3,
"DT_HASH": 4,
"DT_STRTAB": 5,
"DT_SYMTAB": 6,
"DT_RELA": 7,
"DT_RELASZ": 8,
"DT_RELAENT": 9,
"DT_STRSZ": 10,
"DT_SYMENT": 11,
"DT_INIT": 12,
"DT_FINI": 13,
"DT_SONAME": 14,
"DT_RPATH": 15,
"DT_SYMBOLIC": 16,
"DT_REL": 17,
"DT_RELSZ": 18,
"DT_RELENT": 19,
"DT_PLTREL": 20,
"DT_DEBUG": 21,
"DT_TEXTREL": 22,
"DT_JMPREL": 23,
"DT_BIND_NOW": 24,
"DT_INIT_ARRAY": 25,
"DT_FINI_ARRAY": 26,
"DT_INIT_ARRAYSZ": 27,
"DT_FINI_ARRAYSZ": 28,
"DT_RUNPATH": 29,
"DT_FLAGS": 30,
"DT_ENCODING": 32,
"DT_PREINIT_ARRAYSZ": 33,
"DT_SYMTAB_SHNDX": 34,
"DT_RELRSZ": 35,
"DT_RELR": 36,
"DT_RELRENT": 37,
"DT_NUM": 38,
"OLD_DT_LOOS": 1610612736,
"DT_LOOS": 1610612749,
"DT_HIOS": 1879044096,
"DT_VALRNGLO": 1879047424,
"DT_VALRNGHI": 1879047679,
"DT_ADDRRNGLO": 1879047680,
"DT_GNU_HASH": 1879047925,
"DT_ADDRRNGHI": 1879047935,
"DT_VERSYM": 1879048176,
"DT_RELACOUNT": 1879048185,
"DT_RELCOUNT": 1879048186,
"DT_FLAGS_1": 1879048187,
"DT_VERDEF": 1879048188,
"DT_VERDEFNUM": 1879048189,
"DT_VERNEED": 1879048190,
"DT_VERNEEDNUM": 1879048191,
"DT_LOPROC": 1879048192,
"DT_HIPROC": 2147483647
},
"size": 8
}
},
"base_types": {
@@ -958,9 +1086,9 @@
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "ikelos-by-hand",
"datetime": "2019-10-21T22:52:00"
"version": "0.0.2",
"name": "gcmoreira-by-hand",
"datetime": "2024-02-19T14:37:00"
},
"format": "6.1.0"
}
@@ -7,14 +7,13 @@ import logging
import socket as socket_module
from typing import Generator, Iterable, Iterator, Optional, Tuple, List
from volatility3.framework import constants
from volatility3.framework import constants, exceptions, objects, interfaces, symbols
from volatility3.framework.constants.linux import SOCK_TYPES, SOCK_FAMILY
from volatility3.framework.constants.linux import IP_PROTOCOLS, IPV6_PROTOCOLS
from volatility3.framework.constants.linux import TCP_STATES, NETLINK_PROTOCOLS
from volatility3.framework.constants.linux import ETH_PROTOCOLS, BLUETOOTH_STATES
from volatility3.framework.constants.linux import BLUETOOTH_PROTOCOLS, SOCKET_STATES
from volatility3.framework.constants.linux import CAPABILITIES
from volatility3.framework import exceptions, objects, interfaces, symbols
from volatility3.framework.layers import linear
from volatility3.framework.objects import utility
from volatility3.framework.symbols import generic, linux, intermed
@@ -26,14 +25,58 @@ vollog = logging.getLogger(__name__)
class module(generic.GenericIntelProcess):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._mod_mem_type = None # Initialize _mod_mem_type to None for memoization
@property
def mod_mem_type(self):
"""Return the mod_mem_type enum choices if available or an empty dict if not"""
# mod_mem_type and module_memory were added in kernel 6.4 which replaces
# module_layout for storing the information around core_layout etc.
# see commit ac3b43283923440900b4f36ca5f9f0b1ca43b70e for more information
if self._mod_mem_type is None:
try:
self._mod_mem_type = self._context.symbol_space.get_enumeration(
self.get_symbol_table_name() + constants.BANG + "mod_mem_type"
).choices
except exceptions.SymbolError:
vollog.debug(
f"Unable to find mod_mem_type enum. This message can be ignored for kernels < 6.4"
)
# set to empty dict to show that the enum was not found, and so shouldn't be searched for again
self._mod_mem_type = {}
return self._mod_mem_type
def get_module_base(self):
if self.has_member("core_layout"):
if self.has_member("mem"): # kernels 6.4+
try:
return self.mem[self.mod_mem_type["MOD_TEXT"]].base
except KeyError:
raise AttributeError(
"module -> get_module_base: Unable to get module base. Cannot read base from MOD_TEXT."
)
elif self.has_member("core_layout"):
return self.core_layout.base
else:
elif self.has_member("module_core"):
return self.module_core
raise AttributeError("module -> get_module_base: Unable to get module base")
def get_init_size(self):
if self.has_member("init_layout"):
if self.has_member("mem"): # kernels 6.4+
try:
return (
self.mem[self.mod_mem_type["MOD_INIT_TEXT"]].size
+ self.mem[self.mod_mem_type["MOD_INIT_DATA"]].size
+ self.mem[self.mod_mem_type["MOD_INIT_RODATA"]].size
)
except KeyError:
raise AttributeError(
"module -> get_init_size: Unable to determine .init section size of module. Cannot read size of MOD_INIT_TEXT, MOD_INIT_DATA, and MOD_INIT_RODATA"
)
elif self.has_member("init_layout"):
return self.init_layout.size
elif self.has_member("init_size"):
return self.init_size
@@ -42,7 +85,19 @@ class module(generic.GenericIntelProcess):
)
def get_core_size(self):
if self.has_member("core_layout"):
if self.has_member("mem"): # kernels 6.4+
try:
return (
self.mem[self.mod_mem_type["MOD_TEXT"]].size
+ self.mem[self.mod_mem_type["MOD_DATA"]].size
+ self.mem[self.mod_mem_type["MOD_RODATA"]].size
+ self.mem[self.mod_mem_type["MOD_RO_AFTER_INIT"]].size
)
except KeyError:
raise AttributeError(
"module -> get_core_size: Unable to determine core size of module. Cannot read size of MOD_TEXT, MOD_DATA, MOD_RODATA, and MOD_RO_AFTER_INIT."
)
elif self.has_member("core_layout"):
return self.core_layout.size
elif self.has_member("core_size"):
return self.core_size
@@ -51,18 +106,32 @@ class module(generic.GenericIntelProcess):
)
def get_module_core(self):
if self.has_member("core_layout"):
if self.has_member("mem"): # kernels 6.4+
try:
return self.mem[self.mod_mem_type["MOD_TEXT"]].base
except KeyError:
raise AttributeError(
"module -> get_module_core: Unable to get module core. Cannot read base from MOD_TEXT."
)
elif self.has_member("core_layout"):
return self.core_layout.base
elif self.has_member("module_core"):
return self.module_core
raise AttributeError("module -> get_module_core: Unable to get module core")
def get_module_init(self):
if self.has_member("init_layout"):
if self.has_member("mem"): # kernels 6.4+
try:
return self.mem[self.mod_mem_type["MOD_INIT_TEXT"]].base
except KeyError:
raise AttributeError(
"module -> get_module_core: Unable to get module init. Cannot read base from MOD_INIT_TEXT."
)
elif self.has_member("init_layout"):
return self.init_layout.base
elif self.has_member("module_init"):
return self.module_init
raise AttributeError("module -> get_module_core: Unable to get module init")
raise AttributeError("module -> get_module_init: Unable to get module init")
def get_name(self):
"""Get the name of the module as a string"""
@@ -362,7 +431,7 @@ class maple_tree(objects.StructType):
# None. If however you wanted to parse from a node, but ignore some parts of the tree below it then
# this could be populated with the addresses of the nodes you wish to ignore.
if seen == None:
if seen is None:
seen = set()
# protect against unlikely loop
@@ -447,12 +516,25 @@ 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]:
"""Returns an iterator for the mmap list member of an mm_struct."""
"""
Deprecated: Use either get_vma_iter() or _get_mmap_iter().
"""
vollog.warning(
"This method has been deprecated in favour of using the get_vma_iter() method."
)
yield from self.get_vma_iter()
def _get_mmap_iter(self) -> Iterable[interfaces.objects.ObjectInterface]:
"""Returns an iterator for the mmap list member of an mm_struct. Use this only if
required, get_vma_iter() will choose the correct _get_maple_tree_iter() or
_get_mmap_iter() automatically as required."""
if not self.has_member("mmap"):
raise AttributeError(
"get_mmap_iter called on mm_struct where no mmap member exists."
"_get_mmap_iter called on mm_struct where no mmap member exists."
)
if not self.mmap:
return None
@@ -466,12 +548,24 @@ class mm_struct(objects.StructType):
seen.add(link.vol.offset)
link = link.vm_next
# TODO: As of version 3.0.0 this method should be removed
def get_maple_tree_iter(self) -> Iterable[interfaces.objects.ObjectInterface]:
"""Returns an iterator for the mm_mt member of an mm_struct."""
"""
Deprecated: Use either get_vma_iter() or _get_maple_tree_iter().
"""
vollog.warning(
"This method has been deprecated in favour of using the get_vma_iter() method."
)
yield from self.get_vma_iter()
def _get_maple_tree_iter(self) -> Iterable[interfaces.objects.ObjectInterface]:
"""Returns an iterator for the mm_mt member of an mm_struct. Use this only if
required, get_vma_iter() will choose the correct _get_maple_tree_iter() or
get_mmap_iter() automatically as required."""
if not self.has_member("mm_mt"):
raise AttributeError(
"get_maple_tree_iter called on mm_struct where no mm_mt member exists."
"_get_maple_tree_iter called on mm_struct where no mm_mt member exists."
)
symbol_table_name = self.get_symbol_table_name()
for vma_pointer in self.mm_mt.get_slot_iter():
@@ -487,9 +581,9 @@ class mm_struct(objects.StructType):
"""Returns an iterator for the VMAs in an mm_struct. Automatically choosing the mmap or mm_mt as required."""
if self.has_member("mmap"):
yield from self.get_mmap_iter()
yield from self._get_mmap_iter()
elif self.has_member("mm_mt"):
yield from self.get_maple_tree_iter()
yield from self._get_maple_tree_iter()
else:
raise AttributeError("Unable to find mmap or mm_mt in mm_struct")
@@ -612,7 +706,8 @@ class vm_area_struct(objects.StructType):
def get_page_offset(self) -> int:
if self.vm_file == 0:
return 0
return self.vm_pgoff << constants.linux.PAGE_SHIFT
parent_layer = self._context.layers[self.vol.layer_name]
return self.vm_pgoff << parent_layer.page_shift
def get_name(self, context, task):
if self.vm_file != 0:
@@ -641,7 +736,7 @@ class vm_area_struct(objects.StructType):
elif flags_str == "r-x" and self.vm_file.dereference().vol.offset == 0:
ret = True
elif proclayer and "x" in flags_str:
for i in range(self.vm_start, self.vm_end, 1 << constants.linux.PAGE_SHIFT):
for i in range(self.vm_start, self.vm_end, proclayer.page_size):
try:
if proclayer.is_dirty(i):
vollog.warning(
@@ -1042,17 +1137,17 @@ class vfsmount(objects.StructType):
"""Helper to make sure it is comparing two pointers to 'vfsmount'.
Depending on the kernel version, the calling object (self) could be
a 'vfsmount \*' (<3.3.8) or a 'vfsmount' (>=3.3.8). This way we trust
a 'vfsmount \\*' (<3.3.8) or a 'vfsmount' (>=3.3.8). This way we trust
in the framework "auto" dereferencing ability to assure that when we
reach this point 'self' will be a 'vfsmount' already and self.vol.offset
a 'vfsmount \*' and not a 'vfsmount \*\*'. The argument must be a 'vfsmount \*'.
a 'vfsmount \\*' and not a 'vfsmount \\*\\*'. The argument must be a 'vfsmount \\*'.
Typically, it's called from do_get_path().
Args:
vfsmount_ptr (vfsmount \*): A pointer to a 'vfsmount'
vfsmount_ptr (vfsmount *): A pointer to a 'vfsmount'
Raises:
exceptions.VolatilityException: If vfsmount_ptr is not a 'vfsmount \*'
exceptions.VolatilityException: If vfsmount_ptr is not a 'vfsmount \\*'
Returns:
bool: 'True' if the given argument points to the the same 'vfsmount'
@@ -6,6 +6,10 @@ from typing import Dict, Tuple
import logging
from volatility3.framework import constants
from volatility3.framework.constants.linux import (
ELF_IDENT,
ELF_CLASS,
)
from volatility3.framework import objects, interfaces, exceptions
vollog = logging.getLogger(__name__)
@@ -59,13 +63,15 @@ class elf(objects.StructType):
ei_class = self._context.object(
symbol_table_name + constants.BANG + "unsigned char",
layer_name=layer_name,
offset=object_info.offset + 0x4,
offset=object_info.offset + ELF_IDENT.EI_CLASS,
)
if ei_class == 1:
if ei_class == ELF_CLASS.ELFCLASS32:
self._type_prefix = "Elf32_"
elif ei_class == 2:
self._ei_class_size = 32
elif ei_class == ELF_CLASS.ELFCLASS64:
self._type_prefix = "Elf64_"
self._ei_class_size = 64
else:
raise ValueError(f"Unsupported ei_class value {ei_class}")
@@ -140,36 +146,137 @@ class elf(objects.StructType):
)
return section_headers
def get_link_maps(self, kernel_symbol_table_name):
"""Get the ELF link map objects for the given VMA address
Args:
kernel_symbol_table_name (str): Kernel symbol table name
Yields:
The ELF link map objects
"""
got_entry_size = self._ei_class_size // 8
elf_symbol_table = self.get_symbol_table_name()
link_maps_seen = set()
for phdr in self.get_program_headers():
try:
if phdr.p_type.description != "PT_DYNAMIC":
continue
except ValueError:
vollog.log(
constants.LOGLEVEL_VVVV,
f"Skipping unknown ELF program header type: {phdr.p_type}",
)
continue
for dsec in phdr.dynamic_sections():
try:
if dsec.d_tag.description != "DT_PLTGOT":
continue
except ValueError:
vollog.log(
constants.LOGLEVEL_VVVV,
f"Skipping unknown ELF dynamic section type: {dsec.d_tag}",
)
continue
got_start = dsec.d_ptr
# link_map is stored at the second GOT entry
link_map_addr = got_start + got_entry_size
# It needs the kernel symbol table to create a pointer
link_map_ptr = self._context.object(
kernel_symbol_table_name + constants.BANG + "pointer",
offset=link_map_addr,
layer_name=self.vol.layer_name,
)
if not link_map_ptr:
vollog.log(
constants.LOGLEVEL_VVVV,
f"Invalid ELF link map pointer at 0x{link_map_addr:x}",
)
continue
linkmap_symname = (
elf_symbol_table + constants.BANG + self._type_prefix + "LinkMap"
)
try:
link_map = self._context.object(
object_type=linkmap_symname,
offset=link_map_ptr,
layer_name=self.vol.layer_name,
)
except exceptions.InvalidAddressException:
vollog.log(
constants.LOGLEVEL_VVVV,
f"Invalid ELF link map address at 0x{link_map_ptr:x}",
)
continue
while link_map and link_map.vol.offset != 0:
if link_map.vol.offset in link_maps_seen:
break
link_maps_seen.add(link_map.vol.offset)
yield link_map
try:
link_map = self._context.object(
object_type=linkmap_symname,
offset=link_map.l_next,
layer_name=self.vol.layer_name,
)
except exceptions.InvalidAddressException:
vollog.log(
constants.LOGLEVEL_VVVV,
f"ELF link map linked list is corrupt at 0x{self.vol.offset:x}",
)
break
def _find_symbols(self):
dt_strtab = None
dt_symtab = None
dt_strent = None
for phdr in self.get_program_headers():
# Find PT_DYNAMIC segment
try:
# Find PT_DYNAMIC segment
if str(phdr.p_type.description) != "PT_DYNAMIC":
if phdr.p_type.description != "PT_DYNAMIC":
continue
except ValueError:
# If the p_type value is outside the ones declared in the enumeration, an
# exception is raised
return None
vollog.log(
constants.LOGLEVEL_VVVV,
f"Skipping unknown ELF program header type: {phdr.p_type}",
)
continue
# This section contains pointers to the strtab, symtab, and strent sections
for dsec in phdr.dynamic_sections():
if dsec.d_tag == 5:
try:
dtag = dsec.d_tag.description
except ValueError:
vollog.log(
constants.LOGLEVEL_VVVV,
f"Skipping unknown ELF dynamic section type: {dsec.d_tag}",
)
continue
if dtag == "DT_STRTAB":
dt_strtab = dsec.d_ptr
elif dsec.d_tag == 6:
elif dtag == "DT_SYMTAB":
dt_symtab = dsec.d_ptr
elif dsec.d_tag == 11:
elif dtag == "DT_SYMENT":
# Size of the symtab symbol entry
dt_strent = dsec.d_ptr
break
if dt_strtab is None or dt_symtab is None or dt_strent is None:
if not (dt_strtab and dt_symtab and dt_strent):
return None
self._cached_symtab = dt_symtab
@@ -274,19 +381,31 @@ class elf_phdr(objects.StructType):
def get_vaddr(self):
offset = self.__getattr__("p_vaddr")
if self._parent_e_type == 3: # ET_DYN
offset = self._parent_offset + offset
try:
if self._parent_e_type.description == "ET_DYN":
offset = self._parent_offset + offset
except ValueError:
# Unknown ELF object file type. Anyway, if the ELF object file type is not a
# shared object (ET_DYN), the virtual address is 'p_vaddr'.
vollog.log(
constants.LOGLEVEL_VVVV,
f"Skipping unknown ELF object type: {self._parent_e_type}",
)
return offset
def dynamic_sections(self):
# sanity check
try:
if str(self.p_type.description) != "PT_DYNAMIC":
if self.p_type.description != "PT_DYNAMIC":
return None
except ValueError:
# If the value is outside the ones declared in the enumeration, an
# exception is raised
vollog.log(
constants.LOGLEVEL_VVVV,
f"Skipping unknown ELF program header type: {self.p_type}",
)
return None
# the buffer of array starts at elf_base + our virtual address ( offset )
@@ -314,10 +433,30 @@ class elf_phdr(objects.StructType):
break
class elf_linkmap(objects.StructType):
def get_name(self):
try:
buf = self._context.layers.read(self.vol.layer_name, self.l_name, 256)
except exceptions.PagedInvalidAddressException:
# Protection against memory smear
vollog.log(
constants.LOGLEVEL_VVVV,
f"Invalid l_name address for ELF link map at 0x{self.vol.offset:x}",
)
return None
idx = buf.find(b"\x00")
if idx != -1:
buf = buf[:idx]
return buf.decode()
class_types = {
"Elf": elf,
"Elf64_Phdr": elf_phdr,
"Elf32_Phdr": elf_phdr,
"Elf32_Sym": elf_sym,
"Elf64_Sym": elf_sym,
"Elf32_LinkMap": elf_linkmap,
"Elf64_LinkMap": elf_linkmap,
}
@@ -21,12 +21,14 @@ class MacKernelIntermedSymbols(intermed.IntermediateSymbolTable):
self.set_type_class("vm_map_object", extensions.vm_map_object)
self.set_type_class("socket", extensions.socket)
self.set_type_class("inpcb", extensions.inpcb)
self.set_type_class("queue_entry", extensions.queue_entry)
self.set_type_class("ifnet", extensions.ifnet)
self.set_type_class("sockaddr_dl", extensions.sockaddr_dl)
self.set_type_class("sockaddr", extensions.sockaddr)
self.set_type_class("sysctl_oid", extensions.sysctl_oid)
self.set_type_class("kauth_scope", extensions.kauth_scope)
# https://developer.apple.com/documentation/kernel/queue_head_t
self.set_type_class("queue_entry", extensions.queue_entry)
self.optional_set_type_class("queue_head_t", extensions.queue_entry)
class MacUtilities(interfaces.configuration.VersionableInterface):
@@ -490,22 +490,24 @@ class queue_entry(objects.StructType):
for attr in ["next", "prev"]:
with contextlib.suppress(exceptions.InvalidAddressException):
n = getattr(self, attr).dereference().cast(type_name)
while n is not None and n.vol.offset != list_head:
if n.vol.offset in seen:
queue_element = getattr(self, attr).dereference().cast(type_name)
while (
queue_element is not None
and queue_element.vol.offset != list_head.vol.offset
):
if queue_element.vol.offset in seen:
break
yield n
yield queue_element
seen.add(n.vol.offset)
seen.add(queue_element.vol.offset)
yielded = yielded + 1
if yielded == max_size:
return None
n = (
getattr(n.member(attr=member_name), attr)
queue_element = (
getattr(queue_element.member(attr=member_name), attr)
.dereference()
.cast(type_name)
)
+41 -1
View File
@@ -2,9 +2,49 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import datetime
import logging
from typing import Optional, Tuple, Union
from volatility3.framework import interfaces
from volatility3.framework import constants, interfaces
vollog = logging.getLogger(__name__)
class ProducerMetadata(interfaces.symbols.MetadataInterface):
"""Class to handle the Producer metadata from an ISF"""
@property
def name(self) -> Optional[str]:
return self._json_data.get("name", None)
@property
def version(self) -> Optional[Tuple[int]]:
"""Returns the version of the ISF file producer"""
version = self._json_data.get("version", None)
if not version:
return None
if all([x in "0123456789." for x in version]):
return tuple([int(x) for x in version.split(".")])
vollog.log(
constants.LOGLEVEL_VVVV,
f"Metadata version contains unexpected characters: '{version}'",
)
return None
@property
def datetime(self) -> Optional[datetime.datetime]:
"""Returns a timestamp for when the file was produced"""
if "datetime" not in self._json_data:
return None
try:
timestamp = datetime.datetime.strptime(
self._json_data["datetime"], "YYYY-MM-DD"
)
except (TypeError, ValueError):
vollog.debug("Invalid timestamp in producer information of symbol table")
return None
return timestamp
class WindowsMetadata(interfaces.symbols.MetadataInterface):
@@ -492,9 +492,47 @@ class KMUTANT(objects.StructType, pool.ExecutiveObject):
return header.NameInfo.Name.String # type: ignore
class ETHREAD(objects.StructType):
class ETHREAD(objects.StructType, pool.ExecutiveObject):
"""A class for executive thread objects."""
def is_valid(self) -> bool:
"""Determine if the object is valid."""
try:
# validation by TID:
if self.Cid.UniqueThread % 4 != 0: # NT tids are divisible by 4
return False
# validation by PID of parent process:
if self.Cid.UniqueProcess % 4 != 0:
return False
# validation by thread creation time:
if (
self.Cid.UniqueProcess != 4
): # The System process (PID 4) has no create time
ctime = self.get_create_time()
if not isinstance(ctime, datetime.datetime):
return False
if not (1998 < ctime.year < 2030):
return False
except exceptions.InvalidAddressException:
return False
# passed all validations
return True
def get_create_time(self):
# For Windows XPs
if self.has_member("ThreadsProcess"):
return conversion.wintime_to_datetime(self.CreateTime.QuadPart >> 3)
return conversion.wintime_to_datetime(self.CreateTime.QuadPart)
def get_exit_time(self):
return conversion.wintime_to_datetime(self.ExitTime.QuadPart)
def owning_process(self) -> interfaces.objects.ObjectInterface:
"""Return the EPROCESS that owns this thread."""
@@ -0,0 +1,248 @@
{
"symbols": {},
"enums": {
"StateEnum": {
"base": "long",
"constants": {
"SERVICE_START_PENDING": 2,
"SERVICE_STOP_PENDING": 3,
"SERVICE_STOPPED": 1,
"SERVICE_CONTINUE_PENDING": 5,
"SERVICE_PAUSE_PENDING": 6,
"SERVICE_PAUSED": 7,
"SERVICE_RUNNING": 4
},
"size": 4
},
"StartEnum": {
"base": "long",
"constants": {
"SERVICE_DEMAND_START": 3,
"SERVICE_AUTO_START": 2,
"SERVICE_BOOT_START": 0,
"SERVICE_DISABLED": 4,
"SERVICE_SYSTEM_START": 1
},
"size": 4
}
},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned char": {
"kind": "char",
"size": 1,
"signed": false,
"endian": "little"
},
"pointer": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned int": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned short": {
"kind": "int",
"size": 2,
"signed": false,
"endian": "little"
},
"long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
}
},
"user_types": {
"_SERVICE_LIST_ENTRY": {
"fields": {
"Flink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_LIST_ENTRY"
}
},
"offset": 4
},
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_LIST_ENTRY"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_SERVICE_PROCESS": {
"fields": {
"BinaryPath": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 12
},
"ProcessId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 20
}
},
"kind": "struct",
"size": 20
},
"_SERVICE_HEADER": {
"fields": {
"Tag": {
"type": {
"count": 4,
"subtype": {
"kind": "base",
"name": "unsigned char"
},
"kind": "array"
},
"offset": 0
},
"ServiceRecord": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_RECORD"
}
},
"offset": 12
}
},
"kind": "struct",
"size": 12
},
"_SERVICE_RECORD": {
"fields": {
"DisplayName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 48
},
"ServiceProcess": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_PROCESS"
}
},
"offset": 160
},
"PrevEntry": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_RECORD"
}
},
"offset": 12
},
"Start": {
"type": {
"kind": "enum",
"name": "StartEnum"
},
"offset": 24
},
"State": {
"type": {
"kind": "enum",
"name": "StateEnum"
},
"offset": 56
},
"ServiceName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 44
},
"Tag": {
"type": {
"count": 4,
"subtype": {
"kind": "base",
"name": "unsigned char"
},
"kind": "array"
},
"offset": 0
},
"DriverName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 160
},
"Type": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 52
},
"Order": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 20
}
},
"kind": "struct",
"size": 156
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "vtypes_to_json.py",
"datetime": "2019-04-17T13:45:16.417006"
},
"format": "4.1.0"
}
}
@@ -0,0 +1,255 @@
{
"symbols": {},
"enums": {
"StateEnum": {
"base": "long",
"constants": {
"SERVICE_START_PENDING": 2,
"SERVICE_STOP_PENDING": 3,
"SERVICE_STOPPED": 1,
"SERVICE_CONTINUE_PENDING": 5,
"SERVICE_PAUSE_PENDING": 6,
"SERVICE_PAUSED": 7,
"SERVICE_RUNNING": 4
},
"size": 4
},
"StartEnum": {
"base": "long",
"constants": {
"SERVICE_DEMAND_START": 3,
"SERVICE_AUTO_START": 2,
"SERVICE_BOOT_START": 0,
"SERVICE_DISABLED": 4,
"SERVICE_SYSTEM_START": 1
},
"size": 4
}
},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned char": {
"kind": "char",
"size": 1,
"signed": false,
"endian": "little"
},
"pointer": {
"kind": "int",
"size": 8,
"signed": false,
"endian": "little"
},
"unsigned int": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned short": {
"kind": "int",
"size": 2,
"signed": false,
"endian": "little"
},
"long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
}
},
"user_types": {
"_SERVICE_LIST_ENTRY": {
"fields": {
"Flink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_LIST_ENTRY"
}
},
"offset": 16
},
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_LIST_ENTRY"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 16
},
"_SERVICE_PROCESS": {
"fields": {
"BinaryPath": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 24
},
"ProcessId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 40
}
},
"kind": "struct",
"size": 40
},
"_SERVICE_HEADER": {
"fields": {
"Tag": {
"type": {
"count": 4,
"subtype": {
"kind": "base",
"name": "unsigned char"
},
"kind": "array"
},
"offset": 0
},
"ServiceRecord": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_RECORD"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 16
},
"_SERVICE_RECORD": {
"fields": {
"ServiceList": {
"type": {
"kind": "struct",
"name": "_SERVICE_LIST_ENTRY"
},
"offset": 0
},
"Tag": {
"type": {
"count": 4,
"subtype": {
"kind": "base",
"name": "unsigned char"
},
"kind": "array"
},
"offset": 32
},
"DisplayName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 64
},
"ServiceProcess": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_PROCESS"
}
},
"offset": 240
},
"PrevEntry": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_RECORD"
}
},
"offset": 16
},
"Start": {
"type": {
"kind": "enum",
"name": "StartEnum"
},
"offset": 36
},
"State": {
"type": {
"kind": "enum",
"name": "StateEnum"
},
"offset": 76
},
"ServiceName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 56
},
"DriverName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 240
},
"Type": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 72
},
"Order": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 32
}
},
"kind": "struct",
"size": 248
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "vtypes_to_json.py",
"datetime": "2019-04-17T13:45:16.417006"
},
"format": "4.1.0"
}
}
@@ -0,0 +1,248 @@
{
"symbols": {},
"enums": {
"StateEnum": {
"base": "long",
"constants": {
"SERVICE_START_PENDING": 2,
"SERVICE_STOP_PENDING": 3,
"SERVICE_STOPPED": 1,
"SERVICE_CONTINUE_PENDING": 5,
"SERVICE_PAUSE_PENDING": 6,
"SERVICE_PAUSED": 7,
"SERVICE_RUNNING": 4
},
"size": 4
},
"StartEnum": {
"base": "long",
"constants": {
"SERVICE_DEMAND_START": 3,
"SERVICE_AUTO_START": 2,
"SERVICE_BOOT_START": 0,
"SERVICE_DISABLED": 4,
"SERVICE_SYSTEM_START": 1
},
"size": 4
}
},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned char": {
"kind": "char",
"size": 1,
"signed": false,
"endian": "little"
},
"pointer": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned int": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned short": {
"kind": "int",
"size": 2,
"signed": false,
"endian": "little"
},
"long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
}
},
"user_types": {
"_SERVICE_LIST_ENTRY": {
"fields": {
"Flink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_LIST_ENTRY"
}
},
"offset": 4
},
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_LIST_ENTRY"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_SERVICE_PROCESS": {
"fields": {
"BinaryPath": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 12
},
"ProcessId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 20
}
},
"kind": "struct",
"size": 20
},
"_SERVICE_HEADER": {
"fields": {
"Tag": {
"type": {
"count": 4,
"subtype": {
"kind": "base",
"name": "unsigned char"
},
"kind": "array"
},
"offset": 0
},
"ServiceRecord": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_RECORD"
}
},
"offset": 12
}
},
"kind": "struct",
"size": 12
},
"_SERVICE_RECORD": {
"fields": {
"DisplayName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 48
},
"ServiceProcess": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_PROCESS"
}
},
"offset": 164
},
"PrevEntry": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_RECORD"
}
},
"offset": 12
},
"Start": {
"type": {
"kind": "enum",
"name": "StartEnum"
},
"offset": 24
},
"State": {
"type": {
"kind": "enum",
"name": "StateEnum"
},
"offset": 56
},
"ServiceName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 44
},
"Tag": {
"type": {
"count": 4,
"subtype": {
"kind": "base",
"name": "unsigned char"
},
"kind": "array"
},
"offset": 0
},
"DriverName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 164
},
"Type": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 52
},
"Order": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 20
}
},
"kind": "struct",
"size": 156
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "vtypes_to_json.py",
"datetime": "2019-04-17T13:45:16.417006"
},
"format": "4.1.0"
}
}
@@ -0,0 +1,255 @@
{
"symbols": {},
"enums": {
"StateEnum": {
"base": "long",
"constants": {
"SERVICE_START_PENDING": 2,
"SERVICE_STOP_PENDING": 3,
"SERVICE_STOPPED": 1,
"SERVICE_CONTINUE_PENDING": 5,
"SERVICE_PAUSE_PENDING": 6,
"SERVICE_PAUSED": 7,
"SERVICE_RUNNING": 4
},
"size": 4
},
"StartEnum": {
"base": "long",
"constants": {
"SERVICE_DEMAND_START": 3,
"SERVICE_AUTO_START": 2,
"SERVICE_BOOT_START": 0,
"SERVICE_DISABLED": 4,
"SERVICE_SYSTEM_START": 1
},
"size": 4
}
},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned char": {
"kind": "char",
"size": 1,
"signed": false,
"endian": "little"
},
"pointer": {
"kind": "int",
"size": 8,
"signed": false,
"endian": "little"
},
"unsigned int": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned short": {
"kind": "int",
"size": 2,
"signed": false,
"endian": "little"
},
"long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
}
},
"user_types": {
"_SERVICE_LIST_ENTRY": {
"fields": {
"Flink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_LIST_ENTRY"
}
},
"offset": 16
},
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_LIST_ENTRY"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 16
},
"_SERVICE_PROCESS": {
"fields": {
"BinaryPath": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 24
},
"ProcessId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 40
}
},
"kind": "struct",
"size": 40
},
"_SERVICE_HEADER": {
"fields": {
"Tag": {
"type": {
"count": 4,
"subtype": {
"kind": "base",
"name": "unsigned char"
},
"kind": "array"
},
"offset": 0
},
"ServiceRecord": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_RECORD"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 16
},
"_SERVICE_RECORD": {
"fields": {
"ServiceList": {
"type": {
"kind": "struct",
"name": "_SERVICE_LIST_ENTRY"
},
"offset": 0
},
"Tag": {
"type": {
"count": 4,
"subtype": {
"kind": "base",
"name": "unsigned char"
},
"kind": "array"
},
"offset": 32
},
"DisplayName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 64
},
"ServiceProcess": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_PROCESS"
}
},
"offset": 296
},
"PrevEntry": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_RECORD"
}
},
"offset": 16
},
"Start": {
"type": {
"kind": "enum",
"name": "StartEnum"
},
"offset": 36
},
"State": {
"type": {
"kind": "enum",
"name": "StateEnum"
},
"offset": 76
},
"ServiceName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 56
},
"DriverName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 296
},
"Type": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 72
},
"Order": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 32
}
},
"kind": "struct",
"size": 296
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "David McDonald",
"datetime": "2023-11-16T15:05:35-06:00"
},
"format": "4.1.0"
}
}
@@ -0,0 +1,248 @@
{
"symbols": {},
"enums": {
"StateEnum": {
"base": "long",
"constants": {
"SERVICE_START_PENDING": 2,
"SERVICE_STOP_PENDING": 3,
"SERVICE_STOPPED": 1,
"SERVICE_CONTINUE_PENDING": 5,
"SERVICE_PAUSE_PENDING": 6,
"SERVICE_PAUSED": 7,
"SERVICE_RUNNING": 4
},
"size": 4
},
"StartEnum": {
"base": "long",
"constants": {
"SERVICE_DEMAND_START": 3,
"SERVICE_AUTO_START": 2,
"SERVICE_BOOT_START": 0,
"SERVICE_DISABLED": 4,
"SERVICE_SYSTEM_START": 1
},
"size": 4
}
},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned char": {
"kind": "char",
"size": 1,
"signed": false,
"endian": "little"
},
"pointer": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned int": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned short": {
"kind": "int",
"size": 2,
"signed": false,
"endian": "little"
},
"long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
}
},
"user_types": {
"_SERVICE_LIST_ENTRY": {
"fields": {
"Flink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_LIST_ENTRY"
}
},
"offset": 4
},
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_LIST_ENTRY"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 8
},
"_SERVICE_PROCESS": {
"fields": {
"BinaryPath": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 12
},
"ProcessId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 20
}
},
"kind": "struct",
"size": 20
},
"_SERVICE_HEADER": {
"fields": {
"Tag": {
"type": {
"count": 4,
"subtype": {
"kind": "base",
"name": "unsigned char"
},
"kind": "array"
},
"offset": 0
},
"ServiceRecord": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_RECORD"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 12
},
"_SERVICE_RECORD": {
"fields": {
"DisplayName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 48
},
"ServiceProcess": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_PROCESS"
}
},
"offset": 192
},
"PrevEntry": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_RECORD"
}
},
"offset": 12
},
"Start": {
"type": {
"kind": "enum",
"name": "StartEnum"
},
"offset": 24
},
"State": {
"type": {
"kind": "enum",
"name": "StateEnum"
},
"offset": 56
},
"ServiceName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 44
},
"Tag": {
"type": {
"count": 4,
"subtype": {
"kind": "base",
"name": "unsigned char"
},
"kind": "array"
},
"offset": 0
},
"DriverName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 192
},
"Type": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 52
},
"Order": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 20
}
},
"kind": "struct",
"size": 192
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "vtypes_to_json.py",
"datetime": "2019-04-17T13:45:16.417006"
},
"format": "4.1.0"
}
}
@@ -0,0 +1,255 @@
{
"symbols": {},
"enums": {
"StateEnum": {
"base": "long",
"constants": {
"SERVICE_START_PENDING": 2,
"SERVICE_STOP_PENDING": 3,
"SERVICE_STOPPED": 1,
"SERVICE_CONTINUE_PENDING": 5,
"SERVICE_PAUSE_PENDING": 6,
"SERVICE_PAUSED": 7,
"SERVICE_RUNNING": 4
},
"size": 4
},
"StartEnum": {
"base": "long",
"constants": {
"SERVICE_DEMAND_START": 3,
"SERVICE_AUTO_START": 2,
"SERVICE_BOOT_START": 0,
"SERVICE_DISABLED": 4,
"SERVICE_SYSTEM_START": 1
},
"size": 4
}
},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned char": {
"kind": "char",
"size": 1,
"signed": false,
"endian": "little"
},
"pointer": {
"kind": "int",
"size": 8,
"signed": false,
"endian": "little"
},
"unsigned int": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned short": {
"kind": "int",
"size": 2,
"signed": false,
"endian": "little"
},
"long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
}
},
"user_types": {
"_SERVICE_LIST_ENTRY": {
"fields": {
"Flink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_LIST_ENTRY"
}
},
"offset": 16
},
"Blink": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_LIST_ENTRY"
}
},
"offset": 0
}
},
"kind": "struct",
"size": 16
},
"_SERVICE_PROCESS": {
"fields": {
"BinaryPath": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 24
},
"ProcessId": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 40
}
},
"kind": "struct",
"size": 40
},
"_SERVICE_HEADER": {
"fields": {
"Tag": {
"type": {
"count": 4,
"subtype": {
"kind": "base",
"name": "unsigned char"
},
"kind": "array"
},
"offset": 0
},
"ServiceRecord": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_RECORD"
}
},
"offset": 16
}
},
"kind": "struct",
"size": 16
},
"_SERVICE_RECORD": {
"fields": {
"ServiceList": {
"type": {
"kind": "struct",
"name": "_SERVICE_LIST_ENTRY"
},
"offset": 0
},
"Tag": {
"type": {
"count": 4,
"subtype": {
"kind": "base",
"name": "unsigned char"
},
"kind": "array"
},
"offset": 32
},
"DisplayName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 64
},
"ServiceProcess": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_PROCESS"
}
},
"offset": 336
},
"PrevEntry": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_SERVICE_RECORD"
}
},
"offset": 16
},
"Start": {
"type": {
"kind": "enum",
"name": "StartEnum"
},
"offset": 36
},
"State": {
"type": {
"kind": "enum",
"name": "StateEnum"
},
"offset": 84
},
"ServiceName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 56
},
"DriverName": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "unsigned short"
}
},
"offset": 296
},
"Type": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 80
},
"Order": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 32
}
},
"kind": "struct",
"size": 336
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "David McDonald",
"datetime": "2023-11-16T15:05:35-06:00"
},
"format": "4.1.0"
}
}
@@ -151,11 +151,45 @@ is_win10_16299_or_later = OsDistinguisher(
],
)
is_win10_17763_or_later = OsDistinguisher(
version_check=lambda x: x >= (10, 0, 17763),
fallback_checks=[
("_EPROCESS", "TrustletIdentity", False),
("ParentSecurityDomain", None, True),
],
)
is_win10_18362_or_later = OsDistinguisher(
version_check=lambda x: x >= (10, 0, 18362),
fallback_checks=[
("ObHeaderCookie", None, True),
("_CM_CACHED_VALUE_INDEX", None, False),
("_WNF_PROCESS_CONTEXT", None, True),
],
)
is_win10_18363_or_later = OsDistinguisher(
version_check=lambda x: x >= (10, 0, 18363),
fallback_checks=[("_KQOS_GROUPING_SETS", None, True)],
)
is_win10_19041_or_later = OsDistinguisher(
version_check=lambda x: x >= (10, 0, 19041),
fallback_checks=[
("_EPROCESS", "TimerResolutionIgnore", True),
("_EPROCESS", "VmProcessorHostTransition", True),
("_KQOS_GROUPING_SETS", None, True),
],
)
is_win10_25398_or_later = OsDistinguisher(
version_check=lambda x: x >= (10, 0, 25398),
fallback_checks=[
("_EPROCESS", "MmSlabIdentity", True),
("_EPROCESS", "EnableProcessImpersonationLogging", True),
],
)
is_windows_10 = OsDistinguisher(
version_check=lambda x: x >= (10, 0),
fallback_checks=[("ObHeaderCookie", None, True)],
+507
View File
@@ -0,0 +1,507 @@
{
"$schema": "http://json-schema.org/schema#",
"id": "http://volatilityfoundation.org/intermediate-format/schema",
"title": "Symbol Container",
"type": "object",
"definitions": {
"metadata_producer": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"version": {
"type": "string",
"pattern": "^[0-9]+.[0-9]+.[0-9]+$"
},
"datetime": {
"type": "string",
"format": "date-time"
}
},
"required":[
"name",
"version"
]
},
"metadata_windows_pe": {
"type": "object",
"properties": {
"major": {
"type": "integer"
},
"minor": {
"type": "integer"
},
"revision": {
"type": "integer"
},
"build": {
"type": "integer"
}
},
"additionalProperties": false,
"required": [
"major",
"minor",
"revision"
]
},
"metadata_windows_pdb": {
"type": "object",
"properties": {
"GUID": {
"type": "string"
},
"age": {
"type": "integer"
},
"database": {
"type": "string"
},
"machine_type": {
"type": "integer"
}
},
"additionalProperties": false,
"required": [
"GUID",
"age",
"database",
"machine_type"
]
},
"metadata_windows": {
"type": "object",
"properties": {
"pe": {
"$ref": "#/definitions/metadata_windows_pe"
},
"pdb": {
"$ref": "#/definitions/metadata_windows_pdb"
}
},
"additionalProperties": false
},
"metadata_nix": {
"type": "object",
"properties": {
"symbols": {
"type": "array",
"items": {
"$ref": "#/definitions/metadata_nix_item"
}
},
"types": {
"type": "array",
"items": {
"$ref": "#/definitions/metadata_nix_item"
}
}
},
"additionalProperties": false
},
"metadata_format": {
"type": "string",
"pattern": "^6.[0-9]+.[0-9]+$"
},
"metadata_nix_item": {
"type": "object",
"properties": {
"kind": {
"type": "string",
"pattern": "^(dwarf|symtab|system-map)$"
},
"name": {
"type": "string"
},
"hash_type": {
"type": "string",
"pattern": "^(sha256)$"
},
"hash_value": {
"type": "string",
"pattern": "^[a-fA-F0-9]+$"
}
},
"additionalProperties": false
},
"element_metadata": {
"type": "object",
"oneOf": [
{
"properties": {
"format": {
"$ref": "#/definitions/metadata_format"
},
"producer": {
"$ref": "#/definitions/metadata_producer"
}
},
"required": [
"format",
"producer"
],
"additionalProperties": false
},
{
"properties": {
"format": {
"$ref": "#/definitions/metadata_format"
},
"producer": {
"$ref": "#/definitions/metadata_producer"
},
"windows": {
"$ref": "#/definitions/metadata_windows"
}
},
"required": [
"format",
"producer",
"windows"
],
"additionalProperties": false
},
{
"properties": {
"format": {
"$ref": "#/definitions/metadata_format"
},
"producer": {
"$ref": "#/definitions/metadata_producer"
},
"linux": {
"$ref": "#/definitions/metadata_nix"
}
},
"required": [
"format",
"producer",
"linux"
],
"additionalProperties": false
},
{
"properties": {
"format": {
"$ref": "#/definitions/metadata_format"
},
"producer": {
"$ref": "#/definitions/metadata_producer"
},
"mac": {
"$ref": "#/definitions/metadata_nix"
}
},
"required": [
"format",
"producer",
"mac"
],
"additionalProperties": false
}
]
},
"element_enum": {
"properties": {
"size": {
"type": "integer"
},
"base": {
"type": "string"
},
"constants": {
"type": "object",
"additionalProperties": {
"type": "integer"
}
}
},
"required": [
"size",
"base",
"constants"
],
"additionalProperties": false
},
"element_symbol": {
"properties": {
"address": {
"type": "number"
},
"linkage_name": {
"type": "string"
},
"type": {
"$ref": "#/definitions/type_descriptor"
},
"constant_data": {
"type": "string",
"media": {
"binaryEncoding": "base64",
"readOnly": true
}
}
},
"required": [
"address"
],
"additionalProperties": false
},
"element_base_type": {
"properties": {
"size": {
"type": "integer"
},
"signed": {
"type": "boolean"
},
"kind": {
"type": "string",
"pattern": "^(void|int|float|char|bool)$"
},
"endian": {
"type": "string",
"pattern": "^(little|big)$"
}
},
"required": [
"size",
"kind",
"signed",
"endian"
],
"additionalProperties": false
},
"element_user_type": {
"properties": {
"kind": {
"type": "string",
"pattern": "^(struct|union|class)$"
},
"size": {
"type": "integer"
},
"fields": {
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/field"
}
}
},
"required": [
"kind",
"size",
"fields"
],
"additionalProperties": false
},
"field": {
"properties": {
"type": {
"$ref": "#/definitions/type_descriptor"
},
"offset": {
"type": "integer"
},
"anonymous": {
"type": "boolean"
}
},
"required": [
"type",
"offset"
],
"additionalProperties": false
},
"type_descriptor": {
"oneOf": [
{
"$ref": "#/definitions/type_pointer"
},
{
"$ref": "#/definitions/type_base"
},
{
"$ref": "#/definitions/type_array"
},
{
"$ref": "#/definitions/type_struct"
},
{
"$ref": "#/definitions/type_enum"
},
{
"$ref": "#/definitions/type_function"
},
{
"$ref": "#/definitions/type_bitfield"
}
]
},
"type_pointer": {
"properties": {
"kind": {
"type": "string",
"pattern": "^pointer$"
},
"base": {
"type": "string"
},
"subtype": {
"$ref": "#/definitions/type_descriptor"
}
},
"required": [
"kind",
"subtype"
],
"additionalProperties": false
},
"type_base": {
"properties": {
"kind": {
"type": "string",
"pattern": "^base$"
},
"name": {
"type": "string"
}
},
"required": [
"kind",
"name"
],
"additionalProperties": false
},
"type_array": {
"properties": {
"kind": {
"type": "string",
"pattern": "^array$"
},
"subtype": {
"$ref": "#/definitions/type_descriptor"
},
"count": {
"type": "integer"
}
},
"required": [
"kind",
"subtype",
"count"
],
"additionalProperties": false
},
"type_struct": {
"properties": {
"kind": {
"type": "string",
"pattern": "^(struct|class|union)$"
},
"name": {
"type": "string"
}
},
"required": [
"kind",
"name"
],
"additionalProperties": false
},
"type_enum": {
"properties": {
"kind": {
"type": "string",
"pattern": "^enum$"
},
"name": {
"type": "string"
}
},
"required": [
"kind",
"name"
],
"additionalProperties": false
},
"type_function": {
"properties": {
"kind": {
"type": "string",
"pattern": "^function$"
}
},
"required": [
"kind"
],
"additionalProperties": false
},
"type_bitfield": {
"properties": {
"kind": {
"type": "string",
"pattern": "^bitfield$"
},
"bit_position": {
"type": "integer"
},
"bit_length": {
"type": "integer"
},
"type": {
"oneOf": [
{
"$ref": "#/definitions/type_base"
},
{
"$ref": "#/definitions/type_enum"
}
]
}
},
"required": [
"kind",
"bit_position",
"bit_length",
"type"
],
"additionalProperties": false
}
},
"properties": {
"metadata": {
"$ref": "#/definitions/element_metadata"
},
"base_types": {
"additionalProperties": {
"$ref": "#/definitions/element_base_type"
}
},
"user_types": {
"additionalProperties": {
"$ref": "#/definitions/element_user_type"
}
},
"enums": {
"additionalProperties": {
"$ref": "#/definitions/element_enum"
}
},
"symbols": {
"additionalProperties": {
"$ref": "#/definitions/element_symbol"
}
}
},
"required": [
"metadata",
"base_types",
"user_types",
"enums",
"symbols"
],
"additionalProperties": false
}