Merge branch 'develop' into issues/issue1195

This commit is contained in:
ikelos
2024-09-10 21:40:08 +01:00
committed by GitHub
87 changed files with 11040 additions and 596 deletions
+3 -4
View File
@@ -18,7 +18,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: ["3.7"]
python-version: ["3.8"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
@@ -29,12 +29,11 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel
pip install build
- name: Build PyPi packages
run: |
python setup.py sdist --formats=gztar,zip
python setup.py bdist_wheel
python -m build
- name: Archive dist
uses: actions/upload-artifact@v4
+1 -1
View File
@@ -8,7 +8,7 @@ jobs:
fail-fast: false
matrix:
host: [ ubuntu-latest, windows-latest ]
python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11" ]
python-version: [ "3.8", "3.9", "3.10", "3.11" ]
steps:
- uses: actions/checkout@v4
+4 -5
View File
@@ -6,7 +6,7 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: ["3.7"]
python-version: ["3.8"]
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
@@ -18,13 +18,12 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install Cmake
pip install setuptools wheel
pip install build
pip install -r ./test/requirements-testing.txt
- name: Build PyPi packages
run: |
python setup.py sdist --formats=gztar,zip
python setup.py bdist_wheel
python -m build
- name: Download images
run: |
@@ -47,7 +46,7 @@ jobs:
- name: Clean up post-test
run: |
rm -rf *.lime
rm -rf *.bin
rm -rf *.img
cd volatility3/symbols
rm -rf linux
+1
View File
@@ -27,6 +27,7 @@ config*.json
# Pyinstaller files
build
dist
*.egg-info
# Environments
.env
+3 -4
View File
@@ -20,17 +20,16 @@ more details.
## Requirements
Volatility 3 requires Python 3.7.0 or later. To install the most minimal set of dependencies (some plugins will not work) use a command such as:
Volatility 3 requires Python 3.8.0 or later. To install the most minimal set of dependencies (some plugins will not work) use a command such as:
```shell
pip3 install -r requirements-minimal.txt
```
Alternately, the minimal packages will be installed automatically when Volatility 3 is installed using setup.py. However, as noted in the Quick Start section below, Volatility 3 does not *need* to be installed via setup.py prior to using it.
Alternately, the minimal packages will be installed automatically when Volatility 3 is installed using pip. However, as noted in the Quick Start section below, Volatility 3 does not *need* to be installed prior to using it.
```shell
python3 setup.py build
python3 setup.py install
pip3 install .
```
To enable the full range of Volatility 3 functionality, use a command like the one below. For partial functionality, comment out any unnecessary packages in [requirements.txt](requirements.txt) prior to running the command.
+1
View File
@@ -4,5 +4,6 @@ sphinx_autodoc_typehints>=1.4.0
sphinx-rtd-theme>=0.4.3
yara-python
yara-x
pycryptodome
pefile
+32
View File
@@ -0,0 +1,32 @@
[project]
name = "volatility3"
description = "Memory forensics framework"
keywords = ["volatility", "memory", "forensics", "framework", "windows", "linux", "volshell"]
readme = "README.md"
authors = [
{ name = "Volatility Foundation", email = "volatility@volatilityfoundation.org" },
]
requires-python = ">=3.8.0"
license = { text = "VSL" }
dynamic = ["dependencies", "optional-dependencies", "version"]
[project.urls]
Homepage = "https://github.com/volatilityfoundation/volatility3/"
"Bug Tracker" = "https://github.com/volatilityfoundation/volatility3/issues"
Documentation = "https://volatility3.readthedocs.io/"
"Source Code" = "https://github.com/volatilityfoundation/volatility3"
[project.scripts]
vol = "volatility3.cli:main"
volshell = "volatility3.cli.volshell:main"
[tool.setuptools.dynamic]
version = { attr = "volatility3.framework.constants._version.PACKAGE_VERSION" }
dependencies = { file = "requirements-minimal.txt" }
[tool.setuptools.packages.find]
include = ["volatility3*"]
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
+1 -1
View File
@@ -15,7 +15,7 @@ capstone>=3.0.5
pycryptodome
# This is required for memory acquisition via leechcore/pcileech.
leechcorepyc>=2.4.0
leechcorepyc>=2.4.0; sys_platform != 'darwin'
# This is required for memory analysis on a Amazon/MinIO S3 and Google Cloud object storage
gcsfs>=2023.1.0
+6 -35
View File
@@ -4,50 +4,21 @@
import setuptools
from volatility3.framework import constants
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
def get_install_requires():
def get_requires(filename):
requirements = []
with open("requirements-minimal.txt", "r", encoding="utf-8") as fh:
with open(filename, "r", encoding="utf-8") as fh:
for line in fh.readlines():
stripped_line = line.strip()
if stripped_line == "" or stripped_line.startswith("#"):
if stripped_line == "" or stripped_line.startswith(("#", "-r")):
continue
requirements.append(stripped_line)
return requirements
setuptools.setup(
name="volatility3",
description="Memory forensics framework",
version=constants.PACKAGE_VERSION,
license="VSL",
keywords="volatility memory forensics framework windows linux volshell",
author="Volatility Foundation",
long_description=long_description,
long_description_content_type="text/markdown",
author_email="volatility@volatilityfoundation.org",
url="https://github.com/volatilityfoundation/volatility3/",
project_urls={
"Bug Tracker": "https://github.com/volatilityfoundation/volatility3/issues",
"Documentation": "https://volatility3.readthedocs.io/",
"Source Code": "https://github.com/volatilityfoundation/volatility3",
extras_require={
"dev": get_requires("requirements-dev.txt"),
"full": get_requires("requirements.txt"),
},
packages=setuptools.find_namespace_packages(
include=["volatility3", "volatility3.*"]
),
package_dir={"volatility3": "volatility3"},
python_requires=">=3.7.0",
include_package_data=True,
entry_points={
"console_scripts": [
"vol = volatility3.cli:main",
"volshell = volatility3.cli.volshell:main",
],
},
install_requires=get_install_requires(),
)
+1
View File
@@ -6,5 +6,6 @@ pefile>=2017.8.1 #foo
# This is required for the yara plugins
yara-python>=3.8.0
yara-x>=0.5.0
pytest>=7.0.0
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env python3
# PYTHON_ARGCOMPLETE_OK
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
+11
View File
@@ -22,6 +22,13 @@ import traceback
from typing import Any, Dict, List, Tuple, Type, Union
from urllib import parse, request
try:
import argcomplete
HAS_ARGCOMPLETE = True
except ImportError:
HAS_ARGCOMPLETE = False
from volatility3.cli import text_filter
import volatility3.plugins
import volatility3.symbols
@@ -351,6 +358,10 @@ class CommandLine:
# Hand the plugin requirements over to the CLI (us) and let it construct the config tree
# Run the argparser
if HAS_ARGCOMPLETE:
# The autocompletion line must be after the partial_arg handling, so that it doesn't trip it
# before all the plugins have been added
argcomplete.autocomplete(parser)
args = parser.parse_args()
if args.plugin is None:
parser.error("Please select a plugin to run")
+17 -2
View File
@@ -21,8 +21,6 @@ class HelpfulSubparserAction(argparse._SubParsersAction):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
# We don't want the action self-check to kick in, so we remove the choices list, the check happens in __call__
self.choices = None
def __call__(
self,
@@ -100,3 +98,20 @@ class HelpfulArgParser(argparse.ArgumentParser):
# return the number of arguments matched
return len(match.group(1))
def _check_value(self, action: argparse.Action, value: Any) -> None:
"""This is called to ensure a value is correct/valid
In normal operation, it would check that a value provided is valid and return None
If it was not valid, it would throw an ArgumentError
When people provide a partial plugin name, we want to look for a matching plugin name
which happens in the HelpfulSubparserAction's __call_method
To get there without tripping the check_value failure, we have to prevent the exception
being thrown when the value is a HelpfulSubparserAction. This therefore affects no other
checks for normal parameters.
"""
if not isinstance(action, HelpfulSubparserAction):
super()._check_value(action, value)
return None
+12
View File
@@ -21,6 +21,14 @@ from volatility3.framework import (
plugins,
)
try:
import argcomplete
HAS_ARGCOMPLETE = True
except ImportError:
HAS_ARGCOMPLETE = False
# Make sure we log everything
rootlog = logging.getLogger()
@@ -276,6 +284,10 @@ class VolShell(cli.CommandLine):
# Hand the plugin requirements over to the CLI (us) and let it construct the config tree
# Run the argparser
if HAS_ARGCOMPLETE:
# The autocompletion line must be after the partial_arg handling, so that it doesn't trip it
# before all the plugins have been added
argcomplete.autocomplete(parser)
args = parser.parse_args()
vollog.log(
+1 -1
View File
@@ -7,7 +7,7 @@ import glob
import sys
import zipfile
required_python_version = (3, 7, 0)
required_python_version = (3, 8, 0)
if (
sys.version_info.major != required_python_version[0]
or sys.version_info.minor < required_python_version[1]
+7 -14
View File
@@ -14,6 +14,13 @@ from typing import Callable, Optional
import volatility3.framework.constants.linux
import volatility3.framework.constants.windows
from volatility3.framework.constants._version import (
PACKAGE_VERSION,
VERSION_MAJOR,
VERSION_MINOR,
VERSION_PATCH,
VERSION_SUFFIX,
)
PLUGINS_PATH = [
os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "plugins")),
@@ -42,20 +49,6 @@ if hasattr(sys, "frozen") and sys.frozen:
BANG = "!"
"""Constant used to delimit table names from type names when referring to a symbol"""
# We use the SemVer 2.0.0 versioning scheme
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
VERSION_MINOR = 7 # Number of changes that only add to the interface
VERSION_PATCH = 1 # Number of changes that do not change the interface
VERSION_SUFFIX = ""
# TODO: At version 2.0.0, remove the symbol_shift feature
PACKAGE_VERSION = (
".".join([str(x) for x in [VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH]])
+ VERSION_SUFFIX
)
"""The canonical version of the volatility3 package"""
AUTOMAGIC_CONFIG_PATH = "automagic"
"""The root section within the context configuration for automagic values"""
@@ -0,0 +1,11 @@
# We use the SemVer 2.0.0 versioning scheme
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
VERSION_MINOR = 9 # Number of changes that only add to the interface
VERSION_PATCH = 0 # Number of changes that do not change the interface
VERSION_SUFFIX = ""
PACKAGE_VERSION = (
".".join([str(x) for x in [VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH]])
+ VERSION_SUFFIX
)
"""The canonical version of the volatility3 package"""
@@ -0,0 +1,21 @@
from volatility3.framework.layers import intel
WIN_ARCHS = ["Intel32", "Intel64"]
"""Windows supported architectures"""
WIN_ARCHS_LAYERS = [intel.Intel]
"""Windows supported architectures layers"""
LINUX_ARCHS = ["Intel32", "Intel64"]
"""Linux supported architectures"""
LINUX_ARCHS_LAYERS = [intel.Intel]
"""Linux supported architectures layers"""
MAC_ARCHS = ["Intel32", "Intel64"]
"""Mac supported architectures"""
MAC_ARCHS_LAYERS = [intel.Intel]
"""Mac supported architectures layers"""
FRAMEWORK_ARCHS = ["Intel32", "Intel64"]
"""Framework supported architectures"""
FRAMEWORK_ARCHS_LAYERS = [intel.Intel]
"""Framework supported architectures layers"""
@@ -50,7 +50,7 @@ class AutomagicInterface(
context: interfaces.context.ContextInterface,
config_path: str,
*args,
**kwargs
**kwargs,
) -> None:
super().__init__(context, config_path)
for requirement in self.get_requirements():
+3 -1
View File
@@ -180,7 +180,9 @@ class Intel(linear.LinearlyMappedLayer):
position = self._initial_position
entry = self._initial_entry
if self.minimum_address > offset > self.maximum_address:
if not (
self.minimum_address <= (offset & self.address_mask) <= self.maximum_address
):
raise exceptions.PagedInvalidAddressException(
self.name,
offset,
+21
View File
@@ -7,6 +7,27 @@ from typing import Optional, Union
from volatility3.framework import interfaces, objects, constants
def rol(value: int, count: int, max_bits: int = 64) -> int:
"""A rotate-left instruction in Python"""
max_bits_mask = (1 << max_bits) - 1
return (value << count % max_bits) & max_bits_mask | (
(value & max_bits_mask) >> (max_bits - (count % max_bits))
)
def bswap_32(value: int) -> int:
value = ((value << 8) & 0xFF00FF00) | ((value >> 8) & 0x00FF00FF)
return ((value << 16) | (value >> 16)) & 0xFFFFFFFF
def bswap_64(value: int) -> int:
low = bswap_32((value >> 32))
high = bswap_32((value & 0xFFFFFFFF))
return ((high << 32) | low) & 0xFFFFFFFFFFFFFFFF
def array_to_string(
array: "objects.Array", count: Optional[int] = None, errors: str = "replace"
) -> interfaces.objects.ObjectInterface:
@@ -0,0 +1,73 @@
# 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 List
from volatility3.framework import renderers, interfaces, exceptions
from volatility3.framework.renderers import format_hints
from volatility3.framework.interfaces import plugins
from volatility3.framework.configuration import requirements
vollog = logging.getLogger(__name__)
class EBPF(plugins.PluginInterface):
"""Enumerate eBPF programs"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=["Intel32", "Intel64"],
),
]
def get_ebpf_programs(
self,
context: interfaces.context.ContextInterface,
vmlinux_module_name: str,
) -> interfaces.objects.ObjectInterface:
"""Enumerate eBPF programs walking its IDR.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
vmlinux_module_name: The name of the kernel module on which to operate
Yields:
eBPF program objects
"""
vmlinux = context.modules[vmlinux_module_name]
if not vmlinux.has_symbol("prog_idr"):
raise exceptions.VolatilityException(
"Cannot find the eBPF prog idr. Unsupported kernel"
)
prog_idr = vmlinux.object_from_symbol("prog_idr")
for page_addr in prog_idr.get_entries():
bpf_prog = vmlinux.object("bpf_prog", offset=page_addr, absolute=True)
yield bpf_prog
def _generator(self):
for prog in self.get_ebpf_programs(self.context, self.config["kernel"]):
prog_addr = prog.vol.offset
prog_type = prog.get_type() or renderers.NotAvailableValue()
prog_tag = prog.get_tag() or renderers.NotAvailableValue()
prog_name = prog.get_name() or renderers.NotAvailableValue()
fields = (format_hints.Hex(prog_addr), prog_name, prog_tag, prog_type)
yield (0, fields)
def run(self):
headers = [
("Address", format_hints.Hex),
("Name", str),
("Tag", str),
("Type", str),
]
return renderers.TreeGrid(headers, self._generator())
+95 -14
View File
@@ -1,27 +1,27 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# 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
#
"""A module containing a collection of plugins that produce data typically
found in Linux's /proc file system."""
import logging
import logging, datetime
from typing import List, Callable
from volatility3.framework import renderers, interfaces, constants
from volatility3.framework import renderers, interfaces, constants, exceptions
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.objects import utility
from volatility3.framework.symbols import linux
from volatility3.plugins.linux import pslist
from volatility3.plugins import timeliner
vollog = logging.getLogger(__name__)
class Lsof(plugins.PluginInterface):
"""Lists all memory maps for all processes."""
class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Lists open files for each processes."""
_required_framework_version = (2, 0, 0)
_version = (1, 1, 0)
_version = (1, 2, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -45,6 +45,29 @@ class Lsof(plugins.PluginInterface):
),
]
@classmethod
def get_inode_metadata(cls, filp: interfaces.objects.ObjectInterface):
try:
dentry = filp.get_dentry()
if dentry:
inode_object = dentry.d_inode
if inode_object and inode_object.is_valid():
itype = (
inode_object.get_inode_type() or renderers.NotAvailableValue()
)
return (
inode_object.i_ino,
itype,
inode_object.i_size,
inode_object.get_file_mode(),
inode_object.get_change_time(),
inode_object.get_modification_time(),
inode_object.get_access_time(),
)
except (exceptions.InvalidAddressException, AttributeError) as e:
vollog.warning(f"Can't get inode metadata: {e}")
return None
@classmethod
def list_fds(
cls,
@@ -52,7 +75,7 @@ class Lsof(plugins.PluginInterface):
symbol_table: str,
filter_func: Callable[[int], bool] = lambda _: False,
):
linuxutils_symbol_table = None # type: ignore
linuxutils_symbol_table = None
for task in pslist.PsList.list_tasks(context, symbol_table, filter_func):
if linuxutils_symbol_table is None:
if constants.BANG not in task.vol.type_name:
@@ -69,21 +92,79 @@ class Lsof(plugins.PluginInterface):
for fd_fields in fd_generator:
yield pid, task_comm, task, fd_fields
@classmethod
def list_fds_and_inodes(
cls,
context: interfaces.context.ContextInterface,
symbol_table: str,
filter_func: Callable[[int], bool] = lambda _: False,
):
for pid, task_comm, task, (fd_num, filp, full_path) in cls.list_fds(
context, symbol_table, filter_func
):
inode_metadata = cls.get_inode_metadata(filp)
if inode_metadata is None:
inode_metadata = tuple(
interfaces.renderers.BaseAbsentValue() for _ in range(7)
)
yield pid, task_comm, task, fd_num, filp, full_path, inode_metadata
def _generator(self, pids, symbol_table):
filter_func = pslist.PsList.create_pid_filter(pids)
fds_generator = self.list_fds(
fds_generator = self.list_fds_and_inodes(
self.context, symbol_table, filter_func=filter_func
)
for pid, task_comm, _task, fd_fields in fds_generator:
fd_num, _filp, full_path = fd_fields
fields = (pid, task_comm, fd_num, full_path)
for (
pid,
task_comm,
task,
fd_num,
filp,
full_path,
inode_metadata,
) in fds_generator:
inode_num, itype, file_size, imode, ctime, mtime, atime = inode_metadata
fields = (
pid,
task_comm,
fd_num,
full_path,
inode_num,
itype,
imode,
ctime,
mtime,
atime,
file_size,
)
yield (0, fields)
def run(self):
pids = self.config.get("pid", None)
symbol_table = self.config["kernel"]
tree_grid_args = [("PID", int), ("Process", str), ("FD", int), ("Path", str)]
tree_grid_args = [
("PID", int),
("Process", str),
("FD", int),
("Path", str),
("Inode", int),
("Type", str),
("Mode", str),
("Changed", datetime.datetime),
("Modified", datetime.datetime),
("Accessed", datetime.datetime),
("Size", int),
]
return renderers.TreeGrid(tree_grid_args, self._generator(pids, symbol_table))
def generate_timeline(self):
pids = self.config.get("pid", None)
symbol_table = self.config["kernel"]
for row in self._generator(pids, symbol_table):
_depth, row_data = row
description = f'Process {row_data[1]} ({row_data[0]}) Open "{row_data[3]}"'
yield description, timeliner.TimeLinerType.CHANGED, row_data[7]
yield description, timeliner.TimeLinerType.MODIFIED, row_data[8]
yield description, timeliner.TimeLinerType.ACCESSED, row_data[9]
@@ -37,7 +37,7 @@ class MountInfo(plugins.PluginInterface):
_required_framework_version = (2, 2, 0)
_version = (1, 0, 0)
_version = (1, 2, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -143,10 +143,10 @@ class MountInfo(plugins.PluginInterface):
sb_opts,
)
@staticmethod
def _get_tasks_mountpoints(
self,
tasks: Iterable[interfaces.objects.ObjectInterface],
filtered_by_pids: bool,
filtered_by_pids: bool = False,
):
seen_mountpoints = set()
for task in tasks:
@@ -184,8 +184,8 @@ class MountInfo(plugins.PluginInterface):
self,
tasks: Iterable[interfaces.objects.ObjectInterface],
mnt_ns_ids: List[int],
mount_format: bool,
filtered_by_pids: bool,
mount_format: bool = False,
filtered_by_pids: bool = False,
) -> Iterable[Tuple[int, Tuple]]:
show_filter_warning = False
for task, mnt, mnt_ns_id in self._get_tasks_mountpoints(
@@ -247,6 +247,37 @@ class MountInfo(plugins.PluginInterface):
"Could not filter by mount namespace id. This field is not available in this kernel."
)
@classmethod
def get_superblocks(
cls,
context: interfaces.context.ContextInterface,
vmlinux_module_name: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Yield file system superblocks based on the task's mounted filesystems.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
vmlinux_module_name: The name of the kernel module on which to operate
Yields:
super_block: Kernel's struct super_block object
"""
# No filter so that we get all the mount namespaces from all tasks
tasks = pslist.PsList.list_tasks(context, vmlinux_module_name)
seen_sb_ptr = set()
for task, mnt, _mnt_ns_id in cls._get_tasks_mountpoints(tasks):
path_root = linux.LinuxUtilities.get_path_mnt(task, mnt)
if not path_root:
continue
sb_ptr = mnt.get_mnt_sb()
if not sb_ptr or sb_ptr in seen_sb_ptr:
continue
seen_sb_ptr.add(sb_ptr)
yield sb_ptr.dereference(), path_root
def run(self):
pids = self.config.get("pids")
mount_ns_ids = self.config.get("mntns")
@@ -0,0 +1,738 @@
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from dataclasses import dataclass, field
from abc import ABC, abstractmethod
import logging
from typing import Iterator, List, Tuple
from volatility3 import framework
from volatility3.framework import (
constants,
interfaces,
renderers,
exceptions,
)
from volatility3.framework.renderers import format_hints
from volatility3.framework.configuration import requirements
from volatility3.framework.symbols import linux
from volatility3.plugins.linux import lsmod
vollog = logging.getLogger(__name__)
@dataclass
class Proto:
name: str
hooks: Tuple[str] = field(default_factory=tuple)
PROTO_NOT_IMPLEMENTED = Proto(name="UNSPEC")
NF_INET_HOOKS = ("PRE_ROUTING", "LOCAL_IN", "FORWARD", "LOCAL_OUT", "POST_ROUTING")
NF_DEC_HOOKS = (
"PRE_ROUTING",
"LOCAL_IN",
"FORWARD",
"LOCAL_OUT",
"POST_ROUTING",
"HELLO",
"ROUTE",
)
NF_ARP_HOOKS = ("IN", "OUT", "FORWARD")
NF_NETDEV_HOOKS = ("INGRESS", "EGRESS")
LARGEST_HOOK_NUMBER = max(
len(NF_INET_HOOKS), len(NF_DEC_HOOKS), len(NF_ARP_HOOKS), len(NF_NETDEV_HOOKS)
)
class AbstractNetfilter(ABC):
"""Netfilter Abstract Base Classes handling details across various
Netfilter implementations, including constants, helpers, and common
routines.
"""
PROTO_HOOKS = (
PROTO_NOT_IMPLEMENTED, # NFPROTO_UNSPEC
Proto(name="INET", hooks=NF_INET_HOOKS), # From kernels 3.14
Proto(name="IPV4", hooks=NF_INET_HOOKS),
Proto(name="ARP", hooks=NF_ARP_HOOKS),
PROTO_NOT_IMPLEMENTED,
Proto(name="NETDEV", hooks=NF_NETDEV_HOOKS),
PROTO_NOT_IMPLEMENTED,
Proto(name="BRIDGE", hooks=NF_INET_HOOKS),
PROTO_NOT_IMPLEMENTED,
PROTO_NOT_IMPLEMENTED,
Proto(name="IPV6", hooks=NF_INET_HOOKS),
PROTO_NOT_IMPLEMENTED,
Proto(name="DECNET", hooks=NF_DEC_HOOKS), # Removed in kernel 6.1
)
NF_MAX_HOOKS = LARGEST_HOOK_NUMBER + 1
def __init__(
self, context: interfaces.context.ContextInterface, kernel_module_name: str
):
self._context = context
self.vmlinux = context.modules[kernel_module_name]
self.layer_name = self.vmlinux.layer_name
# Set data sizes
self.ptr_size = self.vmlinux.get_type("pointer").size
self.list_head_size = self.vmlinux.get_type("list_head").size
lsmod_required_version = Netfilter._required_lsmod_version
lsmod_current_version = lsmod.Lsmod._version
if not requirements.VersionRequirement.matches_required(
lsmod_required_version, lsmod_current_version
):
raise exceptions.PluginRequirementException(
f"linux.lsmod.Lsmod version not suitable: required {lsmod_required_version} found {lsmod_current_version}"
)
linuxutils_required_version = Netfilter._required_linuxutils_version
linuxutils_current_version = linux.LinuxUtilities._version
if not requirements.VersionRequirement.matches_required(
linuxutils_required_version, linuxutils_current_version
):
raise exceptions.PluginRequirementException(
f"linux.LinuxUtilities version not suitable: required {linuxutils_required_version} found {linuxutils_current_version}"
)
modules = lsmod.Lsmod.list_modules(context, kernel_module_name)
self.handlers = linux.LinuxUtilities.generate_kernel_handler_info(
context, kernel_module_name, modules
)
@classmethod
def run_all(
cls, context: interfaces.context.ContextInterface, kernel_module_name: str
) -> Iterator[Tuple[int, str, str, int, int, str, bool]]:
"""It calls each subclass symtab_checks() to test the required
conditions to that specific kernel implementation.
Args:
context: The volatility3 context on which to operate
kernel_module_name: The name of the table containing the kernel symbols
Yields:
The kmsg records. Same as _run()
"""
vmlinux = context.modules[kernel_module_name]
implementation_inst = None # type: ignore
for subclass in framework.class_subclasses(cls):
if not subclass.symtab_checks(vmlinux=vmlinux):
vollog.log(
constants.LOGLEVEL_VVVV,
"Netfilter implementation '%s' doesn't match this memory dump",
subclass.__name__,
)
continue
vollog.log(
constants.LOGLEVEL_VVVV,
"Netfilter implementation '%s' matches!",
subclass.__name__,
)
implementation_inst = subclass(
context=context, kernel_module_name=kernel_module_name
)
# More than one class could be executed for an specific kernel version
# For instance: Netfilter Ingress hooks
yield from implementation_inst._run()
if implementation_inst is None:
vollog.error("Unsupported Netfilter kernel implementation")
def _run(self) -> Iterator[Tuple[int, str, str, int, int, str, bool]]:
"""Iterates over namespaces and protocols, executing various callbacks that
allow customization of the code to the specific data structure used in a
particular kernel implementation
get_hooks_container(net, proto_name, hook_name)
It returns the data structure used in a specific kernel implementation
to store the hooks for a respective namespace and protocol, basically:
For Ingress hooks:
network_namespace[] -> net_device[] -> nf_hooks_ingress[]
For egress hooks:
network_namespace[] -> net_device[] -> nf_hooks_egress[]
For all the other Netfilter hooks:
<= 4.2.8
nf_hooks[]
>= 4.3
network_namespace[] -> nf.hooks[]
get_hook_ops(hook_container, proto_idx, hook_idx)
Give the 'hook_container' got in get_hooks_container(), it
returns an iterable of 'nf_hook_ops' elements for a respective protocol
and hook type.
Returns:
netns [int]: Network namespace id
proto_name [str]: Protocol name
hook_name [str]: Hook name
priority [int]: Priority
hook_ops_hook [int]: Hook address
module_name [str]: Linux kernel module name
hooked [bool]: hooked?
"""
for netns, net in self.get_net_namespaces():
for proto_idx, proto_name, hook_idx, hook_name in self._proto_hook_loop():
hooks_container = self.get_hooks_container(net, proto_name, hook_name)
for hook_container in hooks_container:
for hook_ops in self.get_hook_ops(
hook_container, proto_idx, hook_idx
):
if not hook_ops:
continue
priority = int(hook_ops.priority)
hook_ops_hook = hook_ops.hook
module_name = self.get_module_name_for_address(hook_ops_hook)
hooked = module_name is not None
yield netns, proto_name, hook_name, priority, hook_ops_hook, module_name, hooked
@classmethod
@abstractmethod
def symtab_checks(cls, vmlinux: interfaces.context.ModuleInterface) -> bool:
"""This method on each sublasss will be called to evaluate if the kernel
being analyzed fulfill the type & symbols requirements for the implementation.
The first class returning True will be instantiated and called via the
run() method.
Returns:
bool: True if the kernel being analyzed fulfill the class requirements.
"""
def _proto_hook_loop(self) -> Iterator[Tuple[int, str, int, str]]:
"""Flattens the protocol families and hooks"""
for proto_idx, proto in enumerate(AbstractNetfilter.PROTO_HOOKS):
if proto == PROTO_NOT_IMPLEMENTED:
continue
if proto.name not in self.subscribed_protocols():
# This protocol is not managed in this object
continue
for hook_idx, hook_name in enumerate(proto.hooks):
yield proto_idx, proto.name, hook_idx, hook_name
def build_nf_hook_ops_array(self, nf_hook_entries):
"""Function helper to build the nf_hook_ops array when it is not part of the
struct 'nf_hook_entries' definition.
nf_hook_ops was stored adjacent in memory to the nf_hook_entry array, in the
new struct 'nf_hook_entries'. However, this 'nf_hooks_ops' array 'orig_ops' is
not part of the 'nf_hook_entries' struct. So, we need to calculate the offset.
struct nf_hook_entries {
u16 num_hook_entries; /* plus padding */
struct nf_hook_entry hooks[];
//const struct nf_hook_ops *orig_ops[];
}
"""
nf_hook_entry_size = self.vmlinux.get_type("nf_hook_entry").size
orig_ops_addr = (
nf_hook_entries.hooks.vol.offset
+ nf_hook_entry_size * nf_hook_entries.num_hook_entries
)
orig_ops = self._context.object(
object_type=self.get_symbol_fullname("array"),
offset=orig_ops_addr,
subtype=self.vmlinux.get_type("pointer"),
layer_name=self.layer_name,
count=nf_hook_entries.num_hook_entries,
)
return orig_ops
def subscribed_protocols(self) -> Tuple[str]:
"""Allows to select which PROTO_HOOKS protocols will be processed by the
Netfiler subclass.
"""
# Most implementation handlers respond to these protocols, except for
# the ingress hook, which specifically handles the 'NETDEV' protocol.
# However, there is no corresponding Netfilter hook implementation for
# the INET protocol in the kernel. AFAIU, this is used as
# 'NFPROTO_INET = NFPROTO_IPV4 || NFPROTO_IPV6'
# in other parts of the kernel source code.
return ("IPV4", "ARP", "BRIDGE", "IPV6", "DECNET")
def get_module_name_for_address(self, addr) -> str:
"""Helper to obtain the module and symbol name in the format needed for the
output of this plugin.
"""
module_name, symbol_name = linux.LinuxUtilities.lookup_module_address(
self.vmlinux, self.handlers, addr
)
if module_name == "UNKNOWN":
module_name = None
if symbol_name != "N/A":
module_name = f"[{symbol_name}]"
return module_name
def get_net_namespaces(self):
"""Common function to retrieve the different namespaces.
From 4.3 on, all the implementations use network namespaces.
"""
nethead = self.vmlinux.object_from_symbol("net_namespace_list")
symbol_net_name = self.get_symbol_fullname("net")
for net in nethead.to_list(symbol_net_name, "list"):
net_ns_id = net.ns.inum
yield net_ns_id, net
def get_hooks_container(self, net, proto_name, hook_name):
"""Returns the data structure used in a specific kernel implementation to store
the hooks for a respective namespace and protocol.
Except for kernels < 4.3, all the implementations use network namespaces.
Also the data structure which contains the hooks, even though it changes its
implementation and/or data type, it is always in this location.
"""
yield net.nf.hooks
def get_hook_ops(self, hook_container, proto_idx, hook_idx):
"""Given the hook_container obtained from get_hooks_container(), it
returns an iterable of 'nf_hook_ops' elements for a corresponding protocol
and hook type.
This is the most variable/unstable part of all Netfilter hook designs, it
changes almost in every single implementation.
"""
raise NotImplementedError("You must implement this method")
def get_symbol_fullname(self, symbol_basename: str) -> str:
"""Given a short symbol or type name, it returns its full name"""
return self.vmlinux.symbol_table_name + constants.BANG + symbol_basename
@staticmethod
def get_member_type(
vol_type: interfaces.objects.Template, member_name: str
) -> List[str]:
"""Returns a list of types/subtypes belonging to the given type member.
Args:
vol_type (interfaces.objects.Template): A vol3 type object
member_name (str): The member name
Returns:
list: A list of types/subtypes
"""
_size, vol_obj = vol_type.vol.members[member_name]
type_name = vol_obj.type_name
type_basename = type_name.split(constants.BANG)[1]
member_type = [type_basename]
cur_type = vol_obj
while hasattr(cur_type, "subtype"):
subtype_name = cur_type.subtype.type_name
subtype_basename = subtype_name.split(constants.BANG)[1]
member_type.append(subtype_basename)
cur_type = cur_type.subtype
return member_type
class NetfilterImp_to_4_3(AbstractNetfilter):
"""At this point, Netfilter hooks were implemented as a linked list of struct
'nf_hook_ops' type. One linked list per protocol per hook type.
It was like that until 4.2.8.
struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS];
"""
@classmethod
def symtab_checks(cls, vmlinux) -> bool:
return vmlinux.has_symbol("nf_hooks")
def get_net_namespaces(self):
# In kernels <= 4.2.8 netfilter hooks are not implemented per namespaces
netns, net = renderers.NotAvailableValue(), renderers.NotAvailableValue()
yield netns, net
def get_hooks_container(self, net, proto_name, hook_name):
nf_hooks = self.vmlinux.object_from_symbol("nf_hooks")
if not nf_hooks:
return
yield nf_hooks
def get_hook_ops(self, hook_container, proto_idx, hook_idx):
list_head = hook_container[proto_idx][hook_idx]
nf_hooks_ops_name = self.get_symbol_fullname("nf_hook_ops")
return list_head.to_list(nf_hooks_ops_name, "list")
class NetfilterImp_4_3_to_4_9(AbstractNetfilter):
"""Netfilter hooks were added to network namepaces in 4.3.
It is still implemented as a linked list of 'struct nf_hook_ops' type but inside a
network namespace. One linked list per protocol per hook type.
struct net { ... struct netns_nf nf; ... }
struct netns_nf { ...
struct list_head hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]; ... }
"""
@classmethod
def symtab_checks(cls, vmlinux) -> bool:
return (
vmlinux.has_symbol("net_namespace_list")
and vmlinux.has_type("netns_nf")
and vmlinux.get_type("netns_nf").has_member("hooks")
and cls.get_member_type(vmlinux.get_type("netns_nf"), "hooks")
== ["array", "array", "list_head"]
)
def get_hook_ops(self, hook_container, proto_idx, hook_idx):
list_head = hook_container[proto_idx][hook_idx]
nf_hooks_ops_name = self.get_symbol_fullname("nf_hook_ops")
return list_head.to_list(nf_hooks_ops_name, "list")
class NetfilterImp_4_9_to_4_14(AbstractNetfilter):
"""In this range of kernel versions, the doubly-linked lists of netfilter hooks were
replaced by an array of arrays of 'nf_hook_entry' pointers in a singly-linked lists.
struct net { ... struct netns_nf nf; ... }
struct netns_nf { ..
struct nf_hook_entry __rcu *hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]; ... }
Also in v4.10 the struct nf_hook_entry changed, a hook function pointer was added to
it. However, for simplicity of this design, we will still take the hook address from
the 'nf_hook_ops'. As per v5.0-rc2, the hook address is duplicated in both sides.
- v4.9:
struct nf_hook_entry {
struct nf_hook_entry *next;
struct nf_hook_ops ops;
const struct nf_hook_ops *orig_ops; };
- v4.10:
struct nf_hook_entry {
struct nf_hook_entry *next;
nf_hookfn *hook;
void *priv;
const struct nf_hook_ops *orig_ops; };
(*) Even though the hook address is in the struct 'nf_hook_entry', we use the
original 'nf_hook_ops' hook address value, the one which was filled by the user, to
make it uniform to all the implementations.
"""
@classmethod
def symtab_checks(cls, vmlinux) -> bool:
hooks_type = ["array", "array", "pointer", "nf_hook_entry"]
return (
vmlinux.has_symbol("net_namespace_list")
and vmlinux.has_type("netns_nf")
and vmlinux.get_type("netns_nf").has_member("hooks")
and cls.get_member_type(vmlinux.get_type("netns_nf"), "hooks") == hooks_type
)
def _get_hook_ops(self, hook_container, proto_idx, hook_idx):
list_head = hook_container[proto_idx][hook_idx]
nf_hooks_ops_name = self.get_symbol_fullname("nf_hook_ops")
return list_head.to_list(nf_hooks_ops_name, "list")
def get_hook_ops(self, hook_container, proto_idx, hook_idx):
nf_hook_entry_list = hook_container[proto_idx][hook_idx]
while nf_hook_entry_list:
yield nf_hook_entry_list.orig_ops
nf_hook_entry_list = nf_hook_entry_list.next
class NetfilterImp_4_14_to_4_16(AbstractNetfilter):
"""'nf_hook_ops' was removed from struct 'nf_hook_entry'. Instead, it was stored
adjacent in memory to the 'nf_hook_entry' array, in the new struct 'nf_hook_entries'
However, 'orig_ops' is not part of the 'nf_hook_entries' struct definition. So, we
have to craft it by hand.
struct net { ... struct netns_nf nf; ... }
struct netns_nf {
struct nf_hook_entries *hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]; ... }
struct nf_hook_entries {
u16 num_hook_entries; /* plus padding */
struct nf_hook_entry hooks[];
//const struct nf_hook_ops *orig_ops[]; }
struct nf_hook_entry {
nf_hookfn *hook;
void *priv; }
(*) Even though the hook address is in the struct 'nf_hook_entry', we use the
original 'nf_hook_ops' hook address value, the one which was filled by the user, to
make it uniform to all the implementations.
"""
@classmethod
def symtab_checks(cls, vmlinux) -> bool:
hooks_type = ["array", "array", "pointer", "nf_hook_entries"]
return (
vmlinux.has_symbol("net_namespace_list")
and vmlinux.has_type("netns_nf")
and vmlinux.get_type("netns_nf").has_member("hooks")
and cls.get_member_type(vmlinux.get_type("netns_nf"), "hooks") == hooks_type
)
def get_nf_hook_entries(self, nf_hooks_addr, proto_idx, hook_idx):
"""This allows to support different hook array implementations from this version
on. For instance, in kernels >= 4.16 this multi-dimensional array is split in
one-dimensional array of pointers to 'nf_hooks_entries' per each protocol."""
return nf_hooks_addr[proto_idx][hook_idx]
def get_hook_ops(self, hook_container, proto_idx, hook_idx):
nf_hook_entries = self.get_nf_hook_entries(hook_container, proto_idx, hook_idx)
if not nf_hook_entries:
return
nf_hook_ops_name = self.get_symbol_fullname("nf_hook_ops")
nf_hook_ops_ptr_arr = self.build_nf_hook_ops_array(nf_hook_entries)
for nf_hook_ops_ptr in nf_hook_ops_ptr_arr:
nf_hook_ops = nf_hook_ops_ptr.dereference().cast(nf_hook_ops_name)
yield nf_hook_ops
class NetfilterImp_4_16_to_latest(NetfilterImp_4_14_to_4_16):
"""The multidimensional array of nf_hook_entries was split in a one-dimensional
array per each protocol.
struct net {
struct netns_nf nf; ... }
struct netns_nf {
struct nf_hook_entries * hooks_ipv4[NF_INET_NUMHOOKS];
struct nf_hook_entries * hooks_ipv6[NF_INET_NUMHOOKS];
struct nf_hook_entries * hooks_arp[NF_ARP_NUMHOOKS];
struct nf_hook_entries * hooks_bridge[NF_INET_NUMHOOKS];
struct nf_hook_entries * hooks_decnet[NF_DN_NUMHOOKS]; ... }
struct nf_hook_entries {
u16 num_hook_entries; /* plus padding */
struct nf_hook_entry hooks[];
//const struct nf_hook_ops *orig_ops[]; }
struct nf_hook_entry {
nf_hookfn *hook;
void *priv; }
(*) Even though the hook address is in the struct nf_hook_entry, we use the original
nf_hook_ops hook address value, the one which was filled by the user, to make it
uniform to all the implementations.
"""
@classmethod
def symtab_checks(cls, vmlinux) -> bool:
return (
vmlinux.has_symbol("net_namespace_list")
and vmlinux.has_type("netns_nf")
and vmlinux.get_type("netns_nf").has_member("hooks_ipv4")
)
def get_hooks_container(self, net, proto_name, hook_name):
try:
if proto_name == "IPV4":
net_nf_hooks = net.nf.hooks_ipv4
elif proto_name == "ARP":
net_nf_hooks = net.nf.hooks_arp
elif proto_name == "BRIDGE":
net_nf_hooks = net.nf.hooks_bridge
elif proto_name == "IPV6":
net_nf_hooks = net.nf.hooks_ipv6
elif proto_name == "DECNET":
net_nf_hooks = net.nf.hooks_decnet
else:
return
yield net_nf_hooks
except AttributeError:
# Protocol family disabled at kernel compilation
# CONFIG_NETFILTER_FAMILY_ARP=n ||
# CONFIG_NETFILTER_FAMILY_BRIDGE=n ||
# CONFIG_DECNET=n
pass
def _get_nf_hook_entries_ptr(self, nf_hooks_addr, proto_idx, hook_idx):
nf_hook_entries_ptr = nf_hooks_addr[hook_idx]
return nf_hook_entries_ptr
def get_nf_hook_entries(self, nf_hooks_addr, proto_idx, hook_idx):
return nf_hooks_addr[hook_idx]
class AbstractNetfilterNetDev(AbstractNetfilter):
"""Base class to handle the Netfilter NetDev hooks.
It won't be executed. It has some common functions to all Netfilter NetDev hook
implementions.
Netfilter NetDev hooks are set per network device which belongs to a network
namespace.
"""
@classmethod
def symtab_checks(cls, vmlinux) -> bool:
return False
def subscribed_protocols(self):
return ("NETDEV",)
def get_hooks_container(self, net, proto_name, hook_name):
net_device_type = self.vmlinux.get_type("net_device")
net_device_name = self.get_symbol_fullname("net_device")
for net_device in net.dev_base_head.to_list(net_device_name, "dev_list"):
if hook_name == "INGRESS":
if net_device_type.has_member("nf_hooks_ingress"):
# CONFIG_NETFILTER_INGRESS=y
yield net_device.nf_hooks_ingress
elif hook_name == "EGRESS":
if net_device_type.has_member("nf_hooks_egress"):
# CONFIG_NETFILTER_EGRESS=y
yield net_device.nf_hooks_egress
class NetfilterNetDevImp_4_2_to_4_9(AbstractNetfilterNetDev):
"""This is the first version of Netfilter Ingress hooks which was implemented using
a doubly-linked list of 'nf_hook_ops'.
struct list_head nf_hooks_ingress;
"""
@classmethod
def symtab_checks(cls, vmlinux) -> bool:
hooks_type = ["list_head"]
return (
vmlinux.has_symbol("net_namespace_list")
and vmlinux.has_type("net_device")
and vmlinux.get_type("net_device").has_member("nf_hooks_ingress")
and cls.get_member_type(vmlinux.get_type("net_device"), "nf_hooks_ingress")
== hooks_type
)
def get_hook_ops(self, hook_container, proto_idx, hook_idx):
nf_hooks_ingress = hook_container
nf_hook_ops_name = self.get_symbol_fullname("nf_hook_ops")
return nf_hooks_ingress.to_list(nf_hook_ops_name, "list")
class NetfilterNetDevImp_4_9_to_4_14(AbstractNetfilterNetDev):
"""In 4.9 it was changed to a simple singly-linked list.
struct nf_hook_entry * nf_hooks_ingress;
"""
@classmethod
def symtab_checks(cls, vmlinux) -> bool:
hooks_type = ["pointer", "nf_hook_entry"]
return (
vmlinux.has_symbol("net_namespace_list")
and vmlinux.has_type("net_device")
and vmlinux.get_type("net_device").has_member("nf_hooks_ingress")
and cls.get_member_type(vmlinux.get_type("net_device"), "nf_hooks_ingress")
== hooks_type
)
def get_hook_ops(self, hook_container, proto_idx, hook_idx):
nf_hooks_ingress_ptr = hook_container
if not nf_hooks_ingress_ptr:
return
while nf_hooks_ingress_ptr:
nf_hook_entry = nf_hooks_ingress_ptr.dereference()
orig_ops = nf_hook_entry.orig_ops.dereference()
yield orig_ops
nf_hooks_ingress_ptr = nf_hooks_ingress_ptr.next
class NetfilterNetDevImp_4_14_to_latest(AbstractNetfilterNetDev):
"""In 4.14 the hook list was converted to an array of pointers inside the struct
'nf_hook_entries':
struct nf_hook_entries * nf_hooks_ingress;
struct nf_hook_entries {
u16 num_hook_entries;
struct nf_hook_entry hooks[];
//const struct nf_hook_ops *orig_ops[]; }
"""
@classmethod
def symtab_checks(cls, vmlinux) -> bool:
hooks_type = ["pointer", "nf_hook_entries"]
return (
vmlinux.has_symbol("net_namespace_list")
and vmlinux.has_type("net_device")
and vmlinux.get_type("net_device").has_member("nf_hooks_ingress")
and cls.get_member_type(vmlinux.get_type("net_device"), "nf_hooks_ingress")
== hooks_type
)
def get_hook_ops(self, hook_container, proto_idx, hook_idx):
nf_hook_entries = hook_container
if not nf_hook_entries:
return
nf_hook_ops_name = self.get_symbol_fullname("nf_hook_ops")
nf_hook_ops_ptr_arr = self.build_nf_hook_ops_array(nf_hook_entries)
for nf_hook_ops_ptr in nf_hook_ops_ptr_arr:
nf_hook_ops = nf_hook_ops_ptr.dereference().cast(nf_hook_ops_name)
yield nf_hook_ops
class Netfilter(interfaces.plugins.PluginInterface):
"""Lists Netfilter hooks."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
_required_linuxutils_version = (2, 1, 0)
_required_lsmod_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="lsmod", plugin=lsmod.Lsmod, version=cls._required_lsmod_version
),
requirements.VersionRequirement(
name="linuxutils",
component=linux.LinuxUtilities,
version=cls._required_linuxutils_version,
),
]
def _format_fields(self, fields):
(
netns,
proto_name,
hook_name,
priority,
hook_func,
module_name,
hooked,
) = fields
return (
netns,
proto_name,
hook_name,
priority,
format_hints.Hex(hook_func),
module_name,
str(hooked),
)
def _generator(self):
kernel_module_name = self.config["kernel"]
for fields in AbstractNetfilter.run_all(
context=self.context, kernel_module_name=kernel_module_name
):
yield (0, self._format_fields(fields))
def run(self):
headers = [
("Net NS", int),
("Proto", str),
("Hook", str),
("Priority", int),
("Handler", format_hints.Hex),
("Module", str),
("Is Hooked", str),
]
return renderers.TreeGrid(headers, self._generator())
@@ -0,0 +1,531 @@
# 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 math
import logging
import datetime
from dataclasses import dataclass, astuple
from typing import List, Set, Type, Iterable
from volatility3.framework import renderers, interfaces
from volatility3.framework.renderers import format_hints
from volatility3.framework.interfaces import plugins
from volatility3.framework.configuration import requirements
from volatility3.plugins import timeliner
from volatility3.plugins.linux import mountinfo
vollog = logging.getLogger(__name__)
@dataclass
class InodeUser:
"""Inode user representation, featuring augmented information and formatted fields.
This is the data the plugin will eventually display.
"""
superblock_addr: int
mountpoint: str
device: str
inode_num: int
inode_addr: int
type: str
inode_pages: int
cached_pages: int
file_mode: str
access_time: str
modification_time: str
change_time: str
path: str
@dataclass
class InodeInternal:
"""Inode internal representation containing only the core objects
Fields:
superblock: 'super_block' struct
mountpoint: Superblock mountpoint path
inode: 'inode' struct
path: Dentry full path
"""
superblock: interfaces.objects.ObjectInterface
mountpoint: str
inode: interfaces.objects.ObjectInterface
path: str
def to_user(
self, kernel_layer: interfaces.layers.TranslationLayerInterface
) -> InodeUser:
"""Augment the inode information to be presented to the user
Args:
kernel_layer: The kernel layer to obtain the page size
Returns:
An InodeUser dataclass
"""
# Ensure all types are atomic immutable. Otherwise, astuple() will take a long
# time doing a deepcopy of the Volatility objects.
superblock_addr = self.superblock.vol.offset
device = f"{self.superblock.major}:{self.superblock.minor}"
inode_num = int(self.inode.i_ino)
inode_addr = self.inode.vol.offset
inode_type = self.inode.get_inode_type() or renderers.UnparsableValue()
# Round up the number of pages to fit the inode's size
inode_pages = int(math.ceil(self.inode.i_size / float(kernel_layer.page_size)))
cached_pages = int(self.inode.i_mapping.nrpages)
file_mode = self.inode.get_file_mode()
access_time_dt = self.inode.get_access_time()
modification_time_str = self.inode.get_modification_time()
change_time_str = self.inode.get_change_time()
inode_user = InodeUser(
superblock_addr=superblock_addr,
mountpoint=self.mountpoint,
device=device,
inode_num=inode_num,
inode_addr=inode_addr,
type=inode_type,
inode_pages=inode_pages,
cached_pages=cached_pages,
file_mode=file_mode,
access_time=access_time_dt,
modification_time=modification_time_str,
change_time=change_time_str,
path=self.path,
)
return inode_user
class Files(plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Lists files from memory"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="mountinfo", plugin=mountinfo.MountInfo, version=(1, 2, 0)
),
requirements.ListRequirement(
name="type",
description="List of space-separated file type filters i.e. --type REG DIR",
element_type=str,
optional=True,
),
requirements.StringRequirement(
name="find",
description="Filename (full path) to find",
optional=True,
),
]
@staticmethod
def _follow_symlink(
inode: interfaces.objects.ObjectInterface,
symlink_path: str,
) -> str:
"""Follows (fast) symlinks (kernels >= 4.2.x).
Fast symlinks are filesystem agnostic.
Args:
inode: The inode (or pointer) to dump
symlink_path: The symlink name
Returns:
If it can resolve the symlink, it returns a string "symlink_path -> target_path"
Otherwise, it returns the same symlink_path
"""
# i_link (fast symlinks) were introduced in 4.2
if inode and inode.is_link and inode.has_member("i_link") and inode.i_link:
i_link_str = inode.i_link.dereference().cast(
"string", max_length=255, encoding="utf-8", errors="replace"
)
symlink_path = f"{symlink_path} -> {i_link_str}"
return symlink_path
@classmethod
def _walk_dentry(
cls,
seen_dentries: Set[int],
root_dentry: interfaces.objects.ObjectInterface,
parent_dir: str,
):
"""Walks dentries recursively
Args:
seen_dentries: A set to ensure each dentry is processed only once
root_dentry: Root dentry object
parent_dir: Parent directory path
Yields:
file_path: Filename including path
dentry: Dentry object
"""
for dentry in root_dentry.get_subdirs():
dentry_addr = dentry.vol.offset
# corruption
if dentry_addr == root_dentry.vol.offset:
continue
if dentry_addr in seen_dentries:
continue
seen_dentries.add(dentry_addr)
inode = dentry.d_inode
if not (inode and inode.is_valid()):
continue
# This allows us to have consistent paths
if dentry.d_name.name:
basename = dentry.d_name.name_as_str()
# Do NOT use os.path.join() below
file_path = parent_dir + "/" + basename
else:
continue
yield file_path, dentry
if inode.is_dir:
yield from cls._walk_dentry(seen_dentries, dentry, parent_dir=file_path)
@classmethod
def get_inodes(
cls,
context: interfaces.context.ContextInterface,
vmlinux_module_name: str,
) -> Iterable[InodeInternal]:
"""Retrieves the inodes from the superblocks
Args:
context: The context that the plugin will operate within
vmlinux_module_name: The name of the kernel module on which to operate
Yields:
An InodeInternal object
"""
superblocks_iter = mountinfo.MountInfo.get_superblocks(
context=context,
vmlinux_module_name=vmlinux_module_name,
)
seen_inodes = set()
seen_dentries = set()
for superblock, mountpoint in superblocks_iter:
parent_dir = "" if mountpoint == "/" else mountpoint
# Superblock root dentry
root_dentry_ptr = superblock.s_root
if not root_dentry_ptr:
continue
root_dentry = root_dentry_ptr.dereference()
# Dentry sanity check
if not root_dentry.is_root():
continue
# More dentry/inode sanity checks
root_inode_ptr = root_dentry.d_inode
if not root_inode_ptr:
continue
root_inode = root_inode_ptr.dereference()
if not root_inode.is_valid():
continue
# Inode already processed?
if root_inode_ptr in seen_inodes:
continue
seen_inodes.add(root_inode_ptr)
root_path = mountpoint
inode_in = InodeInternal(
superblock=superblock,
mountpoint=mountpoint,
inode=root_inode,
path=root_path,
)
yield inode_in
# Children
for file_path, file_dentry in cls._walk_dentry(
seen_dentries, root_dentry, parent_dir
):
if not file_dentry:
continue
# Dentry/inode sanity checks
file_inode_ptr = file_dentry.d_inode
if not file_inode_ptr:
continue
file_inode = file_inode_ptr.dereference()
if not file_inode.is_valid():
continue
# Inode already processed?
if file_inode_ptr in seen_inodes:
continue
seen_inodes.add(file_inode_ptr)
file_path = cls._follow_symlink(file_inode_ptr, file_path)
inode_in = InodeInternal(
superblock=superblock,
mountpoint=mountpoint,
inode=file_inode,
path=file_path,
)
yield inode_in
def _generator(self):
vmlinux_module_name = self.config["kernel"]
vmlinux = self.context.modules[vmlinux_module_name]
vmlinux_layer = self.context.layers[vmlinux.layer_name]
inodes_iter = self.get_inodes(
context=self.context,
vmlinux_module_name=vmlinux_module_name,
)
types_filter = self.config["type"]
for inode_in in inodes_iter:
if types_filter and inode_in.inode.get_inode_type() not in types_filter:
continue
if self.config["find"]:
if inode_in.path == self.config["find"]:
inode_out = inode_in.to_user(vmlinux_layer)
yield (0, astuple(inode_out))
break # Only the first match
else:
inode_out = inode_in.to_user(vmlinux_layer)
yield (0, astuple(inode_out))
def generate_timeline(self):
"""Generates tuples of (description, timestamp_type, timestamp)
These need not be generated in any particular order, sorting
will be done later
"""
vmlinux_module_name = self.config["kernel"]
vmlinux = self.context.modules[vmlinux_module_name]
vmlinux_layer = self.context.layers[vmlinux.layer_name]
inodes_iter = self.get_inodes(
context=self.context,
vmlinux_module_name=vmlinux_module_name,
)
for inode_in in inodes_iter:
inode_out = inode_in.to_user(vmlinux_layer)
description = f"Cached Inode for {inode_out.path}"
yield description, timeliner.TimeLinerType.ACCESSED, inode_out.access_time
yield description, timeliner.TimeLinerType.MODIFIED, inode_out.modification_time
yield description, timeliner.TimeLinerType.CHANGE, inode_out.change_time
@staticmethod
def format_fields_with_headers(headers, generator):
"""Uses the headers type to cast the fields obtained from the generator"""
for level, fields in generator:
formatted_fields = []
for header, field in zip(headers, fields):
header_type = header[1]
if isinstance(
field, (header_type, interfaces.renderers.BaseAbsentValue)
):
formatted_field = field
else:
formatted_field = header_type(field)
formatted_fields.append(formatted_field)
yield level, formatted_fields
def run(self):
headers = [
("SuperblockAddr", format_hints.Hex),
("MountPoint", str),
("Device", str),
("InodeNum", int),
("InodeAddr", format_hints.Hex),
("FileType", str),
("InodePages", int),
("CachedPages", int),
("FileMode", str),
("AccessTime", datetime.datetime),
("ModificationTime", datetime.datetime),
("ChangeTime", datetime.datetime),
("FilePath", str),
]
return renderers.TreeGrid(
headers, self.format_fields_with_headers(headers, self._generator())
)
class InodePages(plugins.PluginInterface):
"""Lists and recovers cached inode pages"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="files", plugin=Files, version=(1, 0, 0)
),
requirements.StringRequirement(
name="find",
description="Filename (full path) to find ",
optional=True,
),
requirements.IntRequirement(
name="inode",
description="Inode address",
optional=True,
),
requirements.StringRequirement(
name="dump",
description="Output file path",
optional=True,
),
]
@staticmethod
def write_inode_content_to_file(
inode: interfaces.objects.ObjectInterface,
filename: str,
open_method: Type[interfaces.plugins.FileHandlerInterface],
vmlinux_layer: interfaces.layers.TranslationLayerInterface,
) -> None:
"""Extracts the inode's contents from the page cache and saves them to a file
Args:
inode: The inode to dump
filename: Filename for writing the inode content
open_method: class for constructing output files
vmlinux_layer: The kernel layer to obtain the page size
"""
if not inode.is_reg:
vollog.error("The inode is not a regular file")
return
# By using truncate/seek, provided the filesystem supports it, a sparse file will be
# created, saving both disk space and I/O time.
# Additionally, using the page index will guarantee that each page is written at the
# appropriate file position.
try:
with open_method(filename) as f:
inode_size = inode.i_size
f.truncate(inode_size)
for page_idx, page_content in inode.get_contents():
current_fp = page_idx * vmlinux_layer.page_size
max_length = inode_size - current_fp
page_bytes = page_content[:max_length]
if current_fp + len(page_bytes) > inode_size:
vollog.error(
"Page out of file bounds: inode 0x%x, inode size %d, page index %d",
inode.vol.object,
inode_size,
page_idx,
)
f.seek(current_fp)
f.write(page_bytes)
except IOError as e:
vollog.error("Unable to write to file (%s): %s", filename, e)
def _generator(self):
vmlinux_module_name = self.config["kernel"]
vmlinux = self.context.modules[vmlinux_module_name]
vmlinux_layer = self.context.layers[vmlinux.layer_name]
if self.config["inode"] and self.config["find"]:
vollog.error("Cannot use --inode and --find simultaneously")
return
if self.config["find"]:
inodes_iter = Files.get_inodes(
context=self.context,
vmlinux_module_name=vmlinux_module_name,
)
for inode_in in inodes_iter:
if inode_in.path == self.config["find"]:
inode = inode_in.inode
break # Only the first match
elif self.config["inode"]:
inode = vmlinux.object("inode", self.config["inode"], absolute=True)
else:
vollog.error("You must use either --inode or --find")
return
if not inode.is_reg:
vollog.error("The inode is not a regular file")
return
inode_size = inode.i_size
if not inode.is_valid():
vollog.error("Invalid inode at 0x%x", self.config["inode"])
return
for page_obj in inode.get_pages():
page_vaddr = page_obj.vol.offset
page_paddr = page_obj.to_paddr()
page_mapping_addr = page_obj.mapping
page_index = int(page_obj.index)
page_file_offset = page_index * vmlinux_layer.page_size
dump_safe = page_file_offset < inode_size
page_flags_list = page_obj.get_flags_list()
page_flags = ",".join([x.replace("PG_", "") for x in page_flags_list])
fields = (
page_vaddr,
page_paddr,
page_mapping_addr,
page_index,
dump_safe,
page_flags,
)
yield 0, fields
if self.config["dump"]:
filename = self.config["dump"]
vollog.info("[*] Writing inode at 0x%x to '%s'", inode.vol.offset, filename)
self.write_inode_content_to_file(inode, filename, self.open, vmlinux_layer)
def run(self):
headers = [
("PageVAddr", format_hints.Hex),
("PagePAddr", format_hints.Hex),
("MappingAddr", format_hints.Hex),
("Index", int),
("DumpSafe", bool),
("Flags", str),
]
return renderers.TreeGrid(
headers, Files.format_fields_with_headers(headers, self._generator())
)
@@ -0,0 +1,258 @@
# 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 List
from volatility3.framework import renderers, interfaces, constants
from volatility3.framework.symbols import linux
from volatility3.framework.renderers import format_hints
from volatility3.framework.interfaces import plugins
from volatility3.framework.configuration import requirements
from volatility3.plugins.linux import pslist
vollog = logging.getLogger(__name__)
class PIDHashTable(plugins.PluginInterface):
"""Enumerates processes through the PID hash table"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0)
),
requirements.BooleanRequirement(
name="decorate_comm",
description="Show `user threads` comm in curly brackets, and `kernel threads` comm in square brackets",
optional=True,
default=False,
),
]
def _is_valid_task(self, task) -> bool:
vmlinux = self.context.modules[self.config["kernel"]]
vmlinux_layer = self.context.layers[vmlinux.layer_name]
return bool(task and task.pid > 0 and vmlinux_layer.is_valid(task.parent))
def _get_pidtype_pid(self):
vmlinux = self.context.modules[self.config["kernel"]]
# The pid_type enumeration is present since 2.5.37, just in case
pid_type_enum = vmlinux.get_enumeration("pid_type")
if not pid_type_enum:
vollog.error("Cannot find pid_type enum. Unsupported kernel")
return None
pidtype_pid = pid_type_enum.choices.get("PIDTYPE_PID")
if pidtype_pid is None:
vollog.error("Cannot find PIDTYPE_PID. Unsupported kernel")
return None
# Typically PIDTYPE_PID = 0
return pidtype_pid
def _get_pidhash_array(self):
vmlinux = self.context.modules[self.config["kernel"]]
pidhash_shift = vmlinux.object_from_symbol("pidhash_shift")
pidhash_size = 1 << pidhash_shift
array_type_name = vmlinux.symbol_table_name + constants.BANG + "array"
pidhash_ptr = vmlinux.object_from_symbol("pid_hash")
# pidhash is an array of hlist_heads
pidhash = self._context.object(
array_type_name,
offset=pidhash_ptr,
subtype=vmlinux.get_type("hlist_head"),
count=pidhash_size,
layer_name=vmlinux.layer_name,
)
return pidhash
def _walk_upid(self, seen_upids, upid):
vmlinux = self.context.modules[self.config["kernel"]]
vmlinux_layer = self.context.layers[vmlinux.layer_name]
while upid and vmlinux_layer.is_valid(upid.vol.offset):
if upid.vol.offset in seen_upids:
break
seen_upids.add(upid.vol.offset)
pid_chain = upid.pid_chain
if not (pid_chain and vmlinux_layer.is_valid(pid_chain.vol.offset)):
break
upid = linux.LinuxUtilities.container_of(
pid_chain.next, "upid", "pid_chain", vmlinux
)
def _get_upids(self):
vmlinux = self.context.modules[self.config["kernel"]]
vmlinux_layer = self.context.layers[vmlinux.layer_name]
# 2.6.24 <= kernels < 4.15
pidhash = self._get_pidhash_array()
seen_upids = set()
for hlist in pidhash:
# each entry in the hlist is a upid which is wrapped in a pid
ent = hlist.first
while ent and vmlinux_layer.is_valid(ent.vol.offset):
# upid->pid_chain exists 2.6.24 <= kernel < 4.15
upid = linux.LinuxUtilities.container_of(
ent.vol.offset, "upid", "pid_chain", vmlinux
)
if upid.vol.offset in seen_upids:
break
self._walk_upid(seen_upids, upid)
ent = ent.next
return seen_upids
def _pid_hash_implementation(self):
vmlinux = self.context.modules[self.config["kernel"]]
# 2.6.24 <= kernels < 4.15
task_pids_off = vmlinux.get_type("task_struct").relative_child_offset("pids")
pidtype_pid = self._get_pidtype_pid()
for upid in self._get_upids():
pid = linux.LinuxUtilities.container_of(upid, "pid", "numbers", vmlinux)
if not pid:
continue
pid_tasks_0 = pid.tasks[pidtype_pid].first
if not pid_tasks_0:
continue
task = vmlinux.object(
"task_struct", offset=pid_tasks_0 - task_pids_off, absolute=True
)
if self._is_valid_task(task):
yield task
def _task_for_radix_pid_node(self, nodep):
vmlinux = self.context.modules[self.config["kernel"]]
# kernels >= 4.15
pid = vmlinux.object("pid", offset=nodep, absolute=True)
pidtype_pid = self._get_pidtype_pid()
pid_tasks_0 = pid.tasks[pidtype_pid].first
if not pid_tasks_0:
return None
task_struct_type = vmlinux.get_type("task_struct")
if task_struct_type.has_member("pids"):
member = "pids"
elif task_struct_type.has_member("pid_links"):
member = "pid_links"
else:
return None
task_pids_off = task_struct_type.relative_child_offset(member)
task = vmlinux.object(
"task_struct", offset=pid_tasks_0 - task_pids_off, absolute=True
)
return task
def _pid_namespace_idr(self):
vmlinux = self.context.modules[self.config["kernel"]]
# kernels >= 4.15
ns_addr = vmlinux.get_symbol("init_pid_ns").address
ns = vmlinux.object("pid_namespace", offset=ns_addr)
for page_addr in ns.idr.get_entries():
task = self._task_for_radix_pid_node(page_addr)
if self._is_valid_task(task):
yield task
def _determine_pid_func(self):
vmlinux = self.context.modules[self.config["kernel"]]
pid_hash = vmlinux.has_symbol("pid_hash") and vmlinux.has_symbol(
"pidhash_shift"
) # 2.5.55 <= kernels < 4.15
has_pid_numbers = vmlinux.has_type("pid") and vmlinux.get_type(
"pid"
).has_member(
"numbers"
) # kernels >= 2.6.24
has_pid_chain = vmlinux.has_type("upid") and vmlinux.get_type(
"upid"
).has_member(
"pid_chain"
) # 2.6.24 <= kernels < 4.15
# kernels >= 4.15
pid_idr = vmlinux.has_type("pid_namespace") and vmlinux.get_type(
"pid_namespace"
).has_member("idr")
if pid_idr:
# kernels >= 4.15
return self._pid_namespace_idr
elif pid_hash and has_pid_numbers and has_pid_numbers and has_pid_chain:
# 2.6.24 <= kernels < 4.15
return self._pid_hash_implementation
return None
def get_tasks(self) -> interfaces.objects.ObjectInterface:
"""Enumerates processes through the PID hash table
Yields:
task_struct objects
"""
pid_func = self._determine_pid_func()
if not pid_func:
vollog.error("Cannot determine which PID hash table this kernel is using")
return
yield from sorted(pid_func(), key=lambda t: (t.tgid, t.pid))
def _generator(
self, decorate_comm: bool = False
) -> interfaces.objects.ObjectInterface:
for task in self.get_tasks():
offset, pid, tid, ppid, name = pslist.PsList.get_task_fields(
task, decorate_comm
)
fields = format_hints.Hex(offset), pid, tid, ppid, name
yield 0, fields
def run(self):
decorate_comm = self.config.get("decorate_comm")
headers = [
("OFFSET", format_hints.Hex),
("PID", int),
("TID", int),
("PPID", int),
("COMM", str),
]
return renderers.TreeGrid(headers, self._generator(decorate_comm=decorate_comm))
@@ -151,17 +151,15 @@ class SockHandlers(interfaces.configuration.VersionableInterface):
bpfprog = sock_filter.prog
BPF_PROG_TYPE_UNSPEC = 0 # cBPF filter
try:
bpfprog_type = bpfprog.get_type()
if bpfprog_type == BPF_PROG_TYPE_UNSPEC:
return # cBPF filter
except AttributeError:
bpfprog_type = bpfprog.get_type()
if not bpfprog_type:
# kernel < 3.18.140, it's a cBPF filter
return None
BPF_PROG_TYPE_SOCKET_FILTER = 1 # eBPF filter
if bpfprog_type != BPF_PROG_TYPE_SOCKET_FILTER:
if bpfprog_type == "BPF_PROG_TYPE_UNSPEC":
return None # cBPF filter
if bpfprog_type != "BPF_PROG_TYPE_SOCKET_FILTER":
socket_filter["bpf_filter_type"] = f"UNK({bpfprog_type})"
vollog.warning(f"Unexpected BPF type {bpfprog_type} for a socket")
return None
@@ -31,7 +31,7 @@ class VmaYaraScan(interfaces.plugins.PluginInterface):
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="yarascan", plugin=yarascan.YaraScan, version=(1, 2, 0)
name="yarascan", plugin=yarascan.YaraScan, version=(2, 0, 0)
),
requirements.ModuleRequirement(
name="kernel",
+3 -1
View File
@@ -105,7 +105,9 @@ class Timeliner(interfaces.plugins.PluginInterface):
data = item[1]
def sortable(timestamp):
max_date = datetime.datetime(day=1, month=12, year=datetime.MAXYEAR)
max_date = datetime.datetime(
day=1, month=12, year=datetime.MAXYEAR, tzinfo=datetime.timezone.utc
)
if isinstance(timestamp, interfaces.renderers.BaseAbsentValue):
return max_date
return timestamp
@@ -1,10 +1,9 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# 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 contextlib
import datetime
import logging
import ntpath
import re
from typing import List, Optional, Type
@@ -14,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, psscan
from volatility3.plugins.windows import info, pslist, psscan, pedump
vollog = logging.getLogger(__name__)
@@ -23,7 +22,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Lists the loaded modules in a particular windows memory image."""
_required_framework_version = (2, 0, 0)
_version = (2, 0, 1)
_version = (3, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -76,67 +75,11 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
default=False,
optional=True,
),
requirements.VersionRequirement(
name="pedump", component=pedump.PEDump, version=(1, 0, 0)
),
]
@classmethod
def dump_pe(
cls,
context: interfaces.context.ContextInterface,
pe_table_name: str,
dll_entry: interfaces.objects.ObjectInterface,
open_method: Type[interfaces.plugins.FileHandlerInterface],
layer_name: str = None,
prefix: str = "",
) -> Optional[interfaces.plugins.FileHandlerInterface]:
"""Extracts the complete data for a process as a FileInterface
Args:
context: the context to operate upon
pe_table_name: the name for the symbol table containing the PE format symbols
dll_entry: the object representing the module
layer_name: the layer that the DLL lives within
open_method: class for constructing output files
Returns:
An open FileHandlerInterface object containing the complete data for the DLL or None in the case of failure
"""
try:
try:
name = dll_entry.FullDllName.get_string()
except exceptions.InvalidAddressException:
name = "UnreadableDLLName"
if layer_name is None:
layer_name = dll_entry.vol.layer_name
file_handle = open_method(
"{}{}.{:#x}.{:#x}.dmp".format(
prefix,
ntpath.basename(name),
dll_entry.vol.offset,
dll_entry.DllBase,
)
)
dos_header = context.object(
pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER",
offset=dll_entry.DllBase,
layer_name=layer_name,
)
for offset, data in dos_header.reconstruct():
file_handle.seek(offset)
file_handle.write(data)
except (
IOError,
exceptions.VolatilityException,
OverflowError,
ValueError,
) as excp:
vollog.debug(f"Unable to dump dll at offset {dll_entry.DllBase}: {excp}")
return None
return file_handle
def _generator(self, procs):
pe_table_name = intermed.IntermediateSymbolTable.create(
self.context, self.config_path, "windows", "pe", class_types=pe.class_types
@@ -204,7 +147,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
file_output = "Disabled"
if self.config["dump"]:
file_handle = self.dump_pe(
file_output = pedump.PEDump.dump_ldr_entry(
self.context,
pe_table_name,
entry,
@@ -212,10 +155,10 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
proc_layer_name,
prefix=f"pid.{proc_id}.",
)
file_output = "Error outputting file"
if file_handle:
file_handle.close()
file_output = file_handle.preferred_filename
if not file_output:
file_output = "Error outputting file"
try:
dllbase = format_hints.Hex(entry.DllBase)
except exceptions.InvalidAddressException:
@@ -14,6 +14,7 @@ class FileScan(interfaces.plugins.PluginInterface):
"""Scans for file objects present in a particular windows memory image."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 1)
@classmethod
def get_requirements(cls):
@@ -67,10 +68,10 @@ class FileScan(interfaces.plugins.PluginInterface):
except exceptions.InvalidAddressException:
continue
yield (0, (format_hints.Hex(fileobj.vol.offset), file_name, fileobj.Size))
yield (0, (format_hints.Hex(fileobj.vol.offset), file_name))
def run(self):
return renderers.TreeGrid(
[("Offset", format_hints.Hex), ("Name", str), ("Size", int)],
[("Offset", format_hints.Hex), ("Name", str)],
self._generator(),
)
@@ -25,7 +25,7 @@ class Handles(interfaces.plugins.PluginInterface):
"""Lists process open handles."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 1)
_version = (1, 0, 2)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -142,6 +142,7 @@ class Handles(interfaces.plugins.PluginInterface):
pointers in the _HANDLE_TABLE_ENTRY which allows us to find the
associated _OBJECT_HEADER.
"""
DEFAULT_SAR_VALUE = 0x10 # to be used only when decoding fails
if self._sar_value is None:
if not has_capstone:
@@ -175,10 +176,11 @@ class Handles(interfaces.plugins.PluginInterface):
virtual_layer_name, func_addr_to_read, num_bytes_to_read
)
except exceptions.InvalidAddressException:
vollog.debug(
f"Failed to read {hex(num_bytes_to_read)} bytes at symbol {hex(func_addr_to_read)}"
vollog.warning(
f"Failed to read {hex(num_bytes_to_read)} bytes at symbol {hex(func_addr_to_read)}. Unable to decode SAR value. Failing back to a common value of {hex(DEFAULT_SAR_VALUE)}"
)
return None
self._sar_value = DEFAULT_SAR_VALUE
return self._sar_value
md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64)
@@ -198,9 +200,10 @@ class Handles(interfaces.plugins.PluginInterface):
break
if self._sar_value is None:
vollog.debug(
f"Failed to to locate SAR value having parsed {instruction_count} instructions"
vollog.warning(
f"Failed to to locate SAR value having parsed {instruction_count} instructions, failing back to a common value of {hex(DEFAULT_SAR_VALUE)}"
)
self._sar_value = DEFAULT_SAR_VALUE
return self._sar_value
@@ -10,7 +10,7 @@ from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.renderers import TreeGrid
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.windows import extensions
from volatility3.framework.symbols.windows.extensions import kdbg, pe
class Info(plugins.PluginInterface):
@@ -94,16 +94,16 @@ class Info(plugins.PluginInterface):
"windows",
"kdbg",
native_types=native_types,
class_types=extensions.kdbg.class_types,
class_types=kdbg.class_types,
)
kdbg = context.object(
kdbg_obj = context.object(
kdbg_table_name + constants.BANG + "_KDDEBUGGER_DATA64",
offset=ntkrnlmp.offset + kdbg_offset,
layer_name=layer_name,
)
return kdbg
return kdbg_obj
@classmethod
def get_kuser_structure(
@@ -173,7 +173,7 @@ class Info(plugins.PluginInterface):
interfaces.configuration.path_join(config_path, "pe"),
"windows",
"pe",
class_types=extensions.pe.class_types,
class_types=pe.class_types,
)
dos_header = context.object(
@@ -0,0 +1,106 @@
# 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 Iterator, List, Tuple
from volatility3.framework import (
renderers,
interfaces,
constants,
)
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
vollog = logging.getLogger(__name__)
class KPCRs(interfaces.plugins.PluginInterface):
"""Print KPCR structure for each processor"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
]
@classmethod
def list_kpcrs(
cls,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
layer_name: str,
symbol_table: str,
) -> interfaces.objects.ObjectInterface:
"""Returns the KPCR structure for each processor
Args:
context: The context to retrieve required elements (layers, symbol tables) from
kernel_module_name: The name of the kernel module on which to operate
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
Returns:
The _KPCR structure for each processor
"""
kernel = context.modules[kernel_module_name]
cpu_count_offset = kernel.get_symbol("KeNumberProcessors").address
cpu_count = kernel.object(
object_type="unsigned int", layer_name=layer_name, offset=cpu_count_offset
)
processor_block = kernel.object(
object_type="pointer",
layer_name=layer_name,
offset=kernel.get_symbol("KiProcessorBlock").address,
)
processor_pointers = utility.array_of_pointers(
context=context,
array=processor_block,
count=cpu_count,
subtype=symbol_table + constants.BANG + "_KPRCB",
)
for pointer in processor_pointers:
kprcb = pointer.dereference()
reloff = kernel.get_type("_KPCR").relative_child_offset("Prcb")
kpcr = context.object(
symbol_table + constants.BANG + "_KPCR",
offset=kprcb.vol.offset - reloff,
layer_name=layer_name,
)
yield kpcr
def _generator(self) -> Iterator[Tuple]:
kernel = self.context.modules[self.config["kernel"]]
layer_name = kernel.layer_name
symbol_table = kernel.symbol_table_name
for kpcr in self.list_kpcrs(
self.context, self.config["kernel"], layer_name, symbol_table
):
yield (
0,
(
format_hints.Hex(kpcr.vol.offset),
format_hints.Hex(kpcr.CurrentPrcb),
),
)
def run(self):
return renderers.TreeGrid(
[
("Offset", format_hints.Hex),
("PRCB Offset", format_hints.Hex),
],
self._generator(),
)
@@ -47,14 +47,6 @@ class LdrModules(interfaces.plugins.PluginInterface):
self.context, self.config_path, "windows", "pe", class_types=pe.class_types
)
def filter_function(x: interfaces.objects.ObjectInterface) -> bool:
try:
return not (x.get_private_memory() == 0 and x.ControlArea)
except AttributeError:
return False
filter_func = filter_function
for proc in procs:
proc_layer_name = proc.add_process_layer()
@@ -69,7 +61,7 @@ class LdrModules(interfaces.plugins.PluginInterface):
# Build dictionary of mapped files, where the VAD start address is the key and value is the file name of the mapped file
mapped_files = {}
for vad in vadinfo.VadInfo.list_vads(proc, filter_func=filter_func):
for vad in vadinfo.VadInfo.list_vads(proc):
dos_header = self.context.object(
pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER",
offset=vad.get_start(),
@@ -29,7 +29,7 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0)
name="yarascanner", component=yarascan.YaraScanner, version=(2, 1, 0)
),
]
@@ -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_string": "/FILE0|FILE\\*|BAAD/"}
)
# Read in the Symbol File
@@ -197,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_string": "/FILE0|FILE\\*|BAAD/"}
)
# Read in the Symbol File
+24 -166
View File
@@ -2,23 +2,24 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import Iterable, List, Generator
from typing import Iterable
from volatility3.framework import renderers, interfaces, exceptions, constants
from volatility3.framework import interfaces
from volatility3.framework.configuration import requirements
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 poolscanner, dlllist, pslist
from volatility3.plugins.windows import poolscanner, modules, pedump
vollog = logging.getLogger(__name__)
class ModScan(interfaces.plugins.PluginInterface):
class ModScan(modules.Modules):
"""Scans for modules present in a particular windows memory image."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
_version = (2, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._enumeration_method = self.scan_modules
@classmethod
def get_requirements(cls):
@@ -32,10 +33,7 @@ class ModScan(interfaces.plugins.PluginInterface):
name="poolscanner", component=poolscanner.PoolScanner, version=(1, 0, 0)
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="dlllist", component=dlllist.DllList, version=(2, 0, 0)
name="modules", component=modules.Modules, version=(2, 0, 0)
),
requirements.BooleanRequirement(
name="dump",
@@ -43,6 +41,20 @@ class ModScan(interfaces.plugins.PluginInterface):
default=False,
optional=True,
),
requirements.IntRequirement(
name="base",
description="Extract a single module with BASE address",
optional=True,
),
requirements.StringRequirement(
name="name",
description="module name/sub string",
optional=True,
default=None,
),
requirements.VersionRequirement(
name="pedump", component=pedump.PEDump, version=(1, 0, 0)
),
]
@classmethod
@@ -72,157 +84,3 @@ class ModScan(interfaces.plugins.PluginInterface):
):
_constraint, mem_object, _header = result
yield mem_object
@classmethod
def get_session_layers(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
pids: List[int] = None,
) -> Generator[str, None, None]:
"""Build a cache of possible virtual layers, in priority starting with
the primary/kernel layer. Then keep one layer per session by cycling
through the process list.
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
pids: A list of process identifiers to include exclusively or None for no filter
Returns:
A list of session layer names
"""
seen_ids: List[interfaces.objects.ObjectInterface] = []
filter_func = pslist.PsList.create_pid_filter(pids or [])
for proc in pslist.PsList.list_processes(
context=context,
layer_name=layer_name,
symbol_table=symbol_table,
filter_func=filter_func,
):
proc_id = "Unknown"
try:
proc_id = proc.UniqueProcessId
proc_layer_name = proc.add_process_layer()
# create the session space object in the process' own layer.
# not all processes have a valid session pointer.
session_space = context.object(
symbol_table + constants.BANG + "_MM_SESSION_SPACE",
layer_name=layer_name,
offset=proc.Session,
)
if session_space.SessionId in seen_ids:
continue
except exceptions.InvalidAddressException:
vollog.log(
constants.LOGLEVEL_VVV,
"Process {} does not have a valid Session or a layer could not be constructed for it".format(
proc_id
),
)
continue
# save the layer if we haven't seen the session yet
seen_ids.append(session_space.SessionId)
yield proc_layer_name
@classmethod
def find_session_layer(
cls,
context: interfaces.context.ContextInterface,
session_layers: Iterable[str],
base_address: int,
):
"""Given a base address and a list of layer names, find a layer that
can access the specified address.
Args:
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
session_layers: A list of session layer names
base_address: The base address to identify the layers that can access it
Returns:
Layer name or None if no layers that contain the base address can be found
"""
for layer_name in session_layers:
if context.layers[layer_name].is_valid(base_address):
return layer_name
return None
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
session_layers = list(
self.get_session_layers(
self.context, kernel.layer_name, kernel.symbol_table_name
)
)
pe_table_name = intermed.IntermediateSymbolTable.create(
self.context, self.config_path, "windows", "pe", class_types=pe.class_types
)
for mod in self.scan_modules(
self.context, kernel.layer_name, kernel.symbol_table_name
):
try:
BaseDllName = mod.BaseDllName.get_string()
except exceptions.InvalidAddressException:
BaseDllName = ""
try:
FullDllName = mod.FullDllName.get_string()
except exceptions.InvalidAddressException:
FullDllName = ""
file_output = "Disabled"
if self.config["dump"]:
session_layer_name = self.find_session_layer(
self.context, session_layers, mod.DllBase
)
file_output = f"Cannot find a viable session layer for {mod.DllBase:#x}"
if session_layer_name:
file_handle = dlllist.DllList.dump_pe(
self.context,
pe_table_name,
mod,
self.open,
layer_name=session_layer_name,
)
file_output = "Error outputting file"
if file_handle:
file_output = file_handle.preferred_filename
yield (
0,
(
format_hints.Hex(mod.vol.offset),
format_hints.Hex(mod.DllBase),
format_hints.Hex(mod.SizeOfImage),
BaseDllName,
FullDllName,
file_output,
),
)
def run(self):
return renderers.TreeGrid(
[
("Offset", format_hints.Hex),
("Base", format_hints.Hex),
("Size", format_hints.Hex),
("Name", str),
("Path", str),
("File output", str),
],
self._generator(),
)
@@ -4,14 +4,12 @@
import logging
from typing import List, Iterable, Generator
from volatility3.framework import constants
from volatility3.framework import exceptions, interfaces
from volatility3.framework import renderers
from volatility3.framework import exceptions, interfaces, constants, renderers
from volatility3.framework.configuration import requirements
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 pslist, dlllist
from volatility3.plugins.windows import pslist, pedump
vollog = logging.getLogger(__name__)
@@ -20,7 +18,11 @@ class Modules(interfaces.plugins.PluginInterface):
"""Lists the loaded kernel modules."""
_required_framework_version = (2, 0, 0)
_version = (1, 1, 0)
_version = (2, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._enumeration_method = self.list_modules
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -33,65 +35,97 @@ class Modules(interfaces.plugins.PluginInterface):
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="dlllist", component=dlllist.DllList, version=(2, 0, 0)
),
requirements.BooleanRequirement(
name="dump",
description="Extract listed modules",
default=False,
optional=True,
),
requirements.IntRequirement(
name="base",
description="Extract a single module with BASE address",
optional=True,
),
requirements.StringRequirement(
name="name",
description="module name/sub string",
optional=True,
default=None,
),
requirements.VersionRequirement(
name="pedump", component=pedump.PEDump, version=(1, 0, 0)
),
]
def dump_module(self, session_layers, pe_table_name, mod):
session_layer_name = self.find_session_layer(
self.context, session_layers, mod.DllBase
)
file_output = f"Cannot find a viable session layer for {mod.DllBase:#x}"
if session_layer_name:
file_output = pedump.PEDump.dump_ldr_entry(
self.context,
pe_table_name,
mod,
self.open,
layer_name=session_layer_name,
)
if not file_output:
file_output = "Error outputting file"
return file_output
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
pe_table_name = intermed.IntermediateSymbolTable.create(
self.context, self.config_path, "windows", "pe", class_types=pe.class_types
)
for mod in self.list_modules(
pe_table_name = None
session_layers = None
if self.config["dump"]:
pe_table_name = intermed.IntermediateSymbolTable.create(
self.context,
self.config_path,
"windows",
"pe",
class_types=pe.class_types,
)
session_layers = list(
self.get_session_layers(
self.context, kernel.layer_name, kernel.symbol_table_name
)
)
for mod in self._enumeration_method(
self.context, kernel.layer_name, kernel.symbol_table_name
):
if self.config["base"] and self.config["base"] != mod.DllBase:
continue
try:
BaseDllName = mod.BaseDllName.get_string()
except exceptions.InvalidAddressException:
BaseDllName = ""
try:
FullDllName = mod.FullDllName.get_string()
except exceptions.InvalidAddressException:
FullDllName = ""
BaseDllName = interfaces.renderers.BaseAbsentValue()
if self.config["name"] and self.config["name"] not in BaseDllName:
continue
try:
FullDllName = mod.FullDllName.get_string()
except exceptions.InvalidAddressException:
FullDllName = interfaces.renderers.BaseAbsentValue()
file_output = "Disabled"
if self.config["dump"]:
file_handle = dlllist.DllList.dump_pe(
self.context, pe_table_name, mod, self.open
)
file_output = "Error outputting file"
if file_handle:
file_handle.close()
file_output = file_handle.preferred_filename
file_output = self.dump_module(session_layers, pe_table_name, mod)
yield (
0,
(
format_hints.Hex(mod.vol.offset),
format_hints.Hex(mod.DllBase),
format_hints.Hex(mod.SizeOfImage),
BaseDllName,
FullDllName,
file_output,
),
yield 0, (
format_hints.Hex(mod.vol.offset),
format_hints.Hex(mod.DllBase),
format_hints.Hex(mod.SizeOfImage),
BaseDllName,
FullDllName,
file_output,
)
@classmethod
@@ -218,6 +218,7 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
(10, 0, 18362, 0): "netscan-win10-18362-x64",
(10, 0, 18363, 0): "netscan-win10-18363-x64",
(10, 0, 19041, 0): "netscan-win10-19041-x64",
(10, 0, 20348, 0): "netscan-win10-20348-x64",
}
# we do not need to check for tcpip's specific FileVersion in every case
@@ -35,7 +35,7 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
name="netscan", component=netscan.NetScan, version=(1, 0, 0)
),
requirements.VersionRequirement(
name="modules", component=modules.Modules, version=(1, 0, 0)
name="modules", component=modules.Modules, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="pdbutil", component=pdbutil.PDBUtility, version=(1, 0, 0)
@@ -0,0 +1,96 @@
# 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 List, Generator
from volatility3.framework import interfaces, symbols
from volatility3.framework.configuration import requirements
from volatility3.plugins.windows import thrdscan, ssdt
vollog = logging.getLogger(__name__)
class Threads(thrdscan.ThrdScan):
"""Lists process threads"""
_required_framework_version = (2, 4, 0)
_version = (1, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.implementation = self.list_orphan_kernel_threads
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="thrdscan", plugin=thrdscan.ThrdScan, version=(1, 1, 0)
),
requirements.PluginRequirement(
name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0)
),
]
@classmethod
def list_orphan_kernel_threads(
cls,
context: interfaces.context.ContextInterface,
module_name: str,
) -> Generator[interfaces.objects.ObjectInterface, None, None]:
"""Yields thread objects of kernel threads that do not map to a module
Args:
cls
context: the context to operate upon
module_name: name of the module to use for scanning
Returns:
A generator of thread objects of orphaned threads
"""
module = context.modules[module_name]
layer_name = module.layer_name
symbol_table = module.symbol_table_name
collection = ssdt.SSDT.build_module_collection(
context, layer_name, symbol_table
)
# FIXME - use a proper constant once established
# used to filter out smeared pointers
if symbols.symbol_table_is_64bit(context, symbol_table):
kernel_start = 0xFFFFF80000000000
else:
kernel_start = 0x80000000
for thread in thrdscan.ThrdScan.scan_threads(context, module_name):
# we don't want smeared or terminated threads
try:
proc = thread.owning_process()
except AttributeError:
continue
# we only care about kernel threads, 4 = System
# previous methods for determining if a thread was a kernel thread
# such as bit fields and flags are not stable in Win10+
# so we check if the thread is from the kernel itself or one its child
# kernel processes (MemCompression, Regsitry, ...)
if proc.UniqueProcessId != 4 and proc.InheritedFromUniqueProcessId != 4:
continue
if thread.StartAddress < kernel_start:
continue
module_symbols = list(
collection.get_module_symbols_by_absolute_location(thread.StartAddress)
)
# alert on threads that do not map to a module
if not module_symbols:
yield thread
@@ -0,0 +1,270 @@
# 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
import ntpath
from typing import List, Type, Optional
from volatility3.framework import constants, exceptions, interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.windows.extensions import pe
from volatility3.plugins.windows import pslist, modules
vollog = logging.getLogger(__name__)
class PEDump(interfaces.plugins.PluginInterface):
"""Allows extracting PE Files from a specific address in a specific address space"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [
requirements.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 IDs to include (all other processes are excluded)",
optional=True,
),
requirements.IntRequirement(
name="base",
description="Base address to reconstruct a PE file",
optional=False,
),
requirements.BooleanRequirement(
name="kernel_module",
description="Extract from kernel address space.",
default=False,
optional=True,
),
]
@classmethod
def dump_pe(
cls,
context: interfaces.context.ContextInterface,
pe_table_name: str,
layer_name: str,
open_method: Type[interfaces.plugins.FileHandlerInterface],
file_name: str,
base: int,
) -> Optional[str]:
"""
Returns the filename of the dump file or None
"""
try:
file_handle = open_method(file_name)
dos_header = context.object(
pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER",
offset=base,
layer_name=layer_name,
)
for offset, data in dos_header.reconstruct():
file_handle.seek(offset)
file_handle.write(data)
except (
IOError,
exceptions.VolatilityException,
OverflowError,
ValueError,
) as excp:
vollog.debug(f"Unable to dump PE file at offset {base}: {excp}")
return None
finally:
file_handle.close()
return file_handle.preferred_filename
@classmethod
def dump_ldr_entry(
cls,
context: interfaces.context.ContextInterface,
pe_table_name: str,
ldr_entry: interfaces.objects.ObjectInterface,
open_method: Type[interfaces.plugins.FileHandlerInterface],
layer_name: str = None,
prefix: str = "",
) -> Optional[str]:
"""Extracts the PE file referenced an LDR_DATA_TABLE_ENTRY (DLL, kernel module) instance
Args:
context: the context to operate upon
pe_table_name: the name for the symbol table containing the PE format symbols
ldr_entry: the object representing the module
open_method: class for constructing output files
layer_name: the layer that the DLL lives within
prefix: optional string to prepend to filename
Returns:
The output file name or None in the case of failure
"""
try:
name = ldr_entry.FullDllName.get_string()
except exceptions.InvalidAddressException:
name = "UnreadableDLLName"
if layer_name is None:
layer_name = ldr_entry.vol.layer_name
file_name = "{}{}.{:#x}.{:#x}.dmp".format(
prefix,
ntpath.basename(name),
ldr_entry.vol.offset,
ldr_entry.DllBase,
)
return cls.dump_pe(
context,
pe_table_name,
layer_name,
open_method,
file_name,
ldr_entry.DllBase,
)
@classmethod
def dump_pe_at_base(
cls,
context: interfaces.context.ContextInterface,
pe_table_name: str,
layer_name: str,
open_method: Type[interfaces.plugins.FileHandlerInterface],
proc_offset: int,
pid: int,
base: int,
) -> Optional[str]:
file_name = "PE.{:#x}.{:d}.{:#x}.dmp".format(
proc_offset,
pid,
base,
)
return PEDump.dump_pe(
context, pe_table_name, layer_name, open_method, file_name, base
)
@classmethod
def dump_kernel_pe_at_base(cls, context, kernel, pe_table_name, open_method, base):
session_layers = modules.Modules.get_session_layers(
context, kernel.layer_name, kernel.symbol_table_name
)
session_layer_name = modules.Modules.find_session_layer(
context, session_layers, base
)
if session_layer_name:
system_pid = 4
file_output = PEDump.dump_pe_at_base(
context,
pe_table_name,
session_layer_name,
open_method,
0,
system_pid,
base,
)
if file_output:
yield system_pid, "Kernel", file_output
else:
vollog.warning(
"Unable to find a session layer with the provided base address mapped in the kernel."
)
@classmethod
def dump_processes(
cls, context, kernel, pe_table_name, open_method, filter_func, base
):
""" """
for proc in pslist.PsList.list_processes(
context=context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
filter_func=filter_func,
):
pid = proc.UniqueProcessId
proc_name = proc.ImageFileName.cast(
"string",
max_length=proc.ImageFileName.vol.count,
errors="replace",
)
proc_layer_name = proc.add_process_layer()
file_output = PEDump.dump_pe_at_base(
context,
pe_table_name,
proc_layer_name,
open_method,
proc.vol.offset,
pid,
base,
)
if file_output:
yield pid, proc_name, file_output
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
pe_table_name = intermed.IntermediateSymbolTable.create(
self.context, self.config_path, "windows", "pe", class_types=pe.class_types
)
if self.config["kernel_module"] and self.config["pid"]:
vollog.error("Only --kernel_module or --pid should be set. Not both")
return
if not self.config["kernel_module"] and not self.config["pid"]:
vollog.error("--kernel_module or --pid must be set")
return
if self.config["kernel_module"]:
pe_files = self.dump_kernel_pe_at_base(
self.context, kernel, pe_table_name, self.open, self.config["base"]
)
else:
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
pe_files = self.dump_processes(
self.context,
kernel,
pe_table_name,
self.open,
filter_func,
self.config["base"],
)
for pid, proc_name, file_output in pe_files:
yield (
0,
(
pid,
proc_name,
file_output,
),
)
def run(self):
return renderers.TreeGrid(
[
("PID", int),
("Process", str),
("File output", str),
],
self._generator(),
)
@@ -0,0 +1,104 @@
# 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
import contextlib
from volatility3.framework import interfaces, exceptions
from volatility3.framework import renderers
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
vollog = logging.getLogger(__name__)
class ProcessGhosting(interfaces.plugins.PluginInterface):
"""Lists processes whose DeletePending bit is set or whose FILE_OBJECT is set to 0"""
_required_framework_version = (2, 4, 0)
@classmethod
def get_requirements(cls):
# Since we're calling the plugin, make sure we have the plugin's requirements
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
),
]
def _generator(self, procs):
kernel = self.context.modules[self.config["kernel"]]
if not kernel.get_type("_EPROCESS").has_member("ImageFilePointer"):
vollog.warning(
"This plugin only supports Windows 10 builds when the ImageFilePointer member of _EPROCESS is present"
)
return
for proc in procs:
delete_pending = renderers.UnreadableValue()
process_name = utility.array_to_string(proc.ImageFileName)
# if it is 0 then its a side effect of process ghosting
if proc.ImageFilePointer.vol.offset != 0:
try:
file_object = proc.ImageFilePointer
delete_pending = file_object.DeletePending
except exceptions.InvalidAddressException:
file_object = 0
# ImageFilePointer equal to 0 means process ghosting or similar techniques were used
else:
file_object = 0
if isinstance(delete_pending, int) and delete_pending not in [0, 1]:
vollog.debug(
f"Invalid delete_pending value {delete_pending} found for {process_name} {proc.UniqueProcessId}"
)
# delete_pending besides 0 or 1 = smear
if file_object == 0 or delete_pending == 1:
path = renderers.UnreadableValue()
if file_object:
with contextlib.suppress(exceptions.InvalidAddressException):
path = file_object.FileName.String
yield (
0,
(
proc.UniqueProcessId,
process_name,
format_hints.Hex(file_object),
delete_pending,
path,
),
)
def run(self):
filter_func = pslist.PsList.create_active_process_filter()
kernel = self.context.modules[self.config["kernel"]]
return renderers.TreeGrid(
[
("PID", int),
("Process", str),
("FILE_OBJECT", format_hints.Hex),
("DeletePending", str),
("Path", 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,
)
),
)
@@ -136,6 +136,29 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
filter_func = lambda x: x.UniqueProcessId not in filter_list
return filter_func
@classmethod
def create_active_process_filter(
cls,
) -> Callable[[interfaces.objects.ObjectInterface], bool]:
"""A factory for producing a filter function that only returns
active, userland processes. This prevents plugins from operating on terminated
processes that are still in the process list due to smear or handle leaks as well
as kernel processes (System, Registry, etc.). Use of this filter for plugins searching
for system state anomalies significantly reduces false positive in smeared and terminated
processes.
Returns:
Filter function for passing to the `list_processes` method
"""
return lambda x: not (
x.is_valid()
and x.ActiveThreads > 0
and x.UniqueProcessId != 4
and x.InheritedFromUniqueProcessId != 4
and x.ExitTime.QuadPart == 0
and x.get_handle_count() != renderers.UnreadableValue()
)
@classmethod
def create_name_filter(
cls, name_list: List[str] = None, exclude: bool = False
+20 -12
View File
@@ -266,20 +266,28 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
if proc.vol.layer_name == kernel.layer_name:
vproc = proc
else:
vproc = self.virtual_process_from_physical(
self.context, kernel.layer_name, kernel.symbol_table_name, proc
try:
vproc = self.virtual_process_from_physical(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
proc,
)
except exceptions.PagedInvalidAddressException:
vproc = None
file_output = "Error outputting file"
if vproc:
file_handle = pslist.PsList.process_dump(
self.context,
kernel.symbol_table_name,
pe_table_name,
vproc,
self.open,
)
file_handle = pslist.PsList.process_dump(
self.context,
kernel.symbol_table_name,
pe_table_name,
vproc,
self.open,
)
file_output = "Error outputting file"
if file_handle:
file_output = file_handle.preferred_filename
if file_handle:
file_output = file_handle.preferred_filename
if not self.config["physical"]:
offset = proc.vol.offset
@@ -0,0 +1,251 @@
import datetime, logging, string
from volatility3.framework import constants, exceptions
from volatility3.framework.interfaces import plugins
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints, TreeGrid
from volatility3.plugins.windows import (
handles,
info,
pslist,
psscan,
sessions,
thrdscan,
)
vollog = logging.getLogger(__name__)
class PsXView(plugins.PluginInterface):
"""Lists all processes found via four of the methods described in \"The Art of Memory Forensics,\" which may help
identify processes that are trying to hide themselves. I recommend using -r pretty if you are looking at this
plugin's output in a terminal."""
# I've omitted the desktop thread scanning method because Volatility3 doesn't appear to have the funcitonality
# which the original plugin used to do it.
# The sessions method is omitted because it begins with the list of processes found by Pslist anyway.
# Lastly, I've omitted the pspcid method because I could not for the life of me get it to work. I saved the
# code I do have from it, and will happily share it if anyone else wants to add it.
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
valid_proc_name_chars = set(
string.ascii_lowercase + string.ascii_uppercase + "." + " "
)
@classmethod
def get_requirements(cls):
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="info", component=info.Info, version=(1, 0, 0)
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="psscan", component=psscan.PsScan, version=(1, 0, 0)
),
requirements.VersionRequirement(
name="thrdscan", component=thrdscan.ThrdScan, version=(1, 0, 0)
),
requirements.VersionRequirement(
name="handles", component=handles.Handles, version=(1, 0, 0)
),
requirements.BooleanRequirement(
name="physical-offsets",
description="List processes with physical offsets instead of virtual offsets.",
optional=True,
),
]
def _proc_name_to_string(self, proc):
return proc.ImageFileName.cast(
"string", max_length=proc.ImageFileName.vol.count, errors="replace"
)
def _is_valid_proc_name(self, str):
for c in str:
if not c in self.valid_proc_name_chars:
return False
return True
def _filter_garbage_procs(self, proc_list):
return [
p
for p in proc_list
if p.is_valid() and self._is_valid_proc_name(self._proc_name_to_string(p))
]
def _translate_offset(self, offset):
if not self.config["physical-offsets"]:
return offset
kernel = self.context.modules[self.config["kernel"]]
layer_name = kernel.layer_name
try:
_original_offset, _original_length, offset, _length, _layer_name = list(
self.context.layers[layer_name].mapping(offset=offset, length=0)
)[0]
except exceptions.PagedInvalidAddressException:
vollog.debug(f"Page fault: unable to translate {offset:0x}")
return offset
def _proc_list_to_dict(self, tasks):
tasks = self._filter_garbage_procs(tasks)
return {self._translate_offset(proc.vol.offset): proc for proc in tasks}
def _check_pslist(self, tasks):
return self._proc_list_to_dict(tasks)
def _check_psscan(self, layer_name, symbol_table):
res = psscan.PsScan.scan_processes(
context=self.context, layer_name=layer_name, symbol_table=symbol_table
)
return self._proc_list_to_dict(res)
def _check_thrdscan(self):
ret = []
for ethread in thrdscan.ThrdScan.scan_threads(
self.context, module_name="kernel"
):
process = None
try:
process = ethread.owning_process()
if not process.is_valid():
continue
ret.append(process)
except AttributeError:
vollog.log(
constants.LOGLEVEL_VVV,
"Unable to find the owning process of ethread",
)
return self._proc_list_to_dict(ret)
def _check_csrss_handles(self, tasks, layer_name, symbol_table):
ret = []
for p in tasks:
name = self._proc_name_to_string(p)
if name == "csrss.exe":
try:
if p.has_member("ObjectTable"):
handles_plugin = handles.Handles(
context=self.context, config_path=self.config_path
)
hndls = list(handles_plugin.handles(p.ObjectTable))
for h in hndls:
if (
h.get_object_type(
handles_plugin.get_type_map(
self.context, layer_name, symbol_table
)
)
== "Process"
):
ret.append(h.Body.cast("_EPROCESS"))
except exceptions.InvalidAddressException:
vollog.log(
constants.LOGLEVEL_VVV, "Cannot access eprocess object table"
)
return self._proc_list_to_dict(ret)
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
layer_name = kernel.layer_name
symbol_table = kernel.symbol_table_name
kdbg_list_processes = list(
pslist.PsList.list_processes(
context=self.context, layer_name=layer_name, symbol_table=symbol_table
)
)
# get processes from each source
processes = {}
processes["pslist"] = self._check_pslist(kdbg_list_processes)
processes["psscan"] = self._check_psscan(layer_name, symbol_table)
processes["thrdscan"] = self._check_thrdscan()
processes["csrss"] = self._check_csrss_handles(
kdbg_list_processes, layer_name, symbol_table
)
# print results
# list of lists of offsets
offsets = [list(processes[source].keys()) for source in processes]
# flatten to one list
offsets = sum(offsets, [])
# remove duplicates
offsets = set(offsets)
for offset in offsets:
proc = None
in_sources = {src: False for src in processes}
for source in processes:
if offset in processes[source]:
in_sources[source] = True
if not proc:
proc = processes[source][offset]
pid = proc.UniqueProcessId
name = self._proc_name_to_string(proc)
exit_time = proc.get_exit_time()
if type(exit_time) != datetime.datetime:
exit_time = ""
else:
exit_time = str(exit_time)
yield (
0,
(
format_hints.Hex(offset),
name,
pid,
in_sources["pslist"],
in_sources["psscan"],
in_sources["thrdscan"],
in_sources["csrss"],
exit_time,
),
)
def run(self):
offset_type = "(Physical)" if self.config["physical-offsets"] else "(Virtual)"
offset_str = "Offset" + offset_type
return TreeGrid(
[
(offset_str, format_hints.Hex),
("Name", str),
("PID", int),
("pslist", bool),
("psscan", bool),
("thrdscan", bool),
("csrss", bool),
("Exit Time", str),
],
self._generator(),
)
@@ -17,11 +17,12 @@ from volatility3.framework.layers.registry import RegistryHive
from volatility3.framework.renderers import conversion, format_hints
from volatility3.framework.symbols import intermed
from volatility3.plugins.windows.registry import hivelist
from volatility3.plugins import timeliner
vollog = logging.getLogger(__name__)
class UserAssist(interfaces.plugins.PluginInterface):
class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Print userassist registry keys and information."""
_required_framework_version = (2, 0, 0)
@@ -285,6 +286,10 @@ class UserAssist(interfaces.plugins.PluginInterface):
hive_offsets = [self.config.get("offset", None)]
kernel = self.context.modules[self.config["kernel"]]
self._reg_table_name = intermed.IntermediateSymbolTable.create(
self.context, self._config_path, "windows", "registry"
)
# get all the user hive offsets or use the one specified
for hive in hivelist.HiveList.list_hives(
context=self.context,
@@ -335,11 +340,17 @@ class UserAssist(interfaces.plugins.PluginInterface):
)
yield result
def run(self):
self._reg_table_name = intermed.IntermediateSymbolTable.create(
self.context, self._config_path, "windows", "registry"
)
def generate_timeline(self):
for row in self._generator():
_depth, row_data = row
# check the name and the timestamp to not be empty
if isinstance(row_data[5], str) and not isinstance(
row_data[10], renderers.NotApplicableValue
):
description = f"UserAssist: {row_data[5]} {row_data[2]} ({row_data[7]})"
yield (description, timeliner.TimeLinerType.MODIFIED, row_data[10])
def run(self):
return renderers.TreeGrid(
[
("Hive Offset", renderers.format_hints.Hex),
@@ -0,0 +1,610 @@
# This file is Copyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
import os
from datetime import datetime
from itertools import count
from typing import Iterator, List, Optional, Tuple
from volatility3.framework import constants, exceptions, interfaces, renderers, symbols
from volatility3.framework.configuration import requirements
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 import versions
from volatility3.framework.symbols.windows.extensions import pe, shimcache
from volatility3.plugins import timeliner
from volatility3.plugins.windows import modules, pslist, vadinfo
# from volatility3.plugins.windows import pslist, vadinfo, modules
vollog = logging.getLogger(__name__)
class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Reads Shimcache entries from the ahcache.sys AVL tree"""
_required_framework_version = (2, 0, 0)
# These checks must be completed from newest -> oldest OS version.
_win_version_file_map: List[Tuple[versions.OsDistinguisher, bool, str]] = [
(versions.is_win10, True, "shimcache-win10-x64"),
(versions.is_win10, False, "shimcache-win10-x86"),
(versions.is_windows_8_or_later, True, "shimcache-win8-x64"),
(versions.is_windows_8_or_later, False, "shimcache-win8-x86"),
(versions.is_windows_7, True, "shimcache-win7-x64"),
(versions.is_windows_7, False, "shimcache-win7-x86"),
(versions.is_vista_or_later, True, "shimcache-vista-x64"),
(versions.is_vista_or_later, False, "shimcache-vista-x86"),
(versions.is_2003, False, "shimcache-2003-x86"),
(versions.is_2003, True, "shimcache-2003-x64"),
(versions.is_windows_xp_sp3, False, "shimcache-xp-sp3-x86"),
(versions.is_windows_xp_sp2, False, "shimcache-xp-sp2-x86"),
(versions.is_xp_or_2003, True, "shimcache-xp-2003-x64"),
(versions.is_xp_or_2003, False, "shimcache-xp-2003-x86"),
]
NT_KRNL_MODS = ["ntoskrnl.exe", "ntkrnlpa.exe", "ntkrnlmp.exe", "ntkrpamp.exe"]
def generate_timeline(
self,
) -> Iterator[Tuple[str, timeliner.TimeLinerType, datetime]]:
for _, (_, last_modified, last_update, _, _, file_path) in self._generator():
if isinstance(last_update, datetime):
yield f"Shimcache: File {file_path} executed", timeliner.TimeLinerType.ACCESSED, last_update
if isinstance(last_modified, datetime):
yield f"Shimcache: File {file_path} modified", timeliner.TimeLinerType.MODIFIED, last_modified
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="modules", component=modules.Modules, version=(2, 0, 0)
),
]
@staticmethod
def create_shimcache_table(
context: interfaces.context.ContextInterface,
symbol_table: str,
config_path: str,
) -> str:
"""Creates a shimcache symbol table
Args:
context: The context to retrieve required elements (layers, symbol tables) from
symbol_table: The name of an existing symbol table containing the kernel symbols
config_path: The configuration path within the context of the symbol table to create
Returns:
The name of the constructed shimcache table
"""
native_types = context.symbol_space[symbol_table].natives
is_64bit = symbols.symbol_table_is_64bit(context, symbol_table)
table_mapping = {"nt_symbols": symbol_table}
try:
symbol_filename = next(
filename
for version_check, for_64bit, filename in ShimcacheMem._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!")
vollog.debug(f"Using shimcache table {symbol_filename}")
return intermed.IntermediateSymbolTable.create(
context,
config_path,
os.path.join("windows", "shimcache"),
symbol_filename,
class_types=shimcache.class_types,
native_types=native_types,
table_mapping=table_mapping,
)
@classmethod
def find_shimcache_win_xp(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
kernel_symbol_table: str,
shimcache_symbol_table: str,
) -> Iterator[shimcache.SHIM_CACHE_ENTRY]:
"""Attempts to find the shimcache in a Windows XP memory image
:param context: The context to retrieve required elements (layers, symbol tables) from
:param layer_name: The name of the memory layer on which to operate.
:param kernel_symbol_table: The name of an existing symbol table containing the kernel symbols
:param shimcache_symbol_table: The name of a symbol table containing the hand-crafted shimcache symbols
"""
SHIM_NUM_ENTRIES_OFFSET = 0x8
SHIM_MAX_ENTRIES = 0x60 # 96 max entries in XP shim cache
SHIM_LRU_OFFSET = 0x10
SHIM_HEADER_SIZE = 0x190
SHIM_CACHE_ENTRY_SIZE = 0x228
seen = set()
for process in pslist.PsList.list_processes(
context, layer_name, kernel_symbol_table
):
pid = process.UniqueProcessId
vollog.debug("checking process %d" % pid)
for vad in vadinfo.VadInfo.list_vads(
process, lambda x: x.get_tag() == b"Vad " and x.Protection == 4
):
try:
proc_layer_name = process.add_process_layer()
proc_layer = context.layers[proc_layer_name]
except exceptions.InvalidAddressException:
continue
try:
if proc_layer.read(vad.get_start(), 4) != b"\xEF\xBE\xAD\xDE":
if pid == 624:
vollog.debug("VAD magic bytes don't match DEADBEEF")
continue
except exceptions.InvalidAddressException:
continue
num_entries = context.object(
shimcache_symbol_table + constants.BANG + "unsigned int",
proc_layer_name,
vad.get_start() + SHIM_NUM_ENTRIES_OFFSET,
)
if num_entries > SHIM_MAX_ENTRIES:
continue
cache_idx_ptr = vad.get_start() + SHIM_LRU_OFFSET
for _ in range(num_entries):
cache_idx_val = proc_layer.context.object(
shimcache_symbol_table + constants.BANG + "unsigned long",
proc_layer_name,
cache_idx_ptr,
)
cache_idx_ptr += 4
if cache_idx_val > SHIM_MAX_ENTRIES - 1:
continue
shim_entry_offset = (
vad.get_start()
+ SHIM_HEADER_SIZE
+ (SHIM_CACHE_ENTRY_SIZE * cache_idx_val)
)
if not proc_layer.is_valid(shim_entry_offset):
continue
physical_addr = proc_layer.translate(shim_entry_offset)
if physical_addr in seen:
continue
seen.add(physical_addr)
shim_entry = proc_layer.context.object(
shimcache_symbol_table + constants.BANG + "SHIM_CACHE_ENTRY",
proc_layer_name,
shim_entry_offset,
)
if not proc_layer.is_valid(shim_entry.vol.offset):
continue
if not shim_entry.is_valid():
continue
yield shim_entry
@classmethod
def find_shimcache_win_2k3_to_7(
cls,
context: interfaces.context.ContextInterface,
config_path: str,
kernel_layer_name: str,
nt_symbol_table: str,
shimcache_symbol_table: str,
) -> Iterator[shimcache.SHIM_CACHE_ENTRY]:
"""Implements the algorithm to search for the shim cache on Windows 2000
(x64) through Windows 7 / 2008 R2. The algorithm consists of the following:
1) Find the NT kernel module's .data and PAGE sections
2) Iterate over every 4/8 bytes (depending on OS bitness) in the .data
section and test for the following:
a) offset represents a valid RTL_AVL_TABLE object
b) RTL_AVL_TABLE is preceeded by an ERESOURCE object
c) RTL_AVL_TABLE is followed by the beginning of the SHIM LRU list
:param context: The context to retrieve required elements (layers, symbol tables) from
:param layer_name: The name of the memory layer on which to operate.
:param kernel_symbol_table: The name of an existing symbol table containing the kernel symbols
:param shimcache_symbol_table: The name of a symbol table containing the hand-crafted shimcache symbols
"""
data_sec = cls.get_module_section_range(
context,
config_path,
kernel_layer_name,
nt_symbol_table,
cls.NT_KRNL_MODS,
".data",
)
mod_page = cls.get_module_section_range(
context,
config_path,
kernel_layer_name,
nt_symbol_table,
cls.NT_KRNL_MODS,
"PAGE",
)
# We require both in order to accurately handle AVL table
if not (data_sec and mod_page):
return None
data_sec_offset, data_sec_size = data_sec
mod_page_offset, mod_page_size = mod_page
addr_size = 8 if symbols.symbol_table_is_64bit(context, nt_symbol_table) else 4
shim_head = None
for offset in range(
data_sec_offset, data_sec_offset + data_sec_size, addr_size
):
shim_head = cls.try_get_shim_head_at_offset(
context,
shimcache_symbol_table,
nt_symbol_table,
kernel_layer_name,
mod_page_offset,
mod_page_offset + mod_page_size,
offset,
)
if shim_head:
break
if not shim_head:
return
for shim_entry in shim_head.ListEntry.to_list(
shimcache_symbol_table + constants.BANG + "SHIM_CACHE_ENTRY", "ListEntry"
):
yield shim_entry
@classmethod
def try_get_shim_head_at_offset(
cls,
context: interfaces.context.ContextInterface,
symbol_table: str,
kernel_symbol_table: str,
layer_name: str,
mod_page_start: int,
mod_page_end: int,
offset: int,
) -> Optional[shimcache.SHIM_CACHE_ENTRY]:
"""Attempts to construct a SHIM_CACHE_HEAD within a layer of the given context,
using the provided offset within that layer, as well as the start and end offsets
of the kernel module's `PAGE` section start and end offsets.
If a number of validity checks are passed, this method will return the `SHIM_CACHE_HEAD`
object. Otherwise, `None` is returned.
"""
# print("checking RTL_AVL_TABLE at offset %s" % hex(offset))
rtl_avl_table = context.object(
symbol_table + constants.BANG + "_RTL_AVL_TABLE", layer_name, offset
)
if not rtl_avl_table.is_valid(mod_page_start, mod_page_end):
return None
vollog.debug(f"Candidate RTL_AVL_TABLE found at offset {hex(offset)}")
ersrc_size = context.symbol_space.get_type(
kernel_symbol_table + constants.BANG + "_ERESOURCE"
).size
ersrc_alignment = (
0x20
if symbols.symbol_table_is_64bit(context, kernel_symbol_table)
else 0x10
# 0x20 if context.symbol_space.get_type("pointer").size == 8 else 0x10
)
vollog.debug(
f"ERESOURCE size: {hex(ersrc_size)}, ERESOURCE alignment: {hex(ersrc_alignment)}"
)
eresource_rel_off = ersrc_size + ((offset - ersrc_size) % ersrc_alignment)
eresource_offset = offset - eresource_rel_off
vollog.debug("Constructing ERESOURCE at %s" % hex(eresource_offset))
eresource = context.object(
kernel_symbol_table + constants.BANG + "_ERESOURCE",
layer_name,
eresource_offset,
)
if not eresource.is_valid():
vollog.debug("ERESOURCE Invalid")
return None
shim_head_offset = offset + rtl_avl_table.vol.size
if not context.layers[layer_name].is_valid(shim_head_offset):
return None
shim_head = context.object(
symbol_table + constants.BANG + "SHIM_CACHE_ENTRY",
layer_name,
shim_head_offset,
)
if not shim_head.is_valid():
vollog.debug("shim head invalid")
return None
else:
vollog.debug("returning shim head")
return shim_head
@classmethod
def find_shimcache_win_8_or_later(
cls,
context: interfaces.context.ContextInterface,
config_path: str,
kernel_layer_name: str,
nt_symbol_table: str,
shimcache_symbol_table: str,
) -> Iterator[shimcache.SHIM_CACHE_ENTRY]:
"""Attempts to locate and yield shimcache entries from a Windows 8 or later memory image.
:param context: The context to retrieve required elements (layers, symbol tables) from
:param layer_name: The name of the memory layer on which to operate.
:param kernel_symbol_table: The name of an existing symbol table containing the kernel symbols
:param shimcache_symbol_table: The name of a symbol table containing the hand-crafted shimcache symbols
"""
is_8_1_or_later = versions.is_windows_8_1_or_later(
context, nt_symbol_table
) or versions.is_win10(context, nt_symbol_table)
module_names = ["ahcache.sys"] if is_8_1_or_later else cls.NT_KRNL_MODS
vollog.debug(f"Searching for modules {module_names}")
data_sec = cls.get_module_section_range(
context,
config_path,
kernel_layer_name,
nt_symbol_table,
module_names,
".data",
)
mod_page = cls.get_module_section_range(
context,
config_path,
kernel_layer_name,
nt_symbol_table,
module_names,
"PAGE",
)
if not (data_sec and mod_page):
return None
mod_page_offset, mod_page_size = mod_page
data_sec_offset, data_sec_size = data_sec
# iterate over ahcache kernel module's .data section in search of *two* SHIM handles
shim_heads = []
vollog.debug(f"PAGE offset: {hex(mod_page_offset)}")
vollog.debug(f".data offset: {hex(data_sec_offset)}")
handle_type = context.symbol_space.get_type(
shimcache_symbol_table + constants.BANG + "SHIM_CACHE_HANDLE"
)
for offset in range(
data_sec_offset,
data_sec_offset + data_sec_size,
8 if symbols.symbol_table_is_64bit(context, nt_symbol_table) else 4,
):
vollog.debug(f"Building shim handle pointer at {hex(offset)}")
shim_handle = context.object(
object_type=shimcache_symbol_table + constants.BANG + "pointer",
layer_name=kernel_layer_name,
subtype=handle_type,
offset=offset,
)
if shim_handle.is_valid(mod_page_offset, mod_page_offset + mod_page_size):
if shim_handle.head is not None:
vollog.debug(
f"Found valid shim handle @ {hex(shim_handle.vol.offset)}"
)
shim_heads.append(shim_handle.head)
if len(shim_heads) == 2:
break
if len(shim_heads) != 2:
vollog.debug("Failed to identify two valid SHIM_CACHE_HANDLE structures")
return
# On Windows 8 x64, the frist cache contains the shim cache
# On Windows 8 x86, 8.1 x86/x64, and 10, the second cache contains the shim cache.
if (
not symbols.symbol_table_is_64bit(context, nt_symbol_table)
and not is_8_1_or_later
):
valid_head = shim_heads[1]
elif not is_8_1_or_later:
valid_head = shim_heads[0]
else:
valid_head = shim_heads[1]
for shim_entry in valid_head.ListEntry.to_list(
shimcache_symbol_table + constants.BANG + "SHIM_CACHE_ENTRY", "ListEntry"
):
if shim_entry.is_valid():
yield shim_entry
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
shimcache_table_name = self.create_shimcache_table(
self.context, kernel.symbol_table_name, self.config_path
)
c = count()
if versions.is_windows_8_or_later(self._context, kernel.symbol_table_name):
vollog.info("Finding shimcache entries for Windows 8.0+")
entries = self.find_shimcache_win_8_or_later(
self.context,
self.config_path,
kernel.layer_name,
kernel.symbol_table_name,
shimcache_table_name,
)
elif (
versions.is_2003(self.context, kernel.symbol_table_name)
or versions.is_vista_or_later(self.context, kernel.symbol_table_name)
or versions.is_windows_7(self.context, kernel.symbol_table_name)
):
vollog.info("Finding shimcache entries for Windows 2k3/Vista/7")
entries = self.find_shimcache_win_2k3_to_7(
self.context,
self.config_path,
kernel.layer_name,
kernel.symbol_table_name,
shimcache_table_name,
)
elif versions.is_windows_xp_sp2(
self._context, kernel.symbol_table_name
) or versions.is_windows_xp_sp3(self.context, kernel.symbol_table_name):
vollog.info("Finding shimcache entries for WinXP")
entries = self.find_shimcache_win_xp(
self._context,
kernel.layer_name,
kernel.symbol_table_name,
shimcache_table_name,
)
else:
vollog.warn("Cannot parse shimcache entries for this version of Windows")
return
for entry in entries:
try:
vollog.debug(f"SHIM_CACHE_ENTRY type: {entry.__class__}")
shim_entry = (
entry.last_modified,
entry.last_update,
entry.exec_flag,
(
format_hints.Hex(entry.file_size)
if isinstance(entry.file_size, int)
else entry.file_size
),
entry.file_path,
)
except exceptions.InvalidAddressException:
continue
yield (
0,
(next(c), *shim_entry),
)
def run(self):
return renderers.TreeGrid(
[
("Order", int),
("Last Modified", datetime),
("Last Update", datetime),
("Exec Flag", bool),
("File Size", format_hints.Hex),
("File Path", str),
],
self._generator(),
)
@classmethod
def get_module_section_range(
cls,
context: interfaces.context.ContextInterface,
config_path: str,
layer_name: str,
symbol_table: str,
module_list: List[str],
section_name: str,
) -> Optional[Tuple[int, int]]:
"""Locates the size and offset of the first found module section
specified by name from the list of modules.
:param context: The context to operate on
:param layer_name: The memory layer to read from
:param module_list: A list of module names to search for the given section
:param section_name: The name of the section to search for.
:return: The offset and size of the module, if found; Otherwise, returns `None`
"""
try:
krnl_mod = next(
module
for module in modules.Modules.list_modules(
context, layer_name, symbol_table
)
if module.BaseDllName.String in module_list
)
except StopIteration:
return None
pe_table_name = intermed.IntermediateSymbolTable.create(
context,
interfaces.configuration.path_join(config_path, "pe"),
"windows",
"pe",
class_types=pe.class_types,
)
# code taken from Win32KBase._section_chunks (win32_core.py)
dos_header = context.object(
pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER",
layer_name,
offset=krnl_mod.DllBase,
)
if not dos_header:
return None
nt_header = dos_header.get_nt_header()
try:
section = next(
sec
for sec in nt_header.get_sections()
if section_name.lower() == array_to_string(sec.Name).lower()
)
except StopIteration:
return None
section_offset = krnl_mod.DllBase + section.VirtualAddress
section_size = section.Misc.VirtualSize
return section_offset, section_size
@@ -30,7 +30,7 @@ class SSDT(plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="modules", plugin=modules.Modules, version=(1, 0, 0)
name="modules", plugin=modules.Modules, version=(2, 0, 0)
),
]
@@ -0,0 +1,103 @@
# 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
#
# This module attempts to locate skeleton-key like function hooks.
# It does this by locating the CSystems array through a variety of methods,
# and then validating the entry for RC4 HMAC (0x17 / 23)
#
# For a thorough walkthrough on how the R&D was performed to develop this plugin,
# please see our blogpost here:
#
# https://volatility-labs.blogspot.com/2021/10/memory-forensics-r-illustrated.html
import logging
from volatility3.framework import symbols, interfaces
from volatility3.framework.configuration import requirements
from volatility3.plugins.windows import svclist, svcscan
from volatility3.framework.symbols.windows import versions
vollog = logging.getLogger(__name__)
class SvcDiff(svcscan.SvcScan):
"""Compares services found through list walking versus scanning to find rootkits"""
_required_framework_version = (2, 4, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._enumeration_method = self.service_diff
@classmethod
def get_requirements(cls):
# Since we're calling the plugin, make sure we have the plugin's requirements
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="svclist", component=svclist.SvcList, version=(1, 0, 0)
),
requirements.VersionRequirement(
name="svcscan", component=svcscan.SvcScan, version=(3, 0, 0)
),
]
@classmethod
def service_diff(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
service_table_name: str,
service_binary_dll_map,
filter_func,
):
"""
On Windows 10 version 15063+ 64bit Windows memory samples, walk the services list
and scan for services then report differences
"""
if not symbols.symbol_table_is_64bit(
context, symbol_table
) or not versions.is_win10_15063_or_later(
context=context, symbol_table=symbol_table
):
vollog.warning(
"This plugin only supports Windows 10 version 15063+ 64bit Windows memory samples"
)
return
from_scan = set()
from_list = set()
records = {}
# collect unique service names from scanning
for service in svcscan.SvcScan.service_scan(
context,
layer_name,
symbol_table,
service_table_name,
service_binary_dll_map,
filter_func,
):
from_scan.add(service[6])
records[service[6]] = service
# collect services from listing walking
for service in svclist.SvcList.service_list(
context,
layer_name,
symbol_table,
service_table_name,
service_binary_dll_map,
filter_func,
):
from_list.add(service[6])
# report services found from scanning but not list walking
for hidden_service in from_scan - from_list:
yield records[hidden_service]
@@ -0,0 +1,115 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import List, Optional, Tuple
from volatility3.framework import interfaces, exceptions, symbols
from volatility3.framework.configuration import requirements
from volatility3.framework.symbols.windows import versions
from volatility3.plugins.windows import svcscan, pslist
from volatility3.framework.layers import scanners
vollog = logging.getLogger(__name__)
class SvcList(svcscan.SvcScan):
"""Lists services contained with the services.exe doubly linked list of services"""
_version = (1, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._enumeration_method = self.service_list
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
return [
requirements.PluginRequirement(
name="svcscan", plugin=svcscan.SvcScan, version=(3, 0, 0)
),
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
]
@classmethod
def _get_exe_range(cls, proc) -> Optional[Tuple[int, int]]:
"""
Returns a tuple of starting,ending address for
the VAD containing services.exe
"""
vad_root = proc.get_vad_root()
for vad in vad_root.traverse():
filename = vad.get_file_name()
if isinstance(filename, str) and filename.lower().endswith(
"\\services.exe"
):
return [(vad.get_start(), vad.get_size())]
return None
@classmethod
def service_list(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
service_table_name: str,
service_binary_dll_map,
filter_func,
):
if not symbols.symbol_table_is_64bit(
context, symbol_table
) or not versions.is_win10_15063_or_later(
context=context, symbol_table=symbol_table
):
vollog.warning(
"This plugin only supports Windows 10 version 15063+ 64bit Windows memory samples"
)
return
for proc in pslist.PsList.list_processes(
context=context,
layer_name=layer_name,
symbol_table=symbol_table,
filter_func=filter_func,
):
try:
layer_name = proc.add_process_layer()
except exceptions.InvalidAddressException:
vollog.warning(
"Unable to access memory of services.exe running with PID: {}".format(
proc.UniqueProcessId
)
)
continue
layer = context.layers[layer_name]
exe_range = cls._get_exe_range(proc)
if not exe_range:
vollog.warning(
"Could not find the application executable VAD for services.exe. Unable to proceed."
)
continue
for offset in layer.scan(
context=context,
scanner=scanners.BytesScanner(needle=b"Sc27"),
sections=exe_range,
):
for record in cls.enumerate_vista_or_later_header(
context,
service_table_name,
service_binary_dll_map,
layer_name,
offset,
):
yield record
+113 -58
View File
@@ -19,7 +19,7 @@ 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.framework.symbols.windows.extensions import services as services_types
from volatility3.plugins.windows import poolscanner, pslist, vadyarascan
from volatility3.plugins.windows.registry import hivelist
@@ -39,7 +39,11 @@ class SvcScan(interfaces.plugins.PluginInterface):
"""Scans for windows services."""
_required_framework_version = (2, 0, 0)
_version = (2, 0, 0)
_version = (3, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._enumeration_method = self.service_scan
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -106,7 +110,7 @@ class SvcScan(interfaces.plugins.PluginInterface):
]
@staticmethod
def create_service_table(
def _create_service_table(
context: interfaces.context.ContextInterface,
symbol_table: str,
config_path: str,
@@ -140,18 +144,21 @@ class SvcScan(interfaces.plugins.PluginInterface):
config_path,
os.path.join("windows", "services"),
symbol_filename,
class_types=services.class_types,
class_types=services_types.class_types,
native_types=native_types,
)
def _get_service_key(self, kernel) -> Optional[objects.StructType]:
@staticmethod
def _get_service_key(
context, config_path: str, layer_name: str, symbol_table: str
) -> Optional[objects.StructType]:
for hive in hivelist.HiveList.list_hives(
context=self.context,
context=context,
base_config_path=interfaces.configuration.path_join(
self.config_path, "hivelist"
config_path, "hivelist"
),
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
layer_name=layer_name,
symbol_table=symbol_table,
filter_string="machine\\system",
):
# Get ControlSet\Services.
@@ -232,30 +239,55 @@ class SvcScan(interfaces.plugins.PluginInterface):
for service_key in services
}
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
@classmethod
def enumerate_vista_or_later_header(
cls,
context,
service_table_name,
service_binary_dll_map,
proc_layer_name,
offset,
):
if offset % 8:
return
service_table_name = self.create_service_table(
self.context, kernel.symbol_table_name, self.config_path
service_header = context.object(
service_table_name + constants.BANG + "_SERVICE_HEADER",
offset=offset,
layer_name=proc_layer_name,
)
# 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 {}
)
if not service_header.is_valid():
return
relative_tag_offset = self.context.symbol_space.get_type(
# since we walk the s-list backwards, if we've seen
# an object, then we've also seen all objects that
# exist before it, thus we can break at that time.
for service_record in service_header.ServiceRecord.traverse():
service_info = service_binary_dll_map.get(
service_record.get_name(),
ServiceBinaryInfo(
renderers.UnreadableValue(), renderers.UnreadableValue()
),
)
yield cls.get_record_tuple(service_record, service_info)
@classmethod
def service_scan(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
service_table_name: str,
service_binary_dll_map,
filter_func,
):
relative_tag_offset = context.symbol_space.get_type(
service_table_name + constants.BANG + "_SERVICE_RECORD"
).relative_child_offset("Tag")
filter_func = pslist.PsList.create_name_filter(["services.exe"])
is_vista_or_later = versions.is_vista_or_later(
context=self.context, symbol_table=kernel.symbol_table_name
context=context, symbol_table=symbol_table
)
if is_vista_or_later:
@@ -266,9 +298,9 @@ class SvcScan(interfaces.plugins.PluginInterface):
seen = []
for task in pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
context=context,
layer_name=layer_name,
symbol_table=symbol_table,
filter_func=filter_func,
):
proc_id = "Unknown"
@@ -283,15 +315,15 @@ class SvcScan(interfaces.plugins.PluginInterface):
)
continue
layer = self.context.layers[proc_layer_name]
layer = context.layers[proc_layer_name]
for offset in layer.scan(
context=self.context,
context=context,
scanner=scanners.BytesScanner(needle=service_tag),
sections=vadyarascan.VadYaraScan.get_vad_maps(task),
):
if not is_vista_or_later:
service_record = self.context.object(
service_record = context.object(
service_table_name + constants.BANG + "_SERVICE_RECORD",
offset=offset - relative_tag_offset,
layer_name=proc_layer_name,
@@ -306,37 +338,60 @@ class SvcScan(interfaces.plugins.PluginInterface):
renderers.UnreadableValue(), renderers.UnreadableValue()
),
)
yield (
0,
self.get_record_tuple(service_record, service_info),
)
yield cls.get_record_tuple(service_record, service_info)
else:
service_header = self.context.object(
service_table_name + constants.BANG + "_SERVICE_HEADER",
offset=offset,
layer_name=proc_layer_name,
)
if not service_header.is_valid():
continue
# since we walk the s-list backwards, if we've seen
# an object, then we've also seen all objects that
# exist before it, thus we can break at that time.
for service_record in service_header.ServiceRecord.traverse():
for service_record in cls.enumerate_vista_or_later_header(
context,
service_table_name,
service_binary_dll_map,
proc_layer_name,
offset,
):
if service_record in seen:
break
seen.append(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),
)
yield service_record
@classmethod
def get_prereq_info(cls, context, config_path, layer_name: str, symbol_table: str):
"""
Data structures and information needed to analyze service information
"""
service_table_name = cls._create_service_table(
context, symbol_table, config_path
)
services_key = cls._get_service_key(
context, config_path, layer_name, symbol_table
)
service_binary_dll_map = (
cls._get_service_binary_map(services_key)
if services_key is not None
else {}
)
filter_func = pslist.PsList.create_name_filter(["services.exe"])
return service_table_name, service_binary_dll_map, filter_func
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
service_table_name, service_binary_dll_map, filter_func = self.get_prereq_info(
self.context, self.config_path, kernel.layer_name, kernel.symbol_table_name
)
for record in self._enumeration_method(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
service_table_name,
service_binary_dll_map,
filter_func,
):
yield (0, record)
def run(self):
return renderers.TreeGrid(
@@ -3,7 +3,7 @@
##
import logging
import datetime
from typing import Iterable
from typing import Callable, Iterable
from volatility3.framework import renderers, interfaces, exceptions
from volatility3.framework.configuration import requirements
@@ -19,7 +19,11 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
# version 2.6.0 adds support for scanning for 'Ethread' structures by pool tags
_required_framework_version = (2, 6, 0)
_version = (1, 0, 0)
_version = (1, 1, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.implementation = self.scan_threads
@classmethod
def get_requirements(cls):
@@ -38,20 +42,22 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
def scan_threads(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
module_name: 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
module_name: Name of the module to use for scanning
Returns:
A list of _ETHREAD objects found by scanning memory for the "Thre" / "Thr\\xE5" pool signatures
"""
module = context.modules[module_name]
layer_name = module.layer_name
symbol_table = module.symbol_table_name
constraints = poolscanner.PoolScanner.builtin_constraints(
symbol_table, [b"Thr\xe5", b"Thre"]
)
@@ -76,7 +82,7 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
ethread.get_exit_time()
) # datetime.datetime object / volatility3.framework.renderers.UnparsableValue object
except exceptions.InvalidAddressException:
vollog.debug("Thread invalid address {:#x}".format(thread.vol.offset))
vollog.debug("Thread invalid address {:#x}".format(ethread.vol.offset))
return None
return (
@@ -88,18 +94,19 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
thread_exit_time,
)
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
def _generator(self, filter_func: Callable):
kernel_name = self.config["kernel"]
for ethread in self.scan_threads(
self.context, kernel.layer_name, kernel.symbol_table_name
):
for ethread in self.implementation(self.context, kernel_name):
info = self.gather_thread_info(ethread)
if info:
yield (0, info)
def generate_timeline(self):
for row in self._generator():
filt_func = self.filter_func(self.config)
for row in self._generator(filt_func):
_depth, row_data = row
row_dict = {}
(
@@ -126,7 +133,14 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
row_dict["ExitTime"],
)
@classmethod
def filter_func(cls, config: interfaces.configuration.HierarchicalDict) -> Callable:
"""Returns a function that can filter this plugin's implementation method based on the config"""
return lambda x: False
def run(self):
filt_func = self.filter_func(self.config)
return renderers.TreeGrid(
[
("Offset", format_hints.Hex),
@@ -136,5 +150,5 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
("CreateTime", datetime.datetime),
("ExitTime", datetime.datetime),
],
self._generator(),
self._generator(filt_func),
)
@@ -3,7 +3,7 @@
#
import logging
from typing import List, Generator
from typing import Callable, Iterable, List, Generator
from volatility3.framework import interfaces, constants
from volatility3.framework.configuration import requirements
@@ -18,6 +18,10 @@ class Threads(thrdscan.ThrdScan):
_required_framework_version = (2, 4, 0)
_version = (1, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.implementation = self.list_process_threads
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
@@ -34,7 +38,7 @@ class Threads(thrdscan.ThrdScan):
optional=True,
),
requirements.PluginRequirement(
name="thrdscan", plugin=thrdscan.ThrdScan, version=(1, 0, 0)
name="thrdscan", plugin=thrdscan.ThrdScan, version=(1, 1, 0)
),
]
@@ -46,7 +50,6 @@ class Threads(thrdscan.ThrdScan):
Args:
proc: _EPROCESS object from which to list the VADs
filter_func: Function to take a virtual address descriptor value and return True if it should be filtered out
Returns:
A list of threads based on the process and filtered based on the filter function
@@ -60,18 +63,24 @@ class Threads(thrdscan.ThrdScan):
seen.add(thread.vol.offset)
yield thread
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
@classmethod
def list_process_threads(
cls,
context: interfaces.context.ContextInterface,
module_name: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Runs through all processes and lists threads for each process"""
module = context.modules[module_name]
layer_name = module.layer_name
symbol_table_name = module.symbol_table_name
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
filter_func = pslist.PsList.create_pid_filter(context.config.get("pid", None))
for proc in pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
context=context,
layer_name=layer_name,
symbol_table=symbol_table_name,
filter_func=filter_func,
):
for thread in self.list_threads(kernel, proc):
info = self.gather_thread_info(thread)
if info:
yield (0, info)
for thread in cls.list_threads(module, proc):
yield thread
@@ -0,0 +1,212 @@
# 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 Iterator, List, Tuple, Iterable
from volatility3.framework import (
renderers,
interfaces,
constants,
symbols,
)
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols.windows import versions
from volatility3.plugins.windows import ssdt, kpcrs
vollog = logging.getLogger(__name__)
class Timers(interfaces.plugins.PluginInterface):
"""Print kernel timers and associated module DPCs"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0)
),
requirements.PluginRequirement(
name="kpcrs", plugin=kpcrs.KPCRs, version=(1, 0, 0)
),
]
@classmethod
def list_timers(
cls,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
layer_name: str,
symbol_table: str,
) -> Iterable[Tuple[str, int, str]]:
"""Lists all kernel timers.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
kernel_module_name: The name of the kernel module on which to operate
layer_name: The name of the layer on which to operate
symbol_table: The name of the table containing the kernel symbols
Yields:
A _KTIMER entry
"""
kernel = context.modules[kernel_module_name]
if versions.is_windows_7(
context=context, symbol_table=symbol_table
) or versions.is_windows_8_or_later(context=context, symbol_table=symbol_table):
# Starting with Windows 7, there is no more KiTimerTableListHead. The list is
# at _KPCR.PrcbData.TimerTable.TimerEntries
# See http://pastebin.com/FiRsGW3f
for kpcr in kpcrs.KPCRs.list_kpcrs(
context, kernel_module_name, layer_name, symbol_table
):
if hasattr(kpcr.Prcb.TimerTable, "TableState"):
for timer_entries in kpcr.Prcb.TimerTable.TimerEntries:
for timer_entry in timer_entries:
for timer in timer_entry.Entry.to_list(
symbol_table + constants.BANG + "_KTIMER",
"TimerListEntry",
):
yield timer
else:
for timer_entries in kpcr.Prcb.TimerTable.TimerEntries:
for timer in timer_entries.Entry.to_list(
symbol_table + constants.BANG + "_KTIMER",
"TimerListEntry",
):
yield timer
elif versions.is_xp_or_2003(
context=context, symbol_table=symbol_table
) or versions.is_vista_or_later(context=context, symbol_table=symbol_table):
is_64bit = symbols.symbol_table_is_64bit(context, symbol_table)
if is_64bit or versions.is_vista_or_later(
context=context, symbol_table=symbol_table
):
# On XP x64, Windows 2003 SP1-SP2, and Vista SP0-SP2, KiTimerTableListHead
# is an array of 512 _KTIMER_TABLE_ENTRY structs.
array_size = 512
else:
# On XP SP0-SP3 x86 and Windows 2003 SP0, KiTimerTableListHead
# is an array of 256 _LIST_ENTRY for _KTIMERs.
array_size = 256
timer_table_list_head = kernel.object(
object_type="array",
offset=kernel.get_symbol("KiTimerTableListHead").address,
subtype=kernel.get_type("_LIST_ENTRY"),
count=array_size,
)
for table in timer_table_list_head:
for timer in table.to_list(
symbol_table + constants.BANG + "_KTIMER",
"TimerListEntry",
):
yield timer
else:
raise NotImplementedError("This version of Windows is not supported!")
def _generator(self) -> Iterator[Tuple]:
kernel = self.context.modules[self.config["kernel"]]
layer_name = kernel.layer_name
symbol_table = kernel.symbol_table_name
collection = ssdt.SSDT.build_module_collection(
self.context, kernel.layer_name, kernel.symbol_table_name
)
for timer in self.list_timers(
self.context, self.config["kernel"], layer_name, symbol_table
):
if not timer.valid_type():
continue
try:
dpc = timer.get_dpc()
if dpc == 0:
continue
if dpc.DeferredRoutine == 0:
continue
deferred_routine = dpc.DeferredRoutine
except Exception as e:
continue
module_symbols = list(
collection.get_module_symbols_by_absolute_location(deferred_routine)
)
if module_symbols:
for module_name, symbol_generator in module_symbols:
symbols_found = False
# we might have multiple symbols pointing to the same location
for symbol in symbol_generator:
symbols_found = True
yield (
0,
(
format_hints.Hex(timer.vol.offset),
timer.get_due_time(),
timer.Period,
timer.get_signaled(),
format_hints.Hex(deferred_routine),
module_name,
symbol.split(constants.BANG)[1],
),
)
# no symbols, but we at least can report the module name
if not symbols_found:
yield (
0,
(
format_hints.Hex(timer.vol.offset),
timer.get_due_time(),
timer.Period,
timer.get_signaled(),
format_hints.Hex(deferred_routine),
module_name,
renderers.NotAvailableValue(),
),
)
else:
# no module was found at the absolute location
yield (
0,
(
format_hints.Hex(timer.vol.offset),
timer.get_due_time(),
timer.Period,
timer.get_signaled(),
format_hints.Hex(deferred_routine),
renderers.NotAvailableValue(),
renderers.NotAvailableValue(),
),
)
def run(self):
return renderers.TreeGrid(
[
("Offset", format_hints.Hex),
("DueTime", str),
("Period(ms)", int),
("Signaled", str),
("Routine", format_hints.Hex),
("Module", str),
("Symbol", str),
],
self._generator(),
)
@@ -33,7 +33,7 @@ class Passphrase(interfaces.plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="modules", component=modules.Modules, version=(1, 1, 0)
name="modules", component=modules.Modules, version=(2, 0, 0)
),
requirements.IntRequirement(
name="min-length",
@@ -0,0 +1,160 @@
# 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
import datetime
from typing import List, Iterable
from volatility3.framework import constants
from volatility3.framework import interfaces, symbols
from volatility3.framework import renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import configuration
from volatility3.framework.renderers import format_hints, conversion
from volatility3.framework.symbols import intermed
from volatility3.plugins import timeliner
vollog = logging.getLogger(__name__)
class UnloadedModules(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Lists the unloaded kernel modules."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
]
@staticmethod
def create_unloadedmodules_table(
context: interfaces.context.ContextInterface,
symbol_table: str,
config_path: str,
) -> str:
"""Creates a symbol table for the unloaded modules.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
symbol_table: The name of an existing symbol table containing the kernel symbols
config_path: The configuration path within the context of the symbol table to create
Returns:
The name of the constructed unloaded modules table
"""
native_types = context.symbol_space[symbol_table].natives
is_64bit = symbols.symbol_table_is_64bit(context, symbol_table)
table_mapping = {"nt_symbols": symbol_table}
if is_64bit:
symbol_filename = "unloadedmodules-x64"
else:
symbol_filename = "unloadedmodules-x86"
return intermed.IntermediateSymbolTable.create(
context,
configuration.path_join(config_path, "unloadedmodules"),
"windows",
symbol_filename,
native_types=native_types,
table_mapping=table_mapping,
)
@classmethod
def list_unloadedmodules(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
unloadedmodule_table_name: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Lists all the unloaded modules in the primary layer.
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 Unloaded Modules as retrieved from MmUnloadedDrivers
"""
kvo = context.layers[layer_name].config["kernel_virtual_offset"]
ntkrnlmp = context.module(symbol_table, layer_name=layer_name, offset=kvo)
unloadedmodules_offset = ntkrnlmp.get_symbol("MmUnloadedDrivers").address
unloadedmodules = ntkrnlmp.object(
object_type="pointer",
offset=unloadedmodules_offset,
subtype="array",
)
is_64bit = symbols.symbol_table_is_64bit(context, symbol_table)
if is_64bit:
unloaded_count_type = "unsigned long long"
else:
unloaded_count_type = "unsigned long"
last_unloadedmodule_offset = ntkrnlmp.get_symbol("MmLastUnloadedDriver").address
unloaded_count = ntkrnlmp.object(
object_type=unloaded_count_type, offset=last_unloadedmodule_offset
)
unloadedmodules_array = context.object(
object_type=unloadedmodule_table_name
+ constants.BANG
+ "_UNLOADED_DRIVERS",
layer_name=layer_name,
offset=unloadedmodules,
)
unloadedmodules_array.UnloadedDrivers.count = unloaded_count
for mod in unloadedmodules_array.UnloadedDrivers:
yield mod
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
unloadedmodule_table_name = self.create_unloadedmodules_table(
self.context, kernel.symbol_table_name, self.config_path
)
for mod in self.list_unloadedmodules(
self.context,
kernel.layer_name,
kernel.symbol_table_name,
unloadedmodule_table_name,
):
yield (
0,
(
mod.Name.String,
format_hints.Hex(mod.StartAddress),
format_hints.Hex(mod.EndAddress),
conversion.wintime_to_datetime(mod.CurrentTime),
),
)
def generate_timeline(self):
for row in self._generator():
_depth, row_data = row
description = f"Unloaded Module: {row_data[0]}"
yield (description, timeliner.TimeLinerType.CHANGED, row_data[3])
def run(self):
return renderers.TreeGrid(
[
("Name", str),
("StartAddress", format_hints.Hex),
("EndAddress", format_hints.Hex),
("Time", datetime.datetime),
],
self._generator(),
)
@@ -33,7 +33,7 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="yarascan", plugin=yarascan.YaraScan, version=(1, 3, 0)
name="yarascan", plugin=yarascan.YaraScan, version=(2, 0, 0)
),
requirements.ListRequirement(
name="pid",
@@ -56,7 +56,7 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
sanity_check = 0x1000 * 0x1000 * 0x1000
sanity_check = 1024 * 1024 * 1024 # 1 GB
for task in pslist.PsList.list_processes(
context=self.context,
@@ -66,34 +66,49 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
):
layer_name = task.add_process_layer()
layer = self.context.layers[layer_name]
for start, end in self.get_vad_maps(task):
size = end - start
for start, size in self.get_vad_maps(task):
if size > sanity_check:
vollog.warn(
f"VAD at 0x{start:x} over sanity-check size, not scanning"
)
continue
for match in rules.match(data=layer.read(start, end - start, True)):
if yarascan.YaraScan.yara_returns_instances():
for match_string in match.strings:
for instance in match_string.instances:
data = layer.read(start, size, True)
if not yarascan.YaraScan._yara_x:
for match in rules.match(data=data):
if yarascan.YaraScan.yara_returns_instances():
for match_string in match.strings:
for instance in match_string.instances:
yield 0, (
format_hints.Hex(instance.offset + start),
task.UniqueProcessId,
match.rule,
match_string.identifier,
instance.matched_data,
)
else:
for offset, name, value in match.strings:
yield 0, (
format_hints.Hex(offset + start),
task.UniqueProcessId,
match.rule,
name,
value,
)
else:
for match in rules.scan(data).matching_rules:
for match_string in match.patterns:
for instance in match_string.matches:
yield 0, (
format_hints.Hex(instance.offset + start),
task.UniqueProcessId,
match.rule,
f"{match.namespace}.{match.identifier}",
match_string.identifier,
instance.matched_data,
data[
instance.offset : instance.offset
+ instance.length
],
)
else:
for offset, name, value in match.strings:
yield 0, (
format_hints.Hex(offset + start),
task.UniqueProcessId,
match.rule,
name,
value,
)
@staticmethod
def get_vad_maps(
@@ -106,7 +121,7 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
task: The EPROCESS object of which to traverse the vad tree
Returns:
An iterable of tuples containing start and end addresses for each descriptor
An iterable of tuples containing start and size for each descriptor
"""
vad_root = task.get_vad_root()
for vad in vad_root.traverse():
@@ -46,7 +46,7 @@ class VerInfo(interfaces.plugins.PluginInterface):
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="modules", plugin=modules.Modules, version=(1, 0, 0)
name="modules", plugin=modules.Modules, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="dlllist", component=dlllist.DllList, version=(2, 0, 0)
+85 -48
View File
@@ -13,20 +13,31 @@ from volatility3.framework.renderers import format_hints
vollog = logging.getLogger(__name__)
try:
import yara
USE_YARA_X = False
try:
import yara_x
USE_YARA_X = True
if tuple([int(x) for x in yara.__version__.split(".")]) < (3, 8):
raise ImportError
except ImportError:
vollog.info(
"Python Yara (>3.8.0) module not found, plugin (and dependent plugins) not available"
)
raise
try:
import yara
if tuple(int(x) for x in yara.__version__.split(".")) < (3, 8):
raise ImportError
vollog.debug("Using yara-python module")
except ImportError:
vollog.info(
"Neither yara-x nor yara-python (>3.8.0) module not found, plugin (and dependent plugins) not available"
)
raise
class YaraScanner(interfaces.layers.ScannerInterface):
_version = (2, 0, 0)
_version = (2, 1, 0)
# yara.Rules isn't exposed, so we can't type this properly
def __init__(self, rules) -> None:
@@ -34,37 +45,69 @@ class YaraScanner(interfaces.layers.ScannerInterface):
if rules is None:
raise ValueError("No rules provided to YaraScanner")
self._rules = rules
self.st_object = not tuple([int(x) for x in yara.__version__.split(".")]) < (
4,
3,
self.st_object = (
None
if USE_YARA_X
else not tuple(int(x) for x in yara.__version__.split(".")) < (4, 3)
)
def __call__(
self, data: bytes, data_offset: int
) -> Iterable[Tuple[int, str, str, bytes]]:
for match in self._rules.match(data=data):
if YaraScan.yara_returns_instances():
for match_string in match.strings:
for instance in match_string.instances:
if USE_YARA_X:
for match in self._rules.scan(data).matching_rules:
for match_string in match.patterns:
for instance in match_string.matches:
yield (
instance.offset + data_offset,
match.rule,
f"{match.namespace}.{match.identifier}",
match_string.identifier,
instance.matched_data,
data[instance.offset : instance.offset + instance.length],
)
else:
for offset, name, value in match.strings:
yield (offset + data_offset, match.rule, name, value)
else:
for match in self._rules.match(data=data):
if YaraScan.yara_returns_instances():
for match_string in match.strings:
for instance in match_string.instances:
yield (
instance.offset + data_offset,
match.rule,
match_string.identifier,
instance.matched_data,
)
else:
for offset, name, value in match.strings:
yield (offset + data_offset, match.rule, name, value)
@staticmethod
def get_rule(rule):
if USE_YARA_X:
return yara_x.compile(f"rule r1 {{strings: $a = {rule} condition: $a}}")
return yara.compile(
sources={"n": f"rule r1 {{strings: $a = {rule} condition: $a}}"}
)
@staticmethod
def from_compiled_file(filepath):
with resources.ResourceAccessor().open(filepath, "rb") as fp:
if USE_YARA_X:
return yara_x.Rules.deserialize_from(file=fp)
return yara.load(file=fp)
@staticmethod
def from_file(filepath):
with resources.ResourceAccessor().open(filepath, "rb") as fp:
if USE_YARA_X:
return yara_x.compile(fp.read().decode())
return yara.compile(file=fp)
class YaraScan(plugins.PluginInterface):
"""Scans kernel memory using yara rules (string or file)."""
_required_framework_version = (2, 0, 0)
_version = (1, 3, 0)
# TODO: When the major version is bumped, take the opportunity to rename the yara_rules config to yara_string
# or something that makes more sense
_version = (2, 0, 0)
_yara_x = USE_YARA_X
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -99,10 +142,14 @@ class YaraScan(plugins.PluginInterface):
optional=True,
),
requirements.StringRequirement(
name="yara_rules", description="Yara rules (as a string)", optional=True
name="yara_string",
description="Yara rules (as a string)",
optional=True,
),
requirements.URIRequirement(
name="yara_file", description="Yara rules (as a file)", optional=True
name="yara_file",
description="Yara rules (as a file)",
optional=True,
),
# This additional requirement is to follow suit with upstream, who feel that compiled rules could potentially be used to execute malicious code
# As such, there's a separate option to run compiled files, as happened with yara-3.9 and later
@@ -121,38 +168,28 @@ class YaraScan(plugins.PluginInterface):
@classmethod
def yara_returns_instances(cls) -> bool:
st_object = not tuple([int(x) for x in yara.__version__.split(".")]) < (
4,
3,
)
return st_object
return not tuple(int(x) for x in yara.__version__.split(".")) < (4, 3)
@classmethod
def process_yara_options(cls, config: Dict[str, Any]):
rules = None
if config.get("yara_rules", None) is not None:
rule = config["yara_rules"]
if config.get("yara_string") is not None:
rule = config["yara_string"]
if rule[0] not in ["{", "/"]:
rule = f'"{rule}"'
if config.get("case", False):
rule += " nocase"
if config.get("wide", False):
rule += " wide ascii"
rules = yara.compile(
sources={"n": f"rule r1 {{strings: $a = {rule} condition: $a}}"}
)
elif config.get("yara_source", None) is not None:
rules = yara.compile(source=config["yara_source"])
elif config.get("yara_file", None) is not None:
rules = yara.compile(
file=resources.ResourceAccessor().open(config["yara_file"], "rb")
)
elif config.get("yara_compiled_file", None) is not None:
rules = yara.load(
file=resources.ResourceAccessor().open(
config["yara_compiled_file"], "rb"
)
rules = YaraScanner.get_rule(rule)
elif config.get("yara_file") is not None:
vollog.debug(f"Plain file: {config['yara_file']} - yara-x: {USE_YARA_X}")
rules = YaraScanner.from_file(config["yara_file"])
elif config.get("yara_compiled_file") is not None:
vollog.debug(
f"Compiled file: {config['yara_compiled_file']} - yara-x: {USE_YARA_X}"
)
rules = YaraScanner.from_compiled_file(config["yara_compiled_file"])
else:
vollog.error("No yara rules, nor yara rules file were specified")
return rules
+10 -5
View File
@@ -11,6 +11,7 @@ from typing import Union
from volatility3.framework import interfaces, renderers
# FIXME: Move wintime_to_datetime() and unixtime_to_datetime() out of renderers, possibly framework.objects.utility
def wintime_to_datetime(
wintime: int,
) -> Union[interfaces.renderers.BaseAbsentValue, datetime.datetime]:
@@ -19,9 +20,11 @@ def wintime_to_datetime(
return renderers.NotApplicableValue()
unix_time = unix_time - 11644473600
try:
return datetime.datetime.utcfromtimestamp(unix_time)
# Windows sometimes throws OSErrors rather than ValueErrors when it can't convert a value
except (ValueError, OSError):
return datetime.datetime.fromtimestamp(unix_time, datetime.timezone.utc)
# Windows sometimes throws OSErrors rather than ValueError/OverflowError when it can't convert a value
# Since Python 3.3, this should raise OverflowError instead of ValueError. However, it was observed
# that even in Python 3.7.17, ValueError is still being raised.
except (ValueError, OverflowError, OSError):
return renderers.UnparsableValue()
@@ -33,8 +36,10 @@ def unixtime_to_datetime(
)
if unixtime > 0:
with contextlib.suppress(ValueError):
ret = datetime.datetime.utcfromtimestamp(unixtime)
# Since Python 3.3, this should raise OverflowError instead of ValueError. However, it was observed
# that even in Python 3.7.17, ValueError is still being raised. OSError is also raised on Linux
with contextlib.suppress(ValueError, OverflowError, OSError):
ret = datetime.datetime.fromtimestamp(unixtime, datetime.timezone.utc)
return ret
@@ -10,6 +10,8 @@ Text renderers should attempt to honour all hints provided in this module where
"""
from typing import Type, Union
from volatility3.framework import interfaces
class Bin(int):
"""A class to indicate that the integer value should be represented as a
@@ -66,3 +68,17 @@ class MultiTypeData(bytes):
and self.split_nulls == other.split_nulls
and self.show_hex == other.show_hex
)
BinOrAbsent = lambda x: (
Bin(x) if not isinstance(x, interfaces.renderers.BaseAbsentValue) else x
)
HexOrAbsent = lambda x: (
Hex(x) if not isinstance(x, interfaces.renderers.BaseAbsentValue) else x
)
HexBytesOrAbsent = lambda x: (
HexBytes(x) if not isinstance(x, interfaces.renderers.BaseAbsentValue) else x
)
MultiTypeDataOrAbsent = lambda x: (
MultiTypeData(x) if not isinstance(x, interfaces.renderers.BaseAbsentValue) else x
)
+365 -4
View File
@@ -1,6 +1,9 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import math
import contextlib
from abc import ABC, abstractmethod
from typing import Iterator, List, Tuple, Optional, Union
from volatility3 import framework
@@ -29,12 +32,22 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable):
self.set_type_class("files_struct", extensions.files_struct)
self.set_type_class("kobject", extensions.kobject)
self.set_type_class("cred", extensions.cred)
self.set_type_class("inode", extensions.inode)
self.set_type_class("idr", extensions.IDR)
self.set_type_class("address_space", extensions.address_space)
self.set_type_class("page", extensions.page)
# Might not exist in the current symbols
self.optional_set_type_class("module", extensions.module)
self.optional_set_type_class("bpf_prog", extensions.bpf_prog)
self.optional_set_type_class("bpf_prog_aux", extensions.bpf_prog_aux)
self.optional_set_type_class("kernel_cap_struct", extensions.kernel_cap_struct)
self.optional_set_type_class("kernel_cap_t", extensions.kernel_cap_t)
# kernels >= 4.18
self.optional_set_type_class("timespec64", extensions.timespec64)
# kernels < 4.18. Reuses timespec64 obj extension, since both has the same members
self.optional_set_type_class("timespec", extensions.timespec64)
# Mount
self.set_type_class("vfsmount", extensions.vfsmount)
# Might not exist in older kernels or the current symbols
@@ -61,7 +74,7 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable):
class LinuxUtilities(interfaces.configuration.VersionableInterface):
"""Class with multiple useful linux functions."""
_version = (2, 1, 0)
_version = (2, 1, 1)
_required_framework_version = (2, 0, 0)
framework.require_interface_version(*_required_framework_version)
@@ -406,9 +419,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
Returns:
A kernel object (vmlinux)
"""
symbol_table_arr = volobj.vol.type_name.split("!", 1)
symbol_table = symbol_table_arr[0] if len(symbol_table_arr) == 2 else None
symbol_table = volobj.get_symbol_table_name()
module_names = context.modules.get_modules_by_symbol_tables(symbol_table)
module_names = list(module_names)
@@ -419,3 +430,353 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
kernel = context.modules[kernel_module_name]
return kernel
class IDStorage(ABC):
"""Abstraction to support both XArray and RadixTree"""
# Dynamic values, these will be initialized later
CHUNK_SHIFT = None
CHUNK_SIZE = None
CHUNK_MASK = None
def __init__(
self,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
):
self.vmlinux = context.modules[kernel_module_name]
self.vmlinux_layer = self.vmlinux.context.layers[self.vmlinux.layer_name]
self.pointer_size = self.vmlinux.get_type("pointer").size
# Dynamically work out the (XA_CHUNK|RADIX_TREE_MAP)_SHIFT values based on
# the node.slots[] array size
node_type = self.vmlinux.get_type(self.node_type_name)
slots_array_size = node_type.child_template("slots").count
# Calculate the LSB index - 1
self.CHUNK_SHIFT = slots_array_size.bit_length() - 1
self.CHUNK_SIZE = 1 << self.CHUNK_SHIFT
self.CHUNK_MASK = self.CHUNK_SIZE - 1
@classmethod
def choose_id_storage(
cls,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
) -> "IDStorage":
"""Returns the appropriate ID storage data structure instance for the current kernel implementation.
This is used by the IDR and the PageCache to choose between the XArray and RadixTree.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
kernel_module_name: The name of the kernel module on which to operate
Returns:
The appropriate ID storage instance for the current kernel
"""
vmlinux = context.modules[kernel_module_name]
address_space_type = vmlinux.get_type("address_space")
address_space_has_i_pages = address_space_type.has_member("i_pages")
i_pages_type_name = (
address_space_type.child_template("i_pages").vol.type_name
if address_space_has_i_pages
else ""
)
i_pages_is_xarray = i_pages_type_name.endswith(constants.BANG + "xarray")
i_pages_is_radix_tree_root = i_pages_type_name.endswith(
constants.BANG + "radix_tree_root"
) and vmlinux.get_type("radix_tree_root").has_member("xa_head")
if i_pages_is_xarray or i_pages_is_radix_tree_root:
return XArray(context, kernel_module_name)
else:
return RadixTree(context, kernel_module_name)
@property
@abstractmethod
def node_type_name(self) -> str:
"""Returns the Tree implementation node type name
Returns:
A string with the node type name
"""
raise NotImplementedError()
@property
def tag_internal_value(self) -> int:
"""Returns the internal node flag for the tree"""
raise NotImplementedError()
@abstractmethod
def node_is_internal(self, nodep) -> bool:
"""Checks if the node is internal"""
raise NotImplementedError
@abstractmethod
def is_node_tagged(self, nodep) -> bool:
"""Checks if the node pointer is tagged"""
raise NotImplementedError
@abstractmethod
def untag_node(self, nodep) -> int:
"""Untags a node pointer"""
raise NotImplementedError
@abstractmethod
def get_tree_height(self, treep) -> int:
"""Returns the tree height"""
raise NotImplementedError
@abstractmethod
def get_node_height(self, nodep) -> int:
"""Returns the node height"""
raise NotImplementedError
@abstractmethod
def get_head_node(self, tree) -> int:
"""Returns a pointer to the tree's head"""
raise NotImplementedError
@abstractmethod
def is_valid_node(self, nodep) -> bool:
"""Validates a node pointer"""
raise NotImplementedError
def nodep_to_node(self, nodep) -> interfaces.objects.ObjectInterface:
"""Instanciates a tree node from its pointer
Args:
nodep: Pointer to the XArray/RadixTree node
Returns:
A XArray/RadixTree node instance
"""
node = self.vmlinux.object(self.node_type_name, offset=nodep, absolute=True)
return node
def _slot_to_nodep(self, slot) -> int:
if self.node_is_internal(slot):
nodep = slot & ~self.tag_internal_value
else:
nodep = slot
return nodep
def _iter_node(self, nodep, height) -> int:
node = self.nodep_to_node(nodep)
node_slots = node.slots
for off in range(self.CHUNK_SIZE):
slot = node_slots[off]
if slot == 0:
continue
nodep = self._slot_to_nodep(slot)
if height == 1:
if self.is_valid_node(nodep):
yield nodep
else:
for child_node in self._iter_node(nodep, height - 1):
yield child_node
def get_entries(self, root: interfaces.objects.ObjectInterface) -> int:
"""Walks the tree data structure
Args:
root: The tree root object
Yields:
A tree node pointer
"""
height = self.get_tree_height(root.vol.offset)
nodep = self.get_head_node(root)
if not nodep:
return
# Keep the internal flag before untagging it
is_internal = self.node_is_internal(nodep)
if self.is_node_tagged(nodep):
nodep = self.untag_node(nodep)
if is_internal:
height = self.get_node_height(nodep)
if height == 0:
if self.is_valid_node(nodep):
yield nodep
else:
for child_node in self._iter_node(nodep, height):
yield child_node
class XArray(IDStorage):
XARRAY_TAG_MASK = 3
XARRAY_TAG_INTERNAL = 2
def get_tree_height(self, treep) -> int:
return 0
@property
def node_type_name(self) -> str:
return "xa_node"
@property
def tag_internal_value(self) -> int:
return self.XARRAY_TAG_INTERNAL
def get_node_height(self, nodep) -> int:
node = self.nodep_to_node(nodep)
return (node.shift / self.CHUNK_SHIFT) + 1
def get_head_node(self, tree) -> int:
return tree.xa_head
def node_is_internal(self, nodep) -> bool:
return (nodep & self.XARRAY_TAG_MASK) == self.XARRAY_TAG_INTERNAL
def is_node_tagged(self, nodep) -> bool:
return (nodep & self.XARRAY_TAG_MASK) != 0
def untag_node(self, nodep) -> int:
return nodep & (~self.XARRAY_TAG_MASK)
def is_valid_node(self, nodep) -> bool:
# It should have the tag mask clear
return not self.is_node_tagged(nodep)
class RadixTree(IDStorage):
RADIX_TREE_INTERNAL_NODE = 1
RADIX_TREE_EXCEPTIONAL_ENTRY = 2
RADIX_TREE_ENTRY_MASK = 3
# Dynamic values. These will be initialized later
RADIX_TREE_INDEX_BITS = None
RADIX_TREE_MAX_PATH = None
RADIX_TREE_HEIGHT_SHIFT = None
RADIX_TREE_HEIGHT_MASK = None
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
char_bits = 8
self.RADIX_TREE_INDEX_BITS = char_bits * self.pointer_size
self.RADIX_TREE_MAX_PATH = int(
math.ceil(self.RADIX_TREE_INDEX_BITS / float(self.CHUNK_SHIFT))
)
self.RADIX_TREE_HEIGHT_SHIFT = self.RADIX_TREE_MAX_PATH + 1
self.RADIX_TREE_HEIGHT_MASK = (1 << self.RADIX_TREE_HEIGHT_SHIFT) - 1
if not self.vmlinux.has_type("radix_tree_root"):
# In kernels 4.20, RADIX_TREE_INTERNAL_NODE flag took RADIX_TREE_EXCEPTIONAL_ENTRY's
# value. RADIX_TREE_EXCEPTIONAL_ENTRY was removed but that's managed in is_valid_node()
# Note that the Radix Tree is still in use for IDR, even after kernels 4.20 when XArray
# mostly replace it
self.RADIX_TREE_INTERNAL_NODE = 2
@property
def node_type_name(self) -> str:
return "radix_tree_node"
@property
def tag_internal_value(self) -> int:
return self.RADIX_TREE_INTERNAL_NODE
def get_tree_height(self, treep) -> int:
with contextlib.suppress(exceptions.SymbolError):
if self.vmlinux.get_type("radix_tree_root").has_member("height"):
# kernels < 4.7.10
radix_tree_root = self.vmlinux.object(
"radix_tree_root", offset=treep, absolute=True
)
return radix_tree_root.height
# kernels >= 4.7.10
return 0
def _radix_tree_maxindex(self, node, height) -> int:
"""Return the maximum key which can be store into a radix tree with this height."""
if not self.vmlinux.has_symbol("height_to_maxindex"):
# Kernels >= 4.7
return (self.CHUNK_SIZE << node.shift) - 1
else:
# Kernels < 4.7
height_to_maxindex_array = self.vmlinux.object_from_symbol(
"height_to_maxindex"
)
maxindex = height_to_maxindex_array[height]
return maxindex
def get_node_height(self, nodep) -> int:
node = self.nodep_to_node(nodep)
if hasattr(node, "shift"):
# 4.7 <= Kernels < 4.20
return (node.shift / self.CHUNK_SHIFT) + 1
elif hasattr(node, "path"):
# 3.15 <= Kernels < 4.7
return node.path & self.RADIX_TREE_HEIGHT_MASK
elif hasattr(node, "height"):
# Kernels < 3.15
return node.height
else:
raise exceptions.VolatilityException("Cannot find radix-tree node height")
def get_head_node(self, tree) -> int:
return tree.rnode
def node_is_internal(self, nodep) -> bool:
return (nodep & self.RADIX_TREE_INTERNAL_NODE) != 0
def is_node_tagged(self, nodep) -> bool:
return self.node_is_internal(nodep)
def untag_node(self, nodep) -> int:
return nodep & (~self.RADIX_TREE_ENTRY_MASK)
def is_valid_node(self, nodep) -> bool:
# In kernels 4.20, exceptional nodes were removed and internal entries took their bitmask
if self.vmlinux.has_type("radix_tree_root"):
return (
nodep & self.RADIX_TREE_ENTRY_MASK
) != self.RADIX_TREE_EXCEPTIONAL_ENTRY
return True
class PageCache(object):
"""Linux Page Cache abstraction"""
def __init__(
self,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
page_cache: interfaces.objects.ObjectInterface,
):
"""
Args:
context: interfaces.context.ContextInterface,
kernel_module_name: The name of the kernel module on which to operate
page_cache: Page cache address space
"""
self.vmlinux = context.modules[kernel_module_name]
self._page_cache = page_cache
self._idstorage = IDStorage.choose_id_storage(context, kernel_module_name)
def get_cached_pages(self) -> interfaces.objects.ObjectInterface:
"""Returns all page cache contents
Yields:
Page objects
"""
for page_addr in self._idstorage.get_entries(self._page_cache.i_pages):
if not page_addr:
continue
page = self.vmlinux.object("page", offset=page_addr, absolute=True)
if page:
yield page
@@ -4,10 +4,16 @@
import collections.abc
import logging
import functools
import binascii
import stat
from datetime import datetime
import socket as socket_module
from typing import Generator, Iterable, Iterator, Optional, Tuple, List
from typing import Generator, Iterable, Iterator, Optional, Tuple, List, Union, Dict
from volatility3.framework import constants, exceptions, objects, interfaces, symbols
from volatility3.framework.renderers import conversion
from volatility3.framework.configuration import requirements
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
@@ -44,7 +50,7 @@ class module(generic.GenericIntelProcess):
).choices
except exceptions.SymbolError:
vollog.debug(
f"Unable to find mod_mem_type enum. This message can be ignored for kernels < 6.4"
"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 = {}
@@ -817,6 +823,26 @@ class dentry(objects.StructType):
current_dentry = current_dentry.d_parent
return None
def get_subdirs(self) -> interfaces.objects.ObjectInterface:
"""Walks dentry subdirs
Yields:
A dentry object
"""
if self.has_member("d_sib") and self.has_member("d_children"):
# kernels >= 6.8
walk_member = "d_sib"
list_head_member = self.d_children.first
elif self.has_member("d_child") and self.has_member("d_subdirs"):
# 2.5.0 <= kernels < 6.8
walk_member = "d_child"
list_head_member = self.d_subdirs
else:
raise exceptions.VolatilityException("Unsupported dentry type")
dentry_type_name = self.get_symbol_table_name() + constants.BANG + "dentry"
yield from list_head_member.to_list(dentry_type_name, walk_member)
class struct_file(objects.StructType):
def get_dentry(self) -> interfaces.objects.ObjectInterface:
@@ -933,7 +959,8 @@ class mount(objects.StructType):
MNT_RELATIME: "relatime",
}
def get_mnt_sb(self):
def get_mnt_sb(self) -> int:
"""Returns a pointer to the super_block"""
if self.has_member("mnt"):
return self.mnt.mnt_sb
elif self.has_member("mnt_sb"):
@@ -1248,6 +1275,7 @@ class vfsmount(objects.StructType):
return self._get_real_mnt().has_parent()
def get_mnt_sb(self):
"""Returns a pointer to the super_block"""
return self.mnt_sb
def get_flags_access(self) -> str:
@@ -1581,20 +1609,59 @@ class xdp_sock(objects.StructType):
class bpf_prog(objects.StructType):
def get_type(self):
def get_type(self) -> Union[str, None]:
"""Returns a string with the eBPF program type"""
# The program type was in `bpf_prog_aux::prog_type` from 3.18.140 to
# 4.1.52 before it was moved to `bpf_prog::type`
if self.has_member("type"):
# kernel >= 4.1.52
return self.type
return self.type.description
if self.has_member("aux") and self.aux:
if self.aux.has_member("prog_type"):
# 3.18.140 <= kernel < 4.1.52
return self.aux.prog_type
return self.aux.prog_type.description
# kernel < 3.18.140
raise AttributeError("Unable to find the BPF type")
return None
def get_tag(self) -> Union[str, None]:
"""Returns a string with the eBPF program tag"""
# 'tag' was added in kernels 4.10
if not self.has_member("tag"):
return None
vmlinux = linux.LinuxUtilities.get_module_from_volobj_type(self._context, self)
vmlinux_layer = vmlinux.context.layers[vmlinux.layer_name]
prog_tag_addr = self.tag.vol.offset
prog_tag_size = self.tag.count
prog_tag_bytes = vmlinux_layer.read(prog_tag_addr, prog_tag_size)
prog_tag = binascii.hexlify(prog_tag_bytes).decode()
return prog_tag
def get_name(self) -> Union[str, None]:
"""Returns a string with the eBPF program name"""
if not self.has_member("aux"):
# 'prog_aux' was added in kernels 3.18
return None
return self.aux.get_name()
class bpf_prog_aux(objects.StructType):
def get_name(self) -> Union[str, None]:
"""Returns a string with the eBPF program name"""
if not self.has_member("name"):
# 'name' was added in kernels 4.15
return None
if not self.name:
return None
return utility.array_to_string(self.name)
class cred(objects.StructType):
@@ -1761,3 +1828,383 @@ class kernel_cap_t(kernel_cap_struct):
)
return cap_value & self.get_kernel_cap_full()
class timespec64(objects.StructType):
def to_datetime(self) -> datetime:
"""Returns the respective aware datetime"""
dt = conversion.unixtime_to_datetime(self.tv_sec + self.tv_nsec / 1e9)
return dt
class inode(objects.StructType):
def is_valid(self) -> bool:
# i_count is a 'signed' counter (atomic_t). Smear, or essentially a wrong inode
# pointer, will easily cause an integer overflow here.
return self.i_ino > 0 and self.i_count.counter >= 0
@property
def is_dir(self) -> bool:
"""Returns True if the inode is a directory"""
return stat.S_ISDIR(self.i_mode) != 0
@property
def is_reg(self) -> bool:
"""Returns True if the inode is a regular file"""
return stat.S_ISREG(self.i_mode) != 0
@property
def is_link(self) -> bool:
"""Returns True if the inode is a symlink"""
return stat.S_ISLNK(self.i_mode) != 0
@property
def is_fifo(self) -> bool:
"""Returns True if the inode is a FIFO"""
return stat.S_ISFIFO(self.i_mode) != 0
@property
def is_sock(self) -> bool:
"""Returns True if the inode is a socket"""
return stat.S_ISSOCK(self.i_mode) != 0
@property
def is_block(self) -> bool:
"""Returns True if the inode is a block device"""
return stat.S_ISBLK(self.i_mode) != 0
@property
def is_char(self) -> bool:
"""Returns True if the inode is a char device"""
return stat.S_ISCHR(self.i_mode) != 0
@property
def is_sticky(self) -> bool:
"""Returns True if the sticky bit is set"""
return (self.i_mode & stat.S_ISVTX) != 0
def get_inode_type(self) -> Union[str, None]:
"""Returns inode type name
Returns:
The inode type name
"""
if self.is_dir:
return "DIR"
elif self.is_reg:
return "REG"
elif self.is_link:
return "LNK"
elif self.is_fifo:
return "FIFO"
elif self.is_sock:
return "SOCK"
elif self.is_char:
return "CHR"
elif self.is_block:
return "BLK"
else:
return None
def _time_member_to_datetime(self, member) -> datetime:
if self.has_member(f"{member}_sec") and self.has_member(f"{member}_nsec"):
# kernels >= 6.11 it's i_*_sec -> time64_t and i_*_nsec -> u32
# Ref Linux commit 3aa63a569c64e708df547a8913c84e64a06e7853
return conversion.unixtime_to_datetime(
self.member(f"{member}_sec") + self.has_member(f"{member}_nsec") / 1e9
)
elif self.has_member(f"__{member}"):
# 6.6 <= kernels < 6.11 it's a timespec64
# Ref Linux commit 13bc24457850583a2e7203ded05b7209ab4bc5ef / 12cd44023651666bd44baa36a5c999698890debb
return self.member(f"__{member}").to_datetime()
elif self.has_member(member):
# In kernels < 6.6 it's a timespec64 or timespec
return self.member(member).to_datetime()
else:
raise exceptions.VolatilityException(
"Unsupported kernel inode type implementation"
)
def get_access_time(self) -> datetime:
"""Returns the inode's last access time
This is updated when inode contents are read
Returns:
A datetime with the inode's last access time
"""
return self._time_member_to_datetime("i_atime")
def get_modification_time(self) -> datetime:
"""Returns the inode's last modification time
This is updated when the inode contents change
Returns:
A datetime with the inode's last data modification time
"""
return self._time_member_to_datetime("i_mtime")
def get_change_time(self) -> datetime:
"""Returns the inode's last change time
This is updated when the inode metadata changes
Returns:
A datetime with the inode's last change time
"""
return self._time_member_to_datetime("i_ctime")
def get_file_mode(self) -> str:
"""Returns the inode's file mode as string of the form '-rwxrwxrwx'.
Returns:
The inode's file mode string
"""
return stat.filemode(self.i_mode)
def get_pages(self) -> interfaces.objects.ObjectInterface:
"""Gets the inode's cached pages
Yields:
The inode's cached pages
"""
if not self.i_size:
return
elif not (self.i_mapping and self.i_mapping.nrpages > 0):
return
page_cache = linux.PageCache(
context=self._context,
kernel_module_name="kernel",
page_cache=self.i_mapping.dereference(),
)
yield from page_cache.get_cached_pages()
def get_contents(self):
"""Get the inode cached pages from the page cache
Yields:
page_index (int): The page index in the Tree. File offset is page_index * PAGE_SIZE.
page_content (str): The page content
"""
for page_obj in self.get_pages():
page_index = int(page_obj.index)
page_content = page_obj.get_content()
yield page_index, page_content
class address_space(objects.StructType):
@property
def i_pages(self):
"""Returns the appropriate member containing the page cache tree"""
if self.has_member("i_pages"):
# Kernel >= 4.17
return self.member("i_pages")
elif self.has_member("page_tree"):
# Kernel < 4.17
return self.member("page_tree")
raise exceptions.VolatilityException("Unsupported page cache tree")
class page(objects.StructType):
@property
@functools.lru_cache()
def pageflags_enum(self) -> Dict:
"""Returns 'pageflags' enumeration key/values
Returns:
A dictionary with the pageflags enumeration key/values
"""
# FIXME: It would be even better to use @functools.cached_property instead,
# however, this requires Python +3.8
try:
pageflags_enum = self._context.symbol_space.get_enumeration(
self.get_symbol_table_name() + constants.BANG + "pageflags"
).choices
except exceptions.SymbolError:
vollog.debug(
"Unable to find pageflags enum. This can happen in kernels < 2.6.26 or wrong ISF"
)
# set to empty dict to show that the enum was not found, and so shouldn't be searched for again
pageflags_enum = {}
return pageflags_enum
def get_flags_list(self) -> List[str]:
"""Returns a list of page flags
Returns:
List of page flags
"""
flags = []
for name, value in self.pageflags_enum.items():
if self.flags & (1 << value) != 0:
flags.append(name)
return flags
def to_paddr(self) -> int:
"""Converts a page's virtual address to its physical address using the current physical memory model.
Returns:
int: page physical address
"""
vmlinux = linux.LinuxUtilities.get_module_from_volobj_type(self._context, self)
vmlinux_layer = vmlinux.context.layers[vmlinux.layer_name]
vmemmap_start = None
if vmlinux.has_symbol("mem_section"):
# SPARSEMEM_VMEMMAP physical memory model: memmap is virtually contiguous
if vmlinux.has_symbol("vmemmap_base"):
# CONFIG_DYNAMIC_MEMORY_LAYOUT - KASLR kernels >= 4.9
vmemmap_start = vmlinux.object_from_symbol("vmemmap_base")
else:
# !CONFIG_DYNAMIC_MEMORY_LAYOUT
if vmlinux_layer._maxvirtaddr < 57:
# 4-Level paging -> VMEMMAP_START = __VMEMMAP_BASE_L4
vmemmap_base_l4 = 0xFFFFEA0000000000
vmemmap_start = vmemmap_base_l4
else:
# 5-Level paging -> VMEMMAP_START = __VMEMMAP_BASE_L5
# FIXME: Once 5-level paging is supported, uncomment the following lines and remove the exception
# vmemmap_base_l5 = 0xFFD4000000000000
# vmemmap_start = vmemmap_base_l5
raise exceptions.VolatilityException(
"5-level paging is not yet supported"
)
elif vmlinux.has_symbol("mem_map"):
# FLATMEM physical memory model, typically 32bit
vmemmap_start = vmlinux.object_from_symbol("mem_map")
elif vmlinux.has_symbol("node_data"):
raise exceptions.VolatilityException("NUMA systems are not yet supported")
else:
raise exceptions.VolatilityException("Unsupported Linux memory model")
if not vmemmap_start:
raise exceptions.VolatilityException(
"Something went wrong, we shouldn't be here"
)
page_type_size = vmlinux.get_type("page").size
pagec = vmlinux_layer.canonicalize(self.vol.offset)
pfn = (pagec - vmemmap_start) // page_type_size
page_paddr = pfn * vmlinux_layer.page_size
return page_paddr
def get_content(self) -> Union[str, None]:
"""Returns the page content
Returns:
The page content
"""
vmlinux = linux.LinuxUtilities.get_module_from_volobj_type(self._context, self)
vmlinux_layer = vmlinux.context.layers[vmlinux.layer_name]
physical_layer = vmlinux.context.layers["memory_layer"]
page_paddr = self.to_paddr()
if not page_paddr:
return None
page_data = physical_layer.read(page_paddr, vmlinux_layer.page_size)
return page_data
class IDR(objects.StructType):
IDR_BITS = 8
IDR_MASK = (1 << IDR_BITS) - 1
INT_SIZE = 4
MAX_IDR_SHIFT = INT_SIZE * 8 - 1
MAX_IDR_BIT = 1 << MAX_IDR_SHIFT
def idr_max(self, num_layers: int) -> int:
"""Returns the maximum ID which can be allocated given idr::layers
Args:
num_layers: Number of layers
Returns:
Maximum ID for a given number of layers
"""
# Kernel < 4.17
bits = min([self.INT_SIZE, num_layers * self.IDR_BITS, self.MAX_IDR_SHIFT])
return (1 << bits) - 1
def idr_find(self, idr_id: int) -> int:
"""Finds an ID within the IDR data structure.
Based on idr_find_slowpath(), 3.9 <= Kernel < 4.11
Args:
idr_id: The IDR lookup ID
Returns:
A pointer to the given ID element
"""
vmlinux = linux.LinuxUtilities.get_module_from_volobj_type(self._context, self)
if not vmlinux.get_type("idr_layer").has_member("layer"):
vollog.info(
"Unsupported IDR implementation, it should be a very very old kernel, probabably < 2.6"
)
return None
if idr_id < 0:
return None
idr_layer = self.top
if not idr_layer:
return None
n = (idr_layer.layer + 1) * self.IDR_BITS
if idr_id > self.idr_max(idr_layer.layer + 1):
return None
assert n != 0
while n > 0 and idr_layer:
n -= self.IDR_BITS
assert n == idr_layer.layer * self.IDR_BITS
idr_layer = idr_layer.ary[(idr_id >> n) & self.IDR_MASK]
return idr_layer
def _old_kernel_get_entries(self) -> int:
# Kernels < 4.11
cur = self.cur
total = next_id = 0
while next_id < cur:
entry = self.idr_find(next_id)
if entry:
yield entry
total += 1
next_id += 1
def _new_kernel_get_entries(self) -> int:
# Kernels >= 4.11
id_storage = linux.IDStorage.choose_id_storage(
self._context, kernel_module_name="kernel"
)
for page_addr in id_storage.get_entries(root=self.idr_rt):
yield page_addr
def get_entries(self) -> int:
"""Walks the IDR and yield a pointer associated with each element.
Args:
in_use (int, optional): _description_. Defaults to 0.
Yields:
A pointer associated with each element.
"""
if self.has_member("idr_rt"):
# Kernels >= 4.11
get_entries_func = self._new_kernel_get_entries
else:
# Kernels < 4.11
get_entries_func = self._old_kernel_get_entries
for page_addr in get_entries_func():
yield page_addr
@@ -17,6 +17,7 @@ class WindowsKernelIntermedSymbols(intermed.IntermediateSymbolTable):
self.set_type_class("_KTHREAD", extensions.KTHREAD)
self.set_type_class("_LIST_ENTRY", extensions.LIST_ENTRY)
self.set_type_class("_EPROCESS", extensions.EPROCESS)
self.set_type_class("_ERESOURCE", extensions.ERESOURCE)
self.set_type_class("_UNICODE_STRING", extensions.UNICODE_STRING)
self.set_type_class("_EX_FAST_REF", extensions.EX_FAST_REF)
self.set_type_class("_TOKEN", extensions.TOKEN)
@@ -39,6 +40,7 @@ class WindowsKernelIntermedSymbols(intermed.IntermediateSymbolTable):
self.set_type_class("_VACB", extensions.VACB)
self.set_type_class("_POOL_TRACKER_BIG_PAGES", pool.POOL_TRACKER_BIG_PAGES)
self.set_type_class("_IMAGE_DOS_HEADER", pe.IMAGE_DOS_HEADER)
self.set_type_class("_KTIMER", extensions.KTIMER)
# Might not necessarily defined in every version of windows
self.optional_set_type_class("_IMAGE_NT_HEADERS", pe.IMAGE_NT_HEADERS)
@@ -105,8 +105,11 @@
},
"NotificationRoutine": {
"type": {
"kind": "base",
"name": "unsigned int"
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 24
}
@@ -20,9 +20,10 @@ from volatility3.framework import (
)
from volatility3.framework.interfaces.objects import ObjectInterface
from volatility3.framework.layers import intel
from volatility3.framework.objects import utility
from volatility3.framework.renderers import conversion
from volatility3.framework.symbols import generic
from volatility3.framework.symbols.windows.extensions import kdbg, pe, pool
from volatility3.framework.symbols.windows.extensions import pool
vollog = logging.getLogger(__name__)
@@ -306,16 +307,19 @@ class MMVAD_SHORT(objects.StructType):
raise AttributeError("Unable to find the private memory member")
@property
def Protection(self):
if self.has_member("u"):
return self.u.VadFlags.Protection
elif self.has_member("Core"):
return self.Core.u.VadFlags.Protection
else:
return None
def get_protection(self, protect_values, winnt_protections):
"""Get the VAD's protection constants as a string."""
protect = None
if self.has_member("u"):
protect = self.u.VadFlags.Protection
elif self.has_member("Core"):
protect = self.Core.u.VadFlags.Protection
protect = self.Protection
try:
value = protect_values[protect]
@@ -593,6 +597,38 @@ class UNICODE_STRING(objects.StructType):
String = property(get_string)
class ERESOURCE(objects.StructType):
def is_valid(self) -> bool:
vollog.debug(f"Checking ERESOURCE Validity: {hex(self.vol.offset)}")
if not self._context.layers[self.vol.layer_name].is_valid(self.vol.offset):
return False
sym_table = self.get_symbol_table_name()
waiters_valid = self.SharedWaiters == 0 or self._context.layers[
self.vol.layer_name
].is_valid(
self.SharedWaiters.vol.offset,
self._context.symbol_space.get_type(
sym_table + constants.BANG + "_KSEMAPHORE"
).size,
)
try:
return (
waiters_valid
and self.SystemResourcesList.Flink is not None
and self.SystemResourcesList.Blink is not None
and self.SystemResourcesList.Flink != self.SystemResourcesList.Blink
and self.SystemResourcesList.Flink.Blink == self.vol.offset
and self.SystemResourcesList.Blink.Flink == self.vol.offset
and self.NumberOfSharedWaiters == 0
)
except exceptions.InvalidAddressException:
return False
class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject):
"""A class for executive kernel processes objects."""
@@ -611,11 +647,23 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject):
ctime = self.get_create_time()
if not isinstance(ctime, datetime.datetime):
# A process must have a creation time
return False
if not (1998 < ctime.year < 2030):
current_year = datetime.datetime.now().year
if not (1998 < ctime.year < current_year + 10):
return False
etime = self.get_exit_time()
if isinstance(etime, datetime.datetime):
if not (1998 < etime.year < current_year + 10):
return False
# Exit time, if available, must be after the creation time
# At this point, we are sure both are datetimes, so let's compare them
if ctime > etime:
return False
# NT pids are divisible by 4
if self.UniqueProcessId % 4 != 0:
return False
@@ -633,7 +681,11 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject):
if dtb & ~0xFFF == 0:
return False
## TODO: we can also add the thread Flink and Blink tests if necessary
# Quick smear test on thread Flink and Blink
kernel = 0x80000000 # Yes, it's a quick test
list_head = self.ThreadListHead
if list_head.Flink < kernel or list_head.Blink < kernel:
return False
except exceptions.InvalidAddressException:
return False
@@ -994,6 +1046,83 @@ class TOKEN(objects.StructType):
vollog.log(constants.LOGLEVEL_VVVV, "Broken Token Privileges.")
class KTIMER(objects.StructType):
"""A class for Kernel Timers"""
VALID_TYPES = {
8: "TimerNotificationObject",
9: "TimerSynchronizationObject",
}
def get_signaled(self):
if self.Header.SignalState:
return "Yes"
return "-"
def get_raw_dpc(self):
"""Returns the encoded DPC since it may not look like a pointer after encoding"""
symbol_table_name = self.get_symbol_table_name()
pointer_type = self._context.symbol_space.get_type(
symbol_table_name + constants.BANG + "pointer"
)
return self._context.object(
object_type=pointer_type,
layer_name=self.vol.layer_name,
offset=self.Dpc.vol.offset,
)
def valid_type(self):
return self.Header.Type in self.VALID_TYPES
def get_due_time(self):
return "{0:#010x}:{1:#010x}".format(self.DueTime.HighPart, self.DueTime.LowPart)
def get_dpc(self):
"""Return Dpc, and if Windows 7 or later, decode it"""
symbol_table_name = self.get_symbol_table_name()
kvo = self._context.layers[self.vol.native_layer_name].config[
"kernel_virtual_offset"
]
ntkrnlmp = self._context.module(
symbol_table_name,
layer_name=self.vol.native_layer_name,
offset=kvo,
native_layer_name=self.vol.native_layer_name,
)
if ntkrnlmp.has_symbol("KiWaitNever") and ntkrnlmp.has_symbol("KiWaitAlways"):
wait_never = ntkrnlmp.object(
object_type="unsigned long long",
offset=ntkrnlmp.get_symbol("KiWaitNever").address,
)
wait_always = ntkrnlmp.object(
object_type="unsigned long long",
offset=ntkrnlmp.get_symbol("KiWaitAlways").address,
)
low_byte = (wait_never) & 0xFF
entry = utility.rol(self.get_raw_dpc() ^ wait_never, low_byte)
swap_xor = self._context.layers[self.vol.native_layer_name].canonicalize(
self.vol.offset
)
entry = utility.bswap_64(entry ^ swap_xor)
dpc = entry ^ wait_always
symbol_table_name = self.get_symbol_table_name()
kdpc_type = self._context.symbol_space.get_type(
symbol_table_name + constants.BANG + "_KDPC"
)
return self._context.object(
object_type=kdpc_type,
layer_name=self.vol.layer_name,
offset=dpc,
)
else:
return self.Dpc
class KTHREAD(objects.StructType):
"""A class for thread control block objects."""
@@ -2,6 +2,8 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from typing import Optional
from volatility3.framework import objects, constants, exceptions
@@ -26,7 +28,15 @@ class MFTFileName(objects.StructType):
class MFTAttribute(objects.StructType):
"""This represents an MFT ATTRIBUTE"""
def get_resident_filename(self) -> str:
def get_resident_filename(self) -> Optional[str]:
# 4MB chosen as cutoff instead of 4KB to allow for recovery from format /L created file systems
# Length as 512 as its 256*2, which is the maximum size for an entire file path, so this is even generous
if (
self.Attr_Header.ContentOffset > 0x400000
or self.Attr_Header.NameLength > 512
):
return None
# To get the resident name, we jump to relative name offset and read name length * 2 bytes of data
try:
name = self._context.object(
@@ -41,7 +51,15 @@ class MFTAttribute(objects.StructType):
except exceptions.InvalidAddressException:
return None
def get_resident_filecontent(self) -> bytes:
def get_resident_filecontent(self) -> Optional[bytes]:
# smear observed in mass testing of samples
# 4MB chosen as cutoff instead of 4KB to allow for recovery from format /L created file systems
if (
self.Attr_Header.ContentOffset > 0x400000
or self.Attr_Header.ContentLength > 0x400000
):
return None
# To get the resident content, we jump to relative content offset and read name length * 2 bytes of data
try:
bytesobj = self._context.object(
@@ -0,0 +1,278 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
import struct
from datetime import datetime
from typing import Dict, Optional, Tuple, Union
from volatility3.framework import constants, exceptions, interfaces, objects, renderers
from volatility3.framework.symbols.windows.extensions import conversion
vollog = logging.getLogger(__name__)
class SHIM_CACHE_ENTRY(objects.StructType):
"""Class for abstracting variations in the shimcache LRU list entry structure"""
def __init__(
self,
context: interfaces.context.ContextInterface,
type_name: str,
object_info: interfaces.objects.ObjectInformation,
size: int,
members: Dict[str, Tuple[int, interfaces.objects.Template]],
) -> None:
super().__init__(context, type_name, object_info, size, members)
self._exec_flag = None
self._file_path = None
self._file_size = None
self._last_modified = None
self._last_updated = None
@property
def exec_flag(self) -> Union[bool, interfaces.renderers.BaseAbsentValue]:
"""Checks if InsertFlags fields has been bitwise OR'd with a value of 2.
This behavior was observed when processes are created by CSRSS."""
if self._exec_flag is not None:
return self._exec_flag
if hasattr(self, "ListEntryDetail") and hasattr(
self.ListEntryDetail, "InsertFlags"
):
self._exec_flag = self.ListEntryDetail.InsertFlags & 0x2 == 2
elif hasattr(self, "InsertFlags"):
self._exec_flag = self.InsertFlags & 0x2 == 2
elif hasattr(self, "ListEntryDetail") and hasattr(
self.ListEntryDetail, "BlobBuffer"
):
blob_offset = self.ListEntryDetail.BlobBuffer
blob_size = self.ListEntryDetail.BlobSize
if not self._context.layers[self.vol.native_layer_name].is_valid(
blob_offset, blob_size
):
self._exec_flag = renderers.UnparsableValue()
raw_flag = self._context.layers[self.vol.native_layer_name].read(
blob_offset, blob_size
)
if not raw_flag:
self._exec_flag = renderers.UnparsableValue()
try:
self._exec_flag = bool(struct.unpack("<I", raw_flag)[0])
except struct.error:
self._exec_flag = renderers.UnparsableValue()
else:
# Always set to true for XP/2K3
self._exec_flag = renderers.NotApplicableValue()
return self._exec_flag
@property
def file_size(self) -> Union[int, interfaces.renderers.BaseAbsentValue]:
if self._file_size is not None:
return self._file_size
try:
self._file_size = self.FileSize
if self._file_size < 0:
self._file_size = 0
except AttributeError:
self._file_size = renderers.NotApplicableValue()
except exceptions.InvalidAddressException:
self._file_size = renderers.UnreadableValue()
return self._file_size
@property
def last_modified(self) -> Union[datetime, interfaces.renderers.BaseAbsentValue]:
if self._last_modified is not None:
return self._last_modified
try:
self._last_modified = conversion.wintime_to_datetime(
self.ListEntryDetail.LastModified.QuadPart
)
except AttributeError:
self._last_modified = conversion.wintime_to_datetime(
self.LastModified.QuadPart
)
except exceptions.InvalidAddressException:
self._last_modified = renderers.UnreadableValue()
return self._last_modified
@property
def last_update(self) -> Union[datetime, interfaces.renderers.BaseAbsentValue]:
if self._last_updated is not None:
return self._last_updated
try:
self._last_updated = conversion.wintime_to_datetime(
self.LastUpdate.QuadPart
)
except AttributeError:
self._last_updated = renderers.NotApplicableValue()
return self._last_updated
@property
def file_path(self) -> Union[str, interfaces.renderers.BaseAbsentValue]:
if self._file_path is not None:
return self._file_path
if not hasattr(self.Path, "Buffer"):
return self.Path.cast(
"string", max_length=self.Path.vol.count, encoding="utf-16le"
)
try:
file_path_raw = (
self._context.layers[self.vol.native_layer_name].read(
self.Path.Buffer, self.Path.Length
)
or b""
)
self._file_path = file_path_raw.decode("utf-16", errors="replace")
except exceptions.InvalidAddressException:
self._file_path = renderers.UnreadableValue()
return self._file_path
def is_valid(self) -> bool:
"""Shim cache validation is limited to ensuring that a subset of the
pointers in the LIST_ENTRY field are valid (similar to validation of
ERESOURCE)"""
# shim entries on Windows XP do not have list entry attributes; in this case,
# perform a different set of validations
try:
if not hasattr(self, "ListEntry"):
return bool(self.last_modified and self.last_update and self.file_size)
# on some platforms ListEntry.Blink is null, so this cannot be validated
if (
self.ListEntry.Flink != 0
and (
self.ListEntry.Blink.dereference()
!= self.ListEntry.Flink.dereference()
)
and (
self.ListEntry.Flink.Blink
== self.ListEntry.Flink.Blink.dereference().vol.offset
)
):
return True
else:
return False
except exceptions.InvalidAddressException:
return False
class SHIM_CACHE_HANDLE(objects.StructType):
def __init__(
self,
context: interfaces.context.ContextInterface,
type_name: str,
object_info: interfaces.objects.ObjectInformation,
size: int,
members: Dict[str, Tuple[int, interfaces.objects.Template]],
) -> None:
super().__init__(context, type_name, object_info, size, members)
@property
def head(self) -> Optional[SHIM_CACHE_ENTRY]:
try:
if not self.eresource.is_valid():
return None
except exceptions.InvalidAddressException:
return None
rtl_avl_table = self._context.object(
self.get_symbol_table_name() + constants.BANG + "_RTL_AVL_TABLE",
self.vol.layer_name,
self.rtl_avl_table,
self.vol.native_layer_name,
)
if not self._context.layers[self.vol.layer_name].is_valid(
self.rtl_avl_table.vol.offset
):
return None
offset_head = rtl_avl_table.vol.offset + rtl_avl_table.vol.size
head = self._context.object(
self.get_symbol_table_name() + constants.BANG + "SHIM_CACHE_ENTRY",
self.vol.layer_name,
offset_head,
)
if not head.is_valid():
return None
return head
def is_valid(self, avl_section_start: int, avl_section_end: int) -> bool:
if self.vol.offset == 0:
return False
vollog.debug(f"Checking SHIM_CACHE_HANDLE validity @ {hex(self.vol.offset)}")
if not (
self._context.layers[self.vol.layer_name].is_valid(self.vol.offset)
and self.eresource.is_valid()
and self.rtl_avl_table.is_valid(avl_section_start, avl_section_end)
and self.head
):
return False
return self.head.is_valid()
class RTL_AVL_TABLE(objects.StructType):
def is_valid(self, page_start: int, page_end: int) -> bool:
try:
if self.BalancedRoot.Parent != self.BalancedRoot.vol.offset:
vollog.debug(
f"RTL_AVL_TABLE @ {self.vol.offset} Invalid: Failed BalancedRoot parent equality check"
)
return False
elif self.AllocateRoutine < page_start or self.AllocateRoutine > page_end:
vollog.debug(
f"RTL_AVL_TABLE @ {self.vol.offset} Invalid: Failed AllocateRoutine range check"
)
return False
elif self.CompareRoutine < page_start or self.CompareRoutine > page_end:
vollog.debug(
f"RTL_AVL_TABLE @ {self.vol.offset} Invalid: Failed CompareRoutine range check"
)
return False
elif (
(self.AllocateRoutine.vol.offset == self.CompareRoutine.vol.offset)
or (self.AllocateRoutine.vol.offset == self.FreeRoutine.vol.offset)
or (self.CompareRoutine.vol.offset == self.FreeRoutine.vol.offset)
):
vollog.debug(
f"RTL_AVL_TABLE @ {self.vol.offset} Invalid: Failed (Compare|Allocate|Free)Routine uniqueness check"
)
return False
return True
except exceptions.InvalidAddressException:
return False
class_types = {
"SHIM_CACHE_HANDLE": SHIM_CACHE_HANDLE,
"SHIM_CACHE_ENTRY": SHIM_CACHE_ENTRY,
"_RTL_AVL_TABLE": RTL_AVL_TABLE,
}
@@ -0,0 +1,582 @@
{
"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"
},
"unsigned be short": {
"kind": "int",
"size": 2,
"signed": false,
"endian": "big"
},
"long long": {
"endian": "little",
"kind": "int",
"signed": true,
"size": 8
},
"long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
}
},
"symbols": {},
"user_types": {
"_UDP_ENDPOINT": {
"fields": {
"Owner": {
"offset": 40,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_EPROCESS"
}
}
},
"CreateTime": {
"offset": 88,
"type": {
"kind": "union",
"name": "_LARGE_INTEGER"
}
},
"Next": {
"offset": 112,
"type":{
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_UDP_ENDPOINT"
}
}
},
"LocalAddr": {
"offset": 168,
"type":{
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_LOCAL_ADDRESS_WIN10_UDP"
}
}
},
"InetAF": {
"offset": 32,
"type":{
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_INETAF"
}
}
},
"Port": {
"offset": 160,
"type": {
"kind": "base",
"name": "unsigned be short"
}
}
},
"kind": "struct",
"size": 168
},
"_TCP_LISTENER": {
"fields": {
"Owner": {
"offset": 48,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_EPROCESS"
}
}
},
"CreateTime": {
"offset": 64,
"type": {
"kind": "union",
"name": "_LARGE_INTEGER"
}
},
"LocalAddr": {
"offset": 96,
"type":{
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_LOCAL_ADDRESS"
}
}
},
"InetAF": {
"offset": 40,
"type":{
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_INETAF"
}
}
},
"Next": {
"offset": 120,
"type":{
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_TCP_LISTENER"
}
}
},
"Port": {
"offset": 114,
"type": {
"kind": "base",
"name": "unsigned be short"
}
}
},
"kind": "struct",
"size": 128
},
"_TCP_ENDPOINT": {
"fields": {
"Owner": {
"offset": 752,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_EPROCESS"
}
}
},
"CreateTime": {
"offset": 776,
"type": {
"kind": "union",
"name": "_LARGE_INTEGER"
}
},
"AddrInfo": {
"offset": 24,
"type":{
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_ADDRINFO"
}
}
},
"ListEntry": {
"offset": 40,
"type": {
"kind": "union",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"InetAF": {
"offset": 16,
"type":{
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_INETAF"
}
}
},
"LocalPort": {
"offset": 112,
"type": {
"kind": "base",
"name": "unsigned be short"
}
},
"RemotePort": {
"offset": 114,
"type": {
"kind": "base",
"name": "unsigned be short"
}
},
"State": {
"offset": 108,
"type": {
"kind": "enum",
"name": "TCPStateEnum"
}
}
},
"kind": "struct",
"size": 632
},
"_LOCAL_ADDRESS": {
"fields": {
"pData": {
"offset": 16,
"type": {
"kind": "pointer",
"subtype": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_IN_ADDR"
}
}
}
}
},
"kind": "struct",
"size": 20
},
"_LOCAL_ADDRESS_WIN10_UDP": {
"fields": {
"pData": {
"offset": 0,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_IN_ADDR"
}
}
}
},
"kind": "struct",
"size": 4
},
"_ADDRINFO": {
"fields": {
"Local": {
"offset": 0,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_LOCAL_ADDRESS"
}
}
},
"Remote": {
"offset": 16,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_IN_ADDR"
}
}
}
},
"kind": "struct",
"size": 4
},
"_IN_ADDR": {
"fields": {
"addr4": {
"offset": 0,
"type": {
"count": 4,
"subtype": {
"kind": "base",
"name": "unsigned char"
},
"kind": "array"
}
},
"addr6": {
"offset": 0,
"type": {
"count": 16,
"subtype": {
"kind": "base",
"name": "unsigned char"
},
"kind": "array"
}
}
},
"kind": "struct",
"size": 6
},
"_INETAF": {
"fields": {
"AddressFamily": {
"offset": 24,
"type": {
"kind": "base",
"name": "unsigned short"
}
}
},
"kind": "struct",
"size": 26
},
"_LARGE_INTEGER": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"QuadPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "long long"
}
},
"u": {
"offset": 0,
"type": {
"kind": "struct",
"name": "__unnamed_2"
}
}
},
"kind": "union",
"size": 8
},
"_INET_COMPARTMENT_SET": {
"fields": {
"InetCompartment": {
"offset": 328,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_INET_COMPARTMENT"
}
}
}
},
"kind": "struct",
"size": 384
},
"_INET_COMPARTMENT": {
"fields": {
"ProtocolCompartment": {
"offset": 32,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_PROTOCOL_COMPARTMENT"
}
}
}
},
"kind": "struct",
"size": 48
},
"_PROTOCOL_COMPARTMENT": {
"fields": {
"PortPool": {
"offset": 0,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_INET_PORT_POOL"
}
}
}
},
"kind": "struct",
"size": 16
},
"_PORT_ASSIGNMENT_ENTRY": {
"fields": {
"Entry": {
"offset": 16,
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
}
}
},
"kind": "struct",
"size": 32
},
"_PORT_ASSIGNMENT_LIST": {
"fields": {
"Assignments": {
"offset": 0,
"type": {
"count": 256,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_PORT_ASSIGNMENT_ENTRY"
}
}
}
},
"kind": "struct",
"size": 6144
},
"_PORT_ASSIGNMENT": {
"fields": {
"InPaBigPoolBase": {
"offset": 24,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_PORT_ASSIGNMENT_LIST"
}
}
}
},
"kind": "struct",
"size": 32
},
"_INET_PORT_POOL": {
"fields": {
"PortAssignments": {
"offset": 224,
"type": {
"count": 256,
"kind": "array",
"subtype": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_PORT_ASSIGNMENT"
}
}
}
},
"PortBitMap": {
"offset": 208,
"type": {
"kind": "struct",
"name": "nt_symbols!_RTL_BITMAP"
}
}
},
"kind": "struct",
"size": 11200
},
"_PARTITION": {
"fields": {
"Endpoints" : {
"offset": 8,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_RTL_DYNAMIC_HASH_TABLE"
}
}
},
"UnknownHashTable" : {
"offset": 16,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_RTL_DYNAMIC_HASH_TABLE"
}
}
}
},
"kind": "struct",
"size": 192
},
"_PARTITION_TABLE": {
"fields": {
"Partitions": {
"offset": 0,
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_PARTITION"
}
}
}
},
"kind": "struct",
"size": 128
}
},
"enums": {
"TCPStateEnum": {
"base": "long",
"constants": {
"CLOSED": 0,
"LISTENING": 1,
"SYN_SENT": 2,
"SYN_RCVD": 3,
"ESTABLISHED": 4,
"FIN_WAIT1": 5,
"FIN_WAIT2": 6,
"CLOSE_WAIT": 7,
"CLOSING": 8,
"LAST_ACK": 9,
"TIME_WAIT": 12,
"DELETE_TCB": 13
},
"size": 4
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "dgmcdona-by-hand",
"datetime": "2024-07-30T13:00:00"
},
"format": "6.0.0"
}
}
@@ -0,0 +1,327 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned long long": {
"kind": "int",
"size": 8,
"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": {
"_RTL_BALANCED_LINKS": {
"fields": {
"Parent": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 0
},
"LeftChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 8
},
"RightChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 16
},
"Balance": {
"type": {
"kind": "base",
"name": "unsigned char"
},
"offset": 24
},
"Reserved": {
"type": {
"kind": "array",
"count": 3,
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 25
}
},
"kind": "struct",
"size": 32
},
"_RTL_AVL_TABLE": {
"fields": {
"BalancedRoot": {
"type": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
},
"offset": 0
},
"OrderedPointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 32
},
"WhichOrderedElement": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 40
},
"NumberGenericTableElements": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 44
},
"DepthOfTree": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 48
},
"RestartKey": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 56
},
"DeleteCount": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 64
},
"CompareRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 72
},
"AllocateRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 80
},
"FreeRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 88
},
"TableContext": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 96
}
},
"kind": "struct",
"size": 104
},
"SHIM_CACHE_HANDLE": {
"fields": {
"eresource": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_ERESOURCE"
}
},
"offset": 0
},
"rtl_avl_table": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_AVL_TABLE"
}
},
"offset": 8
}
},
"kind": "struct",
"size": 16
},
"_LARGE_INTEGER": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"QuadPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "long long"
}
},
"u": {
"offset": 0,
"type": {
"kind": "struct",
"name": "__unnamed_2"
}
}
},
"kind": "union",
"size": 8
},
"__unnamed_2": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
}
},
"kind": "struct",
"size": 8
},
"SHIM_CACHE_ENTRY": {
"fields": {
"ListEntry": {
"offset": 0,
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"Path": {
"offset": 16,
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
}
},
"LastModified": {
"offset": 32,
"type": {
"kind": "union",
"name": "_LARGE_INTEGER"
}
},
"FileSize": {
"offset": 40,
"type": {
"kind": "base",
"name": "unsigned long"
}
}
},
"kind": "struct",
"size": 48
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "dgmcdona by hand",
"datetime": "2024-07-05T18:28:00.000000+00:00"
},
"format": "4.0.0"
}
}
@@ -0,0 +1,334 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned long long": {
"kind": "int",
"size": 8,
"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": {
"_RTL_BALANCED_LINKS": {
"fields": {
"Parent": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 0
},
"LeftChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 4
},
"RightChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 8
},
"Balance": {
"type": {
"kind": "base",
"name": "unsigned char"
},
"offset": 12
},
"Reserved": {
"type": {
"kind": "array",
"count": 3,
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 12
}
},
"kind": "struct",
"size": 16
},
"_RTL_AVL_TABLE": {
"fields": {
"BalancedRoot": {
"type": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
},
"offset": 0
},
"OrderedPointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 16
},
"WhichOrderedElement": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 20
},
"NumberGenericTableElements": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 24
},
"DepthOfTree": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 28
},
"RestartKey": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 32
},
"DeleteCount": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 36
},
"CompareRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 40
},
"AllocateRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 44
},
"FreeRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 48
},
"TableContext": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 52
}
},
"kind": "struct",
"size": 56
},
"SHIM_CACHE_HANDLE": {
"fields": {
"eresource": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_ERESOURCE"
}
},
"offset": 0
},
"rtl_avl_table": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_AVL_TABLE"
}
},
"offset": 8
}
},
"kind": "struct",
"size": 8
},
"_LARGE_INTEGER": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"QuadPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "long long"
}
},
"u": {
"offset": 0,
"type": {
"kind": "struct",
"name": "__unnamed_2"
}
}
},
"kind": "union",
"size": 8
},
"__unnamed_2": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
}
},
"kind": "struct",
"size": 8
},
"SHIM_CACHE_ENTRY": {
"fields": {
"ListEntry": {
"offset": 0,
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"Path": {
"offset": 8,
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
}
},
"LastModified": {
"offset": 16,
"type": {
"kind": "union",
"name": "_LARGE_INTEGER"
}
},
"FileSize": {
"offset": 24,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"Padding": {
"offset": 32,
"type": {
"kind": "base",
"name": "unsigned long"
}
}
},
"kind": "struct",
"size": 36
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "dgmcdona by hand",
"datetime": "2024-07-05T18:28:00.000000+00:00"
},
"format": "4.0.0"
}
}
@@ -0,0 +1,334 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned long long": {
"kind": "int",
"size": 8,
"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": {
"_RTL_BALANCED_LINKS": {
"fields": {
"Parent": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 0
},
"LeftChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 8
},
"RightChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 16
},
"Balance": {
"type": {
"kind": "base",
"name": "unsigned char"
},
"offset": 24
},
"Reserved": {
"type": {
"kind": "array",
"count": 3,
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 25
}
},
"kind": "struct",
"size": 32
},
"_RTL_AVL_TABLE": {
"fields": {
"BalancedRoot": {
"type": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
},
"offset": 0
},
"OrderedPointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 32
},
"WhichOrderedElement": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 40
},
"NumberGenericTableElements": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 44
},
"DepthOfTree": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 48
},
"RestartKey": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 56
},
"DeleteCount": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 64
},
"CompareRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 72
},
"AllocateRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 80
},
"FreeRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 88
},
"TableContext": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 96
}
},
"kind": "struct",
"size": 104
},
"SHIM_CACHE_HANDLE": {
"fields": {
"eresource": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_ERESOURCE"
}
},
"offset": 0
},
"rtl_avl_table": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_AVL_TABLE"
}
},
"offset": 8
}
},
"kind": "struct",
"size": 16
},
"_LARGE_INTEGER": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"QuadPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "long long"
}
},
"u": {
"offset": 0,
"type": {
"kind": "struct",
"name": "__unnamed_2"
}
}
},
"kind": "union",
"size": 8
},
"__unnamed_2": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
}
},
"kind": "struct",
"size": 8
},
"SHIM_CACHE_ENTRY": {
"fields": {
"ListEntry": {
"offset": 0,
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"Path": {
"offset": 16,
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
}
},
"LastModified": {
"offset": 32,
"type": {
"kind": "union",
"name": "_LARGE_INTEGER"
}
},
"InsertFlags": {
"offset": 40,
"type": {
"kind": "base",
"name": "unsigned int"
}
},
"ShimFlags": {
"offset": 44,
"type": {
"kind": "base",
"name": "unsigned int"
}
}
},
"kind": "struct",
"size": 48
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "dgmcdona by hand",
"datetime": "2024-07-05T18:28:00.000000+00:00"
},
"format": "4.0.0"
}
}
@@ -0,0 +1,334 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned long long": {
"kind": "int",
"size": 8,
"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": {
"_RTL_BALANCED_LINKS": {
"fields": {
"Parent": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 0
},
"LeftChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 4
},
"RightChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 8
},
"Balance": {
"type": {
"kind": "base",
"name": "unsigned char"
},
"offset": 12
},
"Reserved": {
"type": {
"kind": "array",
"count": 3,
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 12
}
},
"kind": "struct",
"size": 16
},
"_RTL_AVL_TABLE": {
"fields": {
"BalancedRoot": {
"type": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
},
"offset": 0
},
"OrderedPointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 16
},
"WhichOrderedElement": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 20
},
"NumberGenericTableElements": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 24
},
"DepthOfTree": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 28
},
"RestartKey": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 32
},
"DeleteCount": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 36
},
"CompareRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 40
},
"AllocateRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 44
},
"FreeRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 48
},
"TableContext": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 52
}
},
"kind": "struct",
"size": 56
},
"SHIM_CACHE_HANDLE": {
"fields": {
"eresource": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_ERESOURCE"
}
},
"offset": 0
},
"rtl_avl_table": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_AVL_TABLE"
}
},
"offset": 8
}
},
"kind": "struct",
"size": 8
},
"_LARGE_INTEGER": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"QuadPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "long long"
}
},
"u": {
"offset": 0,
"type": {
"kind": "struct",
"name": "__unnamed_2"
}
}
},
"kind": "union",
"size": 8
},
"__unnamed_2": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
}
},
"kind": "struct",
"size": 8
},
"SHIM_CACHE_ENTRY": {
"fields": {
"ListEntry": {
"offset": 0,
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"Path": {
"offset": 8,
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
}
},
"LastModified": {
"offset": 16,
"type": {
"kind": "union",
"name": "_LARGE_INTEGER"
}
},
"InsertFlags": {
"offset": 24,
"type": {
"kind": "base",
"name": "unsigned int"
}
},
"ShimFlags": {
"offset": 28,
"type": {
"kind": "base",
"name": "unsigned int"
}
}
},
"kind": "struct",
"size": 36
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "dgmcdona by hand",
"datetime": "2024-07-05T18:28:00.000000+00:00"
},
"format": "4.0.0"
}
}
@@ -0,0 +1,371 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned long long": {
"kind": "int",
"size": 8,
"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": {
"_RTL_BALANCED_LINKS": {
"fields": {
"Parent": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 0
},
"LeftChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 8
},
"RightChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 16
},
"Balance": {
"type": {
"kind": "base",
"name": "unsigned char"
},
"offset": 24
},
"Reserved": {
"type": {
"kind": "array",
"count": 3,
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 25
}
},
"kind": "struct",
"size": 32
},
"_RTL_AVL_TABLE": {
"fields": {
"BalancedRoot": {
"type": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
},
"offset": 0
},
"OrderedPointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 32
},
"WhichOrderedElement": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 40
},
"NumberGenericTableElements": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 44
},
"DepthOfTree": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 48
},
"RestartKey": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 56
},
"DeleteCount": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 64
},
"CompareRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 72
},
"AllocateRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 80
},
"FreeRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 88
},
"TableContext": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 96
}
},
"kind": "struct",
"size": 104
},
"SHIM_CACHE_HANDLE": {
"fields": {
"eresource": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_ERESOURCE"
}
},
"offset": 0
},
"rtl_avl_table": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_AVL_TABLE"
}
},
"offset": 8
}
},
"kind": "struct",
"size": 16
},
"_LARGE_INTEGER": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"QuadPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "long long"
}
},
"u": {
"offset": 0,
"type": {
"kind": "struct",
"name": "__unnamed_2"
}
}
},
"kind": "union",
"size": 8
},
"__unnamed_2": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
}
},
"kind": "struct",
"size": 8
},
"SHIM_CACHE_ENTRY": {
"fields": {
"ListEntry": {
"offset": 0,
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"u1": {
"offset": 16,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"Path": {
"offset": 24,
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
}
},
"ListEntryDetail": {
"offset": 40,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "SHIM_CACHE_ENTRY_DETAIL"
}
}
}
},
"kind": "struct",
"size": 48
},
"SHIM_CACHE_ENTRY_DETAIL": {
"fields": {
"u1": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"LastModified": {
"offset": 8,
"type": {
"kind": "union",
"name": "_LARGE_INTEGER"
}
},
"BlobSize": {
"offset": 16,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"u2": {
"offset": 20,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"BlobBuffer": {
"offset": 24,
"type": {
"kind": "base",
"name": "unsigned long long"
}
}
},
"kind": "struct",
"size": 32
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "dgmcdona by hand",
"datetime": "2024-07-05T18:28:00.000000+00:00"
},
"format": "4.0.0"
}
}
@@ -0,0 +1,371 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned long long": {
"kind": "int",
"size": 8,
"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": {
"SHIM_CACHE_HANDLE": {
"fields": {
"eresource": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_ERESOURCE"
}
},
"offset": 0
},
"rtl_avl_table": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_AVL_TABLE"
}
},
"offset": 4
}
},
"kind": "struct",
"size": 8
},
"_RTL_BALANCED_LINKS": {
"fields": {
"Parent": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 0
},
"LeftChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 4
},
"RightChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 8
},
"Balance": {
"type": {
"kind": "base",
"name": "unsigned char"
},
"offset": 12
},
"Reserved": {
"type": {
"kind": "array",
"count": 3,
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 12
}
},
"kind": "struct",
"size": 16
},
"_RTL_AVL_TABLE": {
"fields": {
"BalancedRoot": {
"type": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
},
"offset": 0
},
"OrderedPointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 16
},
"WhichOrderedElement": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 20
},
"NumberGenericTableElements": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 24
},
"DepthOfTree": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 28
},
"RestartKey": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 32
},
"DeleteCount": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 36
},
"CompareRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 40
},
"AllocateRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 44
},
"FreeRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 48
},
"TableContext": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 52
}
},
"kind": "struct",
"size": 56
},
"_LARGE_INTEGER": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"QuadPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "long long"
}
},
"u": {
"offset": 0,
"type": {
"kind": "struct",
"name": "__unnamed_2"
}
}
},
"kind": "union",
"size": 8
},
"__unnamed_2": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
}
},
"kind": "struct",
"size": 8
},
"SHIM_CACHE_ENTRY": {
"fields": {
"ListEntry": {
"offset": 0,
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"u1": {
"offset": 8,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"Path": {
"offset": 12,
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
}
},
"ListEntryDetail": {
"offset": 20,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "SHIM_CACHE_ENTRY_DETAIL"
}
}
}
},
"kind": "struct",
"size": 24
},
"SHIM_CACHE_ENTRY_DETAIL": {
"fields": {
"u1": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"InsertFlags": {
"offset": 4,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"LastModified": {
"offset": 8,
"type": {
"kind": "union",
"name": "_LARGE_INTEGER"
}
},
"BlobSize": {
"offset": 16,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"BlobBuffer": {
"offset": 20,
"type": {
"kind": "base",
"name": "unsigned long"
}
}
},
"kind": "struct",
"size": 24
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "dgmcdona by hand",
"datetime": "2024-07-05T18:28:00.000000+00:00"
},
"format": "4.0.0"
}
}
@@ -0,0 +1,348 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned long long": {
"kind": "int",
"size": 8,
"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": {
"_RTL_BALANCED_LINKS": {
"fields": {
"Parent": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 0
},
"LeftChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 8
},
"RightChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 16
},
"Balance": {
"type": {
"kind": "base",
"name": "unsigned char"
},
"offset": 24
},
"Reserved": {
"type": {
"kind": "array",
"count": 3,
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 25
}
},
"kind": "struct",
"size": 32
},
"_RTL_AVL_TABLE": {
"fields": {
"BalancedRoot": {
"type": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
},
"offset": 0
},
"OrderedPointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 32
},
"WhichOrderedElement": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 40
},
"NumberGenericTableElements": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 44
},
"DepthOfTree": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 48
},
"RestartKey": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 56
},
"DeleteCount": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 64
},
"CompareRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 72
},
"AllocateRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 80
},
"FreeRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 88
},
"TableContext": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 96
}
},
"kind": "struct",
"size": 104
},
"SHIM_CACHE_HANDLE": {
"fields": {
"eresource": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_ERESOURCE"
}
},
"offset": 0
},
"rtl_avl_table": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_AVL_TABLE"
}
},
"offset": 8
}
},
"kind": "struct",
"size": 8
},
"_LARGE_INTEGER": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"QuadPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "long long"
}
},
"u": {
"offset": 0,
"type": {
"kind": "struct",
"name": "__unnamed_2"
}
}
},
"kind": "union",
"size": 8
},
"__unnamed_2": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
}
},
"kind": "struct",
"size": 8
},
"SHIM_CACHE_ENTRY": {
"fields": {
"ListEntry": {
"offset": 0,
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"Path": {
"offset": 16,
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
}
},
"LastModified": {
"offset": 32,
"type": {
"kind": "union",
"name": "_LARGE_INTEGER"
}
},
"InsertFlags": {
"offset": 40,
"type": {
"kind": "base",
"name": "unsigned int"
}
},
"ShimFlags": {
"offset": 44,
"type": {
"kind": "base",
"name": "unsigned int"
}
},
"BlobSize": {
"offset": 48,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"BlobBuffer": {
"offset": 56,
"type": {
"kind": "base",
"name": "unsigned long long"
}
}
},
"kind": "struct",
"size": 64
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "dgmcdona by hand",
"datetime": "2024-07-05T18:28:00.000000+00:00"
},
"format": "4.0.0"
}
}
@@ -0,0 +1,348 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned long long": {
"kind": "int",
"size": 8,
"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": {
"_RTL_BALANCED_LINKS": {
"fields": {
"Parent": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 0
},
"LeftChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 4
},
"RightChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 8
},
"Balance": {
"type": {
"kind": "base",
"name": "unsigned char"
},
"offset": 12
},
"Reserved": {
"type": {
"kind": "array",
"count": 3,
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 12
}
},
"kind": "struct",
"size": 16
},
"_RTL_AVL_TABLE": {
"fields": {
"BalancedRoot": {
"type": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
},
"offset": 0
},
"OrderedPointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 16
},
"WhichOrderedElement": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 20
},
"NumberGenericTableElements": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 24
},
"DepthOfTree": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 28
},
"RestartKey": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 32
},
"DeleteCount": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 36
},
"CompareRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 40
},
"AllocateRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 44
},
"FreeRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 48
},
"TableContext": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 52
}
},
"kind": "struct",
"size": 56
},
"SHIM_CACHE_HANDLE": {
"fields": {
"eresource": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_ERESOURCE"
}
},
"offset": 0
},
"rtl_avl_table": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_AVL_TABLE"
}
},
"offset": 4
}
},
"kind": "struct",
"size": 8
},
"_LARGE_INTEGER": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"QuadPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "long long"
}
},
"u": {
"offset": 0,
"type": {
"kind": "struct",
"name": "__unnamed_2"
}
}
},
"kind": "union",
"size": 8
},
"__unnamed_2": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
}
},
"kind": "struct",
"size": 8
},
"SHIM_CACHE_ENTRY": {
"fields": {
"ListEntry": {
"offset": 0,
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"Path": {
"offset": 8,
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
}
},
"LastModified": {
"offset": 16,
"type": {
"kind": "union",
"name": "_LARGE_INTEGER"
}
},
"InsertFlags": {
"offset": 24,
"type": {
"kind": "base",
"name": "unsigned int"
}
},
"ShimFlags": {
"offset": 28,
"type": {
"kind": "base",
"name": "unsigned int"
}
},
"BlobSize": {
"offset": 32,
"type": {
"kind": "base",
"name": "unsigned int"
}
},
"BlobBuffer": {
"offset": 36,
"type": {
"kind": "base",
"name": "unsigned long"
}
}
},
"kind": "struct",
"size": 40
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "dgmcdona by hand",
"datetime": "2024-07-05T18:28:00.000000+00:00"
},
"format": "4.0.0"
}
}
@@ -0,0 +1,392 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned long long": {
"kind": "int",
"size": 8,
"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": {
"_RTL_BALANCED_LINKS": {
"fields": {
"Parent": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 0
},
"LeftChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 8
},
"RightChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 16
},
"Balance": {
"type": {
"kind": "base",
"name": "unsigned char"
},
"offset": 24
},
"Reserved": {
"type": {
"kind": "array",
"count": 3,
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 25
}
},
"kind": "struct",
"size": 32
},
"_RTL_AVL_TABLE": {
"fields": {
"BalancedRoot": {
"type": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
},
"offset": 0
},
"OrderedPointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 32
},
"WhichOrderedElement": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 40
},
"NumberGenericTableElements": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 44
},
"DepthOfTree": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 48
},
"RestartKey": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 56
},
"DeleteCount": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 64
},
"CompareRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 72
},
"AllocateRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 80
},
"FreeRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 88
},
"TableContext": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 96
}
},
"kind": "struct",
"size": 104
},
"SHIM_CACHE_HANDLE": {
"fields": {
"eresource": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_ERESOURCE"
}
},
"offset": 0
},
"rtl_avl_table": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_AVL_TABLE"
}
},
"offset": 8
}
},
"kind": "struct",
"size": 8
},
"_LARGE_INTEGER": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"QuadPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "long long"
}
},
"u": {
"offset": 0,
"type": {
"kind": "struct",
"name": "__unnamed_2"
}
}
},
"kind": "union",
"size": 8
},
"__unnamed_2": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
}
},
"kind": "struct",
"size": 8
},
"SHIM_CACHE_ENTRY": {
"fields": {
"ListEntry": {
"offset": 0,
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"u1": {
"offset": 16,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"Path": {
"offset": 24,
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
}
},
"u2": {
"offset": 40,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"u3": {
"offset": 48,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"ListEntryDetail": {
"offset": 56,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "SHIM_CACHE_ENTRY_DETAIL"
}
}
}
},
"kind": "struct",
"size": 64
},
"SHIM_CACHE_ENTRY_DETAIL": {
"fields": {
"LastModified": {
"offset": 0,
"type": {
"kind": "struct",
"name": "_LARGE_INTEGER"
}
},
"InsertFlags": {
"offset": 8,
"type": {
"kind": "base",
"name": "unsigned int"
}
},
"ShimFlags": {
"offset": 12,
"type": {
"kind": "base",
"name": "unsigned int"
}
},
"BlobSize": {
"offset": 16,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"Padding": {
"offset": 24,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"BlobBuffer": {
"offset": 32,
"type": {
"kind": "base",
"name": "unsigned long long"
}
}
},
"kind": "struct",
"size": 40
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "dgmcdona by hand",
"datetime": "2024-07-05T18:28:00.000000+00:00"
},
"format": "4.0.0"
}
}
@@ -0,0 +1,386 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned long long": {
"kind": "int",
"size": 8,
"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": {
"_RTL_BALANCED_LINKS": {
"fields": {
"Parent": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 0
},
"LeftChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 4
},
"RightChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 8
},
"Balance": {
"type": {
"kind": "base",
"name": "unsigned char"
},
"offset": 12
},
"Reserved": {
"type": {
"kind": "array",
"count": 3,
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 12
}
},
"kind": "struct",
"size": 16
},
"_RTL_AVL_TABLE": {
"fields": {
"BalancedRoot": {
"type": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
},
"offset": 0
},
"OrderedPointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 16
},
"WhichOrderedElement": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 20
},
"NumberGenericTableElements": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 24
},
"DepthOfTree": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 28
},
"RestartKey": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 32
},
"DeleteCount": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 36
},
"CompareRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 40
},
"AllocateRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 44
},
"FreeRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 48
},
"TableContext": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 52
}
},
"kind": "struct",
"size": 56
},
"SHIM_CACHE_HANDLE": {
"fields": {
"eresource": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_ERESOURCE"
}
},
"offset": 0
},
"rtl_avl_table": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_AVL_TABLE"
}
},
"offset": 4
}
},
"kind": "struct",
"size": 8
},
"_LARGE_INTEGER": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"QuadPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "long long"
}
},
"u": {
"offset": 0,
"type": {
"kind": "struct",
"name": "__unnamed_2"
}
}
},
"kind": "union",
"size": 8
},
"__unnamed_2": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
}
},
"kind": "struct",
"size": 8
},
"SHIM_CACHE_ENTRY": {
"fields": {
"ListEntry": {
"offset": 0,
"type": {
"kind": "struct",
"name": "nt_symbols!_LIST_ENTRY"
}
},
"u1": {
"offset": 8,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"u2": {
"offset": 12,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"Path": {
"offset": 16,
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
}
},
"u3": {
"offset": 24,
"type": {
"kind": "base",
"name": "unsigned long long"
}
},
"ListEntryDetail": {
"offset": 32,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "SHIM_CACHE_ENTRY_DETAIL"
}
}
}
},
"kind": "struct",
"size": 36
},
"SHIM_CACHE_ENTRY_DETAIL": {
"fields": {
"LastModified": {
"offset": 0,
"type": {
"kind": "struct",
"name": "_LARGE_INTEGER"
}
},
"InsertFlags": {
"offset": 8,
"type": {
"kind": "base",
"name": "unsigned int"
}
},
"ShimFlags": {
"offset": 12,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"BlobSize": {
"offset": 16,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"BlobBuffer": {
"offset": 20,
"type": {
"kind": "base",
"name": "unsigned long"
}
}
},
"kind": "struct",
"size": 24
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "dgmcdona by hand",
"datetime": "2024-07-05T18:28:00.000000+00:00"
},
"format": "4.0.0"
}
}
@@ -0,0 +1,485 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned long long": {
"kind": "int",
"size": 8,
"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": {
"_LARGE_INTEGER": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"QuadPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "long long"
}
},
"u": {
"offset": 0,
"type": {
"kind": "struct",
"name": "__unnamed_2"
}
}
},
"kind": "union",
"size": 8
},
"__unnamed_2": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
}
},
"kind": "struct",
"size": 8
},
"_RTL_BALANCED_LINKS": {
"fields": {
"Parent": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 0
},
"LeftChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 4
},
"RightChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 8
},
"Balance": {
"type": {
"kind": "base",
"name": "unsigned char"
},
"offset": 12
},
"Reserved": {
"type": {
"kind": "array",
"count": 3,
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 12
}
},
"kind": "struct",
"size": 16
},
"_RTL_AVL_TABLE": {
"fields": {
"BalancedRoot": {
"type": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
},
"offset": 0
},
"OrderedPointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 16
},
"WhichOrderedElement": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 20
},
"NumberGenericTableElements": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 24
},
"DepthOfTree": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 28
},
"RestartKey": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 32
},
"DeleteCount": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 36
},
"CompareRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 40
},
"AllocateRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 44
},
"FreeRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 48
},
"TableContext": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 52
}
},
"kind": "struct",
"size": 56
},
"SHIM_CACHE_HANDLE": {
"fields": {
"eresource": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_ERESOURCE"
}
},
"offset": 0
},
"rtl_avl_table": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_AVL_TABLE"
}
},
"offset": 4
}
},
"kind": "struct",
"size": 8
},
"SHIM_CACHE_HEADER": {
"fields": {
"Magic": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 0
},
"u1": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 4
},
"NumEntries": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 8
},
"u2": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 12
}
},
"kind": "struct",
"size": 400
},
"SHIM_CACHE_ENTRY": {
"fields": {
"Path": {
"type": {
"count": 520,
"kind": "array",
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 0
},
"LastModified": {
"type": {
"kind": "union",
"name": "LARGE_INTEGER"
},
"offset": 4
},
"FileSize": {
"type": {
"kind": "base",
"name": "long long"
},
"offset": 8
},
"LastUpdate": {
"type": {
"kind": "union",
"name": "LARGE_INTEGER"
},
"offset": 12
}
},
"kind": "struct",
"size": 552
},
"_SEGMENT": {
"fields": {
"ControlArea": {
"offset": 0,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_CONTROL_AREA"
}
}
},
"TotalNumberOfPtes": {
"offset": 4,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"NonExtendedPtes": {
"offset": 8,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"WritableUserReferences": {
"offset": 12,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"SizeOfSegment": {
"offset": 16,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"SegmentPteTemplate": {
"offset": 24,
"type": {
"kind": "struct",
"name": "nt_symbols!_MMPTE"
}
},
"NumberOfCommittedPages": {
"offset": 28,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"ExtendInfo": {
"offset": 32,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_MMEXTEND_INFO"
}
}
},
"SystemImageBase": {
"offset": 36,
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
}
},
"BasedAddress": {
"offset": 40,
"type": {
"kind": "base",
"name": "long"
}
},
"u1": {
"offset": 44,
"type": {
"kind": "base",
"name": "long"
}
},
"u2": {
"offset": 48,
"type": {
"kind": "base",
"name": "long"
}
},
"PrototypePte": {
"offset": 52,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_MMPTE"
}
}
},
"ThePtes": {
"offset": 60,
"type": {
"kind": "array",
"count": 1,
"subtype": {
"kind": "base",
"name": "nt_symbols!_MMPTE"
}
}
}
},
"kind": "struct",
"size": 64
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "dgmcdona by hand",
"datetime": "2024-07-05T18:28:00.000000+00:00"
},
"format": "4.0.0"
}
}
@@ -0,0 +1,485 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned long long": {
"kind": "int",
"size": 8,
"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": {
"_LARGE_INTEGER": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"QuadPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "long long"
}
},
"u": {
"offset": 0,
"type": {
"kind": "struct",
"name": "__unnamed_2"
}
}
},
"kind": "union",
"size": 8
},
"__unnamed_2": {
"fields": {
"HighPart": {
"offset": 4,
"type": {
"kind": "base",
"name": "long"
}
},
"LowPart": {
"offset": 0,
"type": {
"kind": "base",
"name": "unsigned long"
}
}
},
"kind": "struct",
"size": 8
},
"_RTL_BALANCED_LINKS": {
"fields": {
"Parent": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 0
},
"LeftChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 4
},
"RightChild": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 8
},
"Balance": {
"type": {
"kind": "base",
"name": "unsigned char"
},
"offset": 12
},
"Reserved": {
"type": {
"kind": "array",
"count": 3,
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 12
}
},
"kind": "struct",
"size": 16
},
"_RTL_AVL_TABLE": {
"fields": {
"BalancedRoot": {
"type": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
},
"offset": 0
},
"OrderedPointer": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 16
},
"WhichOrderedElement": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 20
},
"NumberGenericTableElements": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 24
},
"DepthOfTree": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 28
},
"RestartKey": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_BALANCED_LINKS"
}
},
"offset": 32
},
"DeleteCount": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 36
},
"CompareRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 40
},
"AllocateRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 44
},
"FreeRoutine": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 48
},
"TableContext": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
},
"offset": 52
}
},
"kind": "struct",
"size": 56
},
"SHIM_CACHE_HEADER": {
"fields": {
"Magic": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 0
},
"u1": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 4
},
"NumEntries": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 8
},
"u2": {
"type": {
"kind": "base",
"name": "unsigned int"
},
"offset": 12
}
},
"kind": "struct",
"size": 400
},
"SHIM_CACHE_ENTRY": {
"fields": {
"Path": {
"type": {
"count": 520,
"kind": "array",
"subtype": {
"kind": "base",
"name": "unsigned char"
}
},
"offset": 0
},
"LastModified": {
"type": {
"kind": "union",
"name": "_LARGE_INTEGER"
},
"offset": 528
},
"FileSize": {
"type": {
"kind": "base",
"name": "long long"
},
"offset": 536
},
"LastUpdate": {
"type": {
"kind": "union",
"name": "_LARGE_INTEGER"
},
"offset": 544
}
},
"kind": "struct",
"size": 552
},
"SHIM_CACHE_HANDLE": {
"fields": {
"eresource": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!ERESOURCE"
}
},
"offset": 0
},
"rtl_avl_table": {
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "_RTL_AVL_TABLE"
}
},
"offset": 4
}
},
"kind": "struct",
"size": 8
},
"_SEGMENT": {
"fields": {
"ControlArea": {
"offset": 0,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_CONTROL_AREA"
}
}
},
"TotalNumberOfPtes": {
"offset": 4,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"NonExtendedPtes": {
"offset": 8,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"WritableUserReferences": {
"offset": 12,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"SizeOfSegment": {
"offset": 16,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"SegmentPteTemplate": {
"offset": 24,
"type": {
"kind": "struct",
"name": "nt_symbols!_MMPTE"
}
},
"NumberOfCommittedPages": {
"offset": 32,
"type": {
"kind": "base",
"name": "unsigned long"
}
},
"ExtendInfo": {
"offset": 36,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_MMEXTEND_INFO"
}
}
},
"SystemImageBase": {
"offset": 40,
"type": {
"kind": "pointer",
"subtype": {
"kind": "base",
"name": "void"
}
}
},
"BasedAddress": {
"offset": 44,
"type": {
"kind": "base",
"name": "long"
}
},
"u1": {
"offset": 48,
"type": {
"kind": "base",
"name": "long"
}
},
"u2": {
"offset": 52,
"type": {
"kind": "base",
"name": "long"
}
},
"PrototypePte": {
"offset": 56,
"type": {
"kind": "pointer",
"subtype": {
"kind": "struct",
"name": "nt_symbols!_MMPTE"
}
}
},
"ThePtes": {
"offset": 64,
"type": {
"kind": "array",
"count": 1,
"subtype": {
"kind": "base",
"name": "nt_symbols!_MMPTE"
}
}
}
},
"kind": "struct",
"size": 72
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "dgmcdona by hand",
"datetime": "2024-07-05T18:28:00.000000+00:00"
},
"format": "4.0.0"
}
}
@@ -0,0 +1,109 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned long long": {
"kind": "int",
"size": 8,
"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": {
"_UNLOADED_DRIVER": {
"fields": {
"Name": {
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
},
"offset": 0
},
"StartAddress": {
"type": {
"kind": "base",
"name": "unsigned long long"
},
"offset": 16
},
"EndAddress": {
"type": {
"kind": "base",
"name": "unsigned long long"
},
"offset": 24
},
"CurrentTime": {
"type": {
"kind": "base",
"name": "unsigned long long"
},
"offset": 32
}
},
"kind": "struct",
"size": 40
},
"_UNLOADED_DRIVERS": {
"fields": {
"UnloadedDrivers": {
"offset": 0,
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_UNLOADED_DRIVER"
}
}
}
},
"kind": "struct",
"size": 8
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "Dave Lassalle by hand",
"datetime": "2024-06-19T17:57:16.394003"
},
"format": "4.0.0"
}
}
@@ -0,0 +1,109 @@
{
"symbols": {},
"enums": {},
"base_types": {
"unsigned long": {
"kind": "int",
"size": 4,
"signed": false,
"endian": "little"
},
"unsigned long long": {
"kind": "int",
"size": 8,
"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": {
"_UNLOADED_DRIVER": {
"fields": {
"Name": {
"type": {
"kind": "struct",
"name": "nt_symbols!_UNICODE_STRING"
},
"offset": 0
},
"StartAddress": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 8
},
"EndAddress": {
"type": {
"kind": "base",
"name": "unsigned long"
},
"offset": 12
},
"CurrentTime": {
"type": {
"kind": "base",
"name": "unsigned long long"
},
"offset": 16
}
},
"kind": "struct",
"size": 24
},
"_UNLOADED_DRIVERS": {
"fields": {
"UnloadedDrivers": {
"offset": 0,
"type": {
"count": 1,
"kind": "array",
"subtype": {
"kind": "struct",
"name": "_UNLOADED_DRIVER"
}
}
}
},
"kind": "struct",
"size": 4
}
},
"metadata": {
"producer": {
"version": "0.0.1",
"name": "Dave Lassalle by hand",
"datetime": "2024-06-19T17:57:16.394003"
},
"format": "4.0.0"
}
}
@@ -114,6 +114,24 @@ is_windows_xp = OsDistinguisher(
],
)
is_windows_xp_sp2 = OsDistinguisher(
version_check=lambda x: (5, 1) <= x < (5, 2),
fallback_checks=[
("KdCopyDataBlock", None, False),
("_MMFREE_POOL_ENTRY", None, False),
("_HANDLE_TABLE", "HandleCount", True),
],
)
is_windows_xp_sp3 = OsDistinguisher(
version_check=lambda x: (5, 1) <= x < (5, 2),
fallback_checks=[
("KdCopyDataBlock", None, False),
("_MMFREE_POOL_ENTRY", None, True),
("_HANDLE_TABLE", "HandleCount", True),
],
)
is_xp_or_2003 = OsDistinguisher(
version_check=lambda x: (5, 1) <= x < (6, 0),
fallback_checks=[
@@ -122,6 +140,15 @@ is_xp_or_2003 = OsDistinguisher(
],
)
is_2003 = OsDistinguisher(
version_check=lambda x: (5, 2) <= x < (5, 3),
fallback_checks=[
("KdCopyDataBlock", None, False),
("_HANDLE_TABLE", "HandleCount", True),
("_MM_AVL_TABLE", None, True),
],
)
is_win10_up_to_15063 = OsDistinguisher(
version_check=lambda x: (10, 0) <= x < (10, 0, 15063),
fallback_checks=[
@@ -141,6 +168,15 @@ is_win10_15063 = OsDistinguisher(
],
)
is_win10_15063_or_later = OsDistinguisher(
version_check=lambda x: x >= (10, 0, 15063),
fallback_checks=[
("ObHeaderCookie", None, True),
("_HANDLE_TABLE", "HandleCount", False),
("_EPROCESS", "KeepAliveCounter", False),
],
)
is_win10_16299_or_later = OsDistinguisher(
version_check=lambda x: x >= (10, 0, 16299),
fallback_checks=[
+1
View File
@@ -1,4 +1,5 @@
#!/usr/bin/env python3
# PYTHON_ARGCOMPLETE_OK
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0