mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-10 03:37:39 +02:00
Merge pull request #1156 from volatilityfoundation/release/v2.7.0
Release/v2.7.0
This commit is contained in:
@@ -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
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
@@ -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(
|
||||
|
||||
@@ -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})"
|
||||
@@ -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]
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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__)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
):
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
@@ -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(
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
@@ -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)],
|
||||
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user