mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-11 12:17:38 +02:00
Merge branch 'develop' into issues/issue1195
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -27,6 +27,7 @@ config*.json
|
||||
# Pyinstaller files
|
||||
build
|
||||
dist
|
||||
*.egg-info
|
||||
|
||||
# Environments
|
||||
.env
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -4,5 +4,6 @@ sphinx_autodoc_typehints>=1.4.0
|
||||
sphinx-rtd-theme>=0.4.3
|
||||
|
||||
yara-python
|
||||
yara-x
|
||||
pycryptodome
|
||||
pefile
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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())
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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,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
|
||||
|
||||
Reference in New Issue
Block a user