diff --git a/.github/workflows/black.yml b/.github/workflows/black.yml index 5f45230726..e29ab6f290 100644 --- a/.github/workflows/black.yml +++ b/.github/workflows/black.yml @@ -6,8 +6,10 @@ jobs: lint: runs-on: ubuntu-20.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: psf/black@stable with: options: "--check --diff --verbose" src: "./volatility3" + # FIXME: Remove when Volatility3 minimum Python version is >3.8 + version: "24.8.0" diff --git a/.github/workflows/build-pypi.yml b/.github/workflows/build-pypi.yml index f21898971a..d1a63b4da7 100644 --- a/.github/workflows/build-pypi.yml +++ b/.github/workflows/build-pypi.yml @@ -18,26 +18,25 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - python-version: ["3.7"] + python-version: ["3.8"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - 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@v2 + uses: actions/upload-artifact@v4 with: name: volatility3-pypi path: | diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml new file mode 100644 index 0000000000..398ff8ae36 --- /dev/null +++ b/.github/workflows/install.yml @@ -0,0 +1,31 @@ +name: Install Volatility3 test +on: [push, pull_request] +jobs: + + install_test: + runs-on: ${{ matrix.host }} + strategy: + fail-fast: false + matrix: + host: [ ubuntu-latest, windows-latest ] + python-version: [ "3.8", "3.9", "3.10", "3.11" ] + steps: + - uses: actions/checkout@v4 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: Setup python-pip + run: python -m pip install --upgrade pip + + - name: Install dependencies + run: | + pip install -r requirements.txt + + - name: Install volatility3 + run: pip install . + + - name: Run volatility3 + run: vol --help \ No newline at end of file diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index b1a9dd31bd..6358dd45d3 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -6,11 +6,11 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - python-version: ["3.7"] + python-version: ["3.8"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ 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 diff --git a/.gitignore b/.gitignore index 328ba5f83b..c132736a9c 100644 --- a/.gitignore +++ b/.gitignore @@ -27,6 +27,7 @@ config*.json # Pyinstaller files build dist +*.egg-info # Environments .env diff --git a/.style.yapf b/.style.yapf deleted file mode 100644 index 3f154e07b7..0000000000 --- a/.style.yapf +++ /dev/null @@ -1,261 +0,0 @@ -[style] -# Align closing bracket with visual indentation. -align_closing_bracket_with_visual_indent=True - -# Allow dictionary keys to exist on multiple lines. For example: -# -# x = { -# ('this is the first element of a tuple', -# 'this is the second element of a tuple'): -# value, -# } -allow_multiline_dictionary_keys=False - -# Allow lambdas to be formatted on more than one line. -allow_multiline_lambdas=False - -# Allow splits before the dictionary value. -allow_split_before_dict_value=True - -# Number of blank lines surrounding top-level function and class -# definitions. -blank_lines_around_top_level_definition=2 - -# Insert a blank line before a class-level docstring. -blank_line_before_class_docstring=False - -# Insert a blank line before a module docstring. -blank_line_before_module_docstring=False - -# Insert a blank line before a 'def' or 'class' immediately nested -# within another 'def' or 'class'. For example: -# -# class Foo: -# # <------ this blank line -# def method(): -# ... -blank_line_before_nested_class_or_def=True - -# Do not split consecutive brackets. Only relevant when -# dedent_closing_brackets is set. For example: -# -# call_func_that_takes_a_dict( -# { -# 'key1': 'value1', -# 'key2': 'value2', -# } -# ) -# -# would reformat to: -# -# call_func_that_takes_a_dict({ -# 'key1': 'value1', -# 'key2': 'value2', -# }) -coalesce_brackets=False - -# The column limit. -column_limit=120 - -# The style for continuation alignment. Possible values are: -# -# - SPACE: Use spaces for continuation alignment. This is default behavior. -# - FIXED: Use fixed number (CONTINUATION_INDENT_WIDTH) of columns -# (ie: CONTINUATION_INDENT_WIDTH/INDENT_WIDTH tabs) for continuation -# alignment. -# - LESS: Slightly left if cannot vertically align continuation lines with -# indent characters. -# - VALIGN-RIGHT: Vertically align continuation lines with indent -# characters. Slightly right (one more indent character) if cannot -# vertically align continuation lines with indent characters. -# -# For options FIXED, and VALIGN-RIGHT are only available when USE_TABS is -# enabled. -continuation_align_style=SPACE - -# Indent width used for line continuations. -continuation_indent_width=4 - -# Put closing brackets on a separate line, dedented, if the bracketed -# expression can't fit in a single line. Applies to all kinds of brackets, -# including function definitions and calls. For example: -# -# config = { -# 'key1': 'value1', -# 'key2': 'value2', -# } # <--- this bracket is dedented and on a separate line -# -# time_series = self.remote_client.query_entity_counters( -# entity='dev3246.region1', -# key='dns.query_latency_tcp', -# transform=Transformation.AVERAGE(window=timedelta(seconds=60)), -# start_ts=now()-timedelta(days=3), -# end_ts=now(), -# ) # <--- this bracket is dedented and on a separate line -dedent_closing_brackets=False - -# Disable the heuristic which places each list element on a separate line -# if the list is comma-terminated. -disable_ending_comma_heuristic=False - -# Place each dictionary entry onto its own line. -each_dict_entry_on_separate_line=True - -# The regex for an i18n comment. The presence of this comment stops -# reformatting of that line, because the comments are required to be -# next to the string they translate. -i18n_comment= - -# The i18n function call names. The presence of this function stops -# reformatting on that line, because the string it has cannot be moved -# away from the i18n comment. -i18n_function_call= - -# Indent the dictionary value if it cannot fit on the same line as the -# dictionary key. For example: -# -# config = { -# 'key1': -# 'value1', -# 'key2': value1 + -# value2, -# } -indent_dictionary_value=False - -# The number of columns to use for indentation. -indent_width=4 - -# Join short lines into one line. E.g., single line 'if' statements. -join_multiple_lines=True - -# Do not include spaces around selected binary operators. For example: -# -# 1 + 2 * 3 - 4 / 5 -# -# will be formatted as follows when configured with "*,/": -# -# 1 + 2*3 - 4/5 -# -no_spaces_around_selected_binary_operators= - -# Use spaces around default or named assigns. -spaces_around_default_or_named_assign=True - -# Use spaces around the power operator. -spaces_around_power_operator=True - -# The number of spaces required before a trailing comment. -spaces_before_comment=2 - -# Insert a space between the ending comma and closing bracket of a list, -# etc. -space_between_ending_comma_and_closing_bracket=True - -# Split before arguments -split_all_comma_separated_values=False - -# Split before arguments if the argument list is terminated by a -# comma. -split_arguments_when_comma_terminated=False - -# Set to True to prefer splitting before '&', '|' or '^' rather than -# after. -split_before_bitwise_operator=True - -# Split before the closing bracket if a list or dict literal doesn't fit on -# a single line. -split_before_closing_bracket=True - -# Split before a dictionary or set generator (comp_for). For example, note -# the split before the 'for': -# -# foo = { -# variable: 'Hello world, have a nice day!' -# for variable in bar if variable != 42 -# } -split_before_dict_set_generator=True - -# Split before the '.' if we need to split a longer expression: -# -# foo = ('This is a really long string: {}, {}, {}, {}'.format(a, b, c, d)) -# -# would reformat to something like: -# -# foo = ('This is a really long string: {}, {}, {}, {}' -# .format(a, b, c, d)) -split_before_dot=False - -# Split after the opening paren which surrounds an expression if it doesn't -# fit on a single line. -split_before_expression_after_opening_paren=False - -# If an argument / parameter list is going to be split, then split before -# the first argument. -split_before_first_argument=False - -# Set to True to prefer splitting before 'and' or 'or' rather than -# after. -split_before_logical_operator=True - -# Split named assignments onto individual lines. -split_before_named_assigns=True - -# Set to True to split list comprehensions and generators that have -# non-trivial expressions and multiple clauses before each of these -# clauses. For example: -# -# result = [ -# a_long_var + 100 for a_long_var in xrange(1000) -# if a_long_var % 10] -# -# would reformat to something like: -# -# result = [ -# a_long_var + 100 -# for a_long_var in xrange(1000) -# if a_long_var % 10] -split_complex_comprehension=True - -# The penalty for splitting right after the opening bracket. -split_penalty_after_opening_bracket=200 - -# The penalty for splitting the line after a unary operator. -split_penalty_after_unary_operator=10000 - -# The penalty for splitting right before an if expression. -split_penalty_before_if_expr=0 - -# The penalty of splitting the line around the '&', '|', and '^' -# operators. -split_penalty_bitwise_operator=300 - -# The penalty for splitting a list comprehension or generator -# expression. -split_penalty_comprehension=80 - -# The penalty for characters over the column limit. -split_penalty_excess_character=7000 - -# The penalty incurred by adding a line split to the unwrapped line. The -# more line splits added the higher the penalty. -split_penalty_for_added_line_split=30 - -# The penalty of splitting a list of "import as" names. For example: -# -# from a_very_long_or_indented_module_name_yada_yad import (long_argument_1, -# long_argument_2, -# long_argument_3) -# -# would reformat to something like: -# -# from a_very_long_or_indented_module_name_yada_yad import ( -# long_argument_1, long_argument_2, long_argument_3) -split_penalty_import_names=0 - -# The penalty of splitting the line around the 'and' and 'or' -# operators. -split_penalty_logical_operator=300 - -# Use the Tab character for indentation. -use_tabs=False - diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000000..ac45dfc18f --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,37 @@ +# This CITATION.cff file was generated with cffinit. +# Visit https://bit.ly/cffinit to generate yours today! + +cff-version: 1.2.0 +title: Volatility 3 +message: >- + If you reference this software, please feel free to cite + it using the information below. +type: software +authors: + - name: Volatility Foundation + country: US + website: 'https://www.volatilityfoundation.org/' +identifiers: + - type: url + value: 'https://github.com/volatilityfoundation/volatility3' + description: Volatility 3 source code repository +repository-code: 'https://github.com/volatilityfoundation/volatility3' +url: 'https://github.com/volatilityfoundation/volatility3' +abstract: >- + Volatility is the world's most widely used framework for + extracting digital artifacts from volatile memory (RAM) + samples. The extraction techniques are performed + completely independent of the system being investigated + but offer visibility into the runtime state of the system. + The framework is intended to introduce people to the + techniques and complexities associated with extracting + digital artifacts from volatile memory samples and provide + a platform for further work into this exciting area of + research. +keywords: + - malware + - forensics + - memory + - python + - ram + - volatility diff --git a/README.md b/README.md index 471735af8b..1463c2bde9 100644 --- a/README.md +++ b/README.md @@ -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. @@ -107,7 +106,7 @@ The latest generated copy of the documentation can be found at: =1.4.0 sphinx-rtd-theme>=0.4.3 yara-python +yara-x pycryptodome pefile diff --git a/doc/source/conf.py b/doc/source/conf.py index d601c1eee3..cabfdc3279 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -169,7 +169,7 @@ def setup(app): # General information about the project. project = "Volatility 3" -copyright = "2012-2022, Volatility Foundation" +copyright = "2012-2024, Volatility Foundation" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/doc/source/getting-started-linux-tutorial.rst b/doc/source/getting-started-linux-tutorial.rst index 26ad2c2e4e..d4b40d0532 100644 --- a/doc/source/getting-started-linux-tutorial.rst +++ b/doc/source/getting-started-linux-tutorial.rst @@ -11,6 +11,7 @@ Volatility3 does not provide the ability to acquire memory. Below are some exam * `AVML - Acquire Volatile Memory for Linux `_ * `LiME - Linux Memory Extract `_ +Be aware that LiME raw format is not supported by volatility3, the padded or lime option should be used instead. `This issue contains further information `_. Procedure to create symbol tables for linux -------------------------------------------- diff --git a/doc/source/vol-cli.rst b/doc/source/vol-cli.rst index cc6f7fe6ac..7b91e815d6 100644 --- a/doc/source/vol-cli.rst +++ b/doc/source/vol-cli.rst @@ -143,3 +143,18 @@ Options `hivescan` would match `windows.registry.hivescan.HiveScan`, but `pslist` is ambiguous because it could match `windows.pslist` or `linux.pslist`. + +Overriding options +------------------ + +The default values for the command line interface are defined by constants within the code, +but can be overridden by creating a JSON file (`%APPDATA%/volatility3/vol.json` for Windows +systems, or `~/.config/volatility3/vol.json` or `volshell.json` for all others). + +The format of this file is a JSON dictionary, containing the options above and their value. +It should be noted that the ordering is (`<` means is overridden by): + +`in-built default value < config file value < command line parameter` + +It should also be noted that boolean flags (such as `offline`) that are overridden as true will +not be unset by not specifying the command line flag. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000..2e1636a437 --- /dev/null +++ b/pyproject.toml @@ -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" diff --git a/requirements-dev.txt b/requirements-dev.txt index 9db14d4411..ae3482290b 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,22 +1,9 @@ -# The following packages are required for core functionality. -pefile>=2017.8.1 - -# The following packages are optional. -# If certain packages are not necessary, place a comment (#) at the start of the line. - -# This is required for the yara plugins -yara-python>=3.8.0 - -# This is required for several plugins that perform malware analysis and disassemble code. -# It can also improve accuracy of Windows 8 and later memory samples. -capstone>=3.0.5 - -# This is required by plugins that decrypt passwords, password hashes, etc. -pycryptodome +-r requirements.txt # This can improve error messages regarding improperly configured ISF files, # but is only recommended for development jsonschema>=2.3.0 -# This is required for memory acquisition via leechcore/pcileech. -leechcorepyc>=2.4.0 +# Used to build executable file +pyinstaller>=6.5.0 +pyinstaller-hooks-contrib>=2024.3 \ No newline at end of file diff --git a/requirements-minimal.txt b/requirements-minimal.txt index 31ac028148..c030b332dd 100644 --- a/requirements-minimal.txt +++ b/requirements-minimal.txt @@ -1,2 +1,2 @@ # These packages are required for core functionality. -pefile>=2017.8.1 #foo \ No newline at end of file +pefile>=2023.2.7 #foo \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 99e0786cc9..bd61edf142 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ -# The following packages are required for core functionality. -pefile>=2017.8.1 +# Include the minimal requirements +-r requirements-minimal.txt # The following packages are optional. # If certain packages are not necessary, place a comment (#) at the start of the line. @@ -15,4 +15,8 @@ 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 +s3fs>=2023.1.0 \ No newline at end of file diff --git a/setup.py b/setup.py index cfcda3d5c7..3af0331602 100644 --- a/setup.py +++ b/setup.py @@ -4,49 +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", - }, - python_requires=">=3.7.0", - include_package_data=True, - exclude_package_data={"": ["development", "development.*"], "development": ["*"]}, - packages=setuptools.find_namespace_packages( - include=["volatility3"] - ), - entry_points={ - "console_scripts": [ - "vol = volatility3.cli:main", - "volshell = volatility3.cli.volshell:main", - ], + extras_require={ + "dev": get_requires("requirements-dev.txt"), + "full": get_requires("requirements.txt"), }, - install_requires=get_install_requires(), ) diff --git a/test/requirements-testing.txt b/test/requirements-testing.txt index 7afe19b945..51c8f602c2 100644 --- a/test/requirements-testing.txt +++ b/test/requirements-testing.txt @@ -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 diff --git a/test/test_volatility.py b/test/test_volatility.py index aaad615bcc..847be88d9b 100644 --- a/test/test_volatility.py +++ b/test/test_volatility.py @@ -6,6 +6,7 @@ # import os +import re import subprocess import sys import shutil @@ -189,6 +190,16 @@ def test_windows_svcscan(image, volatility, python): assert rc == 0 +def test_windows_thrdscan(image, volatility, python): + rc, out, err = runvol_plugin("windows.thrdscan.ThrdScan", image, volatility, python) + # find pid 4 (of system process) which starts with lowest tids + assert out.find(b"\t4\t8") != -1 + assert out.find(b"\t4\t12") != -1 + assert out.find(b"\t4\t16") != -1 + #assert out.find(b"this raieses AssertionError") != -1 + assert rc == 0 + + def test_windows_privileges(image, volatility, python): rc, out, err = runvol_plugin( "windows.privileges.Privs", image, volatility, python, pluginargs=["--pid", "4"] @@ -330,6 +341,43 @@ def test_linux_tty_check(image, volatility, python): assert out.count(b"\n") >= 5 assert rc == 0 +def test_linux_sockstat(image, volatility, python): + rc, out, err = runvol_plugin("linux.sockstat.Sockstat", image, volatility, python) + + assert out.count(b"AF_UNIX") >= 354 + assert out.count(b"AF_BLUETOOTH") >= 5 + assert out.count(b"AF_INET") >= 32 + assert out.count(b"AF_INET6") >= 20 + assert out.count(b"AF_PACKET") >= 1 + assert out.count(b"AF_NETLINK") >= 43 + assert rc == 0 + + +def test_linux_library_list(image, volatility, python): + rc, out, err = runvol_plugin( + "linux.library_list.LibraryList", image, volatility, python + ) + + assert re.search( + rb"NetworkManager\s2363\s0x7f52cdda0000\s/lib/x86_64-linux-gnu/libnss_files.so.2", + out, + ) + assert re.search( + rb"gnome-settings-\s3807\s0x7f7e660b5000\s/lib/x86_64-linux-gnu/libbz2.so.1.0", + out, + ) + assert re.search( + rb"gdu-notificatio\s3878\s0x7f25ce33e000\s/usr/lib/x86_64-linux-gnu/libXau.so.6", + out, + ) + assert re.search( + rb"bash\s8600\s0x7fe78a85f000\s/lib/x86_64-linux-gnu/libnss_files.so.2", + out, + ) + + assert out.count(b"\n") >= 2677 + assert rc == 0 + # MAC diff --git a/vol.py b/vol.py index ff420cad55..c49d5985d1 100755 --- a/vol.py +++ b/vol.py @@ -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 diff --git a/volatility3/cli/__init__.py b/volatility3/cli/__init__.py index 9bfd14c6c6..50b5579afd 100644 --- a/volatility3/cli/__init__.py +++ b/volatility3/cli/__init__.py @@ -19,9 +19,17 @@ import sys import tempfile import traceback -from typing import Any, Dict, Type, Union +from typing import Any, Dict, List, Tuple, Type, Union from urllib import parse, request +try: + import argcomplete + + HAS_ARGCOMPLETE = True +except ImportError: + HAS_ARGCOMPLETE = False + +from volatility3.cli import text_filter import volatility3.plugins import volatility3.symbols from volatility3 import framework @@ -105,6 +113,9 @@ def run(self): ] ) + # Load up system defaults + delayed_logs, default_config = self.load_system_defaults("vol.json") + parser = volargparse.HelpfulArgParser( add_help=False, prog=self.CLI_NAME, @@ -224,12 +235,29 @@ def run(self): default=constants.CACHE_PATH, type=str, ) - parser.add_argument( + isf_group = parser.add_mutually_exclusive_group() + isf_group.add_argument( "--offline", help="Do not search online for additional JSON files", default=False, action="store_true", ) + isf_group.add_argument( + "-u", + "--remote-isf-url", + metavar="URL", + help="Search online for ISF json files", + default=constants.REMOTE_ISF_URL, + type=str, + ) + parser.add_argument( + "--filters", + help="List of filters to apply to the output (in the form of [+-]columname,pattern[!])", + default=[], + action="append", + ) + + parser.set_defaults(**default_config) # We have to filter out help, otherwise parse_known_args will trigger the help message before having # processed the plugin choice or had the plugin subparser added. @@ -241,19 +269,7 @@ def run(self): banner_output = sys.stderr banner_output.write(f"Volatility 3 Framework {constants.PACKAGE_VERSION}\n") - if partial_args.plugin_dirs: - volatility3.plugins.__path__ = [ - os.path.abspath(p) for p in partial_args.plugin_dirs.split(";") - ] + constants.PLUGINS_PATH - - if partial_args.symbol_dirs: - volatility3.symbols.__path__ = [ - os.path.abspath(p) for p in partial_args.symbol_dirs.split(";") - ] + constants.SYMBOL_BASEPATHS - - if partial_args.cache_path: - constants.CACHE_PATH = partial_args.cache_path - + ### Start up logging if partial_args.log: file_logger = logging.FileHandler(partial_args.log) file_logger.setLevel(1) @@ -264,12 +280,31 @@ def run(self): file_logger.setFormatter(file_formatter) rootlog.addHandler(file_logger) vollog.info("Logging started") + + self.order_extra_verbose_levels() if partial_args.verbosity < 3: if partial_args.verbosity < 1: sys.tracebacklimit = None - console.setLevel(30 - (partial_args.verbosity * 10)) + console.setLevel(logging.WARNING - (partial_args.verbosity * 10)) else: - console.setLevel(10 - (partial_args.verbosity - 2)) + console.setLevel(logging.DEBUG - (partial_args.verbosity - 2)) + + for level, msg in delayed_logs: + vollog.log(level, msg) + + ### Alter constants if necessary + if partial_args.plugin_dirs: + volatility3.plugins.__path__ = [ + os.path.abspath(p) for p in partial_args.plugin_dirs.split(";") + ] + constants.PLUGINS_PATH + + if partial_args.symbol_dirs: + volatility3.symbols.__path__ = [ + os.path.abspath(p) for p in partial_args.symbol_dirs.split(";") + ] + constants.SYMBOL_BASEPATHS + + if partial_args.cache_path: + constants.CACHE_PATH = partial_args.cache_path vollog.info(f"Volatility plugins path: {volatility3.plugins.__path__}") vollog.info(f"Volatility symbols path: {volatility3.symbols.__path__}") @@ -287,6 +322,8 @@ def run(self): if partial_args.offline: constants.OFFLINE = partial_args.offline + elif partial_args.remote_isf_url: + constants.REMOTE_ISF_URL = partial_args.remote_isf_url # Do the initialization ctx = contexts.Context() # Construct a blank context @@ -332,6 +369,10 @@ def run(self): # 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") @@ -444,7 +485,10 @@ def run(self): try: # Construct and run the plugin if constructed: - renderers[args.renderer]().render(constructed.run()) + grid = constructed.run() + renderer = renderers[args.renderer]() + renderer.filter = text_filter.CLIFilter(grid, args.filters) + renderer.render(grid) except exceptions.VolatilityException as excp: self.process_exceptions(excp) @@ -463,6 +507,50 @@ def location_from_file(cls, filename: str) -> str: ) return requirements.URIRequirement.location_from_file(filename) + def load_system_defaults( + self, filename: str + ) -> Tuple[List[Tuple[int, str]], Dict[str, Any]]: + """Modify the main configuration based on the default configuration override""" + # Build the config path + default_config_path = os.path.join( + os.path.expanduser("~"), ".config", "volatility3", filename + ) + if sys.platform == "win32": + default_config_path = os.path.join( + os.environ.get("APPDATA", os.path.expanduser("~")), + "volatility3", + filename, + ) + + delayed_logs = [] + + # Process it if the files exist + if os.path.exists(default_config_path): + with open(default_config_path, "rb") as config_json: + result = json.load(config_json) + if not isinstance(result, dict): + delayed_logs.append( + ( + logging.INFO, + f"Default configuration file {default_config_path} does not contain a dictionary", + ) + ) + else: + delayed_logs.append( + ( + logging.INFO, + f"Loading default configuration options from {default_config_path}", + ) + ) + delayed_logs.append( + ( + logging.DEBUG, + f"Loaded configuration: {json.dumps(result, indent = 2, sort_keys = True)}", + ) + ) + return delayed_logs, result + return delayed_logs, {} + def process_exceptions(self, excp): """Provide useful feedback if an exception occurs during a run of a plugin.""" # Ensure there's nothing in the cache @@ -631,6 +719,17 @@ def populate_config( ) context.config[extended_path] = value + def order_extra_verbose_levels(self): + for level, level_value in enumerate( + [ + constants.LOGLEVEL_V, + constants.LOGLEVEL_VV, + constants.LOGLEVEL_VVV, + constants.LOGLEVEL_VVVV, + ] + ): + logging.addLevelName(level_value, f"DETAIL {level+1}") + def file_handler_class_factory(self, direct=True): output_dir = self.output_dir @@ -639,19 +738,17 @@ def _get_final_filename(self): """Gets the final filename""" if output_dir is None: raise TypeError("Output directory is not a string") + os.makedirs(output_dir, exist_ok=True) - pref_name_array = self.preferred_filename.split(".") - filename, extension = ( - os.path.join(output_dir, ".".join(pref_name_array[:-1])), - pref_name_array[-1], - ) - output_filename = f"{filename}.{extension}" + output_filename = os.path.join(output_dir, self.preferred_filename) + filename, extension = os.path.splitext(output_filename) counter = 1 while os.path.exists(output_filename): - output_filename = f"{filename}-{counter}.{extension}" + output_filename = f"{filename}-{counter}{extension}" counter += 1 + return output_filename class CLIMemFileHandler(io.BytesIO, CLIFileHandler): @@ -662,7 +759,7 @@ def __init__(self, filename: str): def close(self): # Don't overcommit if self.closed: - return + return None self.seek(0) @@ -712,10 +809,18 @@ def close(self): """Closes and commits the file (by moving the temporary file to the correct name""" # Don't overcommit if self._file.closed: - return + return None - self._file.close() output_filename = self._get_final_filename() + + # Update the filename, which may have changed if a file with + # the same name already existed. This needs to be done before + # closing the file, otherwise FileHandlerInterface will raise + # an exception. Also, the preferred_filename setter only allows + # a specific set of characters, where '/' is not in that list + self.preferred_filename = os.path.basename(output_filename) + + self._file.close() os.rename(self._name, output_filename) if direct: @@ -763,7 +868,11 @@ def populate_requirements_argparse( requirement, volatility3.framework.configuration.requirements.ListRequirement, ): - additional["type"] = requirement.element_type + # Allow a list of integers, specified with the convenient 0x hexadecimal format + if requirement.element_type == int: + additional["type"] = lambda x: int(x, 0) + else: + additional["type"] = requirement.element_type nargs = "*" if requirement.optional else "+" additional["nargs"] = nargs elif isinstance( diff --git a/volatility3/cli/text_filter.py b/volatility3/cli/text_filter.py new file mode 100644 index 0000000000..3d69934e99 --- /dev/null +++ b/volatility3/cli/text_filter.py @@ -0,0 +1,98 @@ +import logging +from typing import Any, List, Optional +from volatility3.framework import constants, interfaces +import re + +vollog = logging.getLogger(__name__) + + +class CLIFilter: + def __init__(self, treegrid, filters: List[str]): + self._filters = self._prepare(treegrid, filters) + + def _prepare(self, treegrid: interfaces.renderers.TreeGrid, filters: List[str]): + """Runs through the filter strings and creates the necessary filter objects""" + output = [] + + for filter in filters: + exclude = False + regex = False + pattern = None + column_name = None + if filter.startswith("-"): + exclude = True + filter = filter[1:] + elif filter.startswith("+"): + filter = filter[1:] + components = filter.split(",") + if len(components) < 2: + pattern = components[0] + else: + column_name = components[0] + pattern = ",".join(components[1:]) + if pattern and pattern.endswith("!"): + regex = True + pattern = pattern[:-1] + column_num = None + if column_name: + for num, column in enumerate(treegrid.columns): + if column_name.lower() in column.name.lower(): + column_num = num + break + if pattern: + output.append(ColumnFilter(column_num, pattern, regex, exclude)) + + vollog.log(constants.LOGLEVEL_VVV, "Filters:\n" + repr(output)) + + return output + + def filter( + self, + row: List[Any], + ) -> bool: + """Filters the row based on each of the column_filters""" + if not self._filters: + return False + found = any(column_filter.found(row) for column_filter in self._filters) + return not found + + +class ColumnFilter: + def __init__( + self, + column_num: Optional[int], + pattern: str, + regex: bool = False, + exclude: bool = False, + ) -> None: + self.column_num = column_num + self.pattern = pattern + self.exclude = exclude + self.regex = regex + + def find(self, item) -> bool: + """Identifies whether an item is found in the appropriate column""" + try: + if self.regex: + return re.search(self.pattern, f"{item}") + return self.pattern in f"{item}" + except IOError: + return False + + def found(self, row: List[Any]) -> bool: + """Determines whether a row should be filtered + + If the classes exclude value is false, and the necessary pattern is found, the row is not filtered, + otherwise it is filtered. + """ + if self.column_num is None: + found = any(self.find(x) for x in row) + else: + found = self.find(row[self.column_num]) + if self.exclude: + return not found + return found + + def __repr__(self) -> str: + """Returns a display of a column filter""" + return f"ColumnFilter(column={self.column_num},exclude={self.exclude},regex={self.regex},pattern={self.pattern})" diff --git a/volatility3/cli/text_renderer.py b/volatility3/cli/text_renderer.py index ffb8d516bc..da0cdf62a7 100644 --- a/volatility3/cli/text_renderer.py +++ b/volatility3/cli/text_renderer.py @@ -10,6 +10,7 @@ import sys from functools import wraps from typing import Any, Callable, Dict, List, Tuple +from volatility3.cli import text_filter from volatility3.framework import interfaces, renderers from volatility3.framework.renderers import format_hints @@ -24,7 +25,7 @@ vollog.debug("Disassembly library capstone not found") -def hex_bytes_as_text(value: bytes) -> str: +def hex_bytes_as_text(value: bytes, width: int = 16) -> str: """Renders HexBytes as text. Args: @@ -35,19 +36,24 @@ def hex_bytes_as_text(value: bytes) -> str: """ if not isinstance(value, bytes): raise TypeError(f"hex_bytes_as_text takes bytes not: {type(value)}") - ascii = [] - hex = [] - count = 0 - output = "" - for byte in value: - hex.append(f"{byte:02x}") - ascii.append(chr(byte) if 0x20 < byte <= 0x7E else ".") - if (count % 8) == 7: - output += "\n" - output += " ".join(hex[count - 7 : count + 1]) - output += "\t" - output += "".join(ascii[count - 7 : count + 1]) - count += 1 + + printables = "" + output = "\n" + for count, byte in enumerate(value): + output += f"{byte:02x} " + char = chr(byte) + printables += char if 0x20 <= byte <= 0x7E else "." + if count % width == width - 1: + output += printables + if count < len(value) - 1: + output += "\n" + printables = "" + + # Handle leftovers when the lenght is not mutiple of width + if printables: + output += " " * (width - len(printables)) + output += printables + return output @@ -134,6 +140,7 @@ class CLIRenderer(interfaces.renderers.Renderer): name = "unnamed" structured_output = False + filter: text_filter.CLIFilter = None class QuickTextRenderer(CLIRenderer): @@ -172,19 +179,22 @@ def render(self, grid: interfaces.renderers.TreeGrid) -> None: outfd.write("\n{}\n".format("\t".join(line))) def visitor(node: interfaces.renderers.TreeNode, accumulator): + line = [] + for column_index, column in enumerate(grid.columns): + renderer = self._type_renderers.get( + column.type, self._type_renderers["default"] + ) + line.append(renderer(node.values[column_index])) + + if self.filter and self.filter.filter(line): + return accumulator + accumulator.write("\n") # Nodes always have a path value, giving them a path_depth of at least 1, we use max just in case accumulator.write( "*" * max(0, node.path_depth - 1) + ("" if (node.path_depth <= 1) else " ") ) - line = [] - for column_index in range(len(grid.columns)): - column = grid.columns[column_index] - renderer = self._type_renderers.get( - column.type, self._type_renderers["default"] - ) - line.append(renderer(node.values[column_index])) accumulator.write("{}".format("\t".join(line))) accumulator.flush() return accumulator @@ -249,12 +259,17 @@ def render(self, grid: interfaces.renderers.TreeGrid) -> None: def visitor(node: interfaces.renderers.TreeNode, accumulator): # Nodes always have a path value, giving them a path_depth of at least 1, we use max just in case row = {"TreeDepth": str(max(0, node.path_depth - 1))} - for column_index in range(len(grid.columns)): - column = grid.columns[column_index] + line = [] + for column_index, column in enumerate(grid.columns): renderer = self._type_renderers.get( column.type, self._type_renderers["default"] ) row[f"{column.name}"] = renderer(node.values[column_index]) + line.append(row[f"{column.name}"]) + + if self.filter and self.filter.filter(line): + return accumulator + accumulator.writerow(row) return accumulator @@ -306,9 +321,10 @@ def visitor( max_column_widths[tree_indent_column] = max( max_column_widths.get(tree_indent_column, 0), node.path_depth ) + line = {} - for column_index in range(len(grid.columns)): - column = grid.columns[column_index] + rendered_line = [] + for column_index, column in enumerate(grid.columns): renderer = self._type_renderers.get( column.type, self._type_renderers["default"] ) @@ -320,6 +336,11 @@ def visitor( max_column_widths.get(column.name, len(column.name)), field_width ) line[column] = data.split("\n") + rendered_line.append(data) + + if self.filter and self.filter.filter(rendered_line): + return accumulator + accumulator.append((node.path_depth, line)) return accumulator @@ -333,8 +354,7 @@ def visitor( format_string_list = [ "{0:<" + str(max_column_widths.get(tree_indent_column, 0)) + "s}" ] - for column_index in range(len(grid.columns)): - column = grid.columns[column_index] + for column_index, column in enumerate(grid.columns): format_string_list.append( "{" + str(column_index + 1) @@ -389,9 +409,11 @@ class JsonRenderer(CLIRenderer): interfaces.renderers.Disassembly: quoted_optional(display_disassembly), format_hints.MultiTypeData: quoted_optional(multitypedata_as_text), bytes: optional(lambda x: " ".join([f"{b:02x}" for b in x])), - datetime.datetime: lambda x: x.isoformat() - if not isinstance(x, interfaces.renderers.BaseAbsentValue) - else None, + datetime.datetime: lambda x: ( + x.isoformat() + if not isinstance(x, interfaces.renderers.BaseAbsentValue) + else None + ), "default": lambda x: x, } @@ -421,8 +443,8 @@ def visitor( # Nodes always have a path value, giving them a path_depth of at least 1, we use max just in case acc_map, final_tree = accumulator node_dict: Dict[str, Any] = {"__children": []} - for column_index in range(len(grid.columns)): - column = grid.columns[column_index] + line = [] + for column_index, column in enumerate(grid.columns): renderer = self._type_renderers.get( column.type, self._type_renderers["default"] ) @@ -430,6 +452,11 @@ def visitor( if isinstance(data, interfaces.renderers.BaseAbsentValue): data = None node_dict[column.name] = data + line.append(data) + + if self.filter and self.filter.filter(line): + return accumulator + if node.parent: acc_map[node.parent.path]["__children"].append(node_dict) else: diff --git a/volatility3/cli/volargparse.py b/volatility3/cli/volargparse.py index 3048a0885e..fd61ddce02 100644 --- a/volatility3/cli/volargparse.py +++ b/volatility3/cli/volargparse.py @@ -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 @@ def _match_argument(self, action, arg_strings_pattern) -> int: # 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 diff --git a/volatility3/cli/volshell/__init__.py b/volatility3/cli/volshell/__init__.py index 3b982394fd..5172c5363f 100644 --- a/volatility3/cli/volshell/__init__.py +++ b/volatility3/cli/volshell/__init__.py @@ -21,13 +21,23 @@ plugins, ) +try: + import argcomplete + + HAS_ARGCOMPLETE = True +except ImportError: + HAS_ARGCOMPLETE = False + + # Make sure we log everything + +rootlog = logging.getLogger() vollog = logging.getLogger() vollog.setLevel(0) -# Trim the console down by default console = logging.StreamHandler() console.setLevel(logging.WARNING) formatter = logging.Formatter("%(levelname)-8s %(name)-12s: %(message)s") +# Trim the console down by default console.setFormatter(formatter) vollog.addHandler(console) @@ -53,6 +63,9 @@ def run(self): framework.require_interface_version(2, 0, 0) + # Load up system defaults + delayed_logs, default_config = self.load_system_defaults("volshell.json") + parser = argparse.ArgumentParser( prog=self.CLI_NAME, description="A tool for interactivate forensic analysis of memory images", @@ -146,6 +159,21 @@ def run(self): default=constants.CACHE_PATH, type=str, ) + isf_group = parser.add_mutually_exclusive_group() + isf_group.add_argument( + "--offline", + help="Do not search online for additional JSON files", + default=False, + action="store_true", + ) + isf_group.add_argument( + "-u", + "--remote-isf-url", + metavar="URL", + help="Search online for ISF json files", + default=constants.REMOTE_ISF_URL, + type=str, + ) # Volshell specific flags os_specific = parser.add_mutually_exclusive_group(required=False) @@ -167,10 +195,35 @@ def run(self): "-m", "--mac", default=False, action="store_true", help="Run a Mac volshell" ) + parser.set_defaults(**default_config) + # We have to filter out help, otherwise parse_known_args will trigger the help message before having # processed the plugin choice or had the plugin subparser added. known_args = [arg for arg in sys.argv if arg != "--help" and arg != "-h"] partial_args, _ = parser.parse_known_args(known_args) + + ### Start up logging + if partial_args.log: + file_logger = logging.FileHandler(partial_args.log) + file_logger.setLevel(0) + file_formatter = logging.Formatter( + datefmt="%y-%m-%d %H:%M:%S", + fmt="%(asctime)s %(name)-12s %(levelname)-8s %(message)s", + ) + file_logger.setFormatter(file_formatter) + vollog.addHandler(file_logger) + vollog.info("Logging started") + + self.order_extra_verbose_levels() + if partial_args.verbosity < 3: + console.setLevel(logging.WARNING - (partial_args.verbosity * 10)) + else: + console.setLevel(logging.DEBUG - (partial_args.verbosity - 2)) + + for level, msg in delayed_logs: + vollog.log(level, msg) + + ### Alter constants if necessary if partial_args.plugin_dirs: volatility3.plugins.__path__ = [ os.path.abspath(p) for p in partial_args.plugin_dirs.split(";") @@ -187,25 +240,14 @@ def run(self): vollog.info(f"Volatility plugins path: {volatility3.plugins.__path__}") vollog.info(f"Volatility symbols path: {volatility3.symbols.__path__}") - if partial_args.log: - file_logger = logging.FileHandler(partial_args.log) - file_logger.setLevel(0) - file_formatter = logging.Formatter( - datefmt="%y-%m-%d %H:%M:%S", - fmt="%(asctime)s %(name)-12s %(levelname)-8s %(message)s", - ) - file_logger.setFormatter(file_formatter) - vollog.addHandler(file_logger) - vollog.info("Logging started") - - if partial_args.verbosity < 3: - console.setLevel(30 - (partial_args.verbosity * 10)) - else: - console.setLevel(10 - (partial_args.verbosity - 2)) - if partial_args.clear_cache: framework.clear_cache() + if partial_args.offline: + constants.OFFLINE = partial_args.offline + elif partial_args.remote_isf_url: + constants.REMOTE_ISF_URL = partial_args.remote_isf_url + # Do the initialization ctx = contexts.Context() # Construct a blank context failures = framework.import_files( @@ -253,6 +295,10 @@ def run(self): # 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( diff --git a/volatility3/cli/volshell/generic.py b/volatility3/cli/volshell/generic.py index ea9e65d9b5..c1ca7b5fa7 100644 --- a/volatility3/cli/volshell/generic.py +++ b/volatility3/cli/volshell/generic.py @@ -108,11 +108,11 @@ def help(self, *args): """Describes the available commands""" if args: help(*args) - return + return None variables = [] print("\nMethods:") - for aliases, item in self.construct_locals(): + for aliases, item in sorted(self.construct_locals()): name = ", ".join(aliases) if item.__doc__ and callable(item): print(f"* {name}") @@ -125,8 +125,7 @@ def help(self, *args): print(f" {var}") def construct_locals(self) -> List[Tuple[List[str], Any]]: - """Returns a dictionary listing the functions to be added to the - environment.""" + """Returns a listing of the functions to be added to the environment.""" return [ (["dt", "display_type"], self.display_type), (["db", "display_bytes"], self.display_bytes), @@ -325,7 +324,7 @@ def display_type( (str, interfaces.objects.ObjectInterface, interfaces.objects.Template), ): print("Cannot display information about non-type object") - return + return None if not isinstance(object, str): # Mypy requires us to order things this way @@ -453,7 +452,7 @@ def display_symbols(self, symbol_table: str = None): """Prints an alphabetical list of symbols for a symbol table""" if symbol_table is None: print("No symbol table provided") - return + return None longest_offset = longest_name = 0 table = self.context.symbol_space[symbol_table] @@ -529,7 +528,7 @@ def create_configurable( val, interfaces.configuration.BasicTypes ) and not isinstance(val, list): if not isinstance(val, list) or all( - [isinstance(x, interfaces.configuration.BasicTypes) for x in val] + isinstance(x, interfaces.configuration.BasicTypes) for x in val ): raise TypeError( "Configurable values must be simple types (int, bool, str, bytes)" diff --git a/volatility3/cli/volshell/linux.py b/volatility3/cli/volshell/linux.py index 8c23bbec3e..c5e555ec7d 100644 --- a/volatility3/cli/volshell/linux.py +++ b/volatility3/cli/volshell/linux.py @@ -35,9 +35,9 @@ def change_task(self, pid=None): process_layer = task.add_process_layer() if process_layer is not None: self.change_layer(process_layer) - return + return None print(f"Layer for task ID {pid} could not be constructed") - return + return None print(f"No task with task ID {pid} found") def list_tasks(self): diff --git a/volatility3/cli/volshell/mac.py b/volatility3/cli/volshell/mac.py index b709511b14..2b32ad6776 100644 --- a/volatility3/cli/volshell/mac.py +++ b/volatility3/cli/volshell/mac.py @@ -35,9 +35,9 @@ def change_task(self, pid=None): process_layer = task.add_process_layer() if process_layer is not None: self.change_layer(process_layer) - return + return None print(f"Layer for task ID {pid} could not be constructed") - return + return None print(f"No task with task ID {pid} found") def list_tasks(self, method=None): diff --git a/volatility3/cli/volshell/windows.py b/volatility3/cli/volshell/windows.py index 652b2e66b0..5c2190c027 100644 --- a/volatility3/cli/volshell/windows.py +++ b/volatility3/cli/volshell/windows.py @@ -32,7 +32,7 @@ def change_process(self, pid=None): if process.UniqueProcessId == pid: process_layer = process.add_process_layer() self.change_layer(process_layer) - return + return None print(f"No process with process ID {pid} found") def list_processes(self): diff --git a/volatility3/framework/__init__.py b/volatility3/framework/__init__.py index 9c17846a80..51310bfa29 100644 --- a/volatility3/framework/__init__.py +++ b/volatility3/framework/__init__.py @@ -7,7 +7,7 @@ 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] @@ -206,9 +206,9 @@ def _zipwalk(path: str): if not file.is_dir(): dirlist = zip_results.get(os.path.dirname(file.filename), []) dirlist.append(os.path.basename(file.filename)) - zip_results[ - os.path.join(path, os.path.dirname(file.filename)) - ] = dirlist + zip_results[os.path.join(path, os.path.dirname(file.filename))] = ( + dirlist + ) for value in zip_results: yield value, zip_results[value] @@ -223,8 +223,14 @@ def list_plugins() -> Dict[str, Type[interfaces.plugins.PluginInterface]]: return plugin_list -def clear_cache(complete=False): +def clear_cache(complete=True): try: + if complete: + glob_pattern = "*.cache" + for cache_filename in glob.glob( + os.path.join(constants.CACHE_PATH, glob_pattern) + ): + os.unlink(cache_filename) os.unlink(os.path.join(constants.CACHE_PATH, constants.IDENTIFIERS_FILENAME)) except FileNotFoundError: vollog.log(constants.LOGLEVEL_VVVV, "Attempting to clear a non-existant cache") diff --git a/volatility3/framework/automagic/linux.py b/volatility3/framework/automagic/linux.py index 2eebcc2dcd..52a73f45a8 100644 --- a/volatility3/framework/automagic/linux.py +++ b/volatility3/framework/automagic/linux.py @@ -159,7 +159,10 @@ def find_aslr( # This we get for free aslr_shift = ( - init_task.files.cast("long unsigned int") + int.from_bytes( + init_task.files.cast("bytes", length=init_task.files.vol.size), + byteorder=init_task.files.vol.data_format.byteorder, + ) - module.get_symbol("init_files").address ) kaslr_shift = init_task_address - cls.virtual_to_physical_address( diff --git a/volatility3/framework/automagic/mac.py b/volatility3/framework/automagic/mac.py index aa75fbc3d7..e517531394 100644 --- a/volatility3/framework/automagic/mac.py +++ b/volatility3/framework/automagic/mac.py @@ -138,9 +138,9 @@ def stack( config_path = join("automagic", "MacIntelHelper", new_layer_name) context.config[join(config_path, "memory_layer")] = layer_name context.config[join(config_path, "page_map_offset")] = dtb - context.config[ - join(config_path, MacSymbolFinder.banner_config_key) - ] = str(banner, "latin-1") + context.config[join(config_path, MacSymbolFinder.banner_config_key)] = ( + str(banner, "latin-1") + ) new_layer = intel.Intel32e( context, diff --git a/volatility3/framework/automagic/module.py b/volatility3/framework/automagic/module.py index 2bdaf3f625..ff13db9055 100644 --- a/volatility3/framework/automagic/module.py +++ b/volatility3/framework/automagic/module.py @@ -29,21 +29,21 @@ def __call__( requirement.requirements[req], progress_callback, ) - return + return None if not requirement.unsatisfied(context, config_path): - return + return None # The requirement is unfulfilled and is a ModuleRequirement - context.config[ - interfaces.configuration.path_join(new_config_path, "class") - ] = "volatility3.framework.contexts.Module" + context.config[interfaces.configuration.path_join(new_config_path, "class")] = ( + "volatility3.framework.contexts.Module" + ) for req in requirement.requirements: if ( requirement.requirements[req].unsatisfied(context, new_config_path) and req != "offset" ): - return + return None # We now just have the offset requirement, but the layer requirement has been fulfilled. # Unfortunately we don't know the layer name requirement's exact name diff --git a/volatility3/framework/automagic/pdbscan.py b/volatility3/framework/automagic/pdbscan.py index 06b2111b4e..7f38a23e1a 100644 --- a/volatility3/framework/automagic/pdbscan.py +++ b/volatility3/framework/automagic/pdbscan.py @@ -209,10 +209,8 @@ def test_physical_kernel( try: kvp = vlayer.mapping(kvo, 0) if any( - [ - (p == kernel["mz_offset"] and layer_name == physical_layer_name) - for (_, _, p, _, layer_name) in kvp - ] + (p == kernel["mz_offset"] and layer_name == physical_layer_name) + for (_, _, p, _, layer_name) in kvp ): return (virtual_layer_name, kvo, kernel) else: diff --git a/volatility3/framework/automagic/stacker.py b/volatility3/framework/automagic/stacker.py index e611b5f067..c251d3c46b 100644 --- a/volatility3/framework/automagic/stacker.py +++ b/volatility3/framework/automagic/stacker.py @@ -103,7 +103,7 @@ def stack( appropriate_config_path, layer_name = result context.config.merge(appropriate_config_path, subconfig) context.config[appropriate_config_path] = top_layer_name - return + return None self._cached = None new_context = context.clone() @@ -156,6 +156,9 @@ def stack( self._cached = context.config.get(path, None), context.config.branch( path ) + vollog.debug( + f"physical_layer maximum_address: {physical_layer.maximum_address}" + ) vollog.debug(f"Stacked layers: {stacked_layers}") @classmethod diff --git a/volatility3/framework/automagic/symbol_cache.py b/volatility3/framework/automagic/symbol_cache.py index 29f2cfd08b..22f1c94f34 100644 --- a/volatility3/framework/automagic/symbol_cache.py +++ b/volatility3/framework/automagic/symbol_cache.py @@ -429,7 +429,7 @@ def update(self, progress_callback=None): progress_callback(0, "Reading remote ISF list") cursor = self._database.cursor() cursor.execute( - f"SELECT cached FROM cache WHERE local = 0 and cached < datetime('now', {self.cache_period})" + f"SELECT cached FROM cache WHERE local = 0 and cached < datetime('now', '{self.cache_period}')" ) remote_identifiers = RemoteIdentifierFormat(constants.REMOTE_ISF_URL) progress_callback(50, "Reading remote ISF list") @@ -438,9 +438,13 @@ def update(self, progress_callback=None): {}, operating_system=operating_system ) for identifier, location in identifiers: + identifier = identifier.rstrip() + identifier = ( + identifier[:-1] if identifier.endswith(b"\x00") else identifier + ) # Linux banners dumped by dwarf2json end with "\x00\n". If not stripped, the banner cannot match. cursor.execute( "INSERT OR REPLACE INTO cache(identifier, location, operating_system, local, cached) VALUES (?, ?, ?, ?, datetime('now'))", - (location, identifier, operating_system, False), + (identifier, location, operating_system, False), ) progress_callback(100, "Reading remote ISF list") self._database.commit() diff --git a/volatility3/framework/automagic/symbol_finder.py b/volatility3/framework/automagic/symbol_finder.py index f30dff456c..21e594549e 100644 --- a/volatility3/framework/automagic/symbol_finder.py +++ b/volatility3/framework/automagic/symbol_finder.py @@ -69,7 +69,7 @@ def __call__( # Bomb out early if our details haven't been configured if self.symbol_class is None: - return + return None self._requirements = self.find_requirements( context, @@ -120,7 +120,7 @@ def _banner_scan( # Bomb out early if there's no banners if not self.banners: - return + return None mss = scanners.MultiStringScanner([x for x in self.banners if x is not None]) @@ -150,12 +150,12 @@ def _banner_scan( clazz = self.symbol_class # Set the discovered options path_join = interfaces.configuration.path_join - context.config[ - path_join(config_path, requirement.name, "class") - ] = clazz - context.config[ - path_join(config_path, requirement.name, "isf_url") - ] = isf_path + context.config[path_join(config_path, requirement.name, "class")] = ( + clazz + ) + context.config[path_join(config_path, requirement.name, "isf_url")] = ( + isf_path + ) context.config[ path_join(config_path, requirement.name, "symbol_mask") ] = layer.address_mask diff --git a/volatility3/framework/automagic/windows.py b/volatility3/framework/automagic/windows.py index a8530829ba..52296f5ad5 100644 --- a/volatility3/framework/automagic/windows.py +++ b/volatility3/framework/automagic/windows.py @@ -402,19 +402,19 @@ def __call__( if swap_location: context.config[current_layer_path] = current_layer_name try: - context.config[ - layer_loc_path - ] = requirements.URIRequirement.location_from_file( - swap_location + context.config[layer_loc_path] = ( + requirements.URIRequirement.location_from_file( + swap_location + ) ) except ValueError: vollog.warning( f"Volatility swap_location {swap_location} could not be validated - swap layer disabled" ) continue - context.config[ - layer_class_path - ] = "volatility3.framework.layers.physical.FileLayer" + context.config[layer_class_path] = ( + "volatility3.framework.layers.physical.FileLayer" + ) # Add the requirement new_req = requirements.TranslationLayerRequirement( @@ -424,9 +424,9 @@ def __call__( ) swap_req.add_requirement(new_req) - context.config[ - path_join(swap_sub_config, "number_of_elements") - ] = counter + context.config[path_join(swap_sub_config, "number_of_elements")] = ( + counter + ) context.config[swap_sub_config] = True swap_req.construct(context, swap_config) diff --git a/volatility3/framework/configuration/requirements.py b/volatility3/framework/configuration/requirements.py index abdffdbe49..86e1aac529 100644 --- a/volatility3/framework/configuration/requirements.py +++ b/volatility3/framework/configuration/requirements.py @@ -161,7 +161,7 @@ def unsatisfied( "TypeError - Too many values provided to list option.", ) return {config_path: self} - if not all([isinstance(element, self.element_type) for element in value]): + if not all(isinstance(element, self.element_type) for element in value): vollog.log( constants.LOGLEVEL_V, "TypeError - At least one element in the list is not of the correct type.", @@ -181,7 +181,7 @@ def __init__(self, choices: List[str], *args, **kwargs) -> None: """ super().__init__(*args, **kwargs) if not isinstance(choices, list) or any( - [not isinstance(choice, str) for choice in choices] + not isinstance(choice, str) for choice in choices ): raise TypeError("ChoiceRequirement takes a list of strings as choices") self.choices = choices @@ -410,11 +410,9 @@ def construct( args = {"context": context, "config_path": config_path, "name": name} if any( - [ - subreq.unsatisfied(context, config_path) - for subreq in self.requirements.values() - if not subreq.optional - ] + subreq.unsatisfied(context, config_path) + for subreq in self.requirements.values() + if not subreq.optional ): return None @@ -485,11 +483,9 @@ def construct( args = {"context": context, "config_path": config_path, "name": name} if any( - [ - subreq.unsatisfied(context, config_path) - for subreq in self.requirements.values() - if not subreq.optional - ] + subreq.unsatisfied(context, config_path) + for subreq in self.requirements.values() + if not subreq.optional ): return None @@ -527,12 +523,14 @@ class VersionRequirement(interfaces.configuration.RequirementInterface): def __init__( self, name: str, - description: str = None, + description: Optional[str] = None, default: bool = False, optional: bool = False, component: Type[interfaces.configuration.VersionableInterface] = None, version: Optional[Tuple[int, ...]] = None, ) -> None: + if description is None: + description = f"Version {'.'.join([str(x) for x in version])} dependency on {component.__module__}.{component.__name__} unmet" super().__init__( name=name, description=description, default=default, optional=optional ) @@ -544,15 +542,51 @@ def __init__( self._version = version def unsatisfied( - self, context: interfaces.context.ContextInterface, config_path: str + self, + context: interfaces.context.ContextInterface, + config_path: str, + accumulator: Optional[ + List[interfaces.configuration.VersionableInterface] + ] = None, ) -> Dict[str, interfaces.configuration.RequirementInterface]: # Mypy doesn't appreciate our classproperty implementation, self._plugin.version has no type config_path = interfaces.configuration.path_join(config_path, self.name) if not self.matches_required(self._version, self._component.version): return {config_path: self} - context.config[ - interfaces.configuration.path_join(config_path, self.name) - ] = True + + recurse = True + if accumulator is None: + accumulator = set([self._component]) + else: + if self._component in accumulator: + recurse = False + else: + accumulator.add(self._component) + + # Check for child requirements + if ( + issubclass(self._component, interfaces.configuration.ConfigurableInterface) + and recurse + ): + result = {} + for requirement in self._component.get_requirements(): + if not requirement.optional and isinstance( + requirement, VersionRequirement + ): + result.update( + requirement.unsatisfied( + context, config_path, accumulator.copy() + ) + ) + + if result: + result.update({config_path: self}) + return result + + context.config[interfaces.configuration.path_join(config_path, self.name)] = ( + True + ) + return {} @classmethod @@ -672,11 +706,9 @@ def construct( args = {"context": context, "config_path": config_path, "name": name} if any( - [ - subreq.unsatisfied(context, config_path) - for subreq in self.requirements.values() - if not subreq.optional - ] + subreq.unsatisfied(context, config_path) + for subreq in self.requirements.values() + if not subreq.optional ): return None diff --git a/volatility3/framework/constants/__init__.py b/volatility3/framework/constants/__init__.py index de1674885f..27fae4ba1f 100644 --- a/volatility3/framework/constants/__init__.py +++ b/volatility3/framework/constants/__init__.py @@ -14,6 +14,13 @@ 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,31 +49,21 @@ 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 = 5 # Number of changes that only add to the interface -VERSION_PATCH = 0 # Number of changes that do not change the interface -VERSION_SUFFIX = "" - -# TODO: At version 2.0.0, remove the symbol_shift feature - -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""" +LOGLEVEL_INFO = 20 +"""Logging level for information data, showed when use the requests any logging: -v""" +LOGLEVEL_DEBUG = 10 +"""Logging level for debugging data, showed when the user requests more logging detail: -vv""" LOGLEVEL_V = 9 -"""Logging level for a single -v""" +"""Logging level for the lowest "extra" level of logging: -vvv""" LOGLEVEL_VV = 8 -"""Logging level for -vv""" +"""Logging level for two levels of detail: -vvvv""" LOGLEVEL_VVV = 7 -"""Logging level for -vvv""" +"""Logging level for three levels of detail: -vvvvv""" LOGLEVEL_VVVV = 6 -"""Logging level for -vvvv""" +"""Logging level for four levels of detail: -vvvvvv""" CACHE_PATH = os.path.join(os.path.expanduser("~"), ".cache", "volatility3") """Default path to store cached data""" @@ -137,4 +134,5 @@ def __getattr__(name): ]: warnings.warn(f"{name} is deprecated", FutureWarning) return globals()[f"{deprecated_tag}{name}"] - return None + + return getattr(__import__(__name__), name) diff --git a/volatility3/framework/constants/_version.py b/volatility3/framework/constants/_version.py new file mode 100644 index 0000000000..ce803a6877 --- /dev/null +++ b/volatility3/framework/constants/_version.py @@ -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 = 11 # 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""" diff --git a/volatility3/framework/constants/architectures.py b/volatility3/framework/constants/architectures.py new file mode 100644 index 0000000000..a8822d7d32 --- /dev/null +++ b/volatility3/framework/constants/architectures.py @@ -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""" diff --git a/volatility3/framework/constants/linux/__init__.py b/volatility3/framework/constants/linux/__init__.py index 6e8883f195..3eabc2341a 100644 --- a/volatility3/framework/constants/linux/__init__.py +++ b/volatility3/framework/constants/linux/__init__.py @@ -5,11 +5,10 @@ Linux-specific values that aren't found in debug symbols """ +from enum import IntEnum KERNEL_NAME = "__kernel__" -# arch/x86/include/asm/page_types.h -PAGE_SHIFT = 12 """The value hard coded from the Linux Kernel (hence not extracted from the layer itself)""" # include/linux/sched.h @@ -281,3 +280,25 @@ ) ELF_MAX_EXTRACTION_SIZE = 1024 * 1024 * 1024 * 4 - 1 + + +class ELF_IDENT(IntEnum): + """ELF header e_ident indexes""" + + EI_MAG0 = 0 + EI_MAG1 = 1 + EI_MAG2 = 2 + EI_MAG3 = 3 + EI_CLASS = 4 + EI_DATA = 5 + EI_VERSION = 6 + EI_OSABI = 7 + EI_PAD = 8 + + +class ELF_CLASS(IntEnum): + """ELF header class types""" + + ELFCLASSNONE = 0 + ELFCLASS32 = 1 + ELFCLASS64 = 2 diff --git a/volatility3/framework/contexts/__init__.py b/volatility3/framework/contexts/__init__.py index 73868a58f3..4c169728cc 100644 --- a/volatility3/framework/contexts/__init__.py +++ b/volatility3/framework/contexts/__init__.py @@ -256,12 +256,13 @@ def object( if not absolute: offset += self._offset - # Ensure we don't use a layer_name other than the module's, why would anyone do that? - if "layer_name" in kwargs: - del kwargs["layer_name"] + # We have to allow using an alternative layer name due to pool scanners switching + # to the memory layer for scanning samples prior to Windows 10. + layer_name = kwargs.pop("layer_name", self._layer_name) + return self._context.object( object_type=object_type, - layer_name=self._layer_name, + layer_name=layer_name, offset=offset, native_layer_name=native_layer_name or self._native_layer_name, **kwargs, diff --git a/volatility3/framework/interfaces/automagic.py b/volatility3/framework/interfaces/automagic.py index fe1361b306..0867b1608f 100644 --- a/volatility3/framework/interfaces/automagic.py +++ b/volatility3/framework/interfaces/automagic.py @@ -50,7 +50,7 @@ def __init__( context: interfaces.context.ContextInterface, config_path: str, *args, - **kwargs + **kwargs, ) -> None: super().__init__(context, config_path) for requirement in self.get_requirements(): diff --git a/volatility3/framework/interfaces/configuration.py b/volatility3/framework/interfaces/configuration.py index 3bb3cb0193..da0a4556c6 100644 --- a/volatility3/framework/interfaces/configuration.py +++ b/volatility3/framework/interfaces/configuration.py @@ -494,8 +494,7 @@ def unsatisfied( """Validates the instance requirement based upon its `instance_type`.""" config_path = path_join(config_path, self.name) - - value = self.config_value(context, config_path, None) + value = self.config_value(context, config_path, self.default) if not isinstance(value, self.instance_type): vollog.log( constants.LOGLEVEL_V, @@ -536,7 +535,7 @@ def unsatisfied( """Checks to see if a class can be recovered.""" config_path = path_join(config_path, self.name) - value = self.config_value(context, config_path, None) + value = self.config_value(context, config_path, self.default) self._cls = None if value is not None and isinstance(value, str): if "." in value: diff --git a/volatility3/framework/interfaces/context.py b/volatility3/framework/interfaces/context.py index 29cb41379c..8b5e816e8a 100644 --- a/volatility3/framework/interfaces/context.py +++ b/volatility3/framework/interfaces/context.py @@ -87,7 +87,7 @@ def object( offset: int, native_layer_name: str = None, **arguments, - ): + ) -> "interfaces.objects.ObjectInterface": """Object factory, takes a context, symbol, offset and optional layer_name. @@ -191,6 +191,9 @@ def build_configuration(self) -> "interfaces.configuration.HierarchicalDict": self._native_layer_name ].build_configuration() + # Modules are constructable, and therefore require a class configuration variable + config["class"] = self.__class__.__module__ + "." + self.__class__.__name__ + for subconfig in subconfigs: for req in subconfigs[subconfig]: config[interfaces.configuration.path_join(subconfig, req)] = subconfigs[ diff --git a/volatility3/framework/interfaces/layers.py b/volatility3/framework/interfaces/layers.py index 68592f8cbc..e2a68780a7 100644 --- a/volatility3/framework/interfaces/layers.py +++ b/volatility3/framework/interfaces/layers.py @@ -678,16 +678,12 @@ def del_layer(self, name: str) -> None: name: The name of the layer to delete """ for layer in self._layers: - depend_list = [ - superlayer - for superlayer in self._layers - if name in self._layers[layer].dependencies - ] - if depend_list: + if name in self._layers[layer].dependencies: raise exceptions.LayerException( self._layers[layer].name, - f"Layer {self._layers[layer].name} is depended upon: {', '.join(depend_list)}", + f"Layer {self._layers[layer].name} is depended upon by {layer}", ) + # Otherwise, wipe out the layer self._layers[name].destroy() del self._layers[name] diff --git a/volatility3/framework/interfaces/objects.py b/volatility3/framework/interfaces/objects.py index ab568b927a..51d25510d2 100644 --- a/volatility3/framework/interfaces/objects.py +++ b/volatility3/framework/interfaces/objects.py @@ -216,7 +216,7 @@ def has_valid_members(self, member_names: List[str]) -> bool: Args: member_names: List of names to test as to members with those names validity """ - return all([self.has_valid_member(member_name) for member_name in member_names]) + return all(self.has_valid_member(member_name) for member_name in member_names) class VolTemplateProxy(metaclass=abc.ABCMeta): """A container for proxied methods that the ObjectTemplate of this diff --git a/volatility3/framework/interfaces/plugins.py b/volatility3/framework/interfaces/plugins.py index 0de109c5e8..697e4cdc3d 100644 --- a/volatility3/framework/interfaces/plugins.py +++ b/volatility3/framework/interfaces/plugins.py @@ -43,7 +43,7 @@ def preferred_filename(self): return self._preferred_filename @preferred_filename.setter - def preferred_filename(self, filename): + def preferred_filename(self, filename: str): """Sets the preferred filename""" if self.closed: raise IOError("FileHandler name cannot be changed once closed") @@ -57,6 +57,18 @@ def preferred_filename(self, filename): def close(self): """Method that commits the file and fixes the final filename for use""" + @staticmethod + def sanitize_filename(filename: str) -> str: + """Sanititizes the filename to ensure only a specific whitelist of characters is allowed through""" + allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.- ()[]{}!$%^:#~?<>,|" + result = "" + for char in filename: + if char in allowed: + result += char + else: + result += "?" + return result + def __enter__(self): return self diff --git a/volatility3/framework/layers/avml.py b/volatility3/framework/layers/avml.py index c825464ccc..2e55721923 100644 --- a/volatility3/framework/layers/avml.py +++ b/volatility3/framework/layers/avml.py @@ -224,7 +224,7 @@ def stack( except exceptions.LayerException: return None new_name = context.layers.free_layer_name("AVMLLayer") - context.config[ - interfaces.configuration.path_join(new_name, "base_layer") - ] = layer_name + context.config[interfaces.configuration.path_join(new_name, "base_layer")] = ( + layer_name + ) return AVMLLayer(context, new_name, new_name) diff --git a/volatility3/framework/layers/cloudstorage.py b/volatility3/framework/layers/cloudstorage.py new file mode 100644 index 0000000000..97ed542311 --- /dev/null +++ b/volatility3/framework/layers/cloudstorage.py @@ -0,0 +1,56 @@ +# This file is Copyright 2022 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 urllib.parse +from typing import Optional, Any, List + +try: + import s3fs + + HAS_S3FS = True +except ImportError: + HAS_S3FS = False + +try: + import gcsfs + + HAS_GCSFS = True +except ImportError: + HAS_GCSFS = False + +from volatility3.framework.layers import resources + +vollog = logging.getLogger(__file__) + +if HAS_S3FS: + + class S3FileSystemHandler(resources.VolatilityHandler): + @classmethod + def non_cached_schemes(cls) -> List[str]: + return ["s3"] + + @staticmethod + def default_open(req: urllib.request.Request) -> Optional[Any]: + """Handles the request if it's the s3 scheme.""" + if req.type == "s3": + object_uri = "://".join(req.full_url.split("://")[1:]) + return s3fs.S3FileSystem().open(object_uri) + return None + + +if HAS_GCSFS: + + class GSFileSystemHandler(resources.VolatilityHandler): + @classmethod + def non_cached_schemes(cls) -> List[str]: + return ["gs"] + + @staticmethod + def default_open(req: urllib.request.Request) -> Optional[Any]: + """Handles the request if it's the gs scheme.""" + if req.type == "gs": + object_uri = "://".join(req.full_url.split("://")[1:]) + return gcsfs.GCSFileSystem().open(object_uri) + return None diff --git a/volatility3/framework/layers/crash.py b/volatility3/framework/layers/crash.py index 8efd4f7c7a..3cfc0a25bd 100644 --- a/volatility3/framework/layers/crash.py +++ b/volatility3/framework/layers/crash.py @@ -96,12 +96,13 @@ def get_header(self) -> interfaces.objects.ObjectInterface: def get_summary_header(self) -> interfaces.objects.ObjectInterface: return self.context.object( self._crash_common_table_name + constants.BANG + "_SUMMARY_DUMP", - offset=0x1000 * self.headerpages, + offset=self._page_size * self.headerpages, layer_name=self._base_layer, ) def _load_segments(self) -> None: - """Loads up the segments from the meta_layer.""" + """Loads up the segments from the meta_layer. + A segment is a set of contiguous memory pages.""" segments = [] @@ -119,70 +120,87 @@ def _load_segments(self) -> None: for run in header.PhysicalMemoryBlockBuffer.Run: segments.append( ( - run.BasePage * 0x1000, - offset * 0x1000, - run.PageCount * 0x1000, - run.PageCount * 0x1000, + run.BasePage * self._page_size, + offset * self._page_size, + run.PageCount * self._page_size, + run.PageCount * self._page_size, ) ) offset += run.PageCount elif self.dump_type == 0x05: summary_header = self.get_summary_header() - first_bit = None # First bit in a run - first_offset = 0 # File offset of first bit - last_bit_seen = 0 # Most recent bit processed - offset = summary_header.HeaderSize # Size of file headers - buffer_char = summary_header.get_buffer_char() - buffer_long = summary_header.get_buffer_long() - - for outer_index in range(0, ((summary_header.BitmapSize + 31) // 32)): - if buffer_long[outer_index] == 0: - if first_bit is not None: - last_bit = ((outer_index - 1) * 32) + 31 - segment_length = (last_bit - first_bit + 1) * 0x1000 + seg_first_bit = None # First bit in a run + seg_first_offset = 0 # File offset of first bit + offset = ( + summary_header.HeaderSize + ) # Offset to the start of actual memory dump + ulong_bitmap_array = summary_header.get_buffer_long() + # outer_index points to a 32 bits array inside a list of arrays, + # each bit indicating a page mapping state + for outer_index in range(0, ulong_bitmap_array.vol.count): + ulong_bitmap = ulong_bitmap_array[outer_index] + # All pages in this 32 bits array are mapped (speedup iteration process) + if ulong_bitmap == 0xFFFFFFFF: + # New segment + if seg_first_bit is None: + seg_first_offset = offset + seg_first_bit = outer_index * 32 + offset += 32 * self._page_size + # No pages in this 32 bits array are mapped (speedup iteration process) + elif ulong_bitmap == 0: + # End of segment + if seg_first_bit is not None: + last_bit = (outer_index - 1) * 32 + 31 + segment_length = ( + last_bit - seg_first_bit + 1 + ) * self._page_size segments.append( ( - first_bit * 0x1000, - first_offset, + seg_first_bit * self._page_size, + seg_first_offset, segment_length, segment_length, ) ) - first_bit = None - elif buffer_long[outer_index] == 0xFFFFFFFF: - if first_bit is None: - first_offset = offset - first_bit = outer_index * 32 - offset = offset + (32 * 0x1000) + seg_first_bit = None + # Some pages in this 32 bits array are mapped and some aren't else: - for inner_index in range(0, 32): - bit_addr = outer_index * 32 + inner_index - if (buffer_char[bit_addr >> 3] >> (bit_addr & 0x7)) & 1: - if first_bit is None: - first_offset = offset - first_bit = bit_addr - offset = offset + 0x1000 + for inner_bit_position in range(0, 32): + current_bit = outer_index * 32 + inner_bit_position + page_mapped = ulong_bitmap & (1 << inner_bit_position) + if page_mapped: + # New segment + if seg_first_bit is None: + seg_first_offset = offset + seg_first_bit = current_bit + offset += self._page_size else: - if first_bit is not None: + # End of segment + if seg_first_bit is not None: segment_length = ( - (bit_addr - 1) - first_bit + 1 - ) * 0x1000 + current_bit - 1 - seg_first_bit + 1 + ) * self._page_size segments.append( ( - first_bit * 0x1000, - first_offset, + seg_first_bit * self._page_size, + seg_first_offset, segment_length, segment_length, ) ) - first_bit = None - last_bit_seen = (outer_index * 32) + 31 + seg_first_bit = None + last_bit_seen = outer_index * 32 + 31 - if first_bit is not None: - segment_length = (last_bit_seen - first_bit + 1) * 0x1000 + if seg_first_bit is not None: + segment_length = (last_bit_seen - seg_first_bit + 1) * self._page_size segments.append( - (first_bit * 0x1000, first_offset, segment_length, segment_length) + ( + seg_first_bit * self._page_size, + seg_first_offset, + segment_length, + segment_length, + ) ) else: vollog.log( @@ -261,11 +279,15 @@ def stack( progress_callback: constants.ProgressCallback = None, ) -> Optional[interfaces.layers.DataLayerInterface]: for layer in [WindowsCrashDump32Layer, WindowsCrashDump64Layer]: - with contextlib.suppress(WindowsCrashDumpFormatException): + try: layer.check_header(context.layers[layer_name]) new_name = context.layers.free_layer_name(layer.__name__) context.config[ interfaces.configuration.path_join(new_name, "base_layer") ] = layer_name return layer(context, new_name, new_name) + except WindowsCrashDumpFormatException as excp: + vollog.log( + constants.LOGLEVEL_VVVV, f"Exception reading crashdump: {excp}" + ) return None diff --git a/volatility3/framework/layers/elf.py b/volatility3/framework/layers/elf.py index a10d365926..81f3c36349 100644 --- a/volatility3/framework/layers/elf.py +++ b/volatility3/framework/layers/elf.py @@ -6,9 +6,11 @@ from typing import Optional from volatility3.framework import exceptions, interfaces, constants +from volatility3.framework.constants.linux import ELF_CLASS from volatility3.framework.layers import segmented from volatility3.framework.symbols import intermed + vollog = logging.getLogger(__name__) @@ -21,7 +23,7 @@ class Elf64Layer(segmented.SegmentedLayer): _header_struct = struct.Struct(" None: offset=ehdr.e_phoff + (pindex * ehdr.e_phentsize), ) # We only want PT_TYPES with valid sizes + try: + ptype = phdr.p_type.description + except ValueError: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping unknown ELF program header type: {phdr.p_type}", + ) + continue + if ( - phdr.p_type.lookup() == "PT_LOAD" + ptype == "PT_LOAD" and phdr.p_filesz == phdr.p_memsz and phdr.p_filesz > 0 ): @@ -115,9 +126,9 @@ def stack( vollog.log(constants.LOGLEVEL_VVVV, f"Exception: {excp}") return None new_name = context.layers.free_layer_name("Elf64Layer") - context.config[ - interfaces.configuration.path_join(new_name, "base_layer") - ] = layer_name + context.config[interfaces.configuration.path_join(new_name, "base_layer")] = ( + layer_name + ) try: return Elf64Layer(context, new_name, new_name) diff --git a/volatility3/framework/layers/intel.py b/volatility3/framework/layers/intel.py index 046203fa68..2b0df53728 100644 --- a/volatility3/framework/layers/intel.py +++ b/volatility3/framework/layers/intel.py @@ -67,6 +67,12 @@ def __init__( math.ceil(math.log2(struct.calcsize(self._entry_format))) ) + @classproperty + @functools.lru_cache() + def page_shift(cls) -> int: + """Page shift for the intel memory layers.""" + return cls._page_size_in_bits + @classproperty @functools.lru_cache() def page_size(cls) -> int: @@ -76,6 +82,12 @@ def page_size(cls) -> int: """ return 1 << cls._page_size_in_bits + @classproperty + @functools.lru_cache() + def page_mask(cls) -> int: + """Page mask for the intel memory layers.""" + return ~(cls.page_size - 1) + @classproperty @functools.lru_cache() def bits_per_register(cls) -> int: @@ -168,7 +180,9 @@ def _translate_entry(self, offset: int) -> Tuple[int, int]: 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, @@ -256,10 +270,8 @@ def is_valid(self, offset: int, length: int = 1) -> bool: try: # TODO: Consider reimplementing this, since calls to mapping can call is_valid return all( - [ - self._context.layers[layer].is_valid(mapped_offset) - for _, _, mapped_offset, _, layer in self.mapping(offset, length) - ] + self._context.layers[layer].is_valid(mapped_offset) + for _, _, mapped_offset, _, layer in self.mapping(offset, length) ) except exceptions.InvalidAddressException: return False @@ -277,9 +289,9 @@ def mapping( This allows translation layers to provide maps of contiguous regions in one layer """ - stashed_offset = ( - stashed_mapped_offset - ) = stashed_size = stashed_mapped_size = stashed_map_layer = None + stashed_offset = stashed_mapped_offset = stashed_size = stashed_mapped_size = ( + stashed_map_layer + ) = None for offset, size, mapped_offset, mapped_size, map_layer in self._mapping( offset, length, ignore_errors ): @@ -331,9 +343,9 @@ def _mapping( except exceptions.InvalidAddressException: if not ignore_errors: raise - return + return None yield offset, length, mapped_offset, length, layer_name - return + return None while length > 0: try: chunk_offset, page_size, layer_name = self._translate(offset) diff --git a/volatility3/framework/layers/lime.py b/volatility3/framework/layers/lime.py index 28d646640b..8b93932ab2 100644 --- a/volatility3/framework/layers/lime.py +++ b/volatility3/framework/layers/lime.py @@ -104,7 +104,7 @@ def stack( except LimeFormatException: return None new_name = context.layers.free_layer_name("LimeLayer") - context.config[ - interfaces.configuration.path_join(new_name, "base_layer") - ] = layer_name + context.config[interfaces.configuration.path_join(new_name, "base_layer")] = ( + layer_name + ) return LimeLayer(context, new_name, new_name) diff --git a/volatility3/framework/layers/msf.py b/volatility3/framework/layers/msf.py index 76c645e92b..8d84a774b8 100644 --- a/volatility3/framework/layers/msf.py +++ b/volatility3/framework/layers/msf.py @@ -47,7 +47,7 @@ def pdb_symbol_table(self) -> str: def read_streams(self): # Shortcut in case they've already been read if self._streams: - return + return None # Recover the root table, by recovering the root table index table... module = self.context.module(self.pdb_symbol_table, self._base_layer, offset=0) diff --git a/volatility3/framework/layers/qemu.py b/volatility3/framework/layers/qemu.py index 501b8655e3..ff483291c9 100644 --- a/volatility3/framework/layers/qemu.py +++ b/volatility3/framework/layers/qemu.py @@ -486,9 +486,9 @@ def stack( except exceptions.LayerException: return None new_name = context.layers.free_layer_name("QemuSuspendLayer") - context.config[ - interfaces.configuration.path_join(new_name, "base_layer") - ] = layer_name + context.config[interfaces.configuration.path_join(new_name, "base_layer")] = ( + layer_name + ) layer = QemuSuspendLayer(context, new_name, new_name) cls.stacker_slow_warning() return layer diff --git a/volatility3/framework/layers/registry.py b/volatility3/framework/layers/registry.py index cc8ce1f4ca..471d19f68f 100644 --- a/volatility3/framework/layers/registry.py +++ b/volatility3/framework/layers/registry.py @@ -171,7 +171,15 @@ def get_key( node (default) or a list of nodes from root to the current node (if return_list is true). """ - node_key = [self.get_node(self.root_cell_offset)] + root_node = self.get_node(self.root_cell_offset) + if not root_node.vol.type_name.endswith(constants.BANG + "_CM_KEY_NODE"): + raise RegistryFormatException( + self.name, + "Encountered {} instead of _CM_KEY_NODE".format( + root_node.vol.type_name + ), + ) + node_key = [root_node] if key.endswith("\\"): key = key[:-1] key_array = key.split("\\") @@ -310,10 +318,8 @@ def is_valid(self, offset: int, length: int = 1) -> bool: with contextlib.suppress(exceptions.InvalidAddressException): # Pass this to the lower layers for now return all( - [ - self.context.layers[layer].is_valid(offset, length) - for (_, _, offset, length, layer) in self.mapping(offset, length) - ] + self.context.layers[layer].is_valid(offset, length) + for (_, _, offset, length, layer) in self.mapping(offset, length) ) return False diff --git a/volatility3/framework/layers/resources.py b/volatility3/framework/layers/resources.py index a64fa7d7a9..2dba7caa8f 100644 --- a/volatility3/framework/layers/resources.py +++ b/volatility3/framework/layers/resources.py @@ -151,6 +151,12 @@ def open(self, url: str, mode: str = "rb") -> Any: raise excp else: raise excp + except ValueError as excp: + # Reraise errors such as proxy auth errors as offline exception errors + # Example Proxy auth error - ValueError: AbstractDigestAuthHandler does not support the following scheme: 'Negotiate' + vollog.info(f"Cannot access {url} due to {excp} - Setting OFFLINE") + constants.OFFLINE = True + raise exceptions.OfflineException(url) except exceptions.OfflineException: vollog.info(f"Not accessing {url} in offline mode") raise diff --git a/volatility3/framework/layers/segmented.py b/volatility3/framework/layers/segmented.py index beb667436e..9825ae15cd 100644 --- a/volatility3/framework/layers/segmented.py +++ b/volatility3/framework/layers/segmented.py @@ -51,10 +51,8 @@ def is_valid(self, offset: int, length: int = 1) -> bool: try: base_layer = self._context.layers[self._base_layer] return all( - [ - base_layer.is_valid(mapped_offset) - for _i, _i, mapped_offset, _i, _s in self.mapping(offset, length) - ] + base_layer.is_valid(mapped_offset) + for _i, _i, mapped_offset, _i, _s in self.mapping(offset, length) ) except exceptions.InvalidAddressException: return False @@ -126,9 +124,9 @@ def mapping( current_offset = logical_offset # If it starts too late then we're done if logical_offset > offset + length: - return + return None except exceptions.InvalidAddressException: - return + return None # Crop it to the amount we need left chunk_size = min(size, length + offset - logical_offset) yield logical_offset, chunk_size, mapped_offset, mapped_size, self._base_layer @@ -152,7 +150,7 @@ def maximum_address(self) -> int: raise ValueError("SegmentedLayer must contain some segments") if self._maxaddr is None: mapped, _, length, _ = self._segments[-1] - self._maxaddr = mapped + length + self._maxaddr = mapped + length - 1 return self._maxaddr @property diff --git a/volatility3/framework/layers/vmware.py b/volatility3/framework/layers/vmware.py index 0bc1a350b4..622ff02509 100644 --- a/volatility3/framework/layers/vmware.py +++ b/volatility3/framework/layers/vmware.py @@ -4,6 +4,7 @@ import contextlib import logging import struct +import os from typing import Any, Dict, List, Optional from volatility3.framework import constants, exceptions, interfaces @@ -232,6 +233,11 @@ def stack( ) if not vmss_success and not vmsn_success: + vmem_file_basename = os.path.basename(location) + example_vmss_file_basename = os.path.basename(vmss) + vollog.warning( + f"No metadata file found alongside VMEM file. A VMSS or VMSN file may be required to correctly process a VMEM file. These should be placed in the same directory with the same file name, e.g. {vmem_file_basename} and {example_vmss_file_basename}.", + ) return None new_layer_name = context.layers.free_layer_name("VmwareLayer") context.config[ diff --git a/volatility3/framework/layers/xen.py b/volatility3/framework/layers/xen.py index f7881a0911..e7aa0ccecd 100644 --- a/volatility3/framework/layers/xen.py +++ b/volatility3/framework/layers/xen.py @@ -5,6 +5,7 @@ from volatility3.framework import constants, interfaces, exceptions from volatility3.framework.layers import elf from volatility3.framework.symbols import intermed +from volatility3.framework.constants.linux import ELF_CLASS vollog = logging.getLogger(__name__) @@ -14,7 +15,7 @@ class XenCoreDumpLayer(elf.Elf64Layer): _header_struct = struct.Struct(" None: ) ) elif p2m_data and pfn_data: - raise elf.ElfFormatException( - self.name, f"Both P2M and PFN in Xen Core Dump" - ) + raise elf.ElfFormatException(self.name, "Both P2M and PFN in Xen Core Dump") else: raise elf.ElfFormatException( - self.name, f"Neither P2M nor PFN in Xen Core Dump" + self.name, "Neither P2M nor PFN in Xen Core Dump" ) if len(segments) == 0: @@ -173,8 +172,8 @@ def stack( vollog.log(constants.LOGLEVEL_VVVV, f"Exception: {excp}") return None new_name = context.layers.free_layer_name("XenCoreDumpLayer") - context.config[ - interfaces.configuration.path_join(new_name, "base_layer") - ] = layer_name + context.config[interfaces.configuration.path_join(new_name, "base_layer")] = ( + layer_name + ) return XenCoreDumpLayer(context, new_name, new_name) diff --git a/volatility3/framework/objects/__init__.py b/volatility3/framework/objects/__init__.py index 3b1745718c..b65277067a 100644 --- a/volatility3/framework/objects/__init__.py +++ b/volatility3/framework/objects/__init__.py @@ -593,17 +593,19 @@ def _generate_inverse_choices(cls, choices: Dict[str, int]) -> Dict[int, str]: inverse_choices: Dict[int, str] = {} for k, v in choices.items(): if v in inverse_choices: - # Technically this shouldn't be a problem, but since we inverse cache - # and can't map one value to two possibilities we throw an exception during build - # We can remove/work around this if it proves a common issue - raise ValueError( - f"Enumeration value {v} duplicated as {k} and {inverse_choices[v]}" + vollog.log( + constants.LOGLEVEL_VVV, + f"Enumeration value {v} duplicated as {k}. Keeping name {inverse_choices[v]}", ) + continue inverse_choices[v] = k return inverse_choices def lookup(self, value: int = None) -> str: - """Looks up an individual value and returns the associated name.""" + """Looks up an individual value and returns the associated name. + + If multiple identifiers map to the same value, the first matching identifier will be returned + """ if value is None: return self.lookup(self) if value in self._inverse_choices: @@ -640,7 +642,10 @@ class VolTemplateProxy(interfaces.objects.ObjectInterface.VolTemplateProxy): @classmethod def lookup(cls, template: interfaces.objects.Template, value: int) -> str: - """Looks up an individual value and returns the associated name.""" + """Looks up an individual value and returns the associated name. + + If multiple identifiers map to the same value, the first matching identifier will be returned + """ _inverse_choices = Enumeration._generate_inverse_choices( template.vol["choices"] ) @@ -763,12 +768,10 @@ def child_template( raise IndexError(f"Member not present in array template: {child}") @overload - def __getitem__(self, i: int) -> interfaces.objects.Template: - ... + def __getitem__(self, i: int) -> interfaces.objects.Template: ... @overload - def __getitem__(self, s: slice) -> List[interfaces.objects.Template]: - ... + def __getitem__(self, s: slice) -> List[interfaces.objects.Template]: ... def __getitem__(self, i): """Returns the i-th item from the array.""" @@ -925,10 +928,8 @@ def _check_members( members, collections.abc.Mapping ), f"{agg_name} members parameter must be a mapping: {type(members)}" assert all( - [ - (isinstance(member, tuple) and len(member) == 2) - for member in members.values() - ] + (isinstance(member, tuple) and len(member) == 2) + for member in members.values() ), f"{agg_name} members must be a tuple of relative_offsets and templates" def member(self, attr: str = "member") -> object: diff --git a/volatility3/framework/objects/utility.py b/volatility3/framework/objects/utility.py index 177074cd7a..8aa527cdbe 100644 --- a/volatility3/framework/objects/utility.py +++ b/volatility3/framework/objects/utility.py @@ -7,6 +7,27 @@ 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: @@ -44,8 +65,9 @@ def array_of_pointers( raise TypeError( "Subtype must be a valid template (or string name of an object template)" ) + # We have to clone the pointer class, or we'll be defining the pointer subtype for all future pointers subtype_pointer = context.symbol_space.get_type( symbol_table + constants.BANG + "pointer" - ) + ).clone() subtype_pointer.update_vol(subtype=subtype) return array.cast("array", count=count, subtype=subtype_pointer) diff --git a/volatility3/framework/plugins/layerwriter.py b/volatility3/framework/plugins/layerwriter.py index 1bee5f20d3..24149a390b 100644 --- a/volatility3/framework/plugins/layerwriter.py +++ b/volatility3/framework/plugins/layerwriter.py @@ -18,7 +18,7 @@ class LayerWriter(plugins.PluginInterface): default_block_size = 0x500000 _required_framework_version = (2, 0, 0) - _version = (2, 0, 0) + _version = (2, 0, 1) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -77,7 +77,7 @@ def write_layer( file_handle = open_method(preferred_name) for i in range(0, layer.maximum_address, chunk_size): - current_chunk_size = min(chunk_size, layer.maximum_address - i) + current_chunk_size = min(chunk_size, layer.maximum_address + 1 - i) data = layer.read(i, current_chunk_size, pad=True) file_handle.write(data) if progress_callback: @@ -95,7 +95,7 @@ def _generator(self): if not self.config["layers"]: self.config["layers"] = [] for name in self.context.layers: - if not self.context.layers[name].metadata.get("mapped", False): + if "mapped" not in self.context.layers[name].metadata: self.config["layers"] = [name] for name in self.config["layers"]: @@ -103,7 +103,8 @@ def _generator(self): if name not in self.context.layers: yield 0, (f"Layer Name {name} does not exist",) else: - output_name = self.config.get("output", ".".join([name, "raw"])) + default_output_name = f"{name}.raw" + output_name = self.config.get("output", default_output_name) try: file_handle = self.write_layer( self.context, @@ -114,10 +115,12 @@ def _generator(self): progress_callback=self._progress_callback, ) file_handle.close() + + # Update the filename, which may have changed if a file + # with the same name already existed. + output_name = file_handle.preferred_filename except IOError as excp: - yield 0, ( - f"Layer cannot be written to {self.config['output_name']}: {excp}", - ) + yield 0, (f"Layer cannot be written to {output_name}: {excp}",) yield 0, (f"Layer has been written to {output_name}",) diff --git a/volatility3/framework/plugins/linux/bash.py b/volatility3/framework/plugins/linux/bash.py index b7dc2c16bf..ce4567ca60 100644 --- a/volatility3/framework/plugins/linux/bash.py +++ b/volatility3/framework/plugins/linux/bash.py @@ -75,11 +75,16 @@ def _generator(self, tasks): bang_addrs = [] + # get task memory sections to be used by scanners + task_memory_sections = [ + section for section in task.get_process_memory_sections(heap_only=True) + ] + # find '#' values on the heap for address in proc_layer.scan( self.context, scanners.BytesScanner(b"#"), - sections=task.get_process_memory_sections(heap_only=True), + sections=task_memory_sections, ): bang_addrs.append(struct.pack(pack_format, address)) @@ -89,7 +94,7 @@ def _generator(self, tasks): for address, _ in proc_layer.scan( self.context, scanners.MultiStringScanner(bang_addrs), - sections=task.get_process_memory_sections(heap_only=True), + sections=task_memory_sections, ): hist = self.context.object( bash_table_name + constants.BANG + "hist_entry", diff --git a/volatility3/framework/plugins/linux/capabilities.py b/volatility3/framework/plugins/linux/capabilities.py index 518f526039..bfdb69aba2 100644 --- a/volatility3/framework/plugins/linux/capabilities.py +++ b/volatility3/framework/plugins/linux/capabilities.py @@ -88,7 +88,7 @@ def _check_capabilities_support( kernel_cap_last_cap = vmlinux.object_from_symbol(symbol_name="cap_last_cap") except exceptions.SymbolError: # It should be a kernel < 3.2 - return + return None vol2_last_cap = extensions.kernel_cap_struct.get_last_cap_value() if kernel_cap_last_cap > vol2_last_cap: diff --git a/volatility3/framework/plugins/linux/check_afinfo.py b/volatility3/framework/plugins/linux/check_afinfo.py index 90e714eaab..201a443f7d 100644 --- a/volatility3/framework/plugins/linux/check_afinfo.py +++ b/volatility3/framework/plugins/linux/check_afinfo.py @@ -51,10 +51,20 @@ def _check_members(self, var_ops, var_name, members): yield check, addr def _check_afinfo(self, var_name, var, op_members, seq_members): - for hooked_member, hook_address in self._check_members( - var.seq_fops, var_name, op_members - ): - yield var_name, hooked_member, hook_address + # check if object has a least one of the members used for analysis by this function + required_members = ["seq_fops", "seq_ops", "seq_show"] + has_required_member = any(var.has_member(member) for member in required_members) + if not has_required_member: + vollog.debug( + f"{var_name} object at {hex(var.vol.offset)} had none of the required members: {', '.join([member for member in required_members])}" + ) + raise exceptions.PluginRequirementException + + if var.has_member("seq_fops"): + for hooked_member, hook_address in self._check_members( + var.seq_fops, var_name, op_members + ): + yield var_name, hooked_member, hook_address # newer kernels if var.has_member("seq_ops"): @@ -64,8 +74,10 @@ def _check_afinfo(self, var_name, var, op_members, seq_members): yield var_name, hooked_member, hook_address # this is the most commonly hooked member by rootkits, so a force a check on it - elif not self._is_known_address(var.seq_show): - yield var_name, "show", var.seq_show + else: + if var.has_member("seq_show"): + if not self._is_known_address(var.seq_show): + yield var_name, "show", var.seq_show def _generator(self): vmlinux = self.context.modules[self.config["kernel"]] @@ -85,6 +97,12 @@ def _generator(self): ) protocols = [tcp, udp] + # used to track the calls to _check_afinfo and the + # number of errors produced due to missing members + symbols_checked = set() + symbols_with_errors = set() + + # loop through all symbols for struct_type, global_vars in protocols: for global_var_name in global_vars: # this will lookup fail for the IPv6 protocols on kernels without IPv6 support @@ -97,10 +115,20 @@ def _generator(self): object_type=struct_type, offset=global_var.address ) - for name, member, address in self._check_afinfo( - global_var_name, global_var, op_members, seq_members - ): - yield 0, (name, member, format_hints.Hex(address)) + symbols_checked.add(global_var_name) + try: + for name, member, address in self._check_afinfo( + global_var_name, global_var, op_members, seq_members + ): + yield 0, (name, member, format_hints.Hex(address)) + except exceptions.PluginRequirementException: + symbols_with_errors.add(global_var_name) + + # if every call to _check_afinfo failed show a warning + if symbols_checked == symbols_with_errors: + vollog.warning( + "This plugin was not able to check for hooks. This means you are either analyzing an unsupported kernel version or that your symbol table is corrupt." + ) def run(self): return renderers.TreeGrid( diff --git a/volatility3/framework/plugins/linux/check_creds.py b/volatility3/framework/plugins/linux/check_creds.py index ab6ee4935c..b7f73c3eb0 100644 --- a/volatility3/framework/plugins/linux/check_creds.py +++ b/volatility3/framework/plugins/linux/check_creds.py @@ -2,20 +2,19 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # -import logging - from volatility3.framework import interfaces, renderers +from volatility3.framework.renderers import format_hints from volatility3.framework.configuration import requirements from volatility3.plugins.linux import pslist -vollog = logging.getLogger(__name__) - class Check_creds(interfaces.plugins.PluginInterface): """Checks if any processes are sharing credential structures""" _required_framework_version = (2, 0, 0) + _version = (2, 0, 0) + @classmethod def get_requirements(cls): return [ @@ -46,20 +45,28 @@ def _generator(self): tasks = pslist.PsList.list_tasks(self.context, vmlinux.name) for task in tasks: - cred_addr = task.cred.dereference().vol.offset + task_cred_ptr = task.cred + if not (task_cred_ptr and task_cred_ptr.is_readable()): + continue - if cred_addr not in creds: - creds[cred_addr] = [] + cred_addr = task_cred_ptr.dereference().vol.offset + creds.setdefault(cred_addr, []) creds[cred_addr].append(task.pid) - for _, pids in creds.items(): + for cred_addr, pids in creds.items(): if len(pids) > 1: - pid_str = "" - for pid in pids: - pid_str = pid_str + f"{pid:d}, " - pid_str = pid_str[:-2] - yield (0, [str(pid_str)]) + pid_str = ", ".join([str(pid) for pid in pids]) + + fields = [ + format_hints.Hex(cred_addr), + pid_str, + ] + yield (0, fields) def run(self): - return renderers.TreeGrid([("PIDs", str)], self._generator()) + headers = [ + ("CredVAddr", format_hints.Hex), + ("PIDs", str), + ] + return renderers.TreeGrid(headers, self._generator()) diff --git a/volatility3/framework/plugins/linux/check_syscall.py b/volatility3/framework/plugins/linux/check_syscall.py index b1d2919f9c..b6634d612d 100644 --- a/volatility3/framework/plugins/linux/check_syscall.py +++ b/volatility3/framework/plugins/linux/check_syscall.py @@ -145,7 +145,7 @@ def _generator(self): table_info = self._get_table_info(vmlinux, "sys_call_table", ptr_sz) except exceptions.SymbolError: vollog.error("Unable to find the system call table. Exiting.") - return + return None tables = [(table_name, table_info)] diff --git a/volatility3/framework/plugins/linux/ebpf.py b/volatility3/framework/plugins/linux/ebpf.py new file mode 100644 index 0000000000..2267dd9228 --- /dev/null +++ b/volatility3/framework/plugins/linux/ebpf.py @@ -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()) diff --git a/volatility3/framework/plugins/linux/elfs.py b/volatility3/framework/plugins/linux/elfs.py index e688ecb424..22e39d1276 100644 --- a/volatility3/framework/plugins/linux/elfs.py +++ b/volatility3/framework/plugins/linux/elfs.py @@ -14,8 +14,10 @@ from volatility3.framework.renderers import format_hints from volatility3.framework.symbols import intermed from volatility3.framework.symbols.linux.extensions import elf +from volatility3.framework.constants.linux import ELF_MAX_EXTRACTION_SIZE from volatility3.plugins.linux import pslist + vollog = logging.getLogger(__name__) @@ -23,7 +25,7 @@ class Elfs(plugins.PluginInterface): """Lists all memory mapped ELF files for all processes.""" _required_framework_version = (2, 0, 0) - _version = (2, 0, 0) + _version = (2, 0, 1) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -82,12 +84,20 @@ def elf_dump( ) if not elf_object.is_valid(): + vollog.debug("ELF object to be dumped is not valid") return None sections = {} # TODO: Apply more effort to reconstruct ELF, e.g.: https://github.com/enbarberis/core2ELF64 ? for phdr in elf_object.get_program_headers(): - if phdr.p_type != 1: # PT_LOAD = 1 + try: + if phdr.p_type.description != "PT_LOAD": + continue + except ValueError: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping unknown ELF program header type: {phdr.p_type}", + ) continue start = phdr.p_vaddr @@ -95,18 +105,18 @@ def elf_dump( end = start + size # Use complete memory pages for dumping - # If start isn't a multiple of 4096, stick to the highest multiple < start - # If end isn't a multiple of 4096, stick to the lowest multiple > end - if start % 4096: - start = start & ~0xFFF + # If start isn't a multiple of a page, stick to the highest multiple < start + # If end isn't a multiple of a page, stick to the lowest multiple > end + if start % proc_layer.page_size: + start = start & proc_layer.page_mask - if end % 4096: - end = (end & ~0xFFF) + 4096 + if end % proc_layer.page_size: + end = (end & proc_layer.page_mask) + proc_layer.page_size real_size = end - start # Check if ELF has a legitimate size - if real_size < 0 or real_size > constants.linux.ELF_MAX_EXTRACTION_SIZE: + if real_size < 0 or real_size > ELF_MAX_EXTRACTION_SIZE: raise ValueError(f"The claimed size of the ELF is invalid: {real_size}") sections[start] = real_size @@ -140,12 +150,7 @@ def _generator(self, tasks): for vma in task.mm.get_vma_iter(): hdr = proc_layer.read(vma.vm_start, 4, pad=True) - if not ( - hdr[0] == 0x7F - and hdr[1] == 0x45 - and hdr[2] == 0x4C - and hdr[3] == 0x46 - ): + if hdr != b"\x7fELF": continue path = vma.get_name(self.context, task) diff --git a/volatility3/framework/plugins/linux/envars.py b/volatility3/framework/plugins/linux/envars.py index 7589433129..5cbf0f5020 100644 --- a/volatility3/framework/plugins/linux/envars.py +++ b/volatility3/framework/plugins/linux/envars.py @@ -1,12 +1,121 @@ -from volatility3.plugins.linux import envvars +# This file is Copyright 2022 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + import logging +from volatility3.framework import exceptions, renderers +from volatility3.framework.configuration import requirements +from volatility3.framework.interfaces import plugins +from volatility3.framework.objects import utility +from volatility3.plugins.linux import pslist + vollog = logging.getLogger(__name__) -class Envars(envvars.Envvars): - def run(self, *args, **kwargs): - vollog.warning( - "The linux.envars plugin has been renamed to linux.envvars and will only be accessible through the new name in a future release" +class Envars(plugins.PluginInterface): + """Lists processes with their environment variables""" + + _required_framework_version = (2, 0, 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="Linux kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.PluginRequirement( + name="pslist", plugin=pslist.PsList, version=(2, 0, 0) + ), + requirements.ListRequirement( + name="pid", + description="Filter on specific process IDs", + element_type=int, + optional=True, + ), + ] + + def _generator(self, tasks): + """Generates a listing of processes along with environment variables""" + + # walk the process list and return the envars + for task in tasks: + pid = task.pid + + # get process name as string + name = utility.array_to_string(task.comm) + + # try and get task parent + try: + ppid = task.parent.pid + except exceptions.InvalidAddressException: + vollog.debug( + f"Unable to read parent pid for task {pid} {name}, setting ppid to 0." + ) + ppid = 0 + + # kernel threads never have an mm as they do not have userland mappings + try: + mm = task.mm + except exceptions.InvalidAddressException: + # no mm so cannot get envars + vollog.debug( + f"Unable to access mm for task {pid} {name} it is likely a kernel thread, will not extract any envars." + ) + mm = None + continue + + # if mm exists attempt to get envars + if mm: + # get process layer to read envars from + proc_layer_name = task.add_process_layer() + if proc_layer_name is None: + vollog.debug( + f"Unable to construct process layer for task {pid} {name}, will not extract any envars." + ) + continue + proc_layer = self.context.layers[proc_layer_name] + + # get the size of the envars with sanity checking + envars_size = task.mm.env_end - task.mm.env_start + if not (0 < envars_size <= 8192): + vollog.debug( + f"Task {pid} {name} appears to have envars of size {envars_size} bytes which fails the sanity checking, will not extract any envars." + ) + continue + + # attempt to read all envars data + try: + envar_data = proc_layer.read(task.mm.env_start, envars_size) + except exceptions.InvalidAddressException: + vollog.debug( + f"Unable to read full envars for {pid} {name} starting at virtual offset {hex(task.mm.env_start)} for {envars_size} bytes, will not extract any envars." + ) + continue + + # parse envar data, envars are null terminated, keys and values are separated by '=' + envar_data = envar_data.rstrip(b"\x00") + for envar_pair in envar_data.split(b"\x00"): + try: + key, value = envar_pair.decode().split("=", 1) + except ValueError: + vollog.debug( + f"Unable to extract envars for {pid} {name} starting at virtual offset {hex(task.mm.env_start)}, they don't appear to be '=' separated" + ) + continue + yield (0, (pid, ppid, name, key, value)) + + def run(self): + filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) + + return renderers.TreeGrid( + [("PID", int), ("PPID", int), ("COMM", str), ("KEY", str), ("VALUE", str)], + self._generator( + pslist.PsList.list_tasks( + self.context, self.config["kernel"], filter_func=filter_func + ) + ), ) - return super().run(*args, **kwargs) diff --git a/volatility3/framework/plugins/linux/envvars.py b/volatility3/framework/plugins/linux/envvars.py deleted file mode 100644 index 1d6c8b7846..0000000000 --- a/volatility3/framework/plugins/linux/envvars.py +++ /dev/null @@ -1,121 +0,0 @@ -# This file is Copyright 2022 Volatility Foundation and licensed under the Volatility Software License 1.0 -# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 -# - -import logging - -from volatility3.framework import exceptions, renderers -from volatility3.framework.configuration import requirements -from volatility3.framework.interfaces import plugins -from volatility3.framework.objects import utility -from volatility3.plugins.linux import pslist - -vollog = logging.getLogger(__name__) - - -class Envvars(plugins.PluginInterface): - """Lists processes with their environment variables""" - - _required_framework_version = (2, 0, 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="Linux kernel", - architectures=["Intel32", "Intel64"], - ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(2, 0, 0) - ), - requirements.ListRequirement( - name="pid", - description="Filter on specific process IDs", - element_type=int, - optional=True, - ), - ] - - def _generator(self, tasks): - """Generates a listing of processes along with environment variables""" - - # walk the process list and return the envars - for task in tasks: - pid = task.pid - - # get process name as string - name = utility.array_to_string(task.comm) - - # try and get task parent - try: - ppid = task.parent.pid - except exceptions.InvalidAddressException: - vollog.debug( - f"Unable to read parent pid for task {pid} {name}, setting ppid to 0." - ) - ppid = 0 - - # kernel threads never have an mm as they do not have userland mappings - try: - mm = task.mm - except exceptions.InvalidAddressException: - # no mm so cannot get envars - vollog.debug( - f"Unable to access mm for task {pid} {name} it is likely a kernel thread, will not extract any envars." - ) - mm = None - continue - - # if mm exists attempt to get envars - if mm: - # get process layer to read envars from - proc_layer_name = task.add_process_layer() - if proc_layer_name is None: - vollog.debug( - f"Unable to construct process layer for task {pid} {name}, will not extract any envars." - ) - continue - proc_layer = self.context.layers[proc_layer_name] - - # get the size of the envars with sanity checking - envars_size = task.mm.env_end - task.mm.env_start - if not (0 < envars_size <= 8192): - vollog.debug( - f"Task {pid} {name} appears to have envars of size {envars_size} bytes which fails the sanity checking, will not extract any envars." - ) - continue - - # attempt to read all envars data - try: - envar_data = proc_layer.read(task.mm.env_start, envars_size) - except exceptions.InvalidAddressException: - vollog.debug( - f"Unable to read full envars for {pid} {name} starting at virtual offset {hex(task.mm.env_start)} for {envars_size} bytes, will not extract any envars." - ) - continue - - # parse envar data, envars are null terminated, keys and values are separated by '=' - envar_data = envar_data.rstrip(b"\x00") - for envar_pair in envar_data.split(b"\x00"): - try: - key, value = envar_pair.decode().split("=", 1) - except ValueError: - vollog.debug( - f"Unable to extract envars for {pid} {name} starting at virtual offset {hex(task.mm.env_start)}, they don't appear to be '=' separated" - ) - continue - yield (0, (pid, ppid, name, key, value)) - - def run(self): - filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) - - return renderers.TreeGrid( - [("PID", int), ("PPID", int), ("COMM", str), ("KEY", str), ("VALUE", str)], - self._generator( - pslist.PsList.list_tasks( - self.context, self.config["kernel"], filter_func=filter_func - ) - ), - ) diff --git a/volatility3/framework/plugins/linux/iomem.py b/volatility3/framework/plugins/linux/iomem.py index 785405ef3c..6732084dbc 100644 --- a/volatility3/framework/plugins/linux/iomem.py +++ b/volatility3/framework/plugins/linux/iomem.py @@ -16,7 +16,7 @@ class IOMem(interfaces.plugins.PluginInterface): """Generates an output similar to /proc/iomem on a running system.""" _required_framework_version = (2, 0, 0) - _version = (1, 0, 0) + _version = (1, 0, 1) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -53,7 +53,7 @@ def parse_resource( # create the resource object with protection against memory smear try: - resource = vmlinux.object("resource", resource_offset) + resource = vmlinux.object("resource", resource_offset, absolute=True) except exceptions.InvalidAddressException: vollog.warning( f"Unable to create resource object at {resource_offset:#x}. This resource, " diff --git a/volatility3/framework/plugins/linux/kmsg.py b/volatility3/framework/plugins/linux/kmsg.py index 3f7345bdc5..e26d695438 100644 --- a/volatility3/framework/plugins/linux/kmsg.py +++ b/volatility3/framework/plugins/linux/kmsg.py @@ -1,6 +1,7 @@ # This file is Copyright 2021 Volatility Foundation and licensed under the Volatility Software License 1.0 # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # +import re import logging from abc import ABC, abstractmethod from enum import Enum @@ -9,12 +10,11 @@ from volatility3.framework import ( class_subclasses, constants, - contexts, + exceptions, interfaces, renderers, ) from volatility3.framework.configuration import requirements -from volatility3.framework.interfaces import plugins from volatility3.framework.objects import utility vollog = logging.getLogger(__name__) @@ -64,11 +64,9 @@ def __init__( ): self._context = context self._config = config - vmlinux = context.modules[self._config["kernel"]] - self.layer_name = vmlinux.layer_name # type: ignore - symbol_table_name = vmlinux.symbol_table_name # type: ignore - self.vmlinux = contexts.Module.create(context, symbol_table_name, self.layer_name, 0) # type: ignore - self.long_unsigned_int_size = self.vmlinux.get_type("long unsigned int").size + self.vmlinux = context.modules[self._config["kernel"]] + self.layer_name = self.vmlinux.layer_name # type: ignore + self.long_unsigned_int_size = self.vmlinux.get_type("pointer").size @classmethod def run_all( @@ -84,7 +82,7 @@ def run_all( config: Core configuration Yields: - kmsg records + The kmsg records. Same as run() """ vmlinux = context.modules[config["kernel"]] @@ -104,18 +102,26 @@ def run_all( subclass.__name__, ) kmsg_inst = subclass(context=context, config=config) - # More than one class could be executed for an specific kernel - # version i.e. Netfilter Ingress hooks - # We expect just one implementation to be executed for an specific kernel yield from kmsg_inst.run() + # So far, it only allows a single implementation to be executed for each + # specific kernel. break if kmsg_inst is None: - vollog.error("Unsupported Netfilter kernel implementation") + vollog.error("Unsupported kernel ring buffer implementation") @abstractmethod def run(self) -> Iterator[Tuple[str, str, str, str, str]]: - """Walks through the specific kernel implementation.""" + """Walks through the specific kernel implementation. + + Returns: + tuple: + facility [str]: The log facility: kern, user, etc. See FACILITIES + level [str]: The log level: info, debug, etc. See LEVELS + timestamp [str]: The message timestamp. See nsec_to_sec_str() + caller [str]: The caller ID: CPU(1) or Task(1234). See get_caller() + line [str]: The log message. + """ @classmethod @abstractmethod @@ -125,7 +131,8 @@ def symtab_checks(cls, vmlinux: interfaces.context.ModuleInterface) -> bool: The first class returning True will be instantiated and called via the run() method. - :return: True is the kernel being analysed fulfill the class requirements. + Returns: + bool: True if the kernel being analyzed fulfill the class requirements. """ def get_string(self, addr: int, length: int) -> str: @@ -145,7 +152,7 @@ def nsec_to_sec_str(self, nsec: int) -> str: return "%lu.%06lu" % (nsec / 1000000000, (nsec % 1000000000) / 1000) def get_timestamp_in_sec_str(self, obj) -> str: - # obj could be printk_log or printk_info + # obj could be log, printk_log or printk_info return self.nsec_to_sec_str(obj.ts_nsec) def get_caller(self, obj): @@ -155,7 +162,7 @@ def get_caller(self, obj): if obj.has_member("caller_id"): return self.get_caller_text(obj.caller_id) else: - return "" + return renderers.NotAvailableValue() def get_caller_text(self, caller_id): caller_name = "CPU" if caller_id & 0x80000000 else "Task" @@ -163,7 +170,7 @@ def get_caller_text(self, caller_id): return caller def get_prefix(self, obj) -> Tuple[int, int, str, str]: - # obj could be printk_log or printk_info + # obj could be log, printk_log or printk_info return ( obj.facility, obj.level, @@ -188,39 +195,92 @@ def get_facility_text(cls, facility: int) -> str: return str(facility) -class KmsgLegacy(ABCKmsg): - """Linux kernels prior to v5.10, the ringbuffer is initially kept in - __log_buf, and log_buf is a pointer to the former. __log_buf is declared as - a char array but it actually contains an array of printk_log structs. - The length of this array is defined in the kernel KConfig configuration via - the CONFIG_LOG_BUF_SHIFT value as a power of 2. - This can also be modified by the log_buf_len kernel boot parameter. - In SMP systems with more than 64 CPUs this ringbuffer size is dynamically - allocated according the number of CPUs based on the value of - CONFIG_LOG_CPU_MAX_BUF_SHIFT, and the log_buf pointer is updated - consequently to the new buffer. - In that case, the original static buffer in __log_buf is unused. +class Kmsg_pre_3_5(ABCKmsg): + """The kernel ring buffer (log_buf) is a char array that sequentially stores + log lines, each separated by newline (LF) characters. i.e: + + <6>[ 9565.250411] line1!\\n<6>[ 9565.250412] line2\\n... + """ @classmethod def symtab_checks(cls, vmlinux) -> bool: - return vmlinux.has_type("printk_log") + return ( + vmlinux.has_symbol("log_end") + and not vmlinux.has_symbol("log_first_idx") + and not ( + vmlinux.has_type("log") + and vmlinux.get_type("log").has_member("ts_nsec") + ) + ) + + def run(self) -> Iterator[Tuple[str, str, str, str, str]]: + log_buf_ptr = self.vmlinux.object_from_symbol(symbol_name="log_buf") + log_buf_len = self.vmlinux.object_from_symbol(symbol_name="log_buf_len") + log_buf = utility.pointer_to_string(log_buf_ptr, count=log_buf_len) + log_end = self.vmlinux.object_from_symbol(symbol_name="log_end") + + if log_end > log_buf_len: + start = log_end - log_buf_len + first_half = log_buf[start:] + second_half = log_buf[:start] + log_buf = first_half + second_half + + log_buf_lines = log_buf.splitlines() + + for log_buf_line in log_buf_lines: + m = re.match(r"<(\d+)>\[\s*(\d+\.\d+)\]\s(.*?)$", log_buf_line) + if not m: + # If there was a wrap-around in the ring buffer, it will find + # remnants at the top. As those remnants do not conform to the + # expected line format, they are discarded + continue + + level_facility_str, timestamp_str, line = m.groups() + level_facility = int(level_facility_str) + # The lower 3 bit are the log level, the rest are the log facility + level = level_facility & 7 + facility = level_facility >> 3 + level_txt = self.get_level_text(level) + facility_txt = self.get_facility_text(facility) + caller = renderers.NotAvailableValue() + yield facility_txt, level_txt, timestamp_str, caller, line + + +class Kmsg_3_5_to_3_11(ABCKmsg): + """While 'log_buf' is declared as a pointer and '__log_buf' as a char array, + it essentially holds an array of 'log' structs. + """ + + @classmethod + def symtab_checks(cls, vmlinux) -> bool: + return ( + vmlinux.has_type("log") + and vmlinux.get_type("log").has_member("ts_nsec") + and vmlinux.has_symbol("log_first_idx") + ) - def get_text_from_printk_log(self, msg) -> str: - msg_offset = msg.vol.offset + self.vmlinux.get_type("printk_log").size + def _get_log_struct_name(self): + return "log" + + def get_text_from_log(self, msg) -> str: + log_struct_name = self._get_log_struct_name() + log_struct_size = self.vmlinux.get_type(log_struct_name).size + msg_offset = msg.vol.offset + log_struct_size return self.get_string(msg_offset, msg.text_len) def get_log_lines(self, msg) -> Generator[str, None, None]: if msg.text_len > 0: - text = self.get_text_from_printk_log(msg) + text = self.get_text_from_log(msg) yield from text.splitlines() def get_dict_lines(self, msg) -> Generator[str, None, None]: if msg.dict_len == 0: return None - dict_offset = ( - msg.vol.offset + self.vmlinux.get_type("printk_log").size + msg.text_len - ) + + log_struct_name = self._get_log_struct_name() + log_struct_size = self.vmlinux.get_type(log_struct_name).size + dict_offset = msg.vol.offset + log_struct_size + msg.text_len dict_data = self._context.layers[self.layer_name].read( dict_offset, msg.dict_len ) @@ -228,29 +288,41 @@ def get_dict_lines(self, msg) -> Generator[str, None, None]: yield " " + chunk.decode() def run(self) -> Iterator[Tuple[str, str, str, str, str]]: - log_buf_ptr = self.vmlinux.object_from_symbol(symbol_name="log_buf") - if log_buf_ptr == 0: - # This is weird, let's fallback to check the static ringbuffer. - log_buf_ptr = self.vmlinux.object_from_symbol( - symbol_name="__log_buf" - ).vol.offset - if log_buf_ptr == 0: - raise ValueError("Log buffer is not available") - - log_first_idx = int( - self.vmlinux.object_from_symbol(symbol_name="log_first_idx") - ) + # First, the ring buffer size is determined in the kernel configuration + # by CONFIG_LOG_BUF_SHIFT. This static buffer is held in the '__log_buf' + # global variable, with 'log_buf' serving as a pointer to it. + # The user can also update this size using 'log_buf_len' in the + # kernel boot parameters. Additionally, in SMP systems with over 64 CPUs, + # the ring buffer size dynamically allocates based on the number of CPUs, + # following CONFIG_LOG_CPU_MAX_BUF_SHIFT. + # In the last two cases mentioned above, the 'log_buf' pointer is + # updated to this new buffer. The original static buffer in '__log_buf' + # remains unused. Therefore, it is crucial to read from 'log_buf' rather + # than '__log_buf'. + + log_buf_ptr = self.vmlinux.object_from_symbol("log_buf") + log_buf_len = self.vmlinux.object_from_symbol("log_buf_len") + + log_first_idx = int(self.vmlinux.object_from_symbol("log_first_idx")) + log_next_idx = int(self.vmlinux.object_from_symbol("log_next_idx")) + + log_struct_name = self._get_log_struct_name() + cur_idx = log_first_idx - end_idx = None # We don't need log_next_idx here. See below msg.len == 0 - while cur_idx != end_idx: - end_idx = log_first_idx + if log_first_idx < log_next_idx: + end_idx = log_next_idx + else: + end_idx = log_buf_len + + while cur_idx < end_idx: msg_offset = log_buf_ptr + cur_idx # type: ignore - msg = self.vmlinux.object(object_type="printk_log", offset=msg_offset) + msg = self.vmlinux.object(object_type=log_struct_name, offset=msg_offset) if msg.len == 0: - # As per kernel/printk/printk.c: + # As per kernel/printk.c: # A length == 0 for the next message indicates a wrap-around to # the beginning of the buffer. cur_idx = 0 + end_idx = log_next_idx else: facility, level, timestamp, caller = self.get_prefix(msg) level_txt = self.get_level_text(level) @@ -264,39 +336,53 @@ def run(self) -> Iterator[Tuple[str, str, str, str, str]]: cur_idx += msg.len -class KmsgFiveTen(ABCKmsg): - """In 5.10 the kernel ringbuffer implementation changed. +class Kmsg_3_11_to_5_10(Kmsg_3_5_to_3_11): + """Starting from version 3.11, the struct 'log' was renamed to 'printk_log'. + While 'log_buf' is declared as a pointer and '__log_buf' as a char array, + it essentially holds an array of 'printk_log' structs. + """ + + @classmethod + def symtab_checks(cls, vmlinux) -> bool: + return vmlinux.has_type("printk_log") + + def _get_log_struct_name(self): + return "printk_log" + + +class Kmsg_5_10_to_(ABCKmsg): + """In 5.10 the kernel ring buffer implementation changed. Previously only one process should read /proc/kmsg and it is permanently open and periodically read by the syslog daemon. A high level structure 'printk_ringbuffer' was added to represent the printk - ringbuffer which actually contains two ringbuffers. The descriptor ring + ring buffer which actually contains two ring buffers. The descriptor ring 'desc_ring' contains the records' metadata, text offsets and states. The data block ring 'text_data_ring' contains the records' text strings. A pointer to the high level structure is kept in the prb pointer which is - initialized to a static ringbuffer. + initialized to a static ring buffer. .. code-block:: c static struct printk_ringbuffer *prb = &printk_rb_static; - In SMP systems with more than 64 CPUs this ringbuffer size is dynamically + In SMP systems with more than 64 CPUs this ring buffer size is dynamically allocated according the number of CPUs based on the value of CONFIG_LOG_CPU_MAX_BUF_SHIFT. The prb pointer is updated consequently to - this dynamic ringbuffer in setup_log_buf(). + this dynamic ring buffer in setup_log_buf(). .. code-block:: c prb = &printk_rb_dynamic; - Behind scenes, log_buf is still used as external buffer. - When the static printk_ringbuffer struct is initialized, _DEFINE_PRINTKRB - sets text_data_ring.data pointer to the address in log_buf which points to - the static buffer __log_buff. - If a dynamic ringbuffer takes place, setup_log_buf() sets - text_data_ring.data of printk_rb_dynamic to the new allocated external - buffer via the prb_init function. - In that case, the original external static buffer in __log_buf and - printk_rb_static are unused. + Behind scenes, 'log_buf' is still used as external buffer. + When the static 'printk_ringbuffer' struct is initialized, _DEFINE_PRINTKRB + sets text_data_ring.data pointer to the address in 'log_buf' which points + to the static buffer '__log_buf'. + If a dynamic ring buffer takes place, setup_log_buf() sets + text_data_ring.data of 'printk_rb_dynamic' to the new allocated external + buffer via the 'prb_init' function. + In that case, the original external static buffer in '__log_buf' and + 'printk_rb_static' are unused. .. code-block:: c @@ -354,23 +440,28 @@ def get_dict_lines(self, info) -> Generator[str, None, None]: def run(self) -> Iterator[Tuple[str, str, str, str, str]]: # static struct printk_ringbuffer *prb = &printk_rb_static; - ringbuffers = self.vmlinux.object_from_symbol(symbol_name="prb").dereference() + ringbuffers = self.vmlinux.object_from_symbol("prb").dereference() desc_ring = ringbuffers.desc_ring text_data_ring = ringbuffers.text_data_ring - desc_count = 1 << desc_ring.count_bits - desc_arr = self.vmlinux.object( - object_type="array", + + array_type = self.vmlinux.symbol_table_name + constants.BANG + "array" + + desc_arr = self._context.object( + array_type, offset=desc_ring.descs, subtype=self.vmlinux.get_type("prb_desc"), count=desc_count, + layer_name=self.layer_name, ) - info_arr = self.vmlinux.object( - object_type="array", + + info_arr = self._context.object( + array_type, offset=desc_ring.infos, subtype=self.vmlinux.get_type("printk_info"), count=desc_count, + layer_name=self.layer_name, ) # See kernel/printk/printk_ringbuffer.h @@ -404,12 +495,12 @@ def run(self) -> Iterator[Tuple[str, str, str, str, str]]: cur_id &= desc_id_mask -class Kmsg(plugins.PluginInterface): +class Kmsg(interfaces.plugins.PluginInterface): """Kernel log buffer reader""" - _required_framework_version = (2, 0, 0) + _required_framework_version = (2, 6, 0) - _version = (1, 0, 0) + _version = (1, 0, 2) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -426,6 +517,13 @@ def _generator(self) -> Iterator[Tuple[int, Tuple[str, str, str, str, str]]]: yield (0, values) def run(self): + if not self.context.symbol_space.verify_table_versions( + "dwarf2json", lambda version, _: (not version) or version > (0, 4, 1) + ): + raise exceptions.SymbolSpaceError( + "Invalid symbol table, please ensure the ISF table produced by dwarf2json was produced using a version > 0.4.1" + ) + return renderers.TreeGrid( [ ("facility", str), diff --git a/volatility3/framework/plugins/linux/library_list.py b/volatility3/framework/plugins/linux/library_list.py new file mode 100644 index 0000000000..062ed078eb --- /dev/null +++ b/volatility3/framework/plugins/linux/library_list.py @@ -0,0 +1,169 @@ +# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +from typing import Iterable, Tuple + +from volatility3.framework import interfaces, renderers, constants, exceptions +from volatility3.framework.configuration import requirements +from volatility3.framework.renderers import format_hints +from volatility3.framework.objects import utility +from volatility3.framework.symbols import intermed +from volatility3.framework.symbols.linux.extensions import elf +from volatility3.plugins.linux import pslist + + +vollog = logging.getLogger(__name__) + + +class LibraryList(interfaces.plugins.PluginInterface): + """Enumerate libraries loaded into processes""" + + _required_framework_version = (2, 0, 0) + + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.PluginRequirement( + name="pslist", plugin=pslist.PsList, version=(2, 2, 0) + ), + requirements.ListRequirement( + name="pids", + description="Filter on specific process IDs", + element_type=int, + optional=True, + ), + ] + + def _get_libdl_libraries( + self, proc_layer_name: str, vma_start: int + ) -> interfaces.objects.ObjectInterface: + """Get the ELF link map objects for the given VMA address + + Args: + proc_layer_name (str): Name of the process layer + vma_start (int): VMA start address + + Yields: + ELF link map objects for the given VMA address + """ + elf_table_name = intermed.IntermediateSymbolTable.create( + self.context, + self.config_path, + "linux", + "elf", + class_types=elf.class_types, + ) + elf_object = self.context.object( + elf_table_name + constants.BANG + "Elf", + offset=vma_start, + layer_name=proc_layer_name, + ) + + if not elf_object or not elf_object.is_valid(): + return None + + kernel = self.context.modules[self.config["kernel"]] + + try: + for link_map in elf_object.get_link_maps(kernel.symbol_table_name): + if link_map.l_addr and link_map.l_name: + yield link_map + except exceptions.InvalidAddressException: + # Protection against memory smear in this VMA + pass + + def _get_libdl_maps( + self, task: interfaces.objects.ObjectInterface, proc_layer_name: str + ) -> interfaces.objects.ObjectInterface: + """Get the ELF link maps objects for a task + + Args: + task (task_struct): A reference task + proc_layer_name (str): Name of the process layer + + Yields: + ELF link map objects + """ + + link_map_seen = set() + for vma in task.mm.get_vma_iter(): + for link_map in self._get_libdl_libraries(proc_layer_name, vma.vm_start): + if link_map.l_addr in link_map_seen: + continue + + yield link_map + link_map_seen.add(link_map.l_addr) + + def _get_task_libraries( + self, task: interfaces.objects.ObjectInterface + ) -> Tuple[int, str]: + """Get the task libraries from the ELF headers found within the memory maps + + Args: + task (task_struct): The reference task + + Yields: + Tuples with a ELF link map address and name + """ + proc_layer_name = task.add_process_layer() + if not proc_layer_name: + return + + for elf_link_map in self._get_libdl_maps(task, proc_layer_name): + name = elf_link_map.get_name() + if not name: + continue + yield elf_link_map.l_addr, name + + def _get_tasks_libraries( + self, + tasks: Iterable[interfaces.objects.ObjectInterface], + ) -> Iterable[Tuple[str, int, int, str]]: + """Get the task libraries from the ELF headers found within the memory maps for + all the tasks. + + Args: + tasks: An iterable of tasks + + Yields: + Tuples with a task name, task tgid, an ELF link map address and name + """ + for task in tasks: + task_name = utility.array_to_string(task.comm) + for linkmap_addr, linkmap_name in self._get_task_libraries(task): + yield task_name, task.tgid, linkmap_addr, linkmap_name + + def _format_fields(self, fields): + task_name, task_pid, addr, name = fields + return task_name, task_pid, format_hints.Hex(addr), name + + def _generator( + self, tasks: Iterable[interfaces.objects.ObjectInterface] + ) -> Iterable[Tuple[int, Tuple]]: + for fields in self._get_tasks_libraries(tasks): + yield 0, self._format_fields(fields) + + def run(self): + pids = self.config.get("pids") + pid_filter = pslist.PsList.create_pid_filter(pids) + tasks = pslist.PsList.list_tasks( + self.context, self.config["kernel"], filter_func=pid_filter + ) + + headers = [ + ("Name", str), + ("Pid", int), + ("LoadAddress", format_hints.Hex), + ("Path", str), + ] + + return renderers.TreeGrid(headers, self._generator(tasks)) diff --git a/volatility3/framework/plugins/linux/lsof.py b/volatility3/framework/plugins/linux/lsof.py index d970ad8a95..42b447dfb9 100644 --- a/volatility3/framework/plugins/linux/lsof.py +++ b/volatility3/framework/plugins/linux/lsof.py @@ -1,10 +1,10 @@ -# 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 -from typing import List, Callable +import datetime +import dataclasses +from typing import List, Callable, Tuple, Iterable from volatility3.framework import renderers, interfaces, constants from volatility3.framework.configuration import requirements @@ -12,16 +12,105 @@ 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.""" +@dataclasses.dataclass +class FDUser: + """FD user representation, featuring augmented information and formatted fields. + This is the data the plugin will eventually display. + """ + + task_tgid: int + task_tid: int + task_comm: str + fd_num: int + full_path: str + device: str = dataclasses.field(default=renderers.NotAvailableValue()) + inode_num: int = dataclasses.field(default=renderers.NotAvailableValue()) + inode_type: str = dataclasses.field(default=renderers.NotAvailableValue()) + file_mode: str = dataclasses.field(default=renderers.NotAvailableValue()) + change_time: datetime.datetime = dataclasses.field( + default=renderers.NotAvailableValue() + ) + modification_time: datetime.datetime = dataclasses.field( + default=renderers.NotAvailableValue() + ) + access_time: datetime.datetime = dataclasses.field( + default=renderers.NotAvailableValue() + ) + inode_size: int = dataclasses.field(default=renderers.NotAvailableValue()) + + +@dataclasses.dataclass +class FDInternal: + """FD internal representation containing only the core objects + + Fields: + task: 'task_struct' object + fd_fields: FD fields as obtained from LinuxUtilities.files_descriptors_for_process() + """ + + task: interfaces.objects.ObjectInterface + fd_fields: Tuple[int, int, str] + + def to_user(self) -> FDUser: + """Augment the FD information to be presented to the user + + Returns: + An InodeUser dataclass + """ + # Ensure all types are atomic immutable. Otherwise, astuple() will take a long + # time doing a deepcopy of the Volatility objects. + task_tgid = int(self.task.tgid) + task_tid = int(self.task.pid) + task_comm = utility.array_to_string(self.task.comm) + fd_num, filp, full_path = self.fd_fields + fd_num = int(fd_num) + full_path = str(full_path) + inode = filp.get_inode() + if inode: + superblock_ptr = inode.i_sb + if superblock_ptr and superblock_ptr.is_readable(): + device = f"{superblock_ptr.major}:{superblock_ptr.minor}" + else: + device = renderers.NotAvailableValue() + + fd_user = FDUser( + task_tgid=task_tgid, + task_tid=task_tid, + task_comm=task_comm, + fd_num=fd_num, + full_path=full_path, + device=device, + inode_num=int(inode.i_ino), + inode_type=inode.get_inode_type() or renderers.UnparsableValue(), + file_mode=inode.get_file_mode(), + change_time=inode.get_change_time(), + modification_time=inode.get_modification_time(), + access_time=inode.get_access_time(), + inode_size=int(inode.i_size), + ) + else: + # We use the dataclasses' default values + fd_user = FDUser( + task_tgid=task_tgid, + task_tid=task_tid, + task_comm=task_comm, + fd_num=fd_num, + full_path=full_path, + ) - _required_framework_version = (2, 0, 0) + return fd_user - _version = (1, 1, 0) + +class Lsof(plugins.PluginInterface, timeliner.TimeLinerInterface): + """Lists open files for each processes.""" + + _required_framework_version = (2, 0, 0) + _version = (2, 0, 0) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -49,41 +138,82 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] def list_fds( cls, context: interfaces.context.ContextInterface, - symbol_table: str, + vmlinux_module_name: str, filter_func: Callable[[int], bool] = lambda _: False, - ): - linuxutils_symbol_table = None # type: ignore - for task in pslist.PsList.list_tasks(context, symbol_table, filter_func): + ) -> Iterable[FDInternal]: + """Enumerates open file descriptors in tasks + + 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 + filter_func: A function which takes a process object and returns True if the process + should be ignored/filtered + + Yields: + A FDInternal object + """ + linuxutils_symbol_table = None + for task in pslist.PsList.list_tasks( + context, vmlinux_module_name, filter_func, include_threads=True + ): if linuxutils_symbol_table is None: if constants.BANG not in task.vol.type_name: raise ValueError("Task is not part of a symbol table") linuxutils_symbol_table = task.vol.type_name.split(constants.BANG)[0] - task_comm = utility.array_to_string(task.comm) - pid = int(task.pid) - fd_generator = linux.LinuxUtilities.files_descriptors_for_process( context, linuxutils_symbol_table, task ) for fd_fields in fd_generator: - yield pid, task_comm, task, fd_fields + yield FDInternal(task=task, fd_fields=fd_fields) - def _generator(self, pids, symbol_table): + def _generator(self, pids, vmlinux_module_name): filter_func = pslist.PsList.create_pid_filter(pids) - fds_generator = self.list_fds( - 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) - yield (0, fields) + for fd_internal in self.list_fds( + self.context, vmlinux_module_name, filter_func=filter_func + ): + fd_user = fd_internal.to_user() + yield (0, dataclasses.astuple(fd_user)) def run(self): pids = self.config.get("pid", None) - symbol_table = self.config["kernel"] + vmlinux_module_name = self.config["kernel"] + + tree_grid_args = [ + ("PID", int), + ("TID", int), + ("Process", str), + ("FD", int), + ("Path", str), + ("Device", 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, vmlinux_module_name) + ) + + def generate_timeline(self): + pids = self.config.get("pid", None) + vmlinux_module_name = self.config["kernel"] + + filter_func = pslist.PsList.create_pid_filter(pids) + for fd_internal in self.list_fds( + self.context, vmlinux_module_name, filter_func=filter_func + ): + fd_user = fd_internal.to_user() + + description = ( + f"Process {fd_user.task_comm} ({fd_user.task_tgid}/{fd_user.task_tid}) " + f"Open '{fd_user.full_path}'" + ) - tree_grid_args = [("PID", int), ("Process", str), ("FD", int), ("Path", str)] - return renderers.TreeGrid(tree_grid_args, self._generator(pids, symbol_table)) + yield description, timeliner.TimeLinerType.CHANGED, fd_user.change_time + yield description, timeliner.TimeLinerType.MODIFIED, fd_user.modification_time + yield description, timeliner.TimeLinerType.ACCESSED, fd_user.access_time diff --git a/volatility3/framework/plugins/linux/malfind.py b/volatility3/framework/plugins/linux/malfind.py index 8a21afc03f..18f3dcd56b 100644 --- a/volatility3/framework/plugins/linux/malfind.py +++ b/volatility3/framework/plugins/linux/malfind.py @@ -5,7 +5,7 @@ from typing import List import logging from volatility3.framework import constants, interfaces -from volatility3.framework import renderers +from volatility3.framework import renderers, symbols from volatility3.framework.configuration import requirements from volatility3.framework.objects import utility from volatility3.framework.renderers import format_hints @@ -44,7 +44,7 @@ def _list_injections(self, task): proc_layer_name = task.add_process_layer() if not proc_layer_name: - return + return None proc_layer = self.context.layers[proc_layer_name] @@ -63,15 +63,9 @@ def _list_injections(self, task): def _generator(self, tasks): # determine if we're on a 32 or 64 bit kernel vmlinux = self.context.modules[self.config["kernel"]] - if ( - self.context.symbol_space.get_type( - vmlinux.symbol_table_name + constants.BANG + "pointer" - ).size - == 4 - ): - is_32bit_arch = True - else: - is_32bit_arch = False + is_32bit_arch = not symbols.symbol_table_is_64bit( + self.context, vmlinux.symbol_table_name + ) for task in tasks: process_name = utility.array_to_string(task.comm) diff --git a/volatility3/framework/plugins/linux/mountinfo.py b/volatility3/framework/plugins/linux/mountinfo.py index da743bb601..2499f009e2 100644 --- a/volatility3/framework/plugins/linux/mountinfo.py +++ b/volatility3/framework/plugins/linux/mountinfo.py @@ -37,7 +37,7 @@ class MountInfo(plugins.PluginInterface): _required_framework_version = (2, 2, 0) - _version = (1, 0, 0) + _version = (1, 2, 1) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -143,10 +143,10 @@ def get_mountinfo( 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 @@ def _generator( 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,42 @@ def _generator( "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 and sb_ptr.is_readable()): + continue + + if sb_ptr in seen_sb_ptr: + continue + seen_sb_ptr.add(sb_ptr) + + superblock = sb_ptr.dereference() + + yield superblock, path_root + def run(self): pids = self.config.get("pids") mount_ns_ids = self.config.get("mntns") diff --git a/volatility3/framework/plugins/linux/netfilter.py b/volatility3/framework/plugins/linux/netfilter.py new file mode 100644 index 0000000000..d392a83710 --- /dev/null +++ b/volatility3/framework/plugins/linux/netfilter.py @@ -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()) diff --git a/volatility3/framework/plugins/linux/pagecache.py b/volatility3/framework/plugins/linux/pagecache.py new file mode 100644 index 0000000000..81e1f36015 --- /dev/null +++ b/volatility3/framework/plugins/linux/pagecache.py @@ -0,0 +1,538 @@ +# 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_dt = self.inode.get_modification_time() + change_time_dt = 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_dt, + change_time=change_time_dt, + 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, 1) + + @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_ptr = dentry.d_inode + if not (inode_ptr and inode_ptr.is_readable()): + continue + + inode = inode_ptr.dereference() + if not 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 and root_inode_ptr.is_readable()): + 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 and file_inode_ptr.is_readable()): + 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.CHANGED, 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, 1) + + @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_valid(): + vollog.error("Invalid inode at 0x%x", inode.vol.offset) + return + + if not inode.is_reg: + vollog.error("The inode is not a regular file") + return + + inode_size = inode.i_size + 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()) + ) diff --git a/volatility3/framework/plugins/linux/pidhashtable.py b/volatility3/framework/plugins/linux/pidhashtable.py new file mode 100644 index 0000000000..edafe97e05 --- /dev/null +++ b/volatility3/framework/plugins/linux/pidhashtable.py @@ -0,0 +1,255 @@ +# 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, 1) + + @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: + return bool(task and task.pid > 0 and task.parent.is_readable()) + + 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.next and pid_chain.next.is_readable()): + break + + upid = linux.LinuxUtilities.container_of( + pid_chain.next, "upid", "pid_chain", vmlinux + ) + + def _get_upids(self): + vmlinux = self.context.modules[self.config["kernel"]] + + # 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 ent.is_readable(): + # 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 and pid_tasks_0.is_readable()): + 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 and pid_tasks_0.is_readable()): + 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)) diff --git a/volatility3/framework/plugins/linux/pslist.py b/volatility3/framework/plugins/linux/pslist.py index 328c0de279..72d8678590 100644 --- a/volatility3/framework/plugins/linux/pslist.py +++ b/volatility3/framework/plugins/linux/pslist.py @@ -1,7 +1,7 @@ # This file is Copyright 2021 Volatility Foundation and licensed under the Volatility Software License 1.0 # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # -from typing import Any, Callable, Iterable, List +from typing import Any, Callable, Iterable, List, Tuple from volatility3.framework import interfaces, renderers from volatility3.framework.configuration import requirements @@ -17,7 +17,7 @@ class PsList(interfaces.plugins.PluginInterface): _required_framework_version = (2, 0, 0) - _version = (2, 1, 0) + _version = (2, 2, 1) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -78,6 +78,74 @@ def filter_func(x): else: return lambda _: False + @classmethod + def get_task_fields( + cls, task: interfaces.objects.ObjectInterface, decorate_comm: bool = False + ) -> Tuple[int, int, int, str]: + """Extract the fields needed for the final output + + Args: + task: A task object from where to get the fields. + decorate_comm: If True, it decorates the comm string of user threads in curly brackets, + and of Kernel threads in square brackets. + Defaults to False. + Returns: + A tuple with the fields to show in the plugin output. + """ + pid = task.tgid + tid = task.pid + ppid = task.parent.tgid if task.parent else 0 + name = utility.array_to_string(task.comm) + if decorate_comm: + if task.is_kernel_thread: + name = f"[{name}]" + elif task.is_user_thread: + name = f"{{{name}}}" + + task_fields = (task.vol.offset, pid, tid, ppid, name) + return task_fields + + def _get_file_output(self, task: interfaces.objects.ObjectInterface) -> str: + """Extract the elf for the process if requested + Args: + task: A task object to extract from. + Returns: + A string showing the results of the extraction, either + the filename used or an error. + """ + elf_table_name = intermed.IntermediateSymbolTable.create( + self.context, + self.config_path, + "linux", + "elf", + class_types=elf.class_types, + ) + proc_layer_name = task.add_process_layer() + if not proc_layer_name: + # if we can't build a proc layer we can't + # extract the elf + return renderers.NotApplicableValue() + else: + # Find the vma that belongs to the main ELF of the process + file_output = "Error outputting file" + for v in task.mm.get_vma_iter(): + if v.vm_start == task.mm.start_code: + file_handle = elfs.Elfs.elf_dump( + self.context, + proc_layer_name, + elf_table_name, + v, + task, + self.open, + ) + if file_handle: + file_output = str(file_handle.preferred_filename) + file_handle.close() + break + else: + file_output = "VMA start matching task start_code not found" + return file_output + def _generator( self, pid_filter: Callable[[Any], bool], @@ -104,49 +172,15 @@ def _generator( for task in self.list_tasks( self.context, self.config["kernel"], pid_filter, include_threads ): - elf_table_name = intermed.IntermediateSymbolTable.create( - self.context, - self.config_path, - "linux", - "elf", - class_types=elf.class_types, - ) - file_output = "Disabled" if dump: - proc_layer_name = task.add_process_layer() - if not proc_layer_name: - continue - - # Find the vma that belongs to the main ELF of the process - file_output = "Error outputting file" - - for v in task.mm.get_mmap_iter(): - if v.vm_start == task.mm.start_code: - file_handle = elfs.Elfs.elf_dump( - self.context, - proc_layer_name, - elf_table_name, - v, - task, - self.open, - ) - if file_handle: - file_output = str(file_handle.preferred_filename) - file_handle.close() - break - - pid = task.tgid - tid = task.pid - ppid = task.parent.tgid if task.parent else 0 - name = utility.array_to_string(task.comm) - if decorate_comm: - if task.is_kernel_thread: - name = f"[{name}]" - elif task.is_user_thread: - name = f"{{{name}}}" + file_output = self._get_file_output(task) + else: + file_output = "Disabled" + + offset, pid, tid, ppid, name = self.get_task_fields(task, decorate_comm) yield 0, ( - format_hints.Hex(task.vol.offset), + format_hints.Hex(offset), pid, tid, ppid, diff --git a/volatility3/framework/plugins/linux/psscan.py b/volatility3/framework/plugins/linux/psscan.py index 462577e583..40784a647b 100644 --- a/volatility3/framework/plugins/linux/psscan.py +++ b/volatility3/framework/plugins/linux/psscan.py @@ -28,7 +28,7 @@ class PsScan(interfaces.plugins.PluginInterface): """Scans for processes present in a particular linux image.""" _required_framework_version = (2, 0, 0) - _version = (1, 0, 0) + _version = (1, 0, 1) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -139,7 +139,7 @@ def scan_tasks( kernel_layer_name, f"Layer {kernel_layer_name} has no dependencies" ) memory_layer_name = kernel_layer.dependencies[0] - memory_layer = context.layers[kernel_layer.dependencies[0]] + memory_layer = context.layers[memory_layer_name] # scan the memory_layer for these needles for address, _ in memory_layer.scan( diff --git a/volatility3/framework/plugins/linux/pstree.py b/volatility3/framework/plugins/linux/pstree.py index e07a8aced5..efe5223dfd 100644 --- a/volatility3/framework/plugins/linux/pstree.py +++ b/volatility3/framework/plugins/linux/pstree.py @@ -2,18 +2,49 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # +from volatility3.framework import interfaces, renderers +from volatility3.framework.configuration import requirements +from volatility3.framework.renderers import format_hints from volatility3.plugins.linux import pslist -class PsTree(pslist.PsList): +class PsTree(interfaces.plugins.PluginInterface): """Plugin for listing processes in a tree based on their parent process ID.""" - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._tasks = {} - self._levels = {} - self._children = {} + _required_framework_version = (2, 0, 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="Linux kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.PluginRequirement( + name="pslist", plugin=pslist.PsList, version=(2, 2, 0) + ), + requirements.ListRequirement( + name="pid", + description="Filter on specific process IDs", + element_type=int, + optional=True, + ), + requirements.BooleanRequirement( + name="threads", + description="Include user threads", + optional=True, + default=False, + ), + 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 find_level(self, pid: int) -> None: """Finds how deep the PID is in the tasks hierarchy. @@ -39,15 +70,14 @@ def find_level(self, pid: int) -> None: self._levels[pid] = level def _generator( - self, pid_filter, include_threads: bool = False, decorate_com: bool = False + self, + tasks: list, + decorate_comm: bool = False, ): """Generates the tasks hierarchy tree. Args: - pid_filter: A function which takes a process object and returns True if the process should be ignored/filtered - include_threads: If True, the output will also show the user threads - If False, only the thread group leaders will be shown - Defaults to False. + tasks: A list of task objects to be displayed decorate_comm: If True, it decorates the comm string of - User threads: in curly brackets, - Kernel threads: in square brackets @@ -55,13 +85,12 @@ def _generator( Yields: Each rows """ - vmlinux = self.context.modules[self.config["kernel"]] - for proc in self.list_tasks( - self.context, - vmlinux.name, - filter_func=pid_filter, - include_threads=include_threads, - ): + + self._tasks = {} + self._levels = {} + self._children = {} + + for proc in tasks: self._tasks[proc.pid] = proc # Build the child/level maps @@ -71,7 +100,10 @@ def _generator( def yield_processes(pid): task = self._tasks[pid] - row = self._get_task_fields(task, decorate_com) + row = pslist.PsList.get_task_fields(task, decorate_comm) + # update the first element, the offset, in the row tuple to use format_hints.Hex + # as a simple int is returned from get_task_fields. + row = (format_hints.Hex(row[0]),) + row[1:] tid = task.pid yield (self._levels[tid] - 1, row) @@ -82,3 +114,27 @@ def yield_processes(pid): for pid, level in self._levels.items(): if level == 1: yield from yield_processes(pid) + + def run(self): + filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) + include_threads = self.config.get("threads") + decorate_comm = self.config.get("decorate_comm") + + return renderers.TreeGrid( + [ + ("OFFSET (V)", format_hints.Hex), + ("PID", int), + ("TID", int), + ("PPID", int), + ("COMM", str), + ], + self._generator( + pslist.PsList.list_tasks( + self.context, + self.config["kernel"], + filter_func=filter_func, + include_threads=include_threads, + ), + decorate_comm=decorate_comm, + ), + ) diff --git a/volatility3/framework/plugins/linux/sockstat.py b/volatility3/framework/plugins/linux/sockstat.py index fa67122baa..0ddd3e26d6 100644 --- a/volatility3/framework/plugins/linux/sockstat.py +++ b/volatility3/framework/plugins/linux/sockstat.py @@ -22,9 +22,10 @@ class SockHandlers(interfaces.configuration.VersionableInterface): _required_framework_version = (2, 0, 0) - _version = (1, 0, 0) + _version = (3, 0, 0) - def __init__(self, vmlinux, task): + def __init__(self, vmlinux, task, *args, **kwargs): + super().__init__(*args, **kwargs) self._vmlinux = vmlinux self._task = task @@ -83,7 +84,7 @@ def process_sock( sock: Kernel generic `sock` object Returns a tuple with: - sock: The respective kernel's \*_sock object for that socket family + sock: The respective kernel's \\*_sock object for that socket family sock_stat: A tuple with the source and destination (address and port) along with its state string socket_filter: A dictionary with information about the socket filter """ @@ -147,24 +148,22 @@ def _extract_socket_filter_info( socket_filter["bpf_filter_type"] = "cBPF" if not sock_filter.has_member("prog") or not sock_filter.prog: - return + return None 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 + 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 + return None socket_filter["bpf_filter_type"] = "eBPF" if not bpfprog.has_member("aux") or not bpfprog.aux: @@ -329,17 +328,17 @@ def _xdp_sock( xdp_sock = sock.cast("xdp_sock") device = xdp_sock.dev if not device: - return + return None src_addr = utility.array_to_string(device.name) src_port = dst_addr = dst_port = None bpfprog = device.xdp_prog if not bpfprog: - return + return None if not bpfprog.has_member("aux") or not bpfprog.aux: - return + return None bpfprog_aux = bpfprog.aux if bpfprog_aux.has_member("id"): @@ -440,7 +439,7 @@ class Sockstat(plugins.PluginInterface): _required_framework_version = (2, 0, 0) - _version = (1, 0, 0) + _version = (3, 0, 0) @classmethod def get_requirements(cls): @@ -451,10 +450,10 @@ def get_requirements(cls): architectures=["Intel32", "Intel64"], ), requirements.VersionRequirement( - name="SockHandlers", component=SockHandlers, version=(1, 0, 0) + name="SockHandlers", component=SockHandlers, version=(3, 0, 0) ), requirements.PluginRequirement( - name="lsof", plugin=lsof.Lsof, version=(1, 1, 0) + name="lsof", plugin=lsof.Lsof, version=(2, 0, 0) ), requirements.VersionRequirement( name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0) @@ -501,7 +500,7 @@ def list_sockets( family: Socket family string (AF_UNIX, AF_INET, etc) sock_type: Socket type string (STREAM, DGRAM, etc) protocol: Protocol string (UDP, TCP, etc) - sock_fields: A tuple with the \*_sock object, the sock stats and the extended info dictionary + sock_fields: A tuple with the \\*_sock object, the sock stats and the extended info dictionary """ vmlinux = context.modules[symbol_table] @@ -509,8 +508,9 @@ def list_sockets( dfop_addr = vmlinux.object_from_symbol("sockfs_dentry_operations").vol.offset fd_generator = lsof.Lsof.list_fds(context, vmlinux.name, filter_func) - for _pid, _task_comm, task, fd_fields in fd_generator: - fd_num, filp, _full_path = fd_fields + for fd_internal in fd_generator: + fd_num, filp, _full_path = fd_internal.fd_fields + task = fd_internal.task if filp.f_op not in (sfop_addr, dfop_addr): continue @@ -617,8 +617,12 @@ def _generator(self, pids: List[int], netns_id_arg: int, symbol_table: str): else NotAvailableValue() ) + task_comm = utility.array_to_string(task.comm) + fields = ( netns_id, + task_comm, + task.tgid, task.pid, fd_num, format_hints.Hex(sock.vol.offset), @@ -638,7 +642,9 @@ def run(self): tree_grid_args = [ ("NetNS", int), - ("Pid", int), + ("Process Name", str), + ("PID", int), + ("TID", int), ("FD", int), ("Sock Offset", format_hints.Hex), ("Family", str), diff --git a/volatility3/framework/plugins/linux/vmayarascan.py b/volatility3/framework/plugins/linux/vmayarascan.py new file mode 100644 index 0000000000..9fe06b0c84 --- /dev/null +++ b/volatility3/framework/plugins/linux/vmayarascan.py @@ -0,0 +1,120 @@ +# This file is Copyright 2023 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import Iterable, List, Tuple + +from volatility3.framework import interfaces, renderers +from volatility3.framework.configuration import requirements +from volatility3.framework.renderers import format_hints +from volatility3.plugins import yarascan +from volatility3.plugins.linux import pslist + + +class VmaYaraScan(interfaces.plugins.PluginInterface): + """Scans all virtual memory areas for tasks using yara.""" + + _required_framework_version = (2, 4, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + # create a list of requirements for vmayarascan + vmayarascan_requirements = [ + requirements.ListRequirement( + name="pid", + element_type=int, + description="Process IDs to include (all other processes are excluded)", + optional=True, + ), + requirements.PluginRequirement( + name="pslist", plugin=pslist.PsList, version=(2, 0, 0) + ), + requirements.PluginRequirement( + name="yarascan", plugin=yarascan.YaraScan, version=(2, 0, 0) + ), + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=["Intel32", "Intel64"], + ), + ] + + # get base yarascan requirements for command line options + yarascan_requirements = yarascan.YaraScan.get_yarascan_option_requirements() + + # return the combined requirements + return yarascan_requirements + vmayarascan_requirements + + def _generator(self): + # use yarascan to parse the yara options provided and create the rules + rules = yarascan.YaraScan.process_yara_options(dict(self.config)) + + # filter based on the pid option if provided + filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) + for task in pslist.PsList.list_tasks( + context=self.context, + vmlinux_module_name=self.config["kernel"], + filter_func=filter_func, + ): + # attempt to create a process layer for each task and skip those + # that cannot (e.g. kernel threads) + proc_layer_name = task.add_process_layer() + if not proc_layer_name: + continue + + # get the proc_layer object from the context + proc_layer = self.context.layers[proc_layer_name] + + for start, end in self.get_vma_maps(task): + for match in rules.match( + data=proc_layer.read(start, end - start, True) + ): + 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.tgid, + match.rule, + name, + value, + ) + + @staticmethod + def get_vma_maps( + task: interfaces.objects.ObjectInterface, + ) -> Iterable[Tuple[int, int]]: + """Creates a map of start/end addresses for each virtual memory area in a task. + + Args: + task: The task object of which to read the vmas from + + Returns: + An iterable of tuples containing start and end addresses for each descriptor + """ + if task.mm: + for vma in task.mm.get_vma_iter(): + vm_size = vma.vm_end - vma.vm_start + yield (vma.vm_start, vm_size) + + def run(self): + return renderers.TreeGrid( + [ + ("Offset", format_hints.Hex), + ("PID", int), + ("Rule", str), + ("Component", str), + ("Value", bytes), + ], + self._generator(), + ) diff --git a/volatility3/framework/plugins/mac/check_sysctl.py b/volatility3/framework/plugins/mac/check_sysctl.py index 165aad436b..4f64eaed80 100644 --- a/volatility3/framework/plugins/mac/check_sysctl.py +++ b/volatility3/framework/plugins/mac/check_sysctl.py @@ -69,7 +69,7 @@ def _process_sysctl_list(self, kernel, sysctl_list, recursive=0): try: sysctl = sysctl.oid_link.sle_next.dereference() except exceptions.InvalidAddressException: - return + return None while sysctl: try: diff --git a/volatility3/framework/plugins/mac/dmesg.py b/volatility3/framework/plugins/mac/dmesg.py new file mode 100644 index 0000000000..f9f06a6662 --- /dev/null +++ b/volatility3/framework/plugins/mac/dmesg.py @@ -0,0 +1,79 @@ +# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging +from volatility3.framework import interfaces, renderers, exceptions +from volatility3.framework.configuration import requirements +from volatility3.framework.objects import utility + +vollog = logging.getLogger(__name__) + + +class Dmesg(interfaces.plugins.PluginInterface): + """Prints the kernel log buffer.""" + + _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.ModuleRequirement( + name="kernel", + description="Kernel module for the OS", + architectures=["Intel32", "Intel64"], + ), + ] + + @classmethod + def get_kernel_log_buffer( + cls, context: interfaces.context.ContextInterface, kernel_module_name: str + ): + """ + Online documentation : + - https://github.com/apple-open-source/macos/blob/master/xnu/bsd/sys/msgbuf.h + - https://github.com/apple-open-source/macos/blob/ea4cd5a06831aca49e33df829d2976d6de5316ec/xnu/bsd/kern/subr_log.c#L751 + Volatility 2 plugin : + - https://github.com/volatilityfoundation/volatility/blob/master/volatility/plugins/mac/dmesg.py + """ + + kernel = context.modules[kernel_module_name] + if not kernel.has_symbol("msgbufp"): + raise exceptions.SymbolError( + "msgbufp", + kernel.symbol_table_name, + 'The provided symbol table does not include the "msgbufp" symbol. This means you are either analyzing an unsupported kernel version or that your symbol table is corrupt.', + ) + + msgbufp = kernel.object_from_symbol(symbol_name="msgbufp") + msg_size = msgbufp.msg_size # max buffer size + msg_bufx = msgbufp.msg_bufx # write index of the msg_bufc circular buffer + msg_bufc = msgbufp.msg_bufc + # msg_bufc is circular, meaning that if its size exceeds msg_size, + # msg_bufx will point to the beginning of the buffer and start overwriting. + msg_bufc_data: str = utility.pointer_to_string(msg_bufc, msg_size) + # Avoid OOB reads + msg_bufx = msg_bufx if msg_bufx <= msg_size else 0 + # We directly take into account the case where the write buffer did a loop, + # as older messages will start at msg_bufx offset (not overwritten yet). + dmesg = msg_bufc_data[msg_bufx:] + dmesg += msg_bufc_data[:msg_bufx] + + # Yield each line + for dmesg_line in dmesg.splitlines(): + yield (dmesg_line,) + + def _generator(self): + for value in self.get_kernel_log_buffer( + context=self.context, kernel_module_name=self.config["kernel"] + ): + yield (0, value) + + def run(self): + return renderers.TreeGrid( + [ + ("line", str), + ], + self._generator(), + ) diff --git a/volatility3/framework/plugins/mac/kevents.py b/volatility3/framework/plugins/mac/kevents.py index 3b996bc0af..2a8692b772 100644 --- a/volatility3/framework/plugins/mac/kevents.py +++ b/volatility3/framework/plugins/mac/kevents.py @@ -116,7 +116,7 @@ def _walk_klist_array(cls, kernel, fdp, array_pointer_member, array_size_member) ) except exceptions.InvalidAddressException: - return + return None for klist in klist_array: for kn in mac.MacUtilities.walk_slist(klist, "kn_link"): @@ -140,7 +140,7 @@ def _get_task_kevents(cls, kernel, task): try: p_klist = task.p_klist except exceptions.InvalidAddressException: - return + return None for kn in mac.MacUtilities.walk_slist(p_klist, "kn_link"): yield kn diff --git a/volatility3/framework/plugins/mac/lsmod.py b/volatility3/framework/plugins/mac/lsmod.py index 2979e374be..c6f57f8892 100644 --- a/volatility3/framework/plugins/mac/lsmod.py +++ b/volatility3/framework/plugins/mac/lsmod.py @@ -75,7 +75,7 @@ def list_modules( try: kmod = kmod.next except exceptions.InvalidAddressException: - return + return None return # Generation finished def _generator(self): diff --git a/volatility3/framework/plugins/mac/malfind.py b/volatility3/framework/plugins/mac/malfind.py index 98b282e24b..3094ada852 100644 --- a/volatility3/framework/plugins/mac/malfind.py +++ b/volatility3/framework/plugins/mac/malfind.py @@ -40,7 +40,7 @@ def _list_injections(self, task): proc_layer_name = task.add_process_layer() if proc_layer_name is None: - return + return None proc_layer = self.context.layers[proc_layer_name] diff --git a/volatility3/framework/plugins/mac/proc_maps.py b/volatility3/framework/plugins/mac/proc_maps.py index 781b3ed66c..fe5179dfa7 100644 --- a/volatility3/framework/plugins/mac/proc_maps.py +++ b/volatility3/framework/plugins/mac/proc_maps.py @@ -2,17 +2,23 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # -from volatility3.framework import renderers, interfaces +from volatility3.framework import renderers, interfaces, exceptions from volatility3.framework.configuration import requirements from volatility3.framework.objects import utility from volatility3.framework.renderers import format_hints from volatility3.plugins.mac import pslist +from typing import Callable, Generator, Type, Optional +import logging + +vollog = logging.getLogger(__name__) class Maps(interfaces.plugins.PluginInterface): """Lists process memory ranges that potentially contain injected code.""" _required_framework_version = (2, 0, 0) + _version = (1, 1, 0) + MAXSIZE_DEFAULT = 1024 * 1024 * 1024 # 1 Gb @classmethod def get_requirements(cls): @@ -31,14 +37,152 @@ def get_requirements(cls): element_type=int, optional=True, ), + requirements.BooleanRequirement( + name="dump", + description="Extract listed memory segments", + default=False, + optional=True, + ), + requirements.ListRequirement( + name="address", + description="Process virtual memory addresses to include " + "(all other VMA sections are excluded). This can be any " + "virtual address within the VMA section. Virtual addresses " + "must be separated by a space.", + element_type=int, + optional=True, + ), + requirements.IntRequirement( + name="maxsize", + description="Maximum size for dumped VMA sections " + "(all the bigger sections will be ignored)", + default=cls.MAXSIZE_DEFAULT, + optional=True, + ), ] + @classmethod + def list_vmas( + cls, + task: interfaces.objects.ObjectInterface, + filter_func: Callable[ + [interfaces.objects.ObjectInterface], bool + ] = lambda _: True, + ) -> Generator[interfaces.objects.ObjectInterface, None, None]: + """Lists the Virtual Memory Areas of a specific process. + + Args: + task: task object from which to list the vma + filter_func: Function to take a vma and return False if it should be filtered out + + Returns: + Yields vmas based on the task and filtered based on the filter function + """ + for vma in task.get_map_iter(): + if filter_func(vma): + yield vma + else: + vollog.debug( + f"Excluded vma at offset {vma.vol.offset:#x} for pid {task.p_pid} due to filter_func" + ) + + @classmethod + def vma_dump( + cls, + context: interfaces.context.ContextInterface, + task: interfaces.objects.ObjectInterface, + vm_start: int, + vm_end: int, + open_method: Type[interfaces.plugins.FileHandlerInterface], + maxsize: int = MAXSIZE_DEFAULT, + ) -> Optional[interfaces.plugins.FileHandlerInterface]: + """Extracts the complete data for VMA as a FileInterface. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + task: an task_struct instance + vm_start: The start virtual address from the vma to dump + vm_end: The end virtual address from the vma to dump + open_method: class to provide context manager for opening the file + maxsize: Max size of VMA section (default MAXSIZE_DEFAULT) + + Returns: + An open FileInterface object containing the complete data for the task or None in the case of failure + """ + pid = task.p_pid + + try: + proc_layer_name = task.add_process_layer() + except exceptions.InvalidAddressException as excp: + vollog.debug( + "Process {}: invalid address {} in layer {}".format( + pid, excp.invalid_address, excp.layer_name + ) + ) + return None + vm_size = vm_end - vm_start + + # check if vm_size is negative, this should never happen. + if vm_size < 0: + vollog.warning( + f"Skip virtual memory dump for pid {pid} between {vm_start:#x}-{vm_end:#x} as {vm_size} is negative." + ) + return None + # check if vm_size is larger than the maxsize limit, and therefore is not saved out. + if maxsize <= vm_size: + vollog.warning( + f"Skip virtual memory dump for pid {pid} between {vm_start:#x}-{vm_end:#x} as {vm_size} is larger than maxsize limit of {maxsize}" + ) + return None + proc_layer = context.layers[proc_layer_name] + file_name = f"pid.{pid}.vma.{vm_start:#x}-{vm_end:#x}.dmp" + try: + file_handle = open_method(file_name) + chunk_size = 1024 * 1024 * 10 + offset = vm_start + while offset < vm_start + vm_size: + to_read = min(chunk_size, vm_start + vm_size - offset) + data = proc_layer.read(offset, to_read, pad=True) + file_handle.write(data) + offset += to_read + except Exception as excp: + vollog.debug(f"Unable to dump virtual memory {file_name}: {excp}") + return None + return file_handle + def _generator(self, tasks): + address_list = self.config.get("address", None) + if not address_list: + # do not filter as no address_list was supplied + vma_filter_func = lambda _: True + else: + # filter for any vm_start that matches the supplied address config + def vma_filter_function(task: interfaces.objects.ObjectInterface) -> bool: + addrs_in_vma = [ + addr + for addr in address_list + if task.links.start <= addr <= task.links.end + ] + + # if any of the user supplied addresses would fall within this vma return true + return bool(addrs_in_vma) + + vma_filter_func = vma_filter_function + for task in tasks: process_name = utility.array_to_string(task.p_comm) process_pid = task.p_pid - for vma in task.get_map_iter(): + for vma in self.list_vmas(task, filter_func=vma_filter_func): + try: + vm_start = vma.links.start + vm_end = vma.links.end + except AttributeError: + vollog.debug( + f"Unable to find the vm_start and vm_end for vma at {vma.vol.offset:#x} for pid {process_pid}" + ) + continue + path = vma.get_path( self.context, self.context.modules[self.config["kernel"]].symbol_table_name, @@ -46,15 +190,32 @@ def _generator(self, tasks): if path == "": path = vma.get_special_path() + file_output = "Disabled" + if self.config["dump"]: + file_output = "Error outputting file" + file_handle = self.vma_dump( + self.context, + task, + vm_start, + vm_end, + self.open, + self.config["maxsize"], + ) + + if file_handle: + file_handle.close() + file_output = file_handle.preferred_filename + yield ( 0, ( process_pid, process_name, - format_hints.Hex(vma.links.start), - format_hints.Hex(vma.links.end), + format_hints.Hex(vm_start), + format_hints.Hex(vm_end), vma.get_perms(), path, + file_output, ), ) @@ -72,6 +233,7 @@ def run(self): ("End", format_hints.Hex), ("Protection", str), ("Map Name", str), + ("File output", str), ], self._generator( list_tasks(self.context, self.config["kernel"], filter_func=filter_func) diff --git a/volatility3/framework/plugins/mac/pslist.py b/volatility3/framework/plugins/mac/pslist.py index 2845f71cb1..06b711e2f0 100644 --- a/volatility3/framework/plugins/mac/pslist.py +++ b/volatility3/framework/plugins/mac/pslist.py @@ -49,9 +49,7 @@ def get_requirements(cls): ] @classmethod - def get_list_tasks( - cls, method: str - ) -> Callable[ + def get_list_tasks(cls, method: str) -> Callable[ [interfaces.context.ContextInterface, str, Callable[[int], bool]], Iterable[interfaces.objects.ObjectInterface], ]: diff --git a/volatility3/framework/plugins/timeliner.py b/volatility3/framework/plugins/timeliner.py index d1c1c0f703..c754e43eff 100644 --- a/volatility3/framework/plugins/timeliner.py +++ b/volatility3/framework/plugins/timeliner.py @@ -45,6 +45,7 @@ class Timeliner(interfaces.plugins.PluginInterface): orders the results by time.""" _required_framework_version = (2, 0, 0) + _version = (1, 1, 0) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -65,7 +66,7 @@ def passthrough(name: str, selected: List[str]) -> bool: if selected_list: def filter_plugins(name: str, selected: List[str]) -> bool: - return any([s in name for s in selected]) + return any(s in name for s in selected) filter_func = filter_plugins else: @@ -105,7 +106,9 @@ def _sort_function(self, item): 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 @@ -183,22 +186,23 @@ def _generator( plugin_name, self._sanitize_body_format(item), self._text_format( - times.get(TimeLinerType.ACCESSED, "") + times.get(TimeLinerType.ACCESSED, "0") ), self._text_format( - times.get(TimeLinerType.MODIFIED, "") + times.get(TimeLinerType.MODIFIED, "0") ), self._text_format( - times.get(TimeLinerType.CHANGED, "") + times.get(TimeLinerType.CHANGED, "0") ), self._text_format( - times.get(TimeLinerType.CREATED, "") + times.get(TimeLinerType.CREATED, "0") ), ) ) - except Exception: + except Exception as e: vollog.log( - logging.INFO, f"Exception occurred running plugin: {plugin_name}" + logging.INFO, + f"Exception occurred running plugin: {plugin_name}: {e}", ) vollog.log(logging.DEBUG, traceback.format_exc()) @@ -243,6 +247,16 @@ def run(self): filter_list = self.config["plugin-filter"] # Identify plugins that we can run which output datetimes for plugin_class in self.usable_plugins: + if not issubclass(plugin_class, TimeLinerInterface): + # get_usable_plugins() should filter this, but adding a safeguard just in case + continue + + if filter_list and not any( + filter in plugin_class.__module__ + "." + plugin_class.__name__ + for filter in filter_list + ): + continue + try: automagics = automagic.choose_automagic(self.automagics, plugin_class) @@ -274,15 +288,8 @@ def run(self): config_value, ) - if isinstance(plugin, TimeLinerInterface): - if not len(filter_list) or any( - [ - filter - in plugin.__module__ + "." + plugin.__class__.__name__ - for filter in filter_list - ] - ): - plugins_to_run.append(plugin) + plugins_to_run.append(plugin) + except exceptions.UnsatisfiedException as excp: # Remove the failed plugin from the list and continue vollog.debug( diff --git a/volatility3/framework/plugins/vmscan.py b/volatility3/framework/plugins/vmscan.py new file mode 100644 index 0000000000..64377d7d8c --- /dev/null +++ b/volatility3/framework/plugins/vmscan.py @@ -0,0 +1,217 @@ +# This file is Copyright 2023 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import enum +import logging +import os +import struct +from typing import Dict, List + +from volatility3.framework import constants, exceptions, interfaces, renderers +from volatility3.framework.configuration import requirements +from volatility3.framework.interfaces import configuration, plugins +from volatility3.framework.renderers import format_hints +from volatility3.framework.symbols import intermed + +vollog = logging.getLogger(__name__) + + +class VMCSTest(enum.IntFlag): + VMCS_ABORT_INVALID = enum.auto() + VMCS_LINK_PTR_IS_NOT_FS = enum.auto() + VMCS_HOST_CR4_NO_VTX = enum.auto() + VMCS_CR3_IS_ZERO = enum.auto() + VMCS_GUEST_CR4_RESERVED = enum.auto() + + +class PageStartScanner(interfaces.layers.ScannerInterface): + def __init__(self, signatures: List[bytes], page_size: int = 0x1000): + super().__init__() + if not len(signatures): + raise ValueError("No signatures passed to constructor") + self._siglen = len(signatures[0]) + for item in signatures: + if len(item) != self._siglen: + raise ValueError( + "Signatures of different lengths passed to PageStartScanner" + ) + self._signatures = signatures + self._page_size = page_size + + def __call__(self, data: bytes, data_offset: int): + """Scans only the start of every page, to see whether a signature is present or not""" + for page_start in range( + data_offset % self._page_size, len(data), self._page_size + ): + if data[page_start : page_start + self._siglen] in self._signatures: + yield ( + page_start + data_offset, + data[page_start : page_start + self._siglen], + ) + + +class Vmscan(plugins.PluginInterface): + """Scans for Intel VT-d structues and generates VM volatility configs for them""" + + _required_framework_version = (2, 2, 0) + _version = (1, 0, 0) + + STRICTLY_REQUIRED_TESTS = { + VMCSTest.VMCS_ABORT_INVALID, + VMCSTest.VMCS_LINK_PTR_IS_NOT_FS, + VMCSTest.VMCS_HOST_CR4_NO_VTX, + } + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.TranslationLayerRequirement( + name="primary", description="Physical base memory layer" + ), + requirements.IntRequirement( + name="log-threshold", + description="Number of criteria failed to log to debug output", + default=2, + optional=True, + ), + ] + + # Scan for VMCS structures based on the known VMCS structures + # found in symbols/vmcs directory + + def _gather_vmcs_structures( + self, context: interfaces.context.ContextInterface, config_path: str + ) -> Dict[bytes, str]: + """Enumerate all JSON files containing VMCS information and return the structures + Signatures can be generated using data extracted using the vmcs_layout tool at + https://github.com/google/rekall/tree/master/tools/linux/vmcs_layout + + Args: + context: The volatility context to work against + config_path: The location to store symbol table configurations under + + Returns: + A dictionary of pattern bytes to the string representation of the architecture + """ + filenames = intermed.IntermediateSymbolTable.file_symbol_url( + os.path.join("generic", "vmcs") + ) + table_names = [] + for filename in filenames: + base_name = os.path.basename(filename).split(".")[0] + table_name = intermed.IntermediateSymbolTable.create( + context, + configuration.path_join(config_path, "vmcs"), + os.path.join("generic", "vmcs"), + filename=base_name, + ) + table_names.append(table_name) + + result = {} + for table_name in table_names: + symbol_table = context.symbol_space[table_name] + revision_id = struct.pack( + " List[str]: + """Runs tests to verify whether a block of data is a VMCS page + Some tests based on the Hypervisor Memory Forensics paper by + Mariano Graziano, Andrea Lanzi and Davide Balzarotti + + Args: + context: The volatility context to be used for this call + vmcs: The instantiated VMCS object to verify + + Returns: + The list of failed criteria that the VMCS did not meet + """ + + # The VMCS should have been constructed on the physical layer (even a nested VMCS) + physical_layer_name = vmcs.vol.layer_name + + failed_tests: VMCSTest = VMCSTest(0) + # The abort field must be valid (generally 0, although other abort codes may exist) + if context.layers[physical_layer_name].read(vmcs.vol.offset + 4, 4) not in [ + b"\x00\x00\x00\x00" + ]: + failed_tests |= VMCSTest.VMCS_ABORT_INVALID + # The vmcs link pointer is supposed to always be set + if vmcs.vmcs_link_ptr != 0xFFFFFFFFFFFFFFFF: + failed_tests |= VMCSTest.VMCS_LINK_PTR_IS_NOT_FS + # To have a VMCS the host needs the VTx bit set in CR4, this can false positive often when all bits are set + if (vmcs.host_cr4 & 1 << 13) == 0: + failed_tests |= VMCSTest.VMCS_HOST_CR4_NO_VTX + # The guest CR3 is *exceptionally* unlikely to be 0 and the guest cr4 is likely to have some bits unset + if (vmcs.guest_cr3 == 0) or (vmcs.host_cr3 == 0): + failed_tests |= VMCSTest.VMCS_CR3_IS_ZERO + # CR4 registers have certain bits reserved that should not be set + if vmcs.guest_cr4 & 0xFFFFFFFFFF889000: + failed_tests |= VMCSTest.VMCS_GUEST_CR4_RESERVED + + if failed_tests and failed_tests.name: + failed_list = failed_tests.name.split("|") + return failed_list + + return [] + + def _generator(self): + # Gather VMCS structures + structures = self._gather_vmcs_structures(self.context, self.config_path) + # Scan memory for them + layer = self.context.layers[self.config["primary"]] + + # Try to move down to the highest physical layer + if layer.config.get("memory_layer"): + layer = self.context.layers[layer.config["memory_layer"]] + + # Run the scan + for offset, match in layer.scan( + self.context, + PageStartScanner(list(structures.keys())), + self._progress_callback, + ): + try: + vmcs = self.context.object( + structures[match] + constants.BANG + "_VMCS", + layer.name, + offset=offset, + ) + failed_list = self._verify_vmcs_page(self.context, vmcs) + if not failed_list: + yield ( + 0, + ( + structures[match], + format_hints.Hex(vmcs.vol.offset), + format_hints.Hex(vmcs.ept), + format_hints.Hex(vmcs.guest_cr3), + ), + ) + if len(failed_list) <= self.config["log-threshold"]: + vollog.debug( + f"Potential {structures[match]} VMCS found at {vmcs.vol.offset:x} with failed criteria: {failed_list}" + ) + except (exceptions.InvalidAddressException, AttributeError): + # Not what we're looking for + continue + + def run(self): + return renderers.TreeGrid( + [ + ("Architecture", str), + ("VMCS Physical offset", format_hints.Hex), + ("EPT", format_hints.Hex), + ("Guest CR3", format_hints.Hex), + ], + self._generator(), + ) diff --git a/volatility3/framework/plugins/windows/amcache.py b/volatility3/framework/plugins/windows/amcache.py new file mode 100644 index 0000000000..8d0a4769ea --- /dev/null +++ b/volatility3/framework/plugins/windows/amcache.py @@ -0,0 +1,650 @@ +# 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 dataclasses +import datetime +import enum +import itertools +import logging +from typing import Dict, Iterable, Iterator, List, Optional, Tuple, Union + +from volatility3.framework import interfaces, renderers +from volatility3.framework.configuration import requirements +from volatility3.framework.layers import registry +from volatility3.framework.renderers import conversion +from volatility3.framework.symbols.windows.extensions import registry as reg_extensions +from volatility3.plugins import timeliner +from volatility3.plugins.windows.registry import hivelist + +vollog = logging.getLogger(__name__) + +####################################################################### +# More information about the following enums can be found in the report +# 'Analysis of the AmCache` by Blanche Lagny, 2019 +####################################################################### + + +class Win8FileValName(enum.Enum): + """ + An enumeration that creates a helpful mapping of opaque Windows 8 Amcache + 'File' subkey value names to their human-readable equivalent. + """ + + ProgramID = "100" + SHA1Hash = "101" + Product = "0" + Company = "1" + Size = "6" + SizeOfImage = "7" + PEHeaderChecksum = "9" + LastModTime = "11" # REG_QWORD FILETIME + CreateTime = "12" # REG_QWORD FILETIME + Path = "15" + LastModTime2 = "17" # REG_QWORD FILETIME + Version = "d" + CompileTime = "f" # REG_QWORD UNIX EPOCH + + +class Win8ProgramValName(enum.Enum): + """ + An enumeration that creates a helpful mapping of opaque Windows 8 Amcache + 'Program' subkey value names to their human-readable equivalent. + """ + + Product = "0" + Version = "1" + Publisher = "2" + InstallTime = "a" + MSIProductCode = "11" + MSIPackageCode = "12" + ProductCode = "f" + PackageCode = "10" + + +class Win10InvAppFileValName(enum.Enum): + """ + An enumeration containing the most useful Windows 10 Amcache + 'InventoryApplicationFile' subkey value names. + """ + + FileId = "FileId" + LinkDate = "LinkDate" + LowerCaseLongPath = "LowerCaseLongPath" + ProductName = "ProductName" + ProductVersion = "ProductVersion" + ProgramID = "ProgramId" + Publisher = "Publisher" + + +class Win10InvAppValName(enum.Enum): + """ + An enumeration containing the most useful Windows 10 Amcache + 'InventoryApplication' subkey value names. + """ + + InstallDate = "InstallDate" + Name = "Name" + Publisher = "Publisher" + RootDirPath = "RootDirPath" + Version = "Version" + + +class Win10DriverBinaryValName(enum.Enum): + """ + An enumeration containing the most useful Windows 10 Amcache + 'InventoryDriverBinary' subkey value names. + """ + + DriverId = "DriverId" + DriverName = "DriverName" + DriverCompany = "DriverCompany" + Product = "Product" + Service = "Service" + DriverTimeStamp = "DriverTimeStamp" + + +class AmcacheEntryType(enum.IntEnum): + Driver = 1 + Program = 2 + File = 3 + + +NullableString = Union[str, None, interfaces.renderers.BaseAbsentValue] +NullableDatetime = Union[datetime.datetime, None, interfaces.renderers.BaseAbsentValue] + + +@dataclasses.dataclass +class _AmcacheEntry: + """ + A class containing all information about an entry from the Amcache registry hive. + Because all values could potentially be paged out of memory or malformed, they are all + a union between their expected value and `interfaces.renderers.BaseAbsentValue`. + """ + + entry_type: str + path: NullableString = renderers.NotApplicableValue() + company: NullableString = renderers.NotApplicableValue() + last_modify_time: NullableDatetime = renderers.NotApplicableValue() + last_modify_time_2: NullableDatetime = renderers.NotApplicableValue() + install_time: NullableDatetime = renderers.NotApplicableValue() + compile_time: NullableDatetime = renderers.NotApplicableValue() + sha1_hash: NullableString = renderers.NotApplicableValue() + service: NullableString = renderers.NotApplicableValue() + product_name: NullableString = renderers.NotApplicableValue() + product_version: NullableString = renderers.NotApplicableValue() + + +def _entry_sort_key(entry_tuple: Tuple[NullableString, _AmcacheEntry]) -> str: + """Sorts entries by program ID. This is broken out as a function here + to ensure consistency in sorting between the `group_by` and `sorted` function + invocations. + """ + program_id, _ = entry_tuple + key = program_id if isinstance(program_id, str) else "" + return key + + +def _get_string_value( + values: Dict[str, reg_extensions.CM_KEY_VALUE], name: str +) -> NullableString: + try: + value = values[name] + except KeyError: + return renderers.NotAvailableValue() + + data = value.decode_data() + if not isinstance(data, bytes): + return renderers.UnparsableValue() + + return data.decode("utf-16le", errors="replace").rstrip("\u0000") + + +def _get_datetime_filetime_value( + values: Dict[str, reg_extensions.CM_KEY_VALUE], name: str +) -> NullableDatetime: + try: + value = values[name] + except KeyError: + return renderers.NotAvailableValue() + + data = value.decode_data() + if not isinstance(data, int): + return renderers.UnparsableValue() + + return conversion.wintime_to_datetime(data) + + +def _get_datetime_utc_epoch_value( + values: Dict[str, reg_extensions.CM_KEY_VALUE], name: str +) -> NullableDatetime: + try: + value = values[name] + except KeyError: + return renderers.NotAvailableValue() + + data = value.decode_data() + if not isinstance(data, (int, float)): + return renderers.UnparsableValue() + + try: + return datetime.datetime.fromtimestamp(float(data), datetime.timezone.utc) + except (ValueError, OverflowError, OSError): + return renderers.UnparsableValue() + + +def _get_datetime_str_value( + values: Dict[str, reg_extensions.CM_KEY_VALUE], name: str +) -> NullableDatetime: + try: + value = values[name] + except KeyError: + return renderers.NotAvailableValue() + + data = value.decode_data() + if not isinstance(data, int): + return renderers.UnparsableValue() + + if isinstance(data, str): + try: + return datetime.datetime.strptime(data, "%m/%d/%Y %H:%M:%S") + except ValueError: + return renderers.UnparsableValue() + else: + return renderers.UnparsableValue() + + +class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): + """Scans for windows services.""" + + _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.PluginRequirement( + name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0) + ), + ] + + def generate_timeline( + self, + ) -> Iterator[Tuple[str, timeliner.TimeLinerType, datetime.datetime]]: + for _, entry in self._generator(): + if isinstance(entry.last_modify_time, datetime.datetime): + yield f"Amcache: {entry.entry_type} {entry.path} registry key modified", timeliner.TimeLinerType.MODIFIED, entry.last_modify_time + if isinstance(entry.last_modify_time_2, datetime.datetime): + yield f"Amcache: {entry.entry_type} {entry.path} STANDARD_INFORMATION create time", timeliner.TimeLinerType.CREATED, entry.last_modify_time_2 + if isinstance(entry.install_time, datetime.datetime): + yield f"Amcache: {entry.entry_type} {entry.path} installed", timeliner.TimeLinerType.CREATED, entry.install_time + if isinstance(entry.compile_time, datetime.datetime): + yield f"Amcache: {entry.entry_type} {entry.path} compiled (PE metadata)", timeliner.TimeLinerType.MODIFIED, entry.compile_time + + @classmethod + def get_amcache_hive( + cls, + context: interfaces.context.ContextInterface, + config_path: str, + kernel: interfaces.context.ModuleInterface, + ) -> Optional[registry.RegistryHive]: + """Retrieves the `Amcache.hve` registry hive from the kernel module, if it can be located.""" + return next( + hivelist.HiveList.list_hives( + context=context, + base_config_path=interfaces.configuration.path_join( + config_path, "hivelist" + ), + layer_name=kernel.layer_name, + symbol_table=kernel.symbol_table_name, + filter_string="amcache", + ), + None, + ) + + @classmethod + def parse_file_key( + cls, file_key: reg_extensions.CM_KEY_NODE + ) -> Iterator[Tuple[NullableString, _AmcacheEntry]]: + """Parses File entries from the Windows 8 `Root\\File` key. + + :param programs_key: The `Root\\File` registry key. + + :return: An iterator of tuples, where the first member is the program ID string for + correlating `Root\\Program` entries, and the second member is the `AmcacheEntry`. + """ + + val_enum = Win8FileValName + + wanted_values = [key.value for key in val_enum] + + for file_entry_key in itertools.chain( + *(key.get_subkeys() for key in file_key.get_subkeys()) + ): + vollog.debug(f"Checking Win8 File key {file_entry_key.get_name()}") + values = { + str(value.get_name()): value + for value in file_entry_key.get_values() + if value.get_name() in wanted_values + } + + program_id = _get_string_value(values, val_enum.ProgramID.value) + path = _get_string_value(values, val_enum.Path.value) + company = _get_string_value(values, val_enum.Company.value) + last_mod_time = _get_datetime_filetime_value( + values, val_enum.LastModTime.value + ) + last_mod_time_2 = _get_datetime_filetime_value( + values, val_enum.LastModTime2.value + ) + install_time = _get_datetime_filetime_value( + values, val_enum.CreateTime.value + ) + compile_time = _get_datetime_utc_epoch_value( + values, val_enum.CompileTime.value + ) + sha1_hash = _get_string_value(values, val_enum.SHA1Hash.value) + vollog.debug(f"Found sha1hash {sha1_hash}") + product_name = _get_string_value(values, val_enum.Product.value) + + yield program_id, _AmcacheEntry( + AmcacheEntryType.File.name, + path=path, + company=company, + last_modify_time=last_mod_time, + last_modify_time_2=last_mod_time_2, + install_time=install_time, + compile_time=compile_time, + sha1_hash=( + sha1_hash.lstrip("0000") + if isinstance(sha1_hash, str) + else sha1_hash + ), + product_name=product_name, + ) + + @classmethod + def parse_programs_key( + cls, programs_key: reg_extensions.CM_KEY_NODE + ) -> Iterator[Tuple[str, _AmcacheEntry]]: + """Parses Program entries from the Windows 8 `Root\\Programs` key. + + :param programs_key: The `Root\\Programs` registry key. + + :return: An iterator of tuples, where the first member is the program ID string for + correlating `Root\\File` entries, and the second member is the `AmcacheEntry`. + """ + val_enum = Win8ProgramValName + + wanted_values = [key.value for key in val_enum] + for program_key in programs_key.get_subkeys(): + values = { + str(value.get_name()): value + for value in program_key.get_values() + if value.get_name() in wanted_values + } + vollog.debug(f"Parsing Win8 Program key {program_key.get_name()}") + program_id = program_key.get_name().strip().strip("\u0000") + + product = _get_string_value(values, val_enum.Product.value) + company = _get_string_value(values, val_enum.Publisher.value) + install_time = _get_datetime_utc_epoch_value( + values, val_enum.InstallTime.value + ) + version = _get_string_value(values, val_enum.Version.value) + + yield program_id, _AmcacheEntry( + AmcacheEntryType.Program.name, + company=company, + last_modify_time=conversion.wintime_to_datetime( + program_key.LastWriteTime.QuadPart + ), + install_time=install_time, + product_name=product, + product_version=version, + ) + + @classmethod + def parse_inventory_app_key( + cls, inv_app_key: reg_extensions.CM_KEY_NODE + ) -> Iterator[Tuple[str, _AmcacheEntry]]: + """Parses InventoryApplication entries from the Windows 10 `Root\\InventoryApplication` key. + + :param programs_key: The `Root\\InventoryApplication` registry key. + + :return: An iterator of tuples, where the first member is the program ID string for + correlating `Root\\InventoryApplicationFile` entries, and the second member is the `AmcacheEntry`. + """ + val_enum = Win10InvAppValName + + wanted_values = [key.value for key in val_enum] + + for program_key in inv_app_key.get_subkeys(): + program_id = program_key.get_name() + + values = { + str(value.get_name()): value + for value in program_key.get_values() + if value.get_name() in wanted_values + } + + name = _get_string_value(values, val_enum.Name.value) + version = _get_string_value(values, val_enum.Version.value) + publisher = _get_string_value(values, val_enum.Publisher.value) + path = _get_string_value(values, val_enum.RootDirPath.value) + install_date = _get_datetime_str_value(values, val_enum.InstallDate.value) + last_mod = conversion.wintime_to_datetime( + program_key.LastWriteTime.QuadPart + ) + + product: str = name if isinstance(name, str) else "UNKNOWN" # type: ignore + + yield program_id.strip().strip("\u0000"), _AmcacheEntry( + AmcacheEntryType.Program.name, + path=path, + last_modify_time=last_mod, + install_time=install_date, + product_name=product, + company=publisher, + product_version=version, + ) + + @classmethod + def parse_inventory_app_file_key( + cls, inv_app_file_key: reg_extensions.CM_KEY_NODE + ) -> Iterator[Tuple[NullableString, _AmcacheEntry]]: + """Parses executable file entries from the `Root\\InventoryApplicationFile` registry key. + + :param inv_app_file_key: The `Root\\InventoryApplicationFile` registry key. + :return: An iterator of tuples, where the first member is the program ID string for correlating + with it's parent `InventoryApplication` program entry, and the second member is the `Amcache` entry. + """ + + val_enum = Win10InvAppFileValName + + wanted_values = [key.value for key in val_enum] + + for file_key in inv_app_file_key.get_subkeys(): + + vollog.debug( + f"Parsing Win10 InventoryApplicationFile key {file_key.get_name()}" + ) + + values = { + str(value.get_name()): value + for value in file_key.get_values() + if value.get_name() in wanted_values + } + + last_mod = conversion.wintime_to_datetime(file_key.LastWriteTime.QuadPart) + path = _get_string_value(values, val_enum.LowerCaseLongPath.value) + linkdate = _get_datetime_str_value(values, val_enum.LinkDate.value) + sha1_hash = _get_string_value(values, val_enum.FileId.value) + publisher = _get_string_value(values, val_enum.Publisher.value) + prod_name = _get_string_value(values, val_enum.ProductName.value) + prod_ver = _get_string_value(values, val_enum.ProductVersion.value) + program_id = _get_string_value(values, val_enum.ProgramID.value) + + yield program_id, _AmcacheEntry( + AmcacheEntryType.File.name, + path=path, + company=publisher, + last_modify_time=last_mod, + compile_time=linkdate, + sha1_hash=( + sha1_hash.lstrip("0000") + if isinstance(sha1_hash, str) + else sha1_hash + ), + product_name=prod_name, + product_version=prod_ver, + ) + + @classmethod + def parse_driver_binary_key( + cls, driver_binary_key: reg_extensions.CM_KEY_NODE + ) -> Iterator[_AmcacheEntry]: + """Parses information about installed drivers from the `Root\\InventoryDriverBinary` registry key. + + :param driver_binary_key: The `Root\\InventoryDriverBinary` registry key + :return: An iterator of `AmcacheEntry`s + """ + val_enum = Win10DriverBinaryValName + + wanted_values = [key.value for key in val_enum] + + for binary_key in driver_binary_key.get_subkeys(): + + values = { + str(value.get_name()): value + for value in binary_key.get_values() + if value.get_name() in wanted_values + } + + # Depending on the Windows version, the key name will be either the name + # of the driver, or its SHA1 hash. + if "/" in str(binary_key.get_name()): + driver_name = str(binary_key.get_name()) + sha1_hash = _get_string_value(values, val_enum.DriverId.name) + else: + sha1_hash = str(binary_key.get_name()) + driver_name = _get_string_value(values, val_enum.DriverName.name) + + if isinstance(sha1_hash, str): + sha1_hash = sha1_hash[4:] if sha1_hash.startswith("0000") else sha1_hash + + company, product, service, last_write_time, driver_timestamp = ( + _get_string_value(values, val_enum.DriverCompany.name), + _get_string_value(values, val_enum.Product.name), + _get_string_value(values, val_enum.Service.name), + conversion.wintime_to_datetime(binary_key.LastWriteTime.QuadPart), + _get_datetime_utc_epoch_value(values, val_enum.DriverTimeStamp.name), + ) + + yield _AmcacheEntry( + entry_type=AmcacheEntryType.Driver.name, + path=driver_name, + company=company, + last_modify_time=last_write_time, + compile_time=driver_timestamp, + sha1_hash=( + sha1_hash.lstrip("0000") + if isinstance(sha1_hash, str) + else sha1_hash + ), + service=service, + product_name=product, + ) + + def _generator(self) -> Iterator[Tuple[int, _AmcacheEntry]]: + kernel = self.context.modules[self.config["kernel"]] + + def indented( + entry_gen: Iterable[_AmcacheEntry], indent: int = 0 + ) -> Iterator[Tuple[int, _AmcacheEntry]]: + for item in entry_gen: + yield indent, item + + # Building the dictionary ahead of time is much better for performance + # vs looking up each service's DLL individually. + amcache = self.get_amcache_hive(self.context, self.config_path, kernel) + if amcache is None: + return + + try: + yield from indented( + self.parse_driver_binary_key( + amcache.get_key("Root\\InventoryDriverBinary") # type: ignore + ) + ) + except KeyError: + # Registry key not found + pass + + try: + programs: Dict[str, _AmcacheEntry] = { + program_id: entry + for program_id, entry in self.parse_programs_key( + amcache.get_key("Root\\Programs") + ) # type: ignore + } + except KeyError: + programs = {} + + try: + files = sorted( + list( + self.parse_file_key(amcache.get_key("Root\\File")), # type: ignore + ), + key=_entry_sort_key, + ) + except KeyError: + files = [] + + for program_id, file_entries in itertools.groupby( + files, + key=_entry_sort_key, + ): + files_indent = 0 + if isinstance(program_id, str): + try: + program_entry = programs.pop(program_id.strip().strip("\u0000")) + yield (0, program_entry) + + files_indent = 1 + except KeyError: + # No parent program for this file entry + pass + for _, entry in file_entries: + yield files_indent, entry + + for empty_program in programs.values(): + yield 0, empty_program + + try: + programs: Dict[str, _AmcacheEntry] = dict( + self.parse_inventory_app_key( + amcache.get_key("Root\\InventoryApplication") # type: ignore + ) + ) + except KeyError: + programs = {} + + try: + files = sorted( + list( + self.parse_inventory_app_file_key(amcache.get_key("Root\\InventoryApplicationFile")), # type: ignore + ), + key=_entry_sort_key, + ) + except KeyError: + files = [] + + for program_id, file_entries in itertools.groupby( + files, + key=_entry_sort_key, + ): + files_indent = 0 + + if isinstance(program_id, str): + try: + program_entry = programs.pop(program_id.strip().strip("\u0000")) + yield (0, program_entry) + files_indent = 1 + except KeyError: + # No parent program for this file entry + pass + + for _, entry in file_entries: + yield files_indent, entry + + for empty_program in programs.values(): + yield 0, empty_program + + def run(self): + + return renderers.TreeGrid( + [ + ("EntryType", str), + ("Path", str), + ("Company", str), + ("LastModifyTime", datetime.datetime), + ("LastModifyTime2", datetime.datetime), + ("InstallTime", datetime.datetime), + ("CompileTime", datetime.datetime), + ("SHA1", str), + ("Service", str), + ("ProductName", str), + ("ProductVersion", str), + ], + ( + (indent, dataclasses.astuple(entry)) + for indent, entry in self._generator() + ), + ) diff --git a/volatility3/framework/plugins/windows/cachedump.py b/volatility3/framework/plugins/windows/cachedump.py index a9b669add4..6e667984a2 100644 --- a/volatility3/framework/plugins/windows/cachedump.py +++ b/volatility3/framework/plugins/windows/cachedump.py @@ -108,12 +108,12 @@ def _generator(self, syshive, sechive): vollog.warning("Unable to locate SYSTEM hive") if sechive is None: vollog.warning("Unable to locate SECURITY hive") - return + return None bootkey = hashdump.Hashdump.get_bootkey(syshive) if not bootkey: vollog.warning("Unable to find bootkey") - return + return None kernel = self.context.modules[self.config["kernel"]] @@ -124,17 +124,17 @@ def _generator(self, syshive, sechive): lsakey = lsadump.Lsadump.get_lsa_key(sechive, bootkey, vista_or_later) if not lsakey: vollog.warning("Unable to find lsa key") - return + return None nlkm = self.get_nlkm(sechive, lsakey, vista_or_later) if not nlkm: vollog.warning("Unable to find nlkma key") - return + return None cache = hashdump.Hashdump.get_hive_key(sechive, "Cache") if not cache: vollog.warning("Unable to find cache key") - return + return None for cache_item in cache.get_values(): if cache_item.Name == "NL$Control": diff --git a/volatility3/framework/plugins/windows/callbacks.py b/volatility3/framework/plugins/windows/callbacks.py index 48b2e7c620..562846def5 100644 --- a/volatility3/framework/plugins/windows/callbacks.py +++ b/volatility3/framework/plugins/windows/callbacks.py @@ -2,16 +2,24 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # -import logging import contextlib -from typing import List, Iterable, Tuple, Optional, Union - -from volatility3.framework import constants, exceptions, renderers, interfaces, symbols +import logging +from typing import Dict, Iterable, List, Optional, Tuple, Union, cast + +from volatility3.framework import ( + constants, + exceptions, + interfaces, + objects, + renderers, + symbols, +) from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints from volatility3.framework.symbols import intermed from volatility3.framework.symbols.windows import versions -from volatility3.plugins.windows import ssdt +from volatility3.framework.symbols.windows.extensions import callbacks +from volatility3.plugins.windows import driverirp, handles, poolscanner, ssdt vollog = logging.getLogger(__name__) @@ -20,7 +28,7 @@ class Callbacks(interfaces.plugins.PluginInterface): """Lists kernel callbacks and notification routines.""" _required_framework_version = (2, 0, 0) - _version = (1, 0, 0) + _version = (2, 0, 0) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -33,27 +41,154 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] requirements.PluginRequirement( name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0) ), + requirements.PluginRequirement( + name="poolscanner", plugin=poolscanner.PoolScanner, version=(1, 0, 0) + ), + requirements.PluginRequirement( + name="driverirp", plugin=driverirp.DriverIrp, version=(1, 0, 0) + ), + requirements.PluginRequirement( + name="handles", plugin=handles.Handles, version=(1, 0, 0) + ), ] - @staticmethod - def create_callback_table( + @classmethod + def create_callback_scan_constraints( + cls, context: interfaces.context.ContextInterface, symbol_table: str, + is_vista_or_above: bool, + ) -> List[poolscanner.PoolConstraint]: + """Creates a list of Pool Tag Constraints for callback objects. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + symbol_table: The name of an existing symbol table containing the symbols / types + is_vista_or_above: A boolean indicating whether the OS version is Vista or newer. + + Returns: + The list containing the built constraints. + """ + constraints = cls._create_default_scan_constraints(context, symbol_table) + if is_vista_or_above: + constraints += cls._create_scan_constraints_vista_and_above(symbol_table) + return constraints + + @staticmethod + def _create_default_scan_constraints( + context: interfaces.context.ContextInterface, symbol_table: str + ) -> List[poolscanner.PoolConstraint]: + + shutdown_packet_size = context.symbol_space.get_type( + symbol_table + constants.BANG + "_SHUTDOWN_PACKET" + ).size + generic_callback_size = context.symbol_space.get_type( + symbol_table + constants.BANG + "_GENERIC_CALLBACK" + ).size + notification_packet_size = context.symbol_space.get_type( + symbol_table + constants.BANG + "_NOTIFICATION_PACKET" + ).size + + return [ + poolscanner.PoolConstraint( + b"IoFs", + type_name=symbol_table + constants.BANG + "_NOTIFICATION_PACKET", + size=(notification_packet_size, None), + page_type=poolscanner.PoolType.NONPAGED + | poolscanner.PoolType.PAGED + | poolscanner.PoolType.FREE, + ), + poolscanner.PoolConstraint( + b"IoSh", + type_name=symbol_table + constants.BANG + "_SHUTDOWN_PACKET", + size=(shutdown_packet_size, None), + page_type=poolscanner.PoolType.NONPAGED + | poolscanner.PoolType.PAGED + | poolscanner.PoolType.FREE, + index=(0, 0), + ), + poolscanner.PoolConstraint( + b"Cbrb", + type_name=symbol_table + constants.BANG + "_GENERIC_CALLBACK", + size=(generic_callback_size, None), + page_type=poolscanner.PoolType.NONPAGED + | poolscanner.PoolType.PAGED + | poolscanner.PoolType.FREE, + ), + ] + + @staticmethod + def _create_scan_constraints_vista_and_above( + symbol_table: str, + ) -> List[poolscanner.PoolConstraint]: + """Creates a list of Pool Tag Constraints for callback objects. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + symbol_table: The name of an existing symbol table containing the symbols / types + + Returns: + The list containing the built constraints. + """ + + return [ + poolscanner.PoolConstraint( + b"DbCb", + type_name=symbol_table + constants.BANG + "_DBGPRINT_CALLBACK", + size=(0x20, 0x40), + page_type=poolscanner.PoolType.NONPAGED + | poolscanner.PoolType.PAGED + | poolscanner.PoolType.FREE, + ), + poolscanner.PoolConstraint( + b"Pnp9", + type_name=symbol_table + constants.BANG + "_NOTIFY_ENTRY_HEADER", + size=(0x30, None), + page_type=poolscanner.PoolType.NONPAGED + | poolscanner.PoolType.PAGED + | poolscanner.PoolType.FREE, + index=(1, 1), + ), + poolscanner.PoolConstraint( + b"PnpD", + type_name=symbol_table + constants.BANG + "_NOTIFY_ENTRY_HEADER", + size=(0x40, None), + page_type=poolscanner.PoolType.NONPAGED + | poolscanner.PoolType.PAGED + | poolscanner.PoolType.FREE, + index=(1, 1), + ), + poolscanner.PoolConstraint( + b"PnpC", + type_name=symbol_table + constants.BANG + "_NOTIFY_ENTRY_HEADER", + size=(0x38, None), + page_type=poolscanner.PoolType.NONPAGED + | poolscanner.PoolType.PAGED + | poolscanner.PoolType.FREE, + index=(1, 1), + ), + ] + + @classmethod + def create_callback_symbol_table( + cls, + context: interfaces.context.ContextInterface, + nt_symbol_table: str, config_path: str, ) -> str: - """Creates a symbol table for a set of callbacks. + """Creates a symbol table for kernel callback objects. 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 + nt_symbol_table: The name of the table containing the kernel symbols + config_path: The config path where to find symbol files Returns: - The name of the constructed callback table + The name of the constructed symbol 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} + native_types = context.symbol_space[nt_symbol_table].natives + is_64bit = symbols.symbol_table_is_64bit(context, nt_symbol_table) + table_mapping = {"nt_symbols": nt_symbol_table} if is_64bit: symbol_filename = "callbacks-x64" @@ -65,10 +200,147 @@ def create_callback_table( config_path, "windows", symbol_filename, + class_types=callbacks.class_types_x86 if not is_64bit else None, native_types=native_types, table_mapping=table_mapping, ) + @classmethod + def scan( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + nt_symbol_table: str, + callback_symbol_table: str, + ) -> Iterable[ + Tuple[ + Union[str, interfaces.renderers.BaseAbsentValue], + int, + Union[str, interfaces.renderers.BaseAbsentValue], + ] + ]: + """Scans for callback objects 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 + nt_symbol_table: The name of the table containing the kernel symbols + callback_symbol_table: The name of the table containing the callback object symbols (_SHUTDOWN_PACKET etc.) + + Returns: + A list of callback objects found by scanning the `layer_name` layer for callback pool signatures + """ + is_vista_or_later = versions.is_vista_or_later( + context=context, symbol_table=nt_symbol_table + ) + + type_map = handles.Handles.get_type_map(context, layer_name, nt_symbol_table) + + constraints = cls.create_callback_scan_constraints( + context, callback_symbol_table, is_vista_or_later + ) + + for ( + _constraint, + mem_object, + _header, + ) in poolscanner.PoolScanner.generate_pool_scan( + context, layer_name, nt_symbol_table, constraints + ): + try: + if isinstance(mem_object, callbacks._SHUTDOWN_PACKET): + if not mem_object.is_parseable(type_map): + continue + elif hasattr(mem_object, "is_valid"): + if not mem_object.is_valid(): + continue + + yield cls._process_scanned_callback(mem_object, type_map) + except exceptions.InvalidAddressException: + continue + + @classmethod + def _process_scanned_callback( + cls, memory_object: objects.StructType, type_map: Dict[int, str] + ) -> Tuple[ + Union[str, interfaces.renderers.BaseAbsentValue], + int, + Union[str, interfaces.renderers.BaseAbsentValue], + ]: + symbol_table = memory_object.get_symbol_table_name() + type_name = memory_object.vol.type_name + + if isinstance(memory_object, callbacks._SHUTDOWN_PACKET) or ( + type_name == symbol_table + constants.BANG + "_SHUTDOWN_PACKET" + ): + callback_type = "IoRegisterShutdownNotification" + + try: + driver = memory_object.DeviceObject.DriverObject + index = driverirp.MAJOR_FUNCTIONS.index("IRP_MJ_SHUTDOWN") + callback_address = driver.MajorFunction[index] + details = driver.DriverName.String or renderers.UnparsableValue() + except exceptions.InvalidAddressException: + callback_address = memory_object.vol.offset + details = renderers.NotApplicableValue() + + elif type_name == symbol_table + constants.BANG + "_NOTIFICATION_PACKET": + callback_type = "IoRegisterFsRegistrationChange" + callback_address = memory_object.NotificationRoutine + details = renderers.NotApplicableValue() + + elif type_name == symbol_table + constants.BANG + "_NOTIFY_ENTRY_HEADER": + driver = ( + memory_object.DriverObject.dereference() + if memory_object.DriverObject.is_readable() + else None + ) + + if driver: + # Instantiate an object header for the driver name + header = driver.get_object_header() + try: + if header.get_object_type(type_map) == "Driver": + # Grab the object name + details = header.NameInfo.Name.String + else: + details = renderers.NotApplicableValue() + except exceptions.InvalidAddressException: + details = renderers.UnreadableValue() + else: + details = renderers.UnreadableValue() + + callback_type = ( + memory_object.EventCategory.description + if memory_object.EventCategory.is_valid_choice + else renderers.UnparsableValue() + ) + + callback_address = memory_object.CallbackRoutine + + elif type_name == symbol_table + constants.BANG + "_GENERIC_CALLBACK": + callback_type = "GenericKernelCallback" + callback_address = memory_object.Callback + details = renderers.NotApplicableValue() + + elif type_name == symbol_table + constants.BANG + "_DBGPRINT_CALLBACK": + callback_type = "DbgSetDebugPrintCallback" + callback_address = memory_object.Function + details = renderers.NotApplicableValue() + + else: + raise ValueError(f"Unexpected object type {type_name}") + + return ( + callback_type, + callback_address, + ( + details + if not isinstance(details, interfaces.renderers.BaseAbsentValue) + else details + ), + ) + @classmethod def list_notify_routines( cls, @@ -115,11 +387,14 @@ def list_notify_routines( else: count = 8 - fast_refs = ntkrnlmp.object( - object_type="array", - offset=symbol_offset, - subtype=ntkrnlmp.get_type("_EX_FAST_REF"), - count=count, + fast_refs = cast( + List[objects.Pointer], + ntkrnlmp.object( + object_type="array", + offset=symbol_offset, + subtype=ntkrnlmp.get_type("_EX_FAST_REF"), + count=count, + ), ) for fast_ref in fast_refs: @@ -157,13 +432,16 @@ def _list_registry_callbacks_legacy( ) if callback_count == 0: - return + return None - fast_refs = ntkrnlmp.object( - object_type="array", - offset=symbol_offset, - subtype=ntkrnlmp.get_type("_EX_FAST_REF"), - count=callback_count, + fast_refs = cast( + List[objects.Pointer], + ntkrnlmp.object( + object_type="array", + offset=symbol_offset, + subtype=ntkrnlmp.get_type("_EX_FAST_REF"), + count=callback_count, + ), ) for fast_ref in fast_refs: @@ -199,7 +477,7 @@ def _list_registry_callbacks_new( ) if callback_count == 0: - return + return None callback_list = ntkrnlmp.object(object_type="_LIST_ENTRY", offset=symbol_offset) for callback in callback_list.to_list(full_type_name, "Link"): @@ -256,7 +534,7 @@ def list_registry_callbacks( symbol_status = "exists" vollog.debug(f"symbol {symbol_name} {symbol_status}.") - return + return None @classmethod def list_bugcheck_reason_callbacks( @@ -265,7 +543,13 @@ def list_bugcheck_reason_callbacks( layer_name: str, symbol_table: str, callback_table_name: str, - ) -> Iterable[Tuple[str, int, str]]: + ) -> Iterable[ + Tuple[ + str, + int, + interfaces.renderers.BaseAbsentValue, + ] + ]: """Lists all kernel bugcheck reason callbacks. Args: @@ -287,7 +571,7 @@ def list_bugcheck_reason_callbacks( ).address except exceptions.SymbolError: vollog.debug("Cannot find KeBugCheckReasonCallbackListHead") - return + return None full_type_name = ( callback_table_name + constants.BANG + "_KBUGCHECK_REASON_CALLBACK_RECORD" @@ -323,7 +607,13 @@ def list_bugcheck_callbacks( layer_name: str, symbol_table: str, callback_table_name: str, - ) -> Iterable[Tuple[str, int, str]]: + ) -> Iterable[ + Tuple[ + str, + int, + Union[interfaces.objects.ObjectInterface, renderers.UnreadableValue], + ] + ]: """Lists all kernel bugcheck callbacks. Args: @@ -343,7 +633,7 @@ def list_bugcheck_callbacks( list_offset = ntkrnlmp.get_symbol("KeBugCheckCallbackListHead").address except exceptions.SymbolError: vollog.debug("Cannot find KeBugCheckCallbackListHead") - return + return None full_type_name = ( callback_table_name + constants.BANG + "_KBUGCHECK_CALLBACK_RECORD" @@ -372,7 +662,7 @@ def list_bugcheck_callbacks( def _generator(self): kernel = self.context.modules[self.config["kernel"]] - callback_table_name = self.create_callback_table( + callback_symbol_table = self.create_callback_symbol_table( self.context, kernel.symbol_table_name, self.config_path ) @@ -385,6 +675,7 @@ def _generator(self): self.list_bugcheck_callbacks, self.list_bugcheck_reason_callbacks, self.list_registry_callbacks, + self.scan, ) for callback_method in callback_methods: @@ -392,7 +683,7 @@ def _generator(self): self.context, kernel.layer_name, kernel.symbol_table_name, - callback_table_name, + callback_symbol_table, ): if callback_detail is None: detail = renderers.NotApplicableValue() diff --git a/volatility3/framework/plugins/windows/cmdscan.py b/volatility3/framework/plugins/windows/cmdscan.py new file mode 100644 index 0000000000..9645ee507f --- /dev/null +++ b/volatility3/framework/plugins/windows/cmdscan.py @@ -0,0 +1,381 @@ +# 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 windows console histories. + +import logging +import struct +from typing import Tuple, Generator, Set, Dict, Any, Optional + +from volatility3.framework import interfaces +from volatility3.framework import renderers +from volatility3.framework.configuration import requirements +from volatility3.framework.layers import scanners +from volatility3.framework.objects import utility +from volatility3.framework.renderers import format_hints +from volatility3.plugins.windows import pslist, consoles + + +vollog = logging.getLogger(__name__) + + +class CmdScan(interfaces.plugins.PluginInterface): + """Looks for Windows Command History lists""" + + _required_framework_version = (2, 4, 0) + _version = (1, 0, 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) + ), + requirements.PluginRequirement( + name="consoles", plugin=consoles.Consoles, version=(1, 0, 0) + ), + requirements.BooleanRequirement( + name="no_registry", + description="Don't search the registry for possible values of CommandHistorySize", + optional=True, + default=False, + ), + requirements.ListRequirement( + name="max_history", + element_type=int, + description="CommandHistorySize values to search for.", + optional=True, + default=[50], + ), + ] + + @classmethod + def get_filtered_vads( + cls, + conhost_proc: interfaces.context.ContextInterface, + size_filter: Optional[int] = 0x40000000, + ) -> Generator[Tuple[int, int], None, None]: + """ + Returns vads of a process with size smaller than size_filter + + Args: + conhost_proc: the process object for conhost.exe + + Returns: + A list of tuples of: + vad_base: the base address + vad_size: the size of the VAD + """ + for vad in conhost_proc.get_vad_root().traverse(): + base = vad.get_start() + if vad.get_size() < size_filter: + yield (base, vad.get_size()) + + @classmethod + def get_command_history( + cls, + context: interfaces.context.ContextInterface, + kernel_layer_name: str, + kernel_symbol_table_name: str, + config_path: str, + procs: Generator[interfaces.objects.ObjectInterface, None, None], + max_history: Set[int], + ) -> Tuple[ + interfaces.context.ContextInterface, + interfaces.context.ContextInterface, + Dict[str, Any], + ]: + """Gets the list of commands from each Command History structure + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + kernel_layer_name: The name of the layer on which to operate + kernel_symbol_table_name: The name of the table containing the kernel symbols + config_path: The config path where to find symbol files + procs: list of process objects + max_history: an initial set of CommandHistorySize values + + Returns: + The conhost process object, the command history structure, a dictionary of properties for + that command history structure. + """ + + conhost_symbol_table = None + + for conhost_proc, proc_layer_name in consoles.Consoles.find_conhost_proc(procs): + if not conhost_proc: + vollog.info( + "Unable to find a valid conhost.exe process in the process list. Analysis cannot proceed." + ) + continue + vollog.debug( + f"Found conhost process {conhost_proc} with pid {conhost_proc.UniqueProcessId}" + ) + + conhostexe_base, conhostexe_size = consoles.Consoles.find_conhostexe( + conhost_proc + ) + if not conhostexe_base: + vollog.info( + "Unable to find the location of conhost.exe. Analysis cannot proceed." + ) + continue + vollog.debug(f"Found conhost.exe base at {conhostexe_base:#x}") + + proc_layer = context.layers[proc_layer_name] + + if conhost_symbol_table is None: + conhost_symbol_table = consoles.Consoles.create_conhost_symbol_table( + context, + kernel_layer_name, + kernel_symbol_table_name, + config_path, + proc_layer_name, + conhostexe_base, + ) + + conhost_module = context.module( + conhost_symbol_table, proc_layer_name, offset=conhostexe_base + ) + command_count_max_offset = conhost_module.get_type( + "_COMMAND_HISTORY" + ).relative_child_offset("CommandCountMax") + + sections = cls.get_filtered_vads(conhost_proc) + found_history_for_proc = False + # scan for potential _COMMAND_HISTORY structures by using the CommandHistorySize + for max_history_value in max_history: + max_history_bytes = struct.pack("H", max_history_value) + vollog.debug( + f"Scanning for CommandHistorySize value: {max_history_bytes}" + ) + for address in proc_layer.scan( + context, + scanners.BytesScanner(max_history_bytes), + sections=sections, + ): + command_history = None + command_history_properties = [] + + try: + command_history = conhost_module.object( + "_COMMAND_HISTORY", + offset=address - command_count_max_offset, + absolute=True, + ) + + if not command_history.is_valid(max_history_value): + continue + + vollog.debug( + f"Getting Command History properties for {command_history}" + ) + command_history_properties.append( + { + "level": 0, + "name": "_COMMAND_HISTORY", + "address": command_history.vol.offset, + "data": None, + } + ) + command_history_properties.append( + { + "level": 1, + "name": "_COMMAND_HISTORY.Application", + "address": command_history.Application.vol.offset, + "data": command_history.get_application(), + } + ) + command_history_properties.append( + { + "level": 1, + "name": "_COMMAND_HISTORY.ProcessHandle", + "address": command_history.ConsoleProcessHandle.ProcessHandle.vol.offset, + "data": hex( + command_history.ConsoleProcessHandle.ProcessHandle + ), + } + ) + command_history_properties.append( + { + "level": 1, + "name": "_COMMAND_HISTORY.CommandCount", + "address": None, + "data": command_history.CommandCount, + } + ) + command_history_properties.append( + { + "level": 1, + "name": "_COMMAND_HISTORY.LastDisplayed", + "address": command_history.LastDisplayed.vol.offset, + "data": command_history.LastDisplayed, + } + ) + command_history_properties.append( + { + "level": 1, + "name": "_COMMAND_HISTORY.CommandCountMax", + "address": command_history.CommandCountMax.vol.offset, + "data": command_history.CommandCountMax, + } + ) + + command_history_properties.append( + { + "level": 1, + "name": "_COMMAND_HISTORY.CommandBucket", + "address": command_history.CommandBucket.vol.offset, + "data": "", + } + ) + for ( + cmd_index, + bucket_cmd, + ) in command_history.scan_command_bucket(): + try: + command_history_properties.append( + { + "level": 2, + "name": f"_COMMAND_HISTORY.CommandBucket_Command_{cmd_index}", + "address": bucket_cmd.vol.offset, + "data": bucket_cmd.get_command_string(), + } + ) + except Exception as e: + vollog.debug( + f"reading {bucket_cmd} encountered exception {e}" + ) + except Exception as e: + vollog.debug( + f"reading {command_history} encountered exception {e}" + ) + + if command_history and command_history_properties: + found_history_for_proc = True + yield conhost_proc, command_history, command_history_properties + + # if found_history_for_proc is still False, then none of the scanned locations found + # a valid _COMMAND_HISTORY for the process, so yield the process and some empty data + # so the process can at least be reported that it was found with no history + if not found_history_for_proc: + yield conhost_proc, command_history or None, [] + + def _generator( + self, procs: Generator[interfaces.objects.ObjectInterface, None, None] + ): + """ + Generates the command history to use in rendering + + Args: + procs: the process list filtered to conhost.exe instances + """ + + kernel = self.context.modules[self.config["kernel"]] + + max_history = set(self.config.get("max_history", [50])) + no_registry = self.config.get("no_registry") + + if no_registry is False: + max_history, _ = consoles.Consoles.get_console_settings_from_registry( + self.context, + self.config_path, + kernel.layer_name, + kernel.symbol_table_name, + max_history, + [], + ) + + vollog.debug(f"Possible CommandHistorySize values: {max_history}") + + proc = None + for ( + proc, + command_history, + command_history_properties, + ) in self.get_command_history( + self.context, + kernel.layer_name, + kernel.symbol_table_name, + self.config_path, + procs, + max_history, + ): + process_name = utility.array_to_string(proc.ImageFileName) + process_pid = proc.UniqueProcessId + + if command_history and command_history_properties: + for command_history_property in command_history_properties: + yield ( + command_history_property["level"], + ( + process_pid, + process_name, + format_hints.Hex(command_history.vol.offset), + command_history_property["name"], + ( + renderers.NotApplicableValue() + if command_history_property["address"] is None + else format_hints.Hex( + command_history_property["address"] + ) + ), + str(command_history_property["data"]), + ), + ) + else: + yield ( + 0, + ( + process_pid, + process_name, + ( + format_hints.Hex(command_history.vol.offset) + if command_history + else renderers.NotApplicableValue() + ), + "_COMMAND_HISTORY", + renderers.NotApplicableValue(), + "History Not Found", + ), + ) + + if proc is None: + vollog.warn("No conhost.exe processes found.") + + def _conhost_proc_filter(self, proc: interfaces.objects.ObjectInterface): + """ + Used to filter to only conhost.exe processes + """ + process_name = utility.array_to_string(proc.ImageFileName) + + return process_name != "conhost.exe" + + def run(self): + kernel = self.context.modules[self.config["kernel"]] + + return renderers.TreeGrid( + [ + ("PID", int), + ("Process", str), + ("ConsoleInfo", format_hints.Hex), + ("Property", str), + ("Address", format_hints.Hex), + ("Data", str), + ], + self._generator( + pslist.PsList.list_processes( + context=self.context, + layer_name=kernel.layer_name, + symbol_table=kernel.symbol_table_name, + filter_func=self._conhost_proc_filter, + ) + ), + ) diff --git a/volatility3/framework/plugins/windows/consoles.py b/volatility3/framework/plugins/windows/consoles.py new file mode 100644 index 0000000000..ad1c9d4bde --- /dev/null +++ b/volatility3/framework/plugins/windows/consoles.py @@ -0,0 +1,953 @@ +# 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 windows console histories. + +import logging +import os +import struct +from typing import Tuple, Generator, Set, Dict, Any, Type + +from volatility3.framework import interfaces, symbols, exceptions +from volatility3.framework import renderers +from volatility3.framework.interfaces import configuration +from volatility3.framework.configuration import requirements +from volatility3.framework.layers import scanners +from volatility3.framework.objects import utility +from volatility3.framework.renderers import format_hints +from volatility3.framework.symbols import intermed +from volatility3.framework.symbols.windows.extensions import pe, consoles +from volatility3.plugins.windows import pslist, info, verinfo +from volatility3.plugins.windows.registry import hivelist + + +vollog = logging.getLogger(__name__) + + +class Consoles(interfaces.plugins.PluginInterface): + """Looks for Windows console buffers""" + + _required_framework_version = (2, 4, 0) + _version = (1, 0, 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) + ), + requirements.VersionRequirement( + name="verinfo", component=verinfo.VerInfo, version=(1, 0, 0) + ), + requirements.PluginRequirement( + name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0) + ), + requirements.BooleanRequirement( + name="no_registry", + description="Don't search the registry for possible values of CommandHistorySize and HistoryBufferMax", + optional=True, + default=False, + ), + requirements.ListRequirement( + name="max_history", + element_type=int, + description="CommandHistorySize values to search for.", + optional=True, + default=[50], + ), + requirements.ListRequirement( + name="max_buffers", + element_type=int, + description="HistoryBufferMax values to search for.", + optional=True, + default=[4], + ), + ] + + @classmethod + def find_conhost_proc( + cls, proc_list: Generator[interfaces.objects.ObjectInterface, None, None] + ) -> Tuple[interfaces.context.ContextInterface, str]: + """ + Walks the process list and returns the conhost instances. + + Args: + proc_list: The process list generator + + Return: + The process object and layer name for conhost + """ + + for proc in proc_list: + if utility.array_to_string(proc.ImageFileName).lower() == "conhost.exe": + try: + proc_id = proc.UniqueProcessId + proc_layer_name = proc.add_process_layer() + + yield proc, proc_layer_name + + except exceptions.InvalidAddressException as excp: + vollog.debug( + "Process {}: invalid address {} in layer {}".format( + proc_id, excp.invalid_address, excp.layer_name + ) + ) + + @classmethod + def find_conhostexe( + cls, conhost_proc: interfaces.context.ContextInterface + ) -> Tuple[int, int]: + """ + Finds the base address of conhost.exe + + Args: + conhost_proc: the process object for conhost.exe + + Returns: + A tuple of: + conhostexe_base: the base address of conhost.exe + conhostexe_size: the size of the VAD for conhost.exe + """ + for vad in conhost_proc.get_vad_root().traverse(): + filename = vad.get_file_name() + if isinstance(filename, str) and filename.lower().endswith("conhost.exe"): + base = vad.get_start() + return base, vad.get_size() + + return None, None + + @classmethod + def determine_conhost_version( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + nt_symbol_table: str, + config_path: str, + conhost_layer_name: str, + conhost_base: int, + ) -> Tuple[str, Type]: + """Tries to determine which symbol filename to use for the image's console information. This is similar to the + netstat plugin. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + nt_symbol_table: The name of the table containing the kernel symbols + config_path: The config path where to find symbol files + conhost_layer_name: The name of the conhot process memory layer + conhost_base: the base address of conhost.exe + + Returns: + The filename of the symbol table to use and the associated class types. + """ + + is_64bit = symbols.symbol_table_is_64bit(context, nt_symbol_table) + + if is_64bit: + arch = "x64" + else: + arch = "x86" + + vers = info.Info.get_version_structure(context, layer_name, nt_symbol_table) + + kuser = info.Info.get_kuser_structure(context, layer_name, nt_symbol_table) + + try: + vers_minor_version = int(vers.MinorVersion) + nt_major_version = int(kuser.NtMajorVersion) + nt_minor_version = int(kuser.NtMinorVersion) + except ValueError: + # vers struct exists, but is not an int anymore? + raise NotImplementedError( + "Kernel Debug Structure version format not supported!" + ) + except Exception: + # unsure what to raise here. Also, it might be useful to add some kind of fallback, + # either to a user-provided version or to another method to determine conhost.exe's version + raise exceptions.VolatilityException( + "Kernel Debug Structure missing VERSION/KUSER structure, unable to determine Windows version!" + ) + + vollog.debug( + "Determined OS Version: {}.{} {}.{}".format( + kuser.NtMajorVersion, + kuser.NtMinorVersion, + vers.MajorVersion, + vers.MinorVersion, + ) + ) + + if nt_major_version == 10 and arch == "x64": + # win10 x64 has an additional class type we have to include. + class_types = consoles.win10_x64_class_types + else: + # default to general class types + class_types = consoles.class_types + + # these versions are listed explicitly because symbol files differ based on + # version *and* architecture. this is currently the clearest way to show + # the differences, even if it introduces a fair bit of redundancy. + # furthermore, it is easy to append new versions. + if arch == "x86": + version_dict = {} + else: + version_dict = { + (10, 0, 17763, 1): "consoles-win10-17763-x64", + (10, 0, 17763, 3232): "consoles-win10-17763-3232-x64", + (10, 0, 18362, 0): "consoles-win10-18362-x64", + (10, 0, 19041, 0): "consoles-win10-19041-x64", + (10, 0, 20348, 1): "consoles-win10-20348-x64", + (10, 0, 20348, 1970): "consoles-win10-20348-1970-x64", + (10, 0, 20348, 2461): "consoles-win10-20348-2461-x64", + (10, 0, 20348, 2520): "consoles-win10-20348-2461-x64", + (10, 0, 22000, 0): "consoles-win10-22000-x64", + (10, 0, 22621, 1): "consoles-win10-22621-x64", + (10, 0, 22621, 3527): "consoles-win10-22621-3527-x64", + (10, 0, 25398, 0): "consoles-win10-22000-x64", + } + + # we do not need to check for conhost's specific FileVersion in every case + conhost_mod_version = 0 # keep it 0 as a default + + # we need to define additional version numbers (which are then found via conhost.exe's FileVersion header) in case there is + # ambiguity _within_ an OS version. If such a version number (last number of the tuple) is defined for the current OS + # we need to inspect conhost.exe's headers to see if we can grab the precise version + if [ + (a, b, c, d) + for a, b, c, d in version_dict + if (a, b, c) == (nt_major_version, nt_minor_version, vers_minor_version) + and d != 0 + ]: + vollog.debug( + "Requiring further version inspection due to OS version by checking conhost.exe's FileVersion header" + ) + + pe_table_name = intermed.IntermediateSymbolTable.create( + context, + configuration.path_join(config_path, "conhost"), + "windows", + "pe", + class_types=pe.class_types, + ) + + try: + (major, minor, product, build) = ( + verinfo.VerInfo.get_version_information( + context, pe_table_name, conhost_layer_name, conhost_base + ) + ) + conhost_mod_version = build + vollog.debug( + f"Found conhost.exe version {major}.{minor}.{product}.{build} in {conhost_layer_name} at base {conhost_base:#x}" + ) + except (exceptions.InvalidAddressException, TypeError, AttributeError): + # the following is IntelLayer specific and might need to be adapted to other architectures. + physical_layer_name = context.layers[layer_name].config.get( + "memory_layer", None + ) + if physical_layer_name: + ver = verinfo.VerInfo.find_version_info( + context, physical_layer_name, "CONHOST.EXE" + ) + + if ver: + conhost_mod_version = ver[3] + vollog.debug( + "Determined conhost.exe's FileVersion: {}".format( + conhost_mod_version + ) + ) + else: + vollog.debug("Could not determine conhost.exe's FileVersion.") + else: + vollog.debug( + "Unable to retrieve physical memory layer, skipping FileVersion check." + ) + + # when determining the symbol file we have to consider the following cases: + # the determined version's symbol file is found by intermed.create -> proceed + # the determined version's symbol file is not found by intermed -> intermed will throw an exc and abort + # the determined version has no mapped symbol file -> if win10 use latest, otherwise throw exc + # windows version cannot be determined -> throw exc + + filename = version_dict.get( + ( + nt_major_version, + nt_minor_version, + vers_minor_version, + conhost_mod_version, + ) + ) + + if not filename: + # no match on filename means that we possibly have a version newer than those listed here. + # try to grab the latest supported version of the current image NT version. If that symbol + # version does not work, support has to be added manually. + current_versions = [ + (nt_maj, nt_min, vers_min, conhost_ver) + for nt_maj, nt_min, vers_min, conhost_ver in version_dict + if nt_maj == nt_major_version + and nt_min == nt_minor_version + and vers_min <= vers_minor_version + and conhost_ver <= conhost_mod_version + ] + current_versions.sort() + + if current_versions: + latest_version = current_versions[-1] + + filename = version_dict.get(latest_version) + + vollog.debug( + f"Unable to find exact matching symbol file, going with latest: {filename}" + ) + + else: + raise NotImplementedError( + "This version of Windows is not supported: {}.{} {}.{}!".format( + nt_major_version, + nt_minor_version, + vers.MajorVersion, + vers_minor_version, + ) + ) + + vollog.debug(f"Determined symbol filename: {filename}") + + return filename, class_types + + @classmethod + def create_conhost_symbol_table( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + nt_symbol_table: str, + config_path: str, + conhost_layer_name: str, + conhost_base: int, + ) -> str: + """Creates a symbol table for conhost structures. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + layer_name: The name of the layer on which to operate + nt_symbol_table: The name of the table containing the kernel symbols + config_path: The config path where to find symbol files + + Returns: + The name of the constructed symbol table + """ + table_mapping = {"nt_symbols": nt_symbol_table} + + symbol_filename, class_types = cls.determine_conhost_version( + context, + layer_name, + nt_symbol_table, + config_path, + conhost_layer_name, + conhost_base, + ) + + vollog.debug(f"Using symbol file '{symbol_filename}' and types {class_types}") + + return intermed.IntermediateSymbolTable.create( + context, + configuration.path_join(config_path, "conhost"), + os.path.join("windows", "consoles"), + symbol_filename, + class_types=class_types, + table_mapping=table_mapping, + ) + + @classmethod + def get_console_info( + cls, + context: interfaces.context.ContextInterface, + kernel_layer_name: str, + kernel_table_name: str, + config_path: str, + procs: Generator[interfaces.objects.ObjectInterface, None, None], + max_history: Set[int], + max_buffers: Set[int], + ) -> Tuple[ + interfaces.context.ContextInterface, + interfaces.context.ContextInterface, + Dict[str, Any], + ]: + """Gets the Console Information structure and its related properties for each conhost process + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + kernel_layer_name: The name of the layer on which to operate + kernel_table_name: The name of the table containing the kernel symbols + config_path: The config path where to find symbol files + procs: list of process objects + max_history: an initial set of CommandHistorySize values + max_buffers: an initial list of HistoryBufferMax values + + Returns: + The conhost process object, the console information structure, a dictionary of properties for + that console information structure. + """ + + conhost_symbol_table = None + + for conhost_proc, proc_layer_name in cls.find_conhost_proc(procs): + if not conhost_proc: + vollog.info( + "Unable to find a valid conhost.exe process in the process list. Analysis cannot proceed." + ) + continue + vollog.debug( + f"Found conhost process {conhost_proc} with pid {conhost_proc.UniqueProcessId}" + ) + + conhostexe_base, conhostexe_size = cls.find_conhostexe(conhost_proc) + if not conhostexe_base: + vollog.info( + "Unable to find the location of conhost.exe. Analysis cannot proceed." + ) + continue + vollog.debug(f"Found conhost.exe base at {conhostexe_base:#x}") + + proc_layer = context.layers[proc_layer_name] + + if conhost_symbol_table is None: + conhost_symbol_table = cls.create_conhost_symbol_table( + context, + kernel_layer_name, + kernel_table_name, + config_path, + proc_layer_name, + conhostexe_base, + ) + + conhost_module = context.module( + conhost_symbol_table, proc_layer_name, offset=conhostexe_base + ) + + found_console_info_for_proc = False + # scan for potential _CONSOLE_INFORMATION structures by using the CommandHistorySize + for max_history_value in max_history: + max_history_bytes = struct.pack("H", max_history_value) + vollog.debug( + f"Scanning for CommandHistorySize value: {max_history_bytes}" + ) + for address in proc_layer.scan( + context, + scanners.BytesScanner(max_history_bytes), + sections=[(conhostexe_base, conhostexe_size)], + ): + + console_properties = [] + + try: + console_info = conhost_module.object( + "_CONSOLE_INFORMATION", + offset=address + - conhost_module.get_type( + "_CONSOLE_INFORMATION" + ).relative_child_offset("CommandHistorySize"), + absolute=True, + ) + + if not any( + [ + console_info.is_valid(max_buffer) + for max_buffer in max_buffers + ] + ): + continue + + vollog.debug( + f"Getting Console Information properties for {console_info}" + ) + console_properties.append( + { + "level": 0, + "name": "_CONSOLE_INFORMATION", + "address": console_info.vol.offset, + "data": "", + } + ) + console_properties.append( + { + "level": 1, + "name": "_CONSOLE_INFORMATION.ScreenX", + "address": console_info.ScreenX.vol.offset, + "data": console_info.ScreenX, + } + ) + console_properties.append( + { + "level": 1, + "name": "_CONSOLE_INFORMATION.ScreenY", + "address": console_info.ScreenY.vol.offset, + "data": console_info.ScreenY, + } + ) + console_properties.append( + { + "level": 1, + "name": "_CONSOLE_INFORMATION.CommandHistorySize", + "address": console_info.CommandHistorySize.vol.offset, + "data": console_info.CommandHistorySize, + } + ) + console_properties.append( + { + "level": 1, + "name": "_CONSOLE_INFORMATION.HistoryBufferCount", + "address": console_info.HistoryBufferCount.vol.offset, + "data": console_info.HistoryBufferCount, + } + ) + console_properties.append( + { + "level": 1, + "name": "_CONSOLE_INFORMATION.HistoryBufferMax", + "address": console_info.HistoryBufferMax.vol.offset, + "data": console_info.HistoryBufferMax, + } + ) + console_properties.append( + { + "level": 1, + "name": "_CONSOLE_INFORMATION.Title", + "address": console_info.Title.vol.offset, + "data": console_info.get_title(), + } + ) + console_properties.append( + { + "level": 1, + "name": "_CONSOLE_INFORMATION.OriginalTitle", + "address": console_info.OriginalTitle.vol.offset, + "data": console_info.get_original_title(), + } + ) + + vollog.debug( + f"Getting ConsoleProcessList entries for {console_info.ConsoleProcessList}" + ) + console_properties.append( + { + "level": 1, + "name": "_CONSOLE_INFORMATION.ProcessCount", + "address": console_info.ProcessCount.vol.offset, + "data": console_info.ProcessCount, + } + ) + console_properties.append( + { + "level": 1, + "name": "_CONSOLE_INFORMATION.ConsoleProcessList", + "address": console_info.ConsoleProcessList.vol.offset, + "data": "", + } + ) + for index, attached_proc in enumerate( + console_info.get_processes() + ): + console_properties.append( + { + "level": 2, + "name": f"_CONSOLE_INFORMATION.ConsoleProcessList.ConsoleProcess_{index}", + "address": attached_proc.ConsoleProcess.dereference().vol.offset, + "data": "", + } + ) + console_properties.append( + { + "level": 2, + "name": f"_CONSOLE_INFORMATION.ConsoleProcessList.ConsoleProcess_{index}_ProcessId", + "address": attached_proc.ConsoleProcess.ProcessId.vol.offset, + "data": attached_proc.ConsoleProcess.ProcessId, + } + ) + console_properties.append( + { + "level": 2, + "name": f"_CONSOLE_INFORMATION.ConsoleProcessList.ConsoleProcess_{index}_ProcessHandle", + "address": attached_proc.ConsoleProcess.ProcessHandle.vol.offset, + "data": hex( + attached_proc.ConsoleProcess.ProcessHandle + ), + } + ) + + vollog.debug( + f"Getting ExeAliasList entries for {console_info.ExeAliasList}" + ) + console_properties.append( + { + "level": 1, + "name": "_CONSOLE_INFORMATION.ExeAliasList", + "address": console_info.ExeAliasList.vol.offset, + "data": "", + } + ) + if console_info.ExeAliasList: + for index, exe_alias_list in enumerate( + console_info.get_exe_aliases() + ): + try: + console_properties.append( + { + "level": 2, + "name": f"_CONSOLE_INFORMATION.ExeAliasList.AliasList_{index}", + "address": exe_alias_list.vol.offset, + "data": "", + } + ) + console_properties.append( + { + "level": 2, + "name": f"_CONSOLE_INFORMATION.ExeAliasList.AliasList_{index}.ExeName", + "address": exe_alias_list.ExeName.vol.offset, + "data": exe_alias_list.get_exename(), + } + ) + for alias_index, alias in enumerate( + exe_alias_list.get_aliases() + ): + console_properties.append( + { + "level": 3, + "name": f"_CONSOLE_INFORMATION.ExeAliasList.AliasList_{index}.Alias_{alias_index}.Source", + "address": alias.Source.vol.offset, + "data": alias.get_source(), + } + ) + console_properties.append( + { + "level": 3, + "name": f"_CONSOLE_INFORMATION.ExeAliasList.AliasList_{index}.Alias_{alias_index}.Target", + "address": alias.Target.vol.offset, + "data": alias.get_target(), + } + ) + except Exception as e: + vollog.debug( + f"reading {exe_alias_list} encountered exception {e}" + ) + + vollog.debug( + f"Getting HistoryList entries for {console_info.HistoryList}" + ) + console_properties.append( + { + "level": 1, + "name": "_CONSOLE_INFORMATION.HistoryList", + "address": console_info.HistoryList.vol.offset, + "data": "", + } + ) + for index, command_history in enumerate( + console_info.get_histories() + ): + try: + console_properties.append( + { + "level": 2, + "name": f"_CONSOLE_INFORMATION.HistoryList.CommandHistory_{index}", + "address": command_history.vol.offset, + "data": "", + } + ) + console_properties.append( + { + "level": 2, + "name": f"_CONSOLE_INFORMATION.HistoryList.CommandHistory_{index}_Application", + "address": command_history.Application.vol.offset, + "data": command_history.get_application(), + } + ) + console_properties.append( + { + "level": 2, + "name": f"_CONSOLE_INFORMATION.HistoryList.CommandHistory_{index}_ProcessHandle", + "address": command_history.ConsoleProcessHandle.ProcessHandle.vol.offset, + "data": hex( + command_history.ConsoleProcessHandle.ProcessHandle + ), + } + ) + console_properties.append( + { + "level": 2, + "name": f"_CONSOLE_INFORMATION.HistoryList.CommandHistory_{index}_CommandCount", + "address": None, + "data": command_history.CommandCount, + } + ) + console_properties.append( + { + "level": 2, + "name": f"_CONSOLE_INFORMATION.HistoryList.CommandHistory_{index}_LastDisplayed", + "address": command_history.LastDisplayed.vol.offset, + "data": command_history.LastDisplayed, + } + ) + for ( + cmd_index, + bucket_cmd, + ) in command_history.get_commands(): + try: + console_properties.append( + { + "level": 3, + "name": f"_CONSOLE_INFORMATION.HistoryList.CommandHistory_{index}_Command_{cmd_index}", + "address": bucket_cmd.vol.offset, + "data": bucket_cmd.get_command_string(), + } + ) + except Exception as e: + vollog.debug( + f"reading {bucket_cmd} encountered exception {e}" + ) + except Exception as e: + vollog.debug( + f"reading {command_history} encountered exception {e}" + ) + + try: + vollog.debug( + f"Getting ScreenBuffer entries for {console_info}" + ) + console_properties.append( + { + "level": 1, + "name": "_CONSOLE_INFORMATION.CurrentScreenBuffer", + "address": console_info.CurrentScreenBuffer.vol.offset, + "data": "", + } + ) + for screen_index, screen_info in enumerate( + console_info.get_screens() + ): + try: + console_properties.append( + { + "level": 2, + "name": f"_CONSOLE_INFORMATION.ScreenBuffer_{screen_index}", + "address": screen_info, + "data": "", + } + ) + console_properties.append( + { + "level": 2, + "name": f"_CONSOLE_INFORMATION.ScreenBuffer_{screen_index}.ScreenX", + "address": None, + "data": screen_info.ScreenX, + } + ) + console_properties.append( + { + "level": 2, + "name": f"_CONSOLE_INFORMATION.ScreenBuffer_{screen_index}.ScreenY", + "address": None, + "data": screen_info.ScreenY, + } + ) + console_properties.append( + { + "level": 2, + "name": f"_CONSOLE_INFORMATION.ScreenBuffer_{screen_index}.Dump", + "address": None, + "data": "\n".join(screen_info.get_buffer()), + } + ) + except Exception as e: + vollog.debug( + f"reading {screen_info} encountered exception {e}" + ) + except Exception as e: + vollog.debug( + f"reading _CONSOLE_INFORMATION.CurrentScreenBuffer encountered exception {e}" + ) + + except exceptions.PagedInvalidAddressException as exp: + vollog.debug( + f"Required memory at {exp.invalid_address:#x} is not valid" + ) + continue + + if console_info and console_properties: + found_console_info_for_proc = True + yield conhost_proc, console_info, console_properties + + if not found_console_info_for_proc: + yield conhost_proc, console_info or None, [] + + @classmethod + def get_console_settings_from_registry( + cls, + context: interfaces.context.ContextInterface, + config_path: str, + kernel_layer_name: str, + kernel_symbol_table_name: str, + max_history: Set[int], + max_buffers: Set[int], + ) -> Tuple[Set[int], Set[int]]: + """ + Walks the Registry user hives and extracts any CommandHistorySize and HistoryBufferMax values + for scanning + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + config_path: The config path where to find symbol files + kernel_layer_name: The name of the layer on which to operate + kernel_symbol_table_name: The name of the table containing the kernel symbols + max_history: an initial set of CommandHistorySize values + max_buffers: an initial list of HistoryBufferMax values + + Returns: + The updated max_history and max_buffers sets. + """ + vollog.debug( + f"Possible CommandHistorySize values before checking Registry: {max_history}" + ) + vollog.debug( + f"Possible HistoryBufferMax values before checking Registry: {max_buffers}" + ) + + for hive in hivelist.HiveList.list_hives( + context=context, + base_config_path=config_path, + layer_name=kernel_layer_name, + symbol_table=kernel_symbol_table_name, + hive_offsets=None, + ): + try: + for value in hive.get_key("Console").get_values(): + val_name = str(value.get_name()) + if val_name == "HistoryBufferSize": + max_history.add(value.decode_data()) + elif val_name == "NumberOfHistoryBuffers": + max_buffers.add(value.decode_data()) + except Exception: + continue + + return max_history, max_buffers + + def _generator( + self, procs: Generator[interfaces.objects.ObjectInterface, None, None] + ): + """ + Generates the console information to use in rendering + + Args: + procs: the process list filtered to conhost.exe instances + """ + + kernel = self.context.modules[self.config["kernel"]] + + max_history = set(self.config.get("max_history", [50])) + max_buffers = set(self.config.get("max_buffers", [4])) + no_registry = self.config.get("no_registry") + + if no_registry is False: + max_history, max_buffers = self.get_console_settings_from_registry( + self.context, + self.config_path, + kernel.layer_name, + kernel.symbol_table_name, + max_history, + max_buffers, + ) + + vollog.debug(f"Possible CommandHistorySize values: {max_history}") + vollog.debug(f"Possible HistoryBufferMax values: {max_buffers}") + + proc = None + for proc, console_info, console_properties in self.get_console_info( + self.context, + kernel.layer_name, + kernel.symbol_table_name, + self.config_path, + procs, + max_history, + max_buffers, + ): + process_name = utility.array_to_string(proc.ImageFileName) + process_pid = proc.UniqueProcessId + + if console_info and console_properties: + for console_property in console_properties: + yield ( + console_property["level"], + ( + process_pid, + process_name, + format_hints.Hex(console_info.vol.offset), + console_property["name"], + ( + renderers.NotApplicableValue() + if console_property["address"] is None + else format_hints.Hex(console_property["address"]) + ), + ( + str(console_property["data"]) + if console_property["data"] + else renderers.NotAvailableValue() + ), + ), + ) + else: + yield ( + 0, + ( + process_pid, + process_name, + ( + format_hints.Hex(console_info.vol.offset) + if console_info + else renderers.NotApplicableValue() + ), + "_CONSOLE_INFORMATION", + renderers.NotApplicableValue(), + "Console Information Not Found", + ), + ) + + if proc is None: + vollog.warn("No conhost.exe processes found.") + + def _conhost_proc_filter(self, proc: interfaces.objects.ObjectInterface) -> bool: + """ + Used to filter to only conhost.exe processes + """ + process_name = utility.array_to_string(proc.ImageFileName) + + return process_name.lower() != "conhost.exe" + + def run(self): + kernel = self.context.modules[self.config["kernel"]] + + return renderers.TreeGrid( + [ + ("PID", int), + ("Process", str), + ("ConsoleInfo", format_hints.Hex), + ("Property", str), + ("Address", format_hints.Hex), + ("Data", str), + ], + self._generator( + pslist.PsList.list_processes( + context=self.context, + layer_name=kernel.layer_name, + symbol_table=kernel.symbol_table_name, + filter_func=self._conhost_proc_filter, + ) + ), + ) diff --git a/volatility3/framework/plugins/windows/crashinfo.py b/volatility3/framework/plugins/windows/crashinfo.py index 4ecd850872..862eb60807 100644 --- a/volatility3/framework/plugins/windows/crashinfo.py +++ b/volatility3/framework/plugins/windows/crashinfo.py @@ -46,9 +46,9 @@ def _generator(self, layer: crash.WindowsCrashDump32Layer): bitmap_size = format_hints.Hex(summary_header.BitmapSize) bitmap_pages = format_hints.Hex(summary_header.Pages) else: - bitmap_header_size = ( - bitmap_size - ) = bitmap_pages = renderers.NotApplicableValue() + bitmap_header_size = bitmap_size = bitmap_pages = ( + renderers.NotApplicableValue() + ) yield ( 0, diff --git a/volatility3/framework/plugins/windows/debugregisters.py b/volatility3/framework/plugins/windows/debugregisters.py new file mode 100644 index 0000000000..57dd1822c9 --- /dev/null +++ b/volatility3/framework/plugins/windows/debugregisters.py @@ -0,0 +1,211 @@ +# 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 + +# Full details on the techniques used in these plugins to detect EDR-evading malware +# can be found in our 20 page whitepaper submitted to DEFCON along with the presentation +# https://www.volexity.com/wp-content/uploads/2024/08/Defcon24_EDR_Evasion_Detection_White-Paper_Andrew-Case.pdf + +import logging + +from typing import Tuple, Optional, Generator, List, Dict + +from functools import partial + +from volatility3.framework import renderers, interfaces, exceptions +from volatility3.framework.configuration import requirements +from volatility3.framework.renderers import format_hints +import volatility3.plugins.windows.pslist as pslist +import volatility3.plugins.windows.threads as threads +import volatility3.plugins.windows.pe_symbols as pe_symbols + +vollog = logging.getLogger(__name__) + + +class DebugRegisters(interfaces.plugins.PluginInterface): + # version 2.6.0 adds support for scanning for 'Ethread' structures by pool tags + _required_framework_version = (2, 6, 0) + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Windows kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(2, 0, 0) + ), + requirements.VersionRequirement( + name="pe_symbols", component=pe_symbols.PESymbols, version=(1, 0, 0) + ), + ] + + @staticmethod + def _get_debug_info( + ethread: interfaces.objects.ObjectInterface, + ) -> Optional[Tuple[interfaces.objects.ObjectInterface, int, int, int, int, int]]: + """ + Gathers information related to the debug registers for the given thread + Args: + ethread: the thread (_ETHREAD) to examine + Returns: + Tuple[interfaces.objects.ObjectInterface, int, int, int, int, int]: The owner process of the thread and the values for dr7, dr0, dr1, dr2, dr3 + """ + try: + dr7 = ethread.Tcb.TrapFrame.Dr7 + state = ethread.Tcb.State + except exceptions.InvalidAddressException: + return None + + # 0 = debug registers not active + # 4 = terminated + if dr7 == 0 or state == 4: + return None + + try: + owner_proc = ethread.owning_process() + except (AttributeError, exceptions.InvalidAddressException): + return None + + dr0 = ethread.Tcb.TrapFrame.Dr0 + dr1 = ethread.Tcb.TrapFrame.Dr1 + dr2 = ethread.Tcb.TrapFrame.Dr2 + dr3 = ethread.Tcb.TrapFrame.Dr3 + + # bail if all are 0 + if not (dr0 or dr1 or dr2 or dr3): + return None + + return owner_proc, dr7, dr0, dr1, dr2, dr3 + + def _generator( + self, + ) -> Generator[ + Tuple[ + int, + Tuple[ + str, + int, + int, + int, + int, + format_hints.Hex, + str, + str, + format_hints.Hex, + str, + str, + format_hints.Hex, + str, + str, + format_hints.Hex, + str, + str, + ], + ], + None, + None, + ]: + kernel = self.context.modules[self.config["kernel"]] + + vads_cache: Dict[int, pe_symbols.ranges_type] = {} + + proc_modules = None + + procs = pslist.PsList.list_processes( + context=self.context, + layer_name=kernel.layer_name, + symbol_table=kernel.symbol_table_name, + ) + + for proc in procs: + for thread in threads.Threads.list_threads(kernel, proc): + debug_info = self._get_debug_info(thread) + if not debug_info: + continue + + owner_proc, dr7, dr0, dr1, dr2, dr3 = debug_info + + vads = pe_symbols.PESymbols.get_vads_for_process_cache( + vads_cache, owner_proc + ) + if not vads: + continue + + # this lookup takes a while, so only perform if we need to + if not proc_modules: + proc_modules = pe_symbols.PESymbols.get_process_modules( + self.context, kernel.layer_name, kernel.symbol_table_name, None + ) + path_and_symbol = partial( + pe_symbols.PESymbols.path_and_symbol_for_address, + self.context, + self.config_path, + proc_modules, + ) + + file0, sym0 = path_and_symbol(vads, dr0) + file1, sym1 = path_and_symbol(vads, dr1) + file2, sym2 = path_and_symbol(vads, dr2) + file3, sym3 = path_and_symbol(vads, dr3) + + # if none map to an actual file VAD then bail + if not (file0 or file1 or file2 or file3): + continue + + process_name = owner_proc.ImageFileName.cast( + "string", + max_length=owner_proc.ImageFileName.vol.count, + errors="replace", + ) + + thread_tid = thread.Cid.UniqueThread + + yield ( + 0, + ( + process_name, + owner_proc.UniqueProcessId, + thread_tid, + thread.Tcb.State, + dr7, + format_hints.Hex(dr0), + file0 or renderers.NotApplicableValue(), + sym0 or renderers.NotApplicableValue(), + format_hints.Hex(dr1), + file1 or renderers.NotApplicableValue(), + sym1 or renderers.NotApplicableValue(), + format_hints.Hex(dr2), + file2 or renderers.NotApplicableValue(), + sym2 or renderers.NotApplicableValue(), + format_hints.Hex(dr3), + file3 or renderers.NotApplicableValue(), + sym3 or renderers.NotApplicableValue(), + ), + ) + + def run(self) -> renderers.TreeGrid: + return renderers.TreeGrid( + [ + ("Process", str), + ("PID", int), + ("TID", int), + ("State", int), + ("Dr7", int), + ("Dr0", format_hints.Hex), + ("Range0", str), + ("Symbol0", str), + ("Dr1", format_hints.Hex), + ("Range1", str), + ("Symbol1", str), + ("Dr2", format_hints.Hex), + ("Range2", str), + ("Symbol2", str), + ("Dr3", format_hints.Hex), + ("Range3", str), + ("Symbol3", str), + ], + self._generator(), + ) diff --git a/volatility3/framework/plugins/windows/dlllist.py b/volatility3/framework/plugins/windows/dlllist.py index d73cea652f..5a1b37fcf8 100644 --- a/volatility3/framework/plugins/windows/dlllist.py +++ b/volatility3/framework/plugins/windows/dlllist.py @@ -1,10 +1,10 @@ -# 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 from volatility3.framework import constants, exceptions, interfaces, renderers @@ -13,7 +13,7 @@ from volatility3.framework.symbols import intermed from volatility3.framework.symbols.windows.extensions import pe from volatility3.plugins import timeliner -from volatility3.plugins.windows import info, pslist +from volatility3.plugins.windows import info, pslist, psscan, pedump vollog = logging.getLogger(__name__) @@ -22,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, 0) + _version = (3, 0, 0) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -36,6 +36,9 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(2, 0, 0) ), + requirements.VersionRequirement( + name="psscan", component=psscan.PsScan, version=(1, 1, 0) + ), requirements.VersionRequirement( name="info", component=info.Info, version=(1, 0, 0) ), @@ -45,73 +48,38 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] description="Process IDs to include (all other processes are excluded)", optional=True, ), + requirements.IntRequirement( + name="offset", + description="Process offset in the physical address space", + optional=True, + ), + requirements.StringRequirement( + name="name", + description="Specify a regular expression to match dll name(s)", + optional=True, + ), + requirements.IntRequirement( + name="base", + description="Specify a base virtual address in process memory", + optional=True, + ), + requirements.BooleanRequirement( + name="ignore-case", + description="Specify case insensitivity for the regular expression name matching", + default=False, + optional=True, + ), requirements.BooleanRequirement( name="dump", description="Extract listed DLLs", 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 @@ -136,9 +104,35 @@ def _generator(self, procs): BaseDllName = FullDllName = renderers.UnreadableValue() with contextlib.suppress(exceptions.InvalidAddressException): BaseDllName = entry.BaseDllName.get_string() - # We assume that if the BaseDllName points to an invalid buffer, so will FullDllName + # We assume that if BaseDllName points to invalid buffer, so will FullDllName FullDllName = entry.FullDllName.get_string() + # Check if a name regex was passed and apply it to only show matches + if self.config["name"]: + try: + flags = re.I if self.config["ignore-case"] else 0 + mod_re = re.compile(self.config["name"], flags) + except re.error: + vollog.debug( + "Error parsing regular expression: %s", self.config["name"] + ) + return None + + # If Base or Full Dll Name are invalid, move on + if isinstance(BaseDllName, renderers.UnreadableValue) or isinstance( + FullDllName, renderers.UnreadableValue + ): + continue + + # If regex does not match, move on + if not mod_re.search(BaseDllName) and not mod_re.search( + FullDllName + ): + continue + + if self.config["base"] and self.config["base"] != entry.DllBase: + continue + if dll_load_time_field: # Versions prior to 6.1 won't have the LoadTime attribute # and 32bit version shouldn't have the Quadpart according to MSDN @@ -153,7 +147,7 @@ def _generator(self, procs): 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, @@ -161,10 +155,10 @@ def _generator(self, procs): 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: @@ -221,6 +215,25 @@ def run(self): filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) kernel = self.context.modules[self.config["kernel"]] + if self.config["offset"]: + procs = psscan.PsScan.scan_processes( + self.context, + kernel.layer_name, + kernel.symbol_table_name, + filter_func=psscan.PsScan.create_offset_filter( + self.context, + kernel.layer_name, + self.config["offset"], + ), + ) + else: + procs = pslist.PsList.list_processes( + context=self.context, + layer_name=kernel.layer_name, + symbol_table=kernel.symbol_table_name, + filter_func=filter_func, + ) + return renderers.TreeGrid( [ ("PID", int), @@ -232,12 +245,5 @@ def run(self): ("LoadTime", datetime.datetime), ("File output", str), ], - self._generator( - pslist.PsList.list_processes( - context=self.context, - layer_name=kernel.layer_name, - symbol_table=kernel.symbol_table_name, - filter_func=filter_func, - ) - ), + self._generator(procs=procs), ) diff --git a/volatility3/framework/plugins/windows/driverirp.py b/volatility3/framework/plugins/windows/driverirp.py index b5cd33db76..c3eb7c5c16 100644 --- a/volatility3/framework/plugins/windows/driverirp.py +++ b/volatility3/framework/plugins/windows/driverirp.py @@ -44,6 +44,7 @@ class DriverIrp(interfaces.plugins.PluginInterface): """List IRPs for drivers in a particular windows memory image.""" _required_framework_version = (2, 0, 0) + _version = (1, 0, 0) @classmethod def get_requirements(cls): @@ -81,7 +82,10 @@ def _generator(self): address ) + module_found = False + for module_name, symbol_generator in module_symbols: + module_found = True symbols_found = False for symbol in symbol_generator: @@ -111,6 +115,19 @@ def _generator(self): ), ) + if not module_found: + yield ( + 0, + ( + format_hints.Hex(driver.vol.offset), + driver_name, + MAJOR_FUNCTIONS[i], + format_hints.Hex(address), + renderers.NotAvailableValue(), + renderers.NotAvailableValue(), + ), + ) + def run(self): return renderers.TreeGrid( [ diff --git a/volatility3/framework/plugins/windows/dumpfiles.py b/volatility3/framework/plugins/windows/dumpfiles.py index 38d55d15dc..33d2d0d41d 100755 --- a/volatility3/framework/plugins/windows/dumpfiles.py +++ b/volatility3/framework/plugins/windows/dumpfiles.py @@ -4,11 +4,12 @@ import logging import ntpath +import re from typing import List, Tuple, Type, Optional, Generator from volatility3.framework import interfaces, renderers, exceptions, constants from volatility3.framework.configuration import requirements -from volatility3.framework.renderers import format_hints +from volatility3.framework.renderers import format_hints, UnreadableValue from volatility3.plugins.windows import handles from volatility3.plugins.windows import pslist @@ -53,6 +54,17 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] description="Dump a single _FILE_OBJECT at this physical address", optional=True, ), + requirements.StringRequirement( + name="filter", + description="Dump files matching regular expression FILTER", + optional=True, + ), + requirements.BooleanRequirement( + name="ignore-case", + description="Ignore case in filter match", + default=False, + optional=True, + ), requirements.VersionRequirement( name="pslist", component=pslist.PsList, version=(2, 0, 0) ), @@ -130,7 +142,7 @@ def process_file_object( constants.LOGLEVEL_VVV, f"The file object at {file_obj.vol.offset:#x} is not a file on disk", ) - return + return None # Depending on the type of object (DataSection, ImageSection, SharedCacheMap) we may need to # read from the memory layer or the primary layer. @@ -208,6 +220,10 @@ def process_file_object( def _generator(self, procs: List, offsets: List): kernel = self.context.modules[self.config["kernel"]] + file_re = None + if self.config["filter"]: + flags = re.I if self.config["ignore-case"] else 0 + file_re = re.compile(self.config["filter"], flags) if procs: # The handles plugin doesn't expose any staticmethod/classmethod, and it also requires stashing @@ -228,6 +244,8 @@ def _generator(self, procs: List, offsets: List): symbol_table=kernel.symbol_table_name, ) + dumped_files = set() + for proc in procs: try: object_table = proc.ObjectTable @@ -243,6 +261,18 @@ def _generator(self, procs: List, offsets: List): obj_type = entry.get_object_type(type_map, cookie) if obj_type == "File": file_obj = entry.Body.cast("_FILE_OBJECT") + + if file_re: + name = file_obj.file_name_with_device() + if isinstance(name, UnreadableValue): + continue + if not file_re.search(name): + continue + + if file_obj.vol.offset in dumped_files: + continue + dumped_files.add(file_obj.vol.offset) + for result in self.process_file_object( self.context, kernel.layer_name, self.open, file_obj ): @@ -272,6 +302,17 @@ def _generator(self, procs: List, offsets: List): if not file_obj.is_valid(): continue + if file_re: + name = file_obj.file_name_with_device() + if isinstance(name, UnreadableValue): + continue + if not file_re.search(name): + continue + + if file_obj.vol.offset in dumped_files: + continue + dumped_files.add(file_obj.vol.offset) + for result in self.process_file_object( self.context, kernel.layer_name, self.open, file_obj ): @@ -315,6 +356,11 @@ def run(self): procs = list() kernel = self.context.modules[self.config["kernel"]] + if self.config["filter"] and ( + self.config["virtaddr"] or self.config["physaddr"] + ): + raise ValueError("Cannot use filter flag with an address flag") + if self.config.get("virtaddr", None) is not None: offsets.append((self.config["virtaddr"], True)) elif self.config.get("physaddr", None) is not None: diff --git a/volatility3/framework/plugins/windows/filescan.py b/volatility3/framework/plugins/windows/filescan.py index 0f68f39d47..82566361de 100644 --- a/volatility3/framework/plugins/windows/filescan.py +++ b/volatility3/framework/plugins/windows/filescan.py @@ -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 @@ def _generator(self): 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(), ) diff --git a/volatility3/framework/plugins/windows/handles.py b/volatility3/framework/plugins/windows/handles.py index dd7c90860a..3e5a2fd826 100644 --- a/volatility3/framework/plugins/windows/handles.py +++ b/volatility3/framework/plugins/windows/handles.py @@ -9,7 +9,7 @@ from volatility3.framework.configuration import requirements from volatility3.framework.objects import utility from volatility3.framework.renderers import format_hints -from volatility3.plugins.windows import pslist +from volatility3.plugins.windows import pslist, psscan vollog = logging.getLogger(__name__) @@ -25,7 +25,7 @@ class Handles(interfaces.plugins.PluginInterface): """Lists process open handles.""" _required_framework_version = (2, 0, 0) - _version = (1, 0, 0) + _version = (1, 0, 2) def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -43,14 +43,22 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] description="Windows kernel", architectures=["Intel32", "Intel64"], ), + requirements.PluginRequirement( + name="pslist", plugin=pslist.PsList, version=(2, 0, 0) + ), + requirements.VersionRequirement( + name="psscan", component=psscan.PsScan, version=(1, 1, 0) + ), requirements.ListRequirement( name="pid", element_type=int, description="Process IDs to include (all other processes are excluded)", optional=True, ), - requirements.PluginRequirement( - name="pslist", plugin=pslist.PsList, version=(2, 0, 0) + requirements.IntRequirement( + name="offset", + description="Process offset in the physical address space", + optional=True, ), ] @@ -134,9 +142,13 @@ def find_sar_value(self): 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: + vollog.debug( + "capstone module is missing, unable to create disassembly of ObpCaptureHandleInformationEx" + ) return None kernel = self.context.modules[self.config["kernel"]] @@ -151,25 +163,48 @@ def find_sar_value(self): try: func_addr = ntkrnlmp.get_symbol("ObpCaptureHandleInformationEx").address except exceptions.SymbolError: + vollog.debug("Unable to locate ObpCaptureHandleInformationEx symbol") return None - data = self.context.layers.read(virtual_layer_name, kvo + func_addr, 0x200) - if data is None: - return None + try: + func_addr_to_read = kvo + func_addr + num_bytes_to_read = 0x200 + vollog.debug( + f"ObpCaptureHandleInformationEx symbol located at {hex(func_addr_to_read)}" + ) + data = self.context.layers.read( + virtual_layer_name, func_addr_to_read, num_bytes_to_read + ) + except exceptions.InvalidAddressException: + 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)}" + ) + self._sar_value = DEFAULT_SAR_VALUE + return self._sar_value md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64) + instruction_count = 0 for address, size, mnemonic, op_str in md.disasm_lite( data, kvo + func_addr ): # print("{} {} {} {}".format(address, size, mnemonic, op_str)) - + instruction_count += 1 if mnemonic.startswith("sar"): # if we don't want to parse op strings, we can disasm the # single sar instruction again, but we use disasm_lite for speed self._sar_value = int(op_str.split(",")[1].strip(), 16) + vollog.debug( + f"SAR located at {hex(address)} with value of {hex(self._sar_value)}" + ) break + if self._sar_value is None: + 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 @classmethod @@ -285,7 +320,7 @@ def _make_handle_array(self, offset, level, depth=0): count = 0x1000 / subtype.size if not self.context.layers[virtual].is_valid(offset): - return + return None table = ntkrnlmp.object( object_type="array", @@ -335,7 +370,7 @@ def handles(self, handle_table): constants.LOGLEVEL_VVV, "Handle table parsing was aborted due to an invalid address exception", ) - return + return None for handle_table_entry in self._make_handle_array(TableCode, table_levels): yield handle_table_entry @@ -416,6 +451,25 @@ def run(self): filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) kernel = self.context.modules[self.config["kernel"]] + if self.config["offset"]: + procs = psscan.PsScan.scan_processes( + self.context, + kernel.layer_name, + kernel.symbol_table_name, + filter_func=psscan.PsScan.create_offset_filter( + self.context, + kernel.layer_name, + self.config["offset"], + ), + ) + else: + procs = pslist.PsList.list_processes( + context=self.context, + layer_name=kernel.layer_name, + symbol_table=kernel.symbol_table_name, + filter_func=filter_func, + ) + return renderers.TreeGrid( [ ("PID", int), @@ -426,12 +480,5 @@ def run(self): ("GrantedAccess", format_hints.Hex), ("Name", str), ], - self._generator( - pslist.PsList.list_processes( - self.context, - kernel.layer_name, - kernel.symbol_table_name, - filter_func=filter_func, - ) - ), + self._generator(procs=procs), ) diff --git a/volatility3/framework/plugins/windows/hollowprocesses.py b/volatility3/framework/plugins/windows/hollowprocesses.py new file mode 100644 index 0000000000..69fa94f063 --- /dev/null +++ b/volatility3/framework/plugins/windows/hollowprocesses.py @@ -0,0 +1,237 @@ +# 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 NamedTuple, Dict, Generator + +from volatility3.framework import interfaces, exceptions, constants +from volatility3.framework import renderers +from volatility3.framework.configuration import requirements +from volatility3.framework.objects import utility +from volatility3.plugins.windows import pslist, vadinfo + +vollog = logging.getLogger(__name__) + +VadData = NamedTuple( + "VadData", + [ + ("protection", str), + ("path", str), + ], +) + +DLLData = NamedTuple( + "DLLData", + [ + ("path", str), + ], +) + +### Useful references on process hollowing +# https://cysinfo.com/detecting-deceptive-hollowing-techniques/ +# https://github.com/m0n0ph1/Process-Hollowing + + +class HollowProcesses(interfaces.plugins.PluginInterface): + """Lists hollowed processes""" + + _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.ListRequirement( + name="pid", + element_type=int, + description="Process IDs to include (all other processes are excluded)", + optional=True, + ), + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(2, 0, 0) + ), + requirements.VersionRequirement( + name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0) + ), + ] + + def _get_vads_data( + self, proc: interfaces.objects.ObjectInterface + ) -> Dict[int, VadData]: + """ + Returns a dictionary of: + base address -> (protection string, file name) + For each mapped VAD in the process. This is used + for quick lookups of data and matching the DLL + at the same base address as the VAD + """ + vads = {} + + kernel = self.context.modules[self.config["kernel"]] + + for vad in proc.get_vad_root().traverse(): + protection_string = vad.get_protection( + vadinfo.VadInfo.protect_values( + self.context, kernel.layer_name, kernel.symbol_table_name + ), + vadinfo.winnt_protections, + ) + + fn = vad.get_file_name() + if not fn or not isinstance(fn, str): + fn = "" + + vads[vad.get_start()] = VadData(protection_string, fn) + + return vads + + def _get_dlls_map( + self, proc: interfaces.objects.ObjectInterface + ) -> Dict[int, DLLData]: + """ + Returns a dictionary of: + base address -> path + for each DLL loaded in the process + + This is used to cross compare with + the corresponding VAD and to have a + backup path source in case of smear + in the VAD + """ + dlls = {} + + for entry in proc.load_order_modules(): + try: + base = entry.DllBase + except exceptions.InvalidAddressException: + continue + + try: + FullDllName = entry.FullDllName.get_string() + except exceptions.InvalidAddressException: + FullDllName = renderers.UnreadableValue() + + dlls[base] = DLLData(FullDllName) + + return dlls + + def _get_image_base(self, proc: interfaces.objects.ObjectInterface) -> int: + """ + Uses the PEB to get the image base of the process + """ + kernel = self.context.modules[self.config["kernel"]] + + try: + proc_layer_name = proc.add_process_layer() + peb = self.context.object( + kernel.symbol_table_name + constants.BANG + "_PEB", + layer_name=proc_layer_name, + offset=proc.Peb, + ) + return peb.ImageBaseAddress + except exceptions.InvalidAddressException: + return None + + def _check_load_address(self, proc, _, __) -> Generator[str, None, None]: + """ + Detects when the image base in the PEB, which is writable by process malware, + does not match the section base address - whose value lives in kernel memory. + Many malware samples will manipulate their image base to fool AVs/EDRs and + as a necessary part of certain hollowing techniques + """ + image_base = self._get_image_base(proc) + if image_base is not None and image_base != proc.SectionBaseAddress: + yield "The ImageBaseAddress reported from the PEB ({:#x}) does not match the process SectionBaseAddress ({:#x})".format( + image_base, proc.SectionBaseAddress + ) + + def _check_exe_protection( + self, proc, vads: Dict[int, VadData], __ + ) -> Generator[str, None, None]: + """ + Legitimately mapped application executables and DLLs + will have a VAD present and its initial protection will be + PAGE_EXECUTE_WRITECOPY. + Many process hollowing and code injection techniques will + unmap the real executable and/or map in executables with + incorrect permissions. + This check verifies the VAD for the application exe. + `_check_dlls_protection` checks for DLLs mapped in the process. + """ + base = proc.SectionBaseAddress + + if base not in vads: + yield "There is no VAD starting at the base address of the process executable ({:#x})".format( + base + ) + elif vads[base].protection != "PAGE_EXECUTE_WRITECOPY": + yield "Unexpected protection ({}) for VAD hosting the process executable ({:#x}) with path {}".format( + vads[base].protection, base, vads[base].path + ) + + def _check_dlls_protection( + self, _, vads: Dict[int, VadData], dlls: Dict[int, DLLData] + ) -> Generator[str, None, None]: + for dll_base in dlls: + # could be malicious but triggers too many FPs from smear + if dll_base not in vads: + continue + + # PAGE_EXECUTE_WRITECOPY is the only valid permission for mapped DLLs and .exe files + if vads[dll_base].protection != "PAGE_EXECUTE_WRITECOPY": + yield "Unexpected protection ({}) for DLL in the PEB's load order list ({:#x}) with path {}".format( + vads[dll_base].protection, dll_base, dlls[dll_base].path + ) + + def _generator(self, procs): + checks = [ + self._check_load_address, + self._check_exe_protection, + self._check_dlls_protection, + ] + + for proc in procs: + # smear and/or terminated process + dlls = self._get_dlls_map(proc) + if len(dlls) < 3: + continue + + vads = self._get_vads_data(proc) + if len(vads) < 5: + continue + + proc_name = utility.array_to_string(proc.ImageFileName) + pid = proc.UniqueProcessId + + for check in checks: + for note in check(proc, vads, dlls): + yield 0, ( + pid, + proc_name, + note, + ) + + def run(self): + filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) + kernel = self.context.modules[self.config["kernel"]] + + return renderers.TreeGrid( + [ + ("PID", int), + ("Process", str), + ("Notes", 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, + ) + ), + ) diff --git a/volatility3/framework/plugins/windows/iat.py b/volatility3/framework/plugins/windows/iat.py new file mode 100644 index 0000000000..d2fdc0ad89 --- /dev/null +++ b/volatility3/framework/plugins/windows/iat.py @@ -0,0 +1,150 @@ +# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 + +import logging, io, pefile +from volatility3.framework.symbols import intermed +from volatility3.framework import renderers, interfaces, exceptions, constants +from volatility3.framework.configuration import requirements +from volatility3.plugins.windows import pslist +from volatility3.framework.renderers import format_hints +from volatility3.framework.symbols.windows.extensions import pe + +vollog = logging.getLogger(__name__) + + +class IAT(interfaces.plugins.PluginInterface): + """Extract Import Address Table to list API (functions) used by a program contained in external libraries""" + + _required_framework_version = (2, 4, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.ModuleRequirement( + name="kernel", + description="Windows kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.VersionRequirement( + name="pslist", component=pslist.PsList, version=(2, 0, 0) + ), + requirements.ListRequirement( + name="pid", + element_type=int, + description="Process ID to include (all other processes are excluded)", + optional=True, + ), + ] + + def _generator(self, procs): + kernel = self.context.modules[self.config["kernel"]] + + for proc in procs: + try: + proc_id = proc.UniqueProcessId + proc_layer_name = proc.add_process_layer() + peb = self.context.object( + kernel.symbol_table_name + constants.BANG + "_PEB", + layer_name=proc_layer_name, + offset=proc.Peb, + ) + + if proc_layer_name is None: + raise TypeError("add_process_layer failed") + + pe_table_name = intermed.IntermediateSymbolTable.create( + self.context, + self.config_path, + "windows", + "pe", + class_types=pe.class_types, + ) + pe_data = io.BytesIO() + + dos_header = self.context.object( + pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER", + offset=peb.ImageBaseAddress, + layer_name=proc_layer_name, + ) + + for offset, data in dos_header.reconstruct(): + pe_data.seek(offset) + pe_data.write(data) + + pe_obj = pefile.PE(data=pe_data.getvalue(), fast_load=True) + pe_obj.parse_data_directories( + [pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_IMPORT"]] + ) + if hasattr(pe_obj, "DIRECTORY_ENTRY_IMPORT"): + for entry in pe_obj.DIRECTORY_ENTRY_IMPORT: + dll_entry = entry.dll + if dll_entry: + dll_entry = dll_entry.decode() + else: + dll_entry = renderers.NotAvailableValue + + bound = True + # Initially set to 0 if not bound + time_date_stamp = entry.struct.TimeDateStamp + if not time_date_stamp: + bound = False + + # Iterate over imported functions + for imp in entry.imports: + import_name = imp.name + if import_name: + import_name = imp.name.decode() + else: + import_name = renderers.NotAvailableValue() + function_address = ( + pe_obj.OPTIONAL_HEADER.ImageBase + imp.address + ) + if not function_address: + function_address = renderers.NotAvailableValue + + yield ( + 0, + ( + proc_id, + proc.ImageFileName.cast( + "string", + max_length=proc.ImageFileName.vol.count, + errors="replace", + ), + dll_entry, + bound, + import_name, + format_hints.Hex(function_address), + ), + ) + except exceptions.InvalidAddressException as excp: + vollog.debug( + "Process {}: invalid address {} in layer {}".format( + proc_id, excp.invalid_address, excp.layer_name + ) + ) + continue + + def run(self): + kernel = self.context.modules[self.config["kernel"]] + + return renderers.TreeGrid( + [ + ("PID", int), + ("Name", str), + ("Library", str), + ("Bound", bool), + ("Function", str), + ("Address", format_hints.Hex), + ], + self._generator( + pslist.PsList.list_processes( + context=self.context, + layer_name=kernel.layer_name, + symbol_table=kernel.symbol_table_name, + filter_func=pslist.PsList.create_pid_filter( + self.config.get("pid", None) + ), + ) + ), + ) diff --git a/volatility3/framework/plugins/windows/info.py b/volatility3/framework/plugins/windows/info.py index 100a677c2e..137d29c225 100644 --- a/volatility3/framework/plugins/windows/info.py +++ b/volatility3/framework/plugins/windows/info.py @@ -10,7 +10,7 @@ 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 @@ def get_kdbg_structure( "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 @@ def get_ntheader_structure( interfaces.configuration.path_join(config_path, "pe"), "windows", "pe", - class_types=extensions.pe.class_types, + class_types=pe.class_types, ) dos_header = context.object( diff --git a/volatility3/framework/plugins/windows/kpcrs.py b/volatility3/framework/plugins/windows/kpcrs.py new file mode 100644 index 0000000000..558ea844c9 --- /dev/null +++ b/volatility3/framework/plugins/windows/kpcrs.py @@ -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(), + ) diff --git a/volatility3/framework/plugins/windows/ldrmodules.py b/volatility3/framework/plugins/windows/ldrmodules.py index 4c8456fa96..a888f22e18 100644 --- a/volatility3/framework/plugins/windows/ldrmodules.py +++ b/volatility3/framework/plugins/windows/ldrmodules.py @@ -1,3 +1,9 @@ +# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +import logging + from volatility3.framework import constants, exceptions, interfaces, renderers from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints @@ -5,12 +11,14 @@ from volatility3.framework.symbols.windows.extensions import pe from volatility3.plugins.windows import pslist, vadinfo +vollog = logging.getLogger(__name__) + class LdrModules(interfaces.plugins.PluginInterface): """Lists the loaded modules in a particular windows memory image.""" _required_framework_version = (2, 0, 0) - _version = (1, 0, 0) + _version = (1, 0, 1) @classmethod def get_requirements(cls): @@ -39,14 +47,6 @@ def _generator(self, procs): 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() @@ -61,7 +61,7 @@ def filter_function(x: interfaces.objects.ObjectInterface) -> bool: # 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(), @@ -71,7 +71,11 @@ def filter_function(x: interfaces.objects.ObjectInterface) -> bool: # Filter out VADs that do not start with a MZ header if dos_header.e_magic != 0x5A4D: continue - except exceptions.PagedInvalidAddressException: + except exceptions.InvalidAddressException: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping vad at {hex(dos_header.vol.offset)} due to InvalidAddressException", + ) continue mapped_files[vad.get_start()] = vad.get_file_name() diff --git a/volatility3/framework/plugins/windows/lsadump.py b/volatility3/framework/plugins/windows/lsadump.py index 12589b07e5..da8dee3258 100644 --- a/volatility3/framework/plugins/windows/lsadump.py +++ b/volatility3/framework/plugins/windows/lsadump.py @@ -168,16 +168,16 @@ def _generator( lsakey = self.get_lsa_key(sechive, bootkey, vista_or_later) if not bootkey: vollog.warning("Unable to find bootkey") - return + return None if not lsakey: vollog.warning("Unable to find lsa key") - return + return None secrets_key = hashdump.Hashdump.get_hive_key(sechive, "Policy\\Secrets") if not secrets_key: vollog.warning("Unable to find secrets key") - return + return None for key in secrets_key.get_subkeys(): sec_val_key = hashdump.Hashdump.get_hive_key( diff --git a/volatility3/framework/plugins/windows/malfind.py b/volatility3/framework/plugins/windows/malfind.py index 4249259555..079274d698 100644 --- a/volatility3/framework/plugins/windows/malfind.py +++ b/volatility3/framework/plugins/windows/malfind.py @@ -110,7 +110,7 @@ def list_injections( proc_id, excp.invalid_address, excp.layer_name ) ) - return + return None proc_layer = context.layers[proc_layer_name] @@ -141,16 +141,30 @@ def _generator(self, procs): # determine if we're on a 32 or 64 bit kernel kernel = self.context.modules[self.config["kernel"]] + # set refined criteria to know when to add to "Notes" column + refined_criteria = { + b"MZ": "MZ header", + b"\x55\x8B": "PE header", + b"\x55\x48": "Function prologue", + b"\x55\x89": "Function prologue", + } + is_32bit_arch = not symbols.symbol_table_is_64bit( self.context, kernel.symbol_table_name ) for proc in procs: + # by default, "Notes" column will be set to N/A process_name = utility.array_to_string(proc.ImageFileName) for vad, data in self.list_injections( self.context, kernel.layer_name, kernel.symbol_table_name, proc ): + notes = renderers.NotApplicableValue() + # Check for unique headers and update "Notes" column if criteria is met + if data[0:2] in refined_criteria: + notes = refined_criteria[data[0:2]] + # if we're on a 64 bit kernel, we may still need 32 bit disasm due to wow64 if is_32bit_arch or proc.get_is_wow64(): architecture = "intel" @@ -196,6 +210,7 @@ def _generator(self, procs): vad.get_commit_charge(), vad.get_private_memory(), file_output, + notes, format_hints.HexBytes(data), disasm, ), @@ -216,6 +231,7 @@ def run(self): ("CommitCharge", int), ("PrivateMemory", int), ("File output", str), + ("Notes", str), ("Hexdump", format_hints.HexBytes), ("Disasm", interfaces.renderers.Disassembly), ], diff --git a/volatility3/framework/plugins/windows/mftscan.py b/volatility3/framework/plugins/windows/mftscan.py index 87416d274d..9e65853450 100644 --- a/volatility3/framework/plugins/windows/mftscan.py +++ b/volatility3/framework/plugins/windows/mftscan.py @@ -29,7 +29,7 @@ def get_requirements(cls): 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 @@ def _generator(self): # 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 @@ -53,7 +53,6 @@ def _generator(self): # get each of the individual Field Sets mft_object = symbol_table + constants.BANG + "MFT_ENTRY" attribute_object = symbol_table + constants.BANG + "ATTRIBUTE" - header_object = symbol_table + constants.BANG + "ATTR_HEADER" si_object = symbol_table + constants.BANG + "STANDARD_INFORMATION_ENTRY" fn_object = symbol_table + constants.BANG + "FILE_NAME_ENTRY" @@ -67,9 +66,8 @@ def _generator(self): ) # We will update this on each pass in the next loop and use it as the new offset. attr_base_offset = mft_record.FirstAttrOffset - - attr_header = self.context.object( - header_object, + attr = self.context.object( + attribute_object, offset=offset + attr_base_offset, layer_name=layer.name, ) @@ -77,17 +75,8 @@ def _generator(self): # There is no field that has a count of Attributes # Keep Attempting to read attributes until we get an invalid attr_header.AttrType - while attr_header.AttrType.is_valid_choice: - vollog.debug(f"Attr Type: {attr_header.AttrType.lookup()}") - - # Offset past the headers to the attribute data - attr_data_offset = ( - offset - + attr_base_offset - + self.context.symbol_space.get_type( - attribute_object - ).relative_child_offset("Attr_Data") - ) + while attr.Attr_Header.AttrType.is_valid_choice: + vollog.debug(f"Attr Type: {attr.Attr_Header.AttrType.lookup()}") # MFT Flags determine the file type or dir # If we don't have a valid enum, coerce to hex so we can keep the record @@ -97,19 +86,16 @@ def _generator(self): mft_flag = hex(mft_record.Flags) # Standard Information Attribute - if attr_header.AttrType.lookup() == "STANDARD_INFORMATION": - attr_data = self.context.object( - si_object, offset=attr_data_offset, layer_name=layer.name - ) - + if attr.Attr_Header.AttrType.lookup() == "STANDARD_INFORMATION": + attr_data = attr.Attr_Data.cast(si_object) yield 0, ( - format_hints.Hex(attr_data_offset), + format_hints.Hex(attr_data.vol.offset), mft_record.get_signature(), mft_record.RecordNumber, mft_record.LinkCount, mft_flag, renderers.NotApplicableValue(), - attr_header.AttrType.lookup(), + attr.Attr_Header.AttrType.lookup(), conversion.wintime_to_datetime(attr_data.CreationTime), conversion.wintime_to_datetime(attr_data.ModifiedTime), conversion.wintime_to_datetime(attr_data.UpdatedTime), @@ -118,10 +104,8 @@ def _generator(self): ) # File Name Attribute - if attr_header.AttrType.lookup() == "FILE_NAME": - attr_data = self.context.object( - fn_object, offset=attr_data_offset, layer_name=layer.name - ) + if attr.Attr_Header.AttrType.lookup() == "FILE_NAME": + attr_data = attr.Attr_Data.cast(fn_object) file_name = attr_data.get_full_name() # If we don't have a valid enum, coerce to hex so we can keep the record @@ -131,13 +115,13 @@ def _generator(self): permissions = hex(attr_data.Flags) yield 1, ( - format_hints.Hex(attr_data_offset), + format_hints.Hex(attr_data.vol.offset), mft_record.get_signature(), mft_record.RecordNumber, mft_record.LinkCount, mft_flag, permissions, - attr_header.AttrType.lookup(), + attr.Attr_Header.AttrType.lookup(), conversion.wintime_to_datetime(attr_data.CreationTime), conversion.wintime_to_datetime(attr_data.ModifiedTime), conversion.wintime_to_datetime(attr_data.UpdatedTime), @@ -146,14 +130,13 @@ def _generator(self): ) # If there's no advancement the loop will never end, so break it now - if attr_header.Length == 0: + if attr.Attr_Header.Length == 0: break # Update the base offset to point to the next attribute - attr_base_offset += attr_header.Length - # Get the next attribute - attr_header = self.context.object( - header_object, + attr_base_offset += attr.Attr_Header.Length + attr = self.context.object( + attribute_object, offset=offset + attr_base_offset, layer_name=layer.name, ) @@ -189,3 +172,140 @@ def run(self): ], self._generator(), ) + + +class ADS(interfaces.plugins.PluginInterface): + """Scans for Alternate Data Stream""" + + _required_framework_version = (2, 0, 0) + + @classmethod + def get_requirements(cls): + return [ + requirements.TranslationLayerRequirement( + name="primary", + description="Memory layer for the kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.VersionRequirement( + name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0) + ), + ] + + def _generator(self): + layer = self.context.layers[self.config["primary"]] + + # Yara Rule to scan for MFT Header Signatures + rules = yarascan.YaraScan.process_yara_options( + {"yara_string": "/FILE0|FILE\\*|BAAD/"} + ) + + # Read in the Symbol File + symbol_table = intermed.IntermediateSymbolTable.create( + context=self.context, + config_path=self.config_path, + sub_path="windows", + filename="mft", + class_types={ + "MFT_ENTRY": mft.MFTEntry, + "FILE_NAME_ENTRY": mft.MFTFileName, + "ATTRIBUTE": mft.MFTAttribute, + }, + ) + + # get each of the individual Field Sets + mft_object = symbol_table + constants.BANG + "MFT_ENTRY" + attribute_object = symbol_table + constants.BANG + "ATTRIBUTE" + fn_object = symbol_table + constants.BANG + "FILE_NAME_ENTRY" + + # Scan the layer for Raw MFT records and parse the fields + for offset, _rule_name, _name, _value in layer.scan( + context=self.context, scanner=yarascan.YaraScanner(rules=rules) + ): + with contextlib.suppress(exceptions.PagedInvalidAddressException): + mft_record = self.context.object( + mft_object, offset=offset, layer_name=layer.name + ) + # We will update this on each pass in the next loop and use it as the new offset. + attr_base_offset = mft_record.FirstAttrOffset + + attr = self.context.object( + attribute_object, + offset=offset + attr_base_offset, + layer_name=layer.name, + ) + + # There is no field that has a count of Attributes + # Keep Attempting to read attributes until we get an invalid attr.AttrType + is_ads = False + file_name = renderers.NotAvailableValue + # The First $DATA Attr is the 'principal' file itself not the ADS + while attr.Attr_Header.AttrType.is_valid_choice: + if attr.Attr_Header.AttrType.lookup() == "FILE_NAME": + attr_data = attr.Attr_Data.cast(fn_object) + file_name = attr_data.get_full_name() + if attr.Attr_Header.AttrType.lookup() == "DATA": + if is_ads: + if not attr.Attr_Header.NonResidentFlag: + # Resident files are the most interesting. + if attr.Attr_Header.NameLength > 0: + ads_name = attr.get_resident_filename() + if not ads_name: + ads_name = renderers.NotAvailableValue + + content = attr.get_resident_filecontent() + if content: + # Preparing for Disassembly + disasm = interfaces.renderers.BaseAbsentValue + architecture = layer.metadata.get( + "architecture", None + ) + if architecture: + disasm = interfaces.renderers.Disassembly( + content, 0, architecture.lower() + ) + content = format_hints.HexBytes(content) + else: + content = renderers.NotAvailableValue() + disasm = interfaces.renderers.BaseAbsentValue() + + yield 0, ( + format_hints.Hex(attr_data.vol.offset), + mft_record.get_signature(), + mft_record.RecordNumber, + attr.Attr_Header.AttrType.lookup(), + file_name, + ads_name, + content, + disasm, + ) + else: + is_ads = True + + # If there's no advancement the loop will never end, so break it now + if attr.Attr_Header.Length == 0: + break + + # Update the base offset to point to the next attribute + attr_base_offset += attr.Attr_Header.Length + # Get the next attribute + attr = self.context.object( + attribute_object, + offset=offset + attr_base_offset, + layer_name=layer.name, + ) + + def run(self): + return renderers.TreeGrid( + [ + ("Offset", format_hints.Hex), + ("Record Type", str), + ("Record Number", int), + ("MFT Type", str), + ("Filename", str), + ("ADS Filename", str), + ("Hexdump", format_hints.HexBytes), + ("Disasm", interfaces.renderers.Disassembly), + ], + self._generator(), + ) diff --git a/volatility3/framework/plugins/windows/modscan.py b/volatility3/framework/plugins/windows/modscan.py index 99fadac07c..fc45e69137 100644 --- a/volatility3/framework/plugins/windows/modscan.py +++ b/volatility3/framework/plugins/windows/modscan.py @@ -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 @@ def get_requirements(cls): 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 @@ def get_requirements(cls): 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 @@ def scan_modules( ): _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(), - ) diff --git a/volatility3/framework/plugins/windows/modules.py b/volatility3/framework/plugins/windows/modules.py index 7dabf69545..ba45834d5c 100644 --- a/volatility3/framework/plugins/windows/modules.py +++ b/volatility3/framework/plugins/windows/modules.py @@ -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 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] 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 = "" + 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 = "" - - if self.config["name"] and self.config["name"] not in BaseDllName: - continue + 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 - - yield ( - 0, - ( - format_hints.Hex(mod.vol.offset), - format_hints.Hex(mod.DllBase), - format_hints.Hex(mod.SizeOfImage), - BaseDllName, - FullDllName, - file_output, - ), + 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, ) @classmethod diff --git a/volatility3/framework/plugins/windows/netscan.py b/volatility3/framework/plugins/windows/netscan.py index d0bbd5cbdd..66a24da5ac 100644 --- a/volatility3/framework/plugins/windows/netscan.py +++ b/volatility3/framework/plugins/windows/netscan.py @@ -76,7 +76,7 @@ def create_netscan_constraints( # ~ vollog.debug("Using pool size constraints: TcpL {}, TcpE {}, UdpA {}".format(tcpl_size, tcpe_size, udpa_size)) - return [ + constraints = [ # TCP listener poolscanner.PoolConstraint( b"TcpL", @@ -100,6 +100,19 @@ def create_netscan_constraints( ), ] + if symbol_table.startswith("netscan-win10-20348"): + vollog.debug("Adding additional pool constraint for `TTcb` tags") + constraints.append( + poolscanner.PoolConstraint( + b"TTcb", + type_name=symbol_table + constants.BANG + "_TCP_ENDPOINT", + size=(tcpe_size, None), + page_type=poolscanner.PoolType.NONPAGED | poolscanner.PoolType.FREE, + ) + ) + + return constraints + @classmethod def determine_tcpip_version( cls, @@ -218,6 +231,7 @@ def determine_tcpip_version( (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 @@ -487,10 +501,12 @@ def generate_timeline(self): if not isinstance(row_data[9], datetime.datetime): continue row_data = [ - "N/A" - if isinstance(i, renderers.UnreadableValue) - or isinstance(i, renderers.UnparsableValue) - else i + ( + "N/A" + if isinstance(i, renderers.UnreadableValue) + or isinstance(i, renderers.UnparsableValue) + else i + ) for i in row_data ] description = ( diff --git a/volatility3/framework/plugins/windows/netstat.py b/volatility3/framework/plugins/windows/netstat.py index d3ce3fd2e2..0908767fc7 100644 --- a/volatility3/framework/plugins/windows/netstat.py +++ b/volatility3/framework/plugins/windows/netstat.py @@ -35,7 +35,7 @@ def get_requirements(cls): 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) @@ -154,7 +154,7 @@ def enumerate_structures_by_port( ) else: # invalid argument. - return + return None vollog.debug(f"Current Port: {port}") # the given port serves as a shifted index into the port pool lists @@ -175,7 +175,7 @@ def enumerate_structures_by_port( assignment = inpa.InPaBigPoolBase.Assignments[truncated_port] if not assignment: - return + return None # the value within assignment.Entry is a) masked and b) points inside of the network object # first decode the pointer diff --git a/volatility3/framework/plugins/windows/orphan_kernel_threads.py b/volatility3/framework/plugins/windows/orphan_kernel_threads.py new file mode 100644 index 0000000000..f4901dc8c9 --- /dev/null +++ b/volatility3/framework/plugins/windows/orphan_kernel_threads.py @@ -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 diff --git a/volatility3/framework/plugins/windows/pe_symbols.py b/volatility3/framework/plugins/windows/pe_symbols.py new file mode 100644 index 0000000000..955098d6bf --- /dev/null +++ b/volatility3/framework/plugins/windows/pe_symbols.py @@ -0,0 +1,1017 @@ +# 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 copy +import io +import logging +import ntpath + +from typing import Dict, Tuple, Optional, List, Generator, Union, Callable + +import pefile + +from volatility3.framework import interfaces, exceptions +from volatility3.framework import renderers, constants +from volatility3.framework.configuration import requirements +from volatility3.framework.renderers import format_hints +from volatility3.framework.symbols import intermed +from volatility3.framework.symbols.windows import pdbutil +from volatility3.framework.symbols.windows.extensions import pe +from volatility3.plugins.windows import pslist, modules +from volatility3.framework.constants.windows import KERNEL_MODULE_NAMES + +vollog = logging.getLogger(__name__) + +# keys for specifying wanted names and/or addresses +# used for consistent access between the API and plugins +wanted_names_identifier = "names" +wanted_addresses_identifier = "addresses" + +# how wanted modules/symbols are specified, such as: +# {"ntdll.dll" : {wanted_addresses : [42, 43, 43]}} +# {"ntdll.dll" : {wanted_names : ["NtCreateThread"]}} +filter_module_info = Union[Dict[str, List[str]], Dict[str, List[int]]] +filter_modules_type = Dict[str, filter_module_info] + +# holds resolved symbols +# {"ntdll.dll": [("Bob", 123), ("Alice", 456)]} +found_symbols_module = List[Tuple[str, int]] +found_symbols_type = Dict[str, found_symbols_module] + +# used to hold informatin about a range (VAD or kernel module) +# (start address, size, file path) +range_type = Tuple[int, int, str] +ranges_type = List[range_type] + +# collected_modules are modules and their symbols found when walking vads or kernel modules +# Tuple of (process or kernel layer name, range start, range size) +collected_module_instance = Tuple[str, int, int] +collected_modules_info = List[collected_module_instance] +collected_modules_type = Dict[str, collected_modules_info] + +PESymbolFinders = Union[interfaces.context.ModuleInterface, pefile.ExportDirData] + + +class PESymbolFinder: + """ + Interface for PE symbol finding classes + This interface provides a standard way for the calling code to + lookup symbols by name or address + """ + + cached_str_dict = Dict[str, Optional[str]] + + cached_int_dict = Dict[str, Optional[int]] + + cached_value = Union[int, str, None] + cached_module_lists = Union[Dict[str, List[str]], Dict[str, List[int]]] + cached_value_dict = Dict[str, cached_module_lists] + + def __init__( + self, + layer_name: str, + mod_name: str, + module_start: int, + symbol_module: PESymbolFinders, + ): + self._layer_name = layer_name + self._mod_name = mod_name + self._module_start = module_start + self._symbol_module = symbol_module + + self._address_cache: PESymbolFinder.cached_int_dict = {} + self._name_cache: PESymbolFinder.cached_str_dict = {} + + def _get_cache_key(self, value: cached_value) -> str: + """ + Maintain a cache for symbol lookups to avoid re-walking of PDB symbols or export tables + within the same module for the same address in the same layer + + Args: + value: The value (address or name) being cached + + Returns: + str: The constructed cache key that includes the layer and module name + """ + return f"{self._layer_name}|{self._mod_name}|{value}" + + def get_name_for_address(self, address: int) -> Optional[str]: + """ + Returns the name for the given address within the particular layer and module + + Args: + address: the address to resolve within the module + + Returns: + str: the name of the symbol, if found + """ + cached_key = self._get_cache_key(address) + if cached_key not in self._name_cache: + name = self._do_get_name(address) + self._name_cache[cached_key] = name + + return self._name_cache[cached_key] + + def get_address_for_name(self, name: str) -> Optional[int]: + """ + Returns the name for the given address within the particular layer and module + + Args: + str: the name of the symbol to resolve + + Returns: + address: the address of the symbol, if found + """ + cached_key = self._get_cache_key(name) + if cached_key not in self._address_cache: + address = self._do_get_address(name) + self._address_cache[cached_key] = address + + return self._address_cache[cached_key] + + def _do_get_name(self, address: int) -> Optional[str]: + """ + Returns the name for the given address within the particular layer and module. + This method must be overwritten by sub classes. + + Args: + address: the address to resolve within the module + + Returns: + str: the name of the symbol, if found + """ + raise NotImplementedError("_do_get_name must be overwritten") + + def _do_get_address(self, name: str) -> Optional[int]: + """ + Returns the name for the given address within the particular layer and module + This method must be overwritten by sub classes. + + Args: + str: the name of the symbol to resolve + + Returns: + address: the address of the symbol, if found + """ + raise NotImplementedError("_do_get_address must be overwritten") + + +class PDBSymbolFinder(PESymbolFinder): + """ + PESymbolFinder implementation for PDB modules + """ + + def _do_get_address(self, name: str) -> Optional[int]: + """ + _do_get_address implementation for PDBSymbolFinder + + Args: + str: the name of the symbol to resolve + + Returns: + address: the address of the symbol, if found + """ + try: + return self._symbol_module.get_absolute_symbol_address(name) + except exceptions.SymbolError: + return None + + def _do_get_name(self, address: int) -> Optional[str]: + """ + _do_get_name implementation for PDBSymbolFinder + + Args: + address: the address to resolve within the module + + Returns: + str: the name of the symbol, if found + """ + try: + name = self._symbol_module.get_symbols_by_absolute_location(address)[0] + return name.split(constants.BANG)[1] + except (exceptions.SymbolError, IndexError): + return None + + +class ExportSymbolFinder(PESymbolFinder): + """ + PESymbolFinder implementation for PDB modules + """ + + def _get_name(self, export: pefile.ExportData) -> Optional[str]: + # AttributeError throws on empty or ordinal-only exports + try: + return export.name.decode("ascii") + except AttributeError: + return None + + def _do_get_name(self, address: int) -> Optional[str]: + """ + _do_get_name implementation for ExportSymbolFinder + + Args: + address: the address to resolve within the module + + Returns: + str: the name of the symbol, if found + """ + for export in self._symbol_module: + if export.address + self._module_start == address: + return self._get_name(export) + + return None + + def _do_get_address(self, name: str) -> Optional[int]: + """ + _do_get_address implementation for ExportSymbolFinder + Args: + str: the name of the symbol to resolve + + Returns: + address: the address of the symbol, if found + """ + + for export in self._symbol_module: + sym_name = self._get_name(export) + if sym_name and sym_name == name: + return self._module_start + export.address + + return None + + +class PESymbols(interfaces.plugins.PluginInterface): + """Prints symbols in PE files in process and kernel memory""" + + _required_framework_version = (2, 7, 0) + + _version = (1, 0, 0) + + # used for special handling of the kernel PDB file. See later notes + os_module_name = "ntoskrnl.exe" + + @classmethod + def get_requirements(cls) -> List: + # 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.VersionRequirement( + name="modules", component=modules.Modules, version=(2, 0, 0) + ), + requirements.VersionRequirement( + name="pdbutil", component=pdbutil.PDBUtility, version=(1, 0, 0) + ), + requirements.ChoiceRequirement( + name="source", + description="Where to resolve symbols.", + choices=["kernel", "processes"], + optional=False, + ), + requirements.StringRequirement( + name="module", + description='Module in which to resolve symbols. Use "ntoskrnl.exe" to resolve in the base kernel executable.', + optional=False, + ), + requirements.ListRequirement( + name="symbols", + element_type=str, + description="Symbol name to resolve", + optional=True, + ), + requirements.ListRequirement( + name="addresses", + element_type=int, + description="Address of symbol to resolve", + optional=True, + ), + ] + + @staticmethod + def _get_pefile_obj( + context: interfaces.context.ContextInterface, + pe_table_name: str, + layer_name: str, + base_address: int, + ) -> Optional[pefile.PE]: + """ + Attempts to pefile object from the bytes of the PE file + + Args: + pe_table_name: name of the pe types table + layer_name: name of the process layer + base_address: base address of the module + + Returns: + the constructed pefile object + """ + pe_data = io.BytesIO() + + try: + dos_header = context.object( + pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER", + offset=base_address, + layer_name=layer_name, + ) + + for offset, data in dos_header.reconstruct(): + pe_data.seek(offset) + pe_data.write(data) + + pe_ret = pefile.PE(data=pe_data.getvalue(), fast_load=True) + + except exceptions.InvalidAddressException: + pe_ret = None + + return pe_ret + + @staticmethod + def range_info_for_address( + ranges: ranges_type, address: int + ) -> Optional[range_type]: + """ + Helper for getting the range information for an address. + Finds the range holding the `address` parameter + + Args: + address: the address to find the range for + + Returns: + Tuple[int, int, str]: The starting address, size, and file path of the range + + """ + for start, size, filepath in ranges: + if start <= address < start + size: + return start, size, filepath + + return None + + @staticmethod + def filepath_for_address(ranges: ranges_type, address: int) -> Optional[str]: + """ + Helper to get the file path for an address + + Args: + ranges: The set of VADs with mapped files to find the address + address: The address to find inside of the VADs set + + Returns: + str: The full path of the file, if found and present + """ + info = PESymbols.range_info_for_address(ranges, address) + if info: + return info[2] + + return None + + @staticmethod + def filename_for_path(filepath: str) -> str: + """ + Consistent way to get the filename regardless of platform + + Args: + str: the file path from `filepath_for_address` + + Returns: + str: the bsae file name of the full path + """ + return ntpath.basename(filepath).lower() + + @staticmethod + def addresses_for_process_symbols( + context: interfaces.context.ContextInterface, + config_path: str, + layer_name: str, + symbol_table_name: str, + symbols: filter_modules_type, + ) -> found_symbols_type: + """ + Used to easily resolve the addresses of names inside of modules. + + See the usage of this function for system call resolution in unhooked_system_calls.py + for an easy to understand example. + + Args: + symbols: The dictionary of symbols requested by the caller + + Returns: + found_symbols_type: The dictionary of symbols that were resolved + """ + collected_modules = PESymbols.get_process_modules( + context, layer_name, symbol_table_name, symbols + ) + + found_symbols, missing_symbols = PESymbols.find_symbols( + context, config_path, symbols, collected_modules + ) + + for mod_name, unresolved_symbols in missing_symbols.items(): + for symbol in unresolved_symbols: + vollog.debug(f"Unable to resolve symbol {symbol} in module {mod_name}") + + return found_symbols + + @staticmethod + def path_and_symbol_for_address( + context: interfaces.context.ContextInterface, + config_path: str, + collected_modules: collected_modules_type, + ranges: ranges_type, + address: int, + ) -> Tuple[Optional[str], Optional[str]]: + """ + Method for plugins to determine the file path and symbol name for a given address + + See debugregisters.py for an example of how this function is used along with get_vads_for_process_cache + for resolving symbols in processes. + + Args: + collected_modules: return value from `get_kernel_modules` or `get_process_modules` + ranges: the memory ranges to examine in this layer. + address: address to resolve to its symbol name + Returns: + Tuple[str|renderers.NotApplicableValue|renderers.NotAvailableValue, str|renderers.NotApplicableValue|renderers.NotAvailableValue] + """ + if not address: + return None, None + + filepath = PESymbols.filepath_for_address(ranges, address) + + if not filepath: + return None, None + + filename = PESymbols.filename_for_path(filepath).lower() + + # setup to resolve the address + filter_module: filter_modules_type = { + filename: {wanted_addresses_identifier: [address]} + } + + found_symbols, _missing_symbols = PESymbols.find_symbols( + context, config_path, filter_module, collected_modules + ) + + if not found_symbols or filename not in found_symbols: + return filepath, None + + return filepath, found_symbols[filename][0][0] + + @staticmethod + def _get_exported_symbols( + context: interfaces.context.ContextInterface, + pe_table_name: str, + mod_name: str, + module_info: collected_module_instance, + ) -> Optional[ExportSymbolFinder]: + """ + Attempts to locate symbols based on export analysis + + Args: + mod_name: lower case name of the module to resolve symbols in + module_info: (layer_name, module_start, module_size) of the module to examine + + Returns: + Optional[ExportSymbolFinder]: If the export table can be resolved, then the ExportSymbolFinder + instance for it + """ + + layer_name = module_info[0] + module_start = module_info[1] + + # we need a valid PE with an export table + pe_module = PESymbols._get_pefile_obj( + context, pe_table_name, layer_name, module_start + ) + if not pe_module: + return None + + pe_module.parse_data_directories( + directories=[pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_EXPORT"]] + ) + if not hasattr(pe_module, "DIRECTORY_ENTRY_EXPORT"): + return None + + return ExportSymbolFinder( + layer_name, + mod_name.lower(), + module_start, + pe_module.DIRECTORY_ENTRY_EXPORT.symbols, + ) + + @staticmethod + def _get_pdb_module( + context: interfaces.context.ContextInterface, + config_path: str, + mod_name: str, + module_info: collected_module_instance, + ) -> Optional[PDBSymbolFinder]: + """ + Attempts to locate symbols based on PDB analysis through each layer where the mod_name module was found + + Args: + mod_name: lower case name of the module to resolve symbols in + module_info: (layer_name, module_start, module_size) of the module to examine + + Returns: + Optional[PDBSymbolFinder]: If the export table can be resolved, then the ExportSymbolFinder + """ + + mod_symbols = None + + layer_name, module_start, module_size = module_info + + # the PDB name of the kernel file is not consistent for an exe, for example, + # a `ntoskrnl.exe` can have an internal PDB name of any of the ones in the following list + # The code attempts to find all possible PDBs to ensure the best chance of recovery + if mod_name == PESymbols.os_module_name: + pdb_names = [fn + ".pdb" for fn in KERNEL_MODULE_NAMES] + + # for non-kernel files, replace the exe, sys, or dll extension with pdb + else: + # in testing we found where some DLLs, such amsi.dll, have its PDB string as Amsi.dll + # in certain Windows versions + mod_name = mod_name[:-3] + "pdb" + first_upper = mod_name[0].upper() + mod_name[1:] + pdb_names = [mod_name, first_upper] + + # loop through each PDB name (all the kernel names or the dll name as lower() + first char upper case) + for pdb_name in pdb_names: + try: + mod_symbols = pdbutil.PDBUtility.symbol_table_from_pdb( + context, + interfaces.configuration.path_join(config_path, mod_name), + layer_name, + pdb_name, + module_start, + module_size, + ) + + if mod_symbols: + break + + # this exception is expected when the PDB can't be found or downloaded + except exceptions.VolatilityException: + continue + + # this is not expected - it means pdbconv broke when parsing the PDB + except TypeError as e: + vollog.error( + f"Unable to parse PDB file for module {pdb_name} -> {e}. Please file a bug on the GitHub issue tracker." + ) + + # cannot do anything without the symbols + if not mod_symbols: + return None + + pdb_module = context.module( + mod_symbols, layer_name=layer_name, offset=module_start + ) + + return PDBSymbolFinder(layer_name, mod_name, module_start, pdb_module) + + @staticmethod + def _find_symbols_through_pdb( + context: interfaces.context.ContextInterface, + config_path: str, + module_instances: collected_modules_info, + mod_name: str, + ) -> Generator[PDBSymbolFinder, None, None]: + """ + Attempts to resolve the symbols in `mod_name` through PDB analysis + + Args: + module_instances: the set of layers in which the module was found + mod_name: name of the module to resolve symbols in + Returns: + Generator[PDBSymbolFinder]: a PDBSymbolFinder instance for each layer in which the module was found + """ + for module_info in module_instances: + mod_module = PESymbols._get_pdb_module( + context, config_path, mod_name, module_info + ) + if mod_module: + yield mod_module + + @staticmethod + def _find_symbols_through_exports( + context: interfaces.context.ContextInterface, + config_path: str, + module_instances: collected_modules_info, + mod_name: str, + ) -> Generator[ExportSymbolFinder, None, None]: + """ + Attempts to resolve the symbols in `mod_name` through export analysis + + Args: + module_instances: the set of layers in which the module was found + mod_name: name of the module to resolve symbols in + Returns: + Generator[ExportSymbolFinder]: an ExportSymbolFinder instance for each layer in which the module was found + """ + pe_table_name = intermed.IntermediateSymbolTable.create( + context, config_path, "windows", "pe", class_types=pe.class_types + ) + + # for each process layer and VAD, construct a PE and examine the export table + for module_info in module_instances: + exported_symbols = PESymbols._get_exported_symbols( + context, pe_table_name, mod_name, module_info + ) + if exported_symbols: + yield exported_symbols + + @staticmethod + def _get_symbol_value( + wanted_symbols: filter_module_info, + symbol_resolver: PESymbolFinder, + ) -> Generator[Tuple[str, int, str, int], None, None]: + """ + Enumerates the symbols specified as wanted by the calling plugin + + Args: + wanted_symbols: the set of symbols for a particular module + symbol_resolver: method in a layer to resolve the symbols + + Returns: + Tuple[str, int, str, int]: the index and value of the found symbol in the wanted list, and the name and address of resolved symbol + """ + if ( + wanted_names_identifier not in wanted_symbols + and wanted_addresses_identifier not in wanted_symbols + ): + vollog.warning( + f"Invalid `wanted_symbols` sent to `find_symbols`. addresses and names keys both misssing." + ) + return + + symbol_keys: List[Tuple[str, Callable]] = [ + (wanted_names_identifier, symbol_resolver.get_address_for_name), + (wanted_addresses_identifier, symbol_resolver.get_name_for_address), + ] + + for symbol_key, symbol_getter in symbol_keys: + # address or name + if symbol_key in wanted_symbols: + # walk each wanted address or name + for value_index, wanted_value in enumerate(wanted_symbols[symbol_key]): + symbol_value = symbol_getter(wanted_value) + + if symbol_value: + # yield out deleteion key, deletion index, symbol name, symbol address + if symbol_key == wanted_names_identifier: + yield symbol_key, value_index, wanted_value, symbol_value # type: ignore + else: + yield symbol_key, value_index, symbol_value, wanted_value # type: ignore + + @staticmethod + def _resolve_symbols_through_methods( + context: interfaces.context.ContextInterface, + config_path: str, + module_instances: collected_modules_info, + wanted_modules: PESymbolFinder.cached_value_dict, + mod_name: str, + ) -> Tuple[found_symbols_module, PESymbolFinder.cached_module_lists]: + """ + Attempts to resolve every wanted symbol in `mod_name` + Every layer is enumerated for maximum chance of recovery + + Args: + module_instances: the set of layers in which the module was found + wanted_modules: The symbols to resolve tied to their module names + mod_name: name of the module to resolve symbols in + Returns: + Tuple[found_symbols_module, PESymbolFinder.cached_module_lists]: The set of found symbols and the ones that could not be resolved + """ + symbol_resolving_methods = [ + PESymbols._find_symbols_through_pdb, + PESymbols._find_symbols_through_exports, + ] + + found: found_symbols_module = [] + + # the symbols wanted from this module by the caller + wanted = wanted_modules[mod_name] + + # make a copy to remove from inside this function for returning to the caller + remaining = copy.deepcopy(wanted) + + done_processing = False + + for method in symbol_resolving_methods: + # every layer where this module was found through the given method + for symbol_resolver in method( + context, config_path, module_instances, mod_name + ): + vollog.debug(f"Have resolver for method {method}") + for ( + symbol_key, + value_index, + symbol_name, + symbol_address, + ) in PESymbols._get_symbol_value(remaining, symbol_resolver): + found.append((symbol_name, symbol_address)) + del remaining[symbol_key][value_index] + + # everything was resolved, stop this resolver + # remove this key from the remaining symbols to resolve + if not remaining[symbol_key]: + del remaining[symbol_key] + done_processing = True + break + + if done_processing: + break + + # stop all resolving + if done_processing: + break + + return found, remaining + + @staticmethod + def find_symbols( + context: interfaces.context.ContextInterface, + config_path: str, + wanted_modules: PESymbolFinder.cached_value_dict, + collected_modules: collected_modules_type, + ) -> Tuple[found_symbols_type, PESymbolFinder.cached_value_dict]: + """ + Loops through each method of symbol analysis until each wanted symbol is found + Returns the resolved symbols as a dictionary that includes the name and runtime address + + Args: + wanted_modules: the dictionary of modules and symbols to resolve. Modified to remove symbols as they are resolved. + collected_modules: return value from `get_kernel_modules` or `get_process_modules` + Returns: + Tuple[found_symbols_type, PESymbolFinder.cached_value_dict]: The set of found symbols but the ones that could not be resolved + """ + found_symbols: found_symbols_type = {} + missing_symbols: PESymbolFinder.cached_value_dict = {} + + for mod_name in wanted_modules: + if mod_name not in collected_modules: + continue + + module_instances = collected_modules[mod_name] + + # try to resolve the symbols for `mod_name` through each method (PDB and export table currently) + ( + found_in_module, + missing_in_module, + ) = PESymbols._resolve_symbols_through_methods( + context, config_path, module_instances, wanted_modules, mod_name + ) + + if found_in_module: + found_symbols[mod_name] = found_in_module + + if missing_in_module: + missing_symbols[mod_name] = missing_in_module + + return found_symbols, missing_symbols + + @staticmethod + def get_kernel_modules( + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str, + filter_modules: Optional[filter_modules_type], + ) -> collected_modules_type: + """ + Walks the kernel module list and finds the session layer, base, and size of each wanted module + + Args: + filter_modules: The modules to filter the gathering to. If left as None, all kernel modules are gathered. + Returns: + collected_modules_type: The collection of modules found with at least one layer present + """ + found_modules: collected_modules_type = {} + + if filter_modules: + # create a tuple of module names for use with `endswith` + filter_modules_check = tuple([key.lower() for key in filter_modules.keys()]) + else: + filter_modules_check = None + + session_layers = list( + modules.Modules.get_session_layers(context, layer_name, symbol_table) + ) + + # special handling for the kernel + gather_kernel = ( + filter_modules_check and PESymbols.os_module_name in filter_modules_check + ) + + for index, mod in enumerate( + modules.Modules.list_modules(context, layer_name, symbol_table) + ): + try: + mod_name = str(mod.BaseDllName.get_string().lower()) + except exceptions.InvalidAddressException: + continue + + # to analyze, it must either be the kernel or a wanted module + if not filter_modules_check or (gather_kernel and index == 0): + mod_name = PESymbols.os_module_name + elif filter_modules_check and not mod_name.endswith(filter_modules_check): + continue + + # we won't find symbol information if we can't analyze the module + session_layer_name = modules.Modules.find_session_layer( + context, session_layers, mod.DllBase + ) + if not session_layer_name: + continue + + if mod_name not in found_modules: + found_modules[mod_name] = [] + + found_modules[mod_name].append( + (session_layer_name, mod.DllBase, mod.SizeOfImage) + ) + + return found_modules + + @staticmethod + def get_vads_for_process_cache( + vads_cache: Dict[int, ranges_type], + owner_proc: interfaces.objects.ObjectInterface, + ) -> Optional[ranges_type]: + """ + Creates and utilizes a cache of a process' VADs for efficient lookups + + Returns the vad information of the VAD hosting the address, if found + + Args: + vads_cache: The existing cache of VADs + owner_proc: The process being inspected + Returns: + Optional[ranges_type]: The range holding the address, if found + """ + if owner_proc.vol.offset in vads_cache: + vads = vads_cache[owner_proc.vol.offset] + else: + vads = PESymbols.get_proc_vads_with_file_paths(owner_proc) + vads_cache[owner_proc.vol.offset] = vads + + # smear or terminated process + if len(vads) == 0: + return None + + return vads + + @staticmethod + def get_proc_vads_with_file_paths( + proc: interfaces.objects.ObjectInterface, + ) -> ranges_type: + """ + Returns a list of the process' vads that map a file + + Args: + proc: The process to gather the VADs for + + Returns: + ranges_type: The list of VADs for this process that map a file + """ + vads: ranges_type = [] + + try: + vad_root = proc.get_vad_root() + except exceptions.InvalidAddressException: + return vads + + for vad in vad_root.traverse(): + filepath = vad.get_file_name() + + if not isinstance(filepath, str) or filepath.count("\\") == 0: + continue + + vads.append((vad.get_start(), vad.get_size(), filepath)) + + return vads + + @classmethod + def get_all_vads_with_file_paths( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table_name: str, + ) -> Generator[ + Tuple[interfaces.objects.ObjectInterface, str, ranges_type], + None, + None, + ]: + """ + Yields each set of vads for a process that have a file mapped, along with the process itself and its layer + + Args: + Generator[Tuple[interfaces.objects.ObjectInterface, str, ranges_type]]: Yields tuple of process objects, layers, and VADs mapping files + """ + procs = pslist.PsList.list_processes( + context=context, + layer_name=layer_name, + symbol_table=symbol_table_name, + ) + + for proc in procs: + try: + proc_layer_name = proc.add_process_layer() + except exceptions.InvalidAddressException: + continue + + vads = cls.get_proc_vads_with_file_paths(proc) + + yield proc, proc_layer_name, vads + + @staticmethod + def get_process_modules( + context: interfaces.context.ContextInterface, + layer_name: str, + symbol_table: str, + filter_modules: Optional[filter_modules_type], + ) -> collected_modules_type: + """ + Walks the process list and each process' VAD to determine the base address and size of wanted modules + + Args: + filter_modules: The modules to filter the gathering to. If left as None, all process modules are gathered. + Returns: + collected_modules_type: The collection of modules found with at least one layer present + """ + proc_modules: collected_modules_type = {} + + if filter_modules: + # create a tuple of module names for use with `endswith` + filter_modules_check = tuple([key.lower() for key in filter_modules.keys()]) + else: + filter_modules_check = None + + for _proc, proc_layer_name, vads in PESymbols.get_all_vads_with_file_paths( + context, layer_name, symbol_table + ): + for vad_start, vad_size, filepath in vads: + filename = PESymbols.filename_for_path(filepath) + + if filter_modules_check and not filename.endswith(filter_modules_check): + continue + + # track each module along with the process layer and range to find it + if filename not in proc_modules: + proc_modules[filename] = [] + + proc_modules[filename].append((proc_layer_name, vad_start, vad_size)) + + return proc_modules + + def _generator(self) -> Generator[Tuple[int, Tuple[str, str, int]], None, None]: + kernel = self.context.modules[self.config["kernel"]] + + if self.config["symbols"]: + filter_module = { + self.config["module"].lower(): { + wanted_names_identifier: self.config["symbols"] + } + } + + elif self.config["addresses"]: + filter_module = { + self.config["module"].lower(): { + wanted_addresses_identifier: self.config["addresses"] + } + } + + else: + vollog.error("--address or --symbol must be specified") + return + + if self.config["source"] == "kernel": + module_resolver = self.get_kernel_modules + else: + module_resolver = self.get_process_modules + + collected_modules = module_resolver( + self.context, kernel.layer_name, kernel.symbol_table_name, filter_module + ) + + found_symbols, _missing_symbols = PESymbols.find_symbols( + self.context, self.config_path, filter_module, collected_modules + ) + + for module, symbols in found_symbols.items(): + for symbol, address in symbols: + yield (0, (module, symbol, format_hints.Hex(address))) + + def run(self) -> renderers.TreeGrid: + return renderers.TreeGrid( + [ + ("Module", str), + ("Symbol", str), + ("Address", format_hints.Hex), + ], + self._generator(), + ) diff --git a/volatility3/framework/plugins/windows/pedump.py b/volatility3/framework/plugins/windows/pedump.py new file mode 100644 index 0000000000..858d0615a5 --- /dev/null +++ b/volatility3/framework/plugins/windows/pedump.py @@ -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(), + ) diff --git a/volatility3/framework/plugins/windows/poolscanner.py b/volatility3/framework/plugins/windows/poolscanner.py index e131c5f785..1f70cfb8c7 100644 --- a/volatility3/framework/plugins/windows/poolscanner.py +++ b/volatility3/framework/plugins/windows/poolscanner.py @@ -222,6 +222,24 @@ def builtin_constraints( type_name=symbol_table + constants.BANG + "_EPROCESS", object_type="Process", size=(600, None), + skip_type_test=True, + page_type=PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE, + ), + # threads on windows before windows8 + PoolConstraint( + b"Thr\xe5", # -> “protected” allocation, MSB is set. + type_name=symbol_table + constants.BANG + "_ETHREAD", + object_type="Thread", + size=(600, None), # -> 0x0258 - size of struct in win5.1 + skip_type_test=True, + page_type=PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE, + ), + # threads on windows starting with windows8 + PoolConstraint( + b"Thre", + type_name=symbol_table + constants.BANG + "_ETHREAD", + object_type="Thread", + size=(600, None), # -> 0x0258 - size of struct in win5.1 page_type=PoolType.PAGED | PoolType.NONPAGED | PoolType.FREE, ), # files on windows before windows 8 diff --git a/volatility3/framework/plugins/windows/processghosting.py b/volatility3/framework/plugins/windows/processghosting.py new file mode 100644 index 0000000000..50f02c926c --- /dev/null +++ b/volatility3/framework/plugins/windows/processghosting.py @@ -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, + ) + ), + ) diff --git a/volatility3/framework/plugins/windows/pslist.py b/volatility3/framework/plugins/windows/pslist.py index 52d5840af7..bc6c29714f 100644 --- a/volatility3/framework/plugins/windows/pslist.py +++ b/volatility3/framework/plugins/windows/pslist.py @@ -4,7 +4,7 @@ import datetime import logging -from typing import Callable, Iterable, List, Type +from typing import Callable, Iterator, List, Type from volatility3.framework import renderers, interfaces, layers, exceptions, constants from volatility3.framework.configuration import requirements @@ -12,6 +12,7 @@ from volatility3.framework.renderers import format_hints from volatility3.framework.symbols import intermed from volatility3.framework.symbols.windows.extensions import pe +from volatility3.framework.symbols.windows import extensions from volatility3.plugins import timeliner vollog = logging.getLogger(__name__) @@ -90,9 +91,19 @@ def process_dump( offset=peb.ImageBaseAddress, layer_name=proc_layer_name, ) + + process_name = proc.ImageFileName.cast( + "string", + max_length=proc.ImageFileName.vol.count, + errors="replace", + ) + file_handle = open_method( - f"pid.{proc.UniqueProcessId}.{peb.ImageBaseAddress:#x}.dmp" + open_method.sanitize_filename( + f"{proc.UniqueProcessId}.{process_name}.{peb.ImageBaseAddress:#x}.dmp" + ) ) + for offset, data in dos_header.reconstruct(): file_handle.seek(offset) file_handle.write(data) @@ -126,6 +137,29 @@ def create_pid_filter( 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 @@ -164,7 +198,7 @@ def list_processes( filter_func: Callable[ [interfaces.objects.ObjectInterface], bool ] = lambda _: False, - ) -> Iterable[interfaces.objects.ObjectInterface]: + ) -> Iterator["extensions.EPROCESS"]: """Lists all the processes in the primary layer that are in the pid config option. diff --git a/volatility3/framework/plugins/windows/psscan.py b/volatility3/framework/plugins/windows/psscan.py index 3d9ae5c1e2..5ce470cd86 100644 --- a/volatility3/framework/plugins/windows/psscan.py +++ b/volatility3/framework/plugins/windows/psscan.py @@ -59,6 +59,75 @@ def get_requirements(cls): ), ] + @classmethod + def physical_offset_from_virtual(cls, context, layer_name, proc): + """Calculate the physical offset from the virtual offset of a process. + + Args: + context: The context containing layers and modules information. + layer_name: The name of the layer containing the process memory. + proc: The process object for which to calculate the physical offset. + + Returns: + int: The physical offset of the process. + Raises: + TypeError: If the primary layer is not an Intel layer. + """ + memory = context.layers[layer_name] + + if not isinstance(memory, layers.intel.Intel): + raise TypeError("Primary layer is not an intel layer") + + (_, _, ph_offset, _, _) = list( + memory.mapping(offset=proc.vol.offset, length=0) + )[0] + + return ph_offset + + @classmethod + def create_offset_filter( + cls, + context: interfaces.context.ContextInterface, + layer_name: str, + offset: int = None, + physical: bool = True, + exclude: bool = False, + ) -> Callable[[interfaces.objects.ObjectInterface], bool]: + """A factory for producing filter functions that filter based on the physical offset of the process. + + Args: + offset: A number that is the physical offset to be filtered out + exclude: Accept only tasks that are not the offset argument + + Returns: + Filter function to be passed to the list of processes. + """ + filter_func = lambda _: False + + if offset: + if physical: + if exclude: + filter_func = ( + lambda proc: cls.physical_offset_from_virtual( + context, layer_name, proc + ) + == offset + ) + else: + filter_func = ( + lambda proc: cls.physical_offset_from_virtual( + context, layer_name, proc + ) + != offset + ) + else: + if exclude: + filter_func = lambda proc: proc.vol.offset == offset + else: + filter_func = lambda proc: proc.vol.offset != offset + + return filter_func + @classmethod def scan_processes( cls, @@ -197,20 +266,28 @@ def _generator(self): 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_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 vproc: + file_handle = pslist.PsList.process_dump( + self.context, + kernel.symbol_table_name, + pe_table_name, + vproc, + self.open, + ) + + if file_handle: + file_output = file_handle.preferred_filename if not self.config["physical"]: offset = proc.vol.offset diff --git a/volatility3/framework/plugins/windows/pstree.py b/volatility3/framework/plugins/windows/pstree.py index 5c78d16828..2be96277ca 100644 --- a/volatility3/framework/plugins/windows/pstree.py +++ b/volatility3/framework/plugins/windows/pstree.py @@ -5,7 +5,7 @@ import logging from typing import Callable, Dict, Set, Tuple -from volatility3.framework import objects, interfaces, renderers +from volatility3.framework import objects, interfaces, renderers, exceptions from volatility3.framework.configuration import requirements from volatility3.framework.renderers import format_hints from volatility3.plugins.windows import pslist @@ -108,13 +108,13 @@ def _generator( def yield_processes(pid, descendant: bool = False): if pid in process_pids: vollog.debug(f"Pid cycle: already processed pid {pid}") - return + return None process_pids.add(pid) if pid not in self._ancestors and not descendant: vollog.debug(f"Pid cycle: pid {pid} not in filtered tree") - return + return None proc, offset = self._processes[pid] row = ( @@ -132,6 +132,25 @@ def yield_processes(pid, descendant: bool = False): proc.get_exit_time(), ) + try: + audit = proc.SeAuditProcessCreationInfo.ImageFileName.Name + # If 'audit' is set to the empty string, display NotAvailableValue + row += (audit.get_string() or renderers.NotAvailableValue(),) + except exceptions.InvalidAddressException: + row += (renderers.NotAvailableValue(),) + + try: + process_params = proc.get_peb().ProcessParameters + row += ( + process_params.CommandLine.get_string(), + process_params.ImagePathName.get_string(), + ) + except exceptions.InvalidAddressException: + row += ( + renderers.NotAvailableValue(), + renderers.NotAvailableValue(), + ) + yield (self._levels[pid] - 1, row) for child_pid in self._children.get(pid, []): yield from yield_processes( @@ -161,6 +180,9 @@ def run(self): ("Wow64", bool), ("CreateTime", datetime.datetime), ("ExitTime", datetime.datetime), + ("Audit", str), + ("Cmd", str), + ("Path", str), ], self._generator( filter_func=pslist.PsList.create_pid_filter( diff --git a/volatility3/framework/plugins/windows/psxview.py b/volatility3/framework/plugins/windows/psxview.py new file mode 100644 index 0000000000..a8d185a2c0 --- /dev/null +++ b/volatility3/framework/plugins/windows/psxview.py @@ -0,0 +1,257 @@ +import datetime +import logging +import string +from itertools import chain +from typing import Dict, Iterable, List + +from volatility3.framework import constants, exceptions +from volatility3.framework.configuration import requirements +from volatility3.framework.interfaces import plugins +from volatility3.framework.renderers import TreeGrid, format_hints +from volatility3.framework.symbols.windows import extensions +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, string: str) -> bool: + return all(c in self.valid_proc_name_chars for c in string) + + def _filter_garbage_procs( + self, proc_list: Iterable[extensions.EPROCESS] + ) -> List[extensions.EPROCESS]: + 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: int) -> int: + 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: Iterable[extensions.EPROCESS] + ) -> Dict[int, extensions.EPROCESS]: + 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: str, symbol_table: str + ) -> Dict[int, extensions.EPROCESS]: + 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) -> Dict[int, extensions.EPROCESS]: + 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: Iterable[extensions.EPROCESS], layer_name: str, symbol_table: str + ) -> Dict[int, extensions.EPROCESS]: + ret: List[extensions.EPROCESS] = [] + + handles_plugin = handles.Handles( + context=self.context, config_path=self.config_path + ) + + type_map = handles_plugin.get_type_map(self.context, layer_name, symbol_table) + + cookie = handles_plugin.find_cookie( + context=self.context, + layer_name=layer_name, + symbol_table=symbol_table, + ) + + for p in tasks: + name = self._proc_name_to_string(p) + if name != "csrss.exe": + continue + + try: + ret += [ + handle.Body.cast("_EPROCESS") + for handle in handles_plugin.handles(p.ObjectTable) + if handle.get_object_type(type_map, cookie) == "Process" + ] + 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: Dict[str, Dict[int, extensions.EPROCESS]] = {} + + 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 + ) + + # Unique set of all offsets from all sources + offsets = set(chain(*(mapping.keys() for mapping in processes.values()))) + + for offset in offsets: + # We know there will be at least one process mapped to each offset + proc: extensions.EPROCESS = next( + mapping[offset] for mapping in processes.values() if offset in mapping + ) + + in_sources = {src: False for src in processes} + + for source, process_mapping in processes.items(): + if offset in process_mapping: + in_sources[source] = True + + 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(), + ) diff --git a/volatility3/framework/plugins/windows/registry/getcellroutine.py b/volatility3/framework/plugins/windows/registry/getcellroutine.py new file mode 100644 index 0000000000..200a45a823 --- /dev/null +++ b/volatility3/framework/plugins/windows/registry/getcellroutine.py @@ -0,0 +1,98 @@ +# 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 constants, exceptions, interfaces, renderers +from volatility3.framework.configuration import requirements +from volatility3.framework.renderers import format_hints +from volatility3.plugins.windows import ssdt +from volatility3.plugins.windows.registry import hivelist + +vollog = logging.getLogger(__name__) + + +class GetCellRoutine(interfaces.plugins.PluginInterface): + """Reports registry hives with a hooked GetCellRoutine handler""" + + _required_framework_version = (2, 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="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0) + ), + requirements.PluginRequirement( + name="ssdt", plugin=ssdt.SSDT, version=(1, 0, 0) + ), + ] + + def _generator(self): + kernel = self.context.modules[self.config["kernel"]] + + collection = ssdt.SSDT.build_module_collection( + self.context, kernel.layer_name, kernel.symbol_table_name + ) + + # walk each hive and validate that the GetCellRoutine handler + # is inside of the kernel (ntoskrnl) + for hive_object in hivelist.HiveList.list_hives( + context=self.context, + base_config_path=self.config_path, + layer_name=kernel.layer_name, + symbol_table=kernel.symbol_table_name, + ): + hive = hive_object.hive + + try: + cellroutine = hive.GetCellRoutine + except exceptions.InvalidAddressException: + continue + + module_symbols = list( + collection.get_module_symbols_by_absolute_location(cellroutine) + ) + + if module_symbols: + for module_name, _ in module_symbols: + # GetCellRoutine handlers should only be in the kernel + if module_name not in constants.windows.KERNEL_MODULE_NAMES: + yield ( + 0, + ( + format_hints.Hex(hive.vol.offset), + hive_object.get_name() or "", + module_name, + format_hints.Hex(cellroutine), + ), + ) + # Doesn't map to any module... + else: + yield ( + 0, + ( + format_hints.Hex(hive.vol.offset), + hive_object.get_name() or "", + renderers.NotAvailableValue(), + format_hints.Hex(cellroutine), + ), + ) + + def run(self): + return renderers.TreeGrid( + [ + ("Hive Offset", renderers.format_hints.Hex), + ("Hive Name", str), + ("GetCellRoutine Module", str), + ("GetCellRoutine Handler", renderers.format_hints.Hex), + ], + self._generator(), + ) diff --git a/volatility3/framework/plugins/windows/registry/hivelist.py b/volatility3/framework/plugins/windows/registry/hivelist.py index 91798de405..ddc9c18555 100644 --- a/volatility3/framework/plugins/windows/registry/hivelist.py +++ b/volatility3/framework/plugins/windows/registry/hivelist.py @@ -2,9 +2,9 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # import logging -from typing import Iterator, List, Tuple, Iterable, Optional +from typing import Iterator, List, Optional, Tuple -from volatility3.framework import renderers, interfaces, exceptions +from volatility3.framework import exceptions, interfaces, renderers from volatility3.framework.configuration import requirements from volatility3.framework.layers import registry from volatility3.framework.renderers import format_hints @@ -30,7 +30,7 @@ def __iter__(self): ): if not hive.is_valid(): self._invalid = hive.vol.offset - return + return None yield hive @property @@ -140,8 +140,8 @@ def list_hives( layer_name: str, symbol_table: str, filter_string: Optional[str] = None, - hive_offsets: List[int] = None, - ) -> Iterable[registry.RegistryHive]: + hive_offsets: Optional[List[int]] = None, + ) -> Iterator[registry.RegistryHive]: """Walks through a registry, hive by hive returning the constructed registry layer name. @@ -200,7 +200,7 @@ def list_hive_objects( context: interfaces.context.ContextInterface, layer_name: str, symbol_table: str, - filter_string: str = None, + filter_string: Optional[str] = None, ) -> Iterator[interfaces.objects.ObjectInterface]: """Lists all the hives in the primary layer. diff --git a/volatility3/framework/plugins/windows/registry/printkey.py b/volatility3/framework/plugins/windows/registry/printkey.py index 537bfc943a..4fe3f97fb8 100644 --- a/volatility3/framework/plugins/windows/registry/printkey.py +++ b/volatility3/framework/plugins/windows/registry/printkey.py @@ -20,7 +20,7 @@ class PrintKey(interfaces.plugins.PluginInterface): """Lists the registry keys under a hive or specific key value.""" _required_framework_version = (2, 0, 0) - _version = (1, 0, 0) + _version = (1, 1, 0) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -74,7 +74,7 @@ def key_iterator( node_path = [hive.get_node(hive.root_cell_offset)] if not isinstance(node_path, list) or len(node_path) < 1: vollog.warning("Hive walker was not passed a valid node_path (or None)") - return + return None node = node_path[-1] key_path_items = [hive] + node_path[1:] key_path = "\\".join([k.get_name() for k in key_path_items]) @@ -153,6 +153,11 @@ def _printkey_iterator( vollog.debug(excp) key_node_name = renderers.UnreadableValue() + # if the item is a subkey, use the LastWriteTime of that subkey + last_write_time = conversion.wintime_to_datetime( + node.LastWriteTime.QuadPart + ) + yield ( depth, ( @@ -188,9 +193,9 @@ def _printkey_iterator( vollog.debug( "Couldn't read registry value type, so data is unreadable" ) - value_data: Union[ - interfaces.renderers.BaseAbsentValue, bytes - ] = renderers.UnreadableValue() + value_data: Union[interfaces.renderers.BaseAbsentValue, bytes] = ( + renderers.UnreadableValue() + ) else: try: value_data = node.decode_data() @@ -277,7 +282,7 @@ def _registry_walker( renderers.UnreadableValue(), format_hints.Hex(hive.hive_offset), "Key", - "?\\" + (key or ""), + f"{hive.get_name()}\\" + (key or ""), renderers.UnreadableValue(), renderers.UnreadableValue(), renderers.UnreadableValue(), diff --git a/volatility3/framework/plugins/windows/registry/userassist.py b/volatility3/framework/plugins/windows/registry/userassist.py index f90724f66c..bd832b20c1 100644 --- a/volatility3/framework/plugins/windows/registry/userassist.py +++ b/volatility3/framework/plugins/windows/registry/userassist.py @@ -17,11 +17,12 @@ 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) @@ -173,11 +174,11 @@ def list_userassist( if not userassist_node_path: vollog.warning("list_userassist did not find a valid node_path (or None)") - return + return None if not isinstance(userassist_node_path, list): vollog.warning("userassist_node_path did not return a list as expected") - return + return None userassist_node = userassist_node_path[-1] # iterate through the GUIDs under the userassist key for guidkey in userassist_node.get_subkeys(): @@ -285,6 +286,10 @@ def _generator(self): 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 @@ def _generator(self): ) 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), diff --git a/volatility3/framework/plugins/windows/shimcachemem.py b/volatility3/framework/plugins/windows/shimcachemem.py new file mode 100644 index 0000000000..6afaf43560 --- /dev/null +++ b/volatility3/framework/plugins/windows/shimcachemem.py @@ -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 diff --git a/volatility3/framework/plugins/windows/skeleton_key_check.py b/volatility3/framework/plugins/windows/skeleton_key_check.py index b697774cb7..d321c2cc0c 100644 --- a/volatility3/framework/plugins/windows/skeleton_key_check.py +++ b/volatility3/framework/plugins/windows/skeleton_key_check.py @@ -601,21 +601,21 @@ def _generator(self, procs): if not symbols.symbol_table_is_64bit(self.context, kernel.symbol_table_name): vollog.info("This plugin only supports 64bit Windows memory samples") - return + return None lsass_proc, proc_layer_name = self._find_lsass_proc(procs) if not lsass_proc: vollog.info( "Unable to find a valid lsass.exe process in the process list. This should never happen. Analysis cannot proceed." ) - return + return None cryptdll_base, cryptdll_size = self._find_cryptdll(lsass_proc) if not cryptdll_base: vollog.info( "Unable to find the location of cryptdll.dll inside of lsass.exe. Analysis cannot proceed." ) - return + return None # the custom type information from binary analysis cryptdll_types = self._get_cryptdll_types( @@ -649,7 +649,7 @@ def _generator(self, procs): vollog.info( "Unable to find CSystems inside of cryptdll.dll. Analysis cannot proceed." ) - return + return None for csystem in csystems: if not self.context.layers[proc_layer_name].is_valid( diff --git a/volatility3/framework/plugins/windows/ssdt.py b/volatility3/framework/plugins/windows/ssdt.py index 6a47c36e9f..1fcb6cc910 100644 --- a/volatility3/framework/plugins/windows/ssdt.py +++ b/volatility3/framework/plugins/windows/ssdt.py @@ -30,7 +30,7 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] architectures=["Intel32", "Intel64"], ), requirements.PluginRequirement( - name="modules", plugin=modules.Modules, version=(1, 0, 0) + name="modules", plugin=modules.Modules, version=(2, 0, 0) ), ] diff --git a/volatility3/framework/plugins/windows/suspicious_threads.py b/volatility3/framework/plugins/windows/suspicious_threads.py new file mode 100644 index 0000000000..2679b191ac --- /dev/null +++ b/volatility3/framework/plugins/windows/suspicious_threads.py @@ -0,0 +1,214 @@ +# 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, Dict, Tuple, Generator +from volatility3.framework import renderers, interfaces +from volatility3.framework.configuration import requirements +from volatility3.framework.objects import utility +from volatility3.framework.renderers import format_hints +from volatility3.plugins.windows import pslist, threads, vadinfo, thrdscan + +vollog = logging.getLogger(__name__) + + +class SupsiciousThreads(interfaces.plugins.PluginInterface): + """Lists suspicious userland process threads""" + + _required_framework_version = (2, 4, 0) + _version = (2, 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.ListRequirement( + name="pid", + description="Filter on specific process IDs", + element_type=int, + optional=True, + ), + requirements.PluginRequirement( + name="threads", plugin=threads.Threads, version=(1, 0, 0) + ), + requirements.VersionRequirement( + name="vadinfo", component=vadinfo.VadInfo, version=(2, 0, 0) + ), + ] + + def _get_ranges( + self, + kernel: interfaces.context.ModuleInterface, + all_ranges: Dict[int, List[Tuple[int, int, str, str]]], + proc, + ) -> Tuple[int, int, str, str]: + """ + Maintains a hash table so each process' VADs + are only enumerated once per plugin run + """ + key = proc.vol.offset + + if key not in all_ranges: + all_ranges[key] = [] + + for vad in proc.get_vad_root().traverse(): + fn = vad.get_file_name() + if not isinstance(fn, str) or not fn: + fn = None + + protection_string = vad.get_protection( + vadinfo.VadInfo.protect_values( + self.context, kernel.layer_name, kernel.symbol_table_name + ), + vadinfo.winnt_protections, + ) + + all_ranges[key].append( + (vad.get_start(), vad.get_end(), protection_string, fn) + ) + + return all_ranges[key] + + def _get_range( + self, ranges: Dict[int, List[Tuple[int, int, str, str]]], address: int + ) -> Tuple[int, str, str]: + """ + Walks a process' VADs looking for the one + containing `address` + + Returns its base address, protection string, and mapped file, if any + """ + for start, end, protection_string, fn in ranges: + if start <= address < end: + return start, protection_string, fn + + return None, None, None + + def _check_thread_address( + self, exe_path: str, ranges, thread_address: int + ) -> Generator[Tuple[str, str], None, None]: + vad_base, prot, vad_path = self._get_range(ranges, thread_address) + + # threads outside of a VAD means either smear from this thread or this process' VAD tree + if vad_base is None: + return + + if vad_path is None: + # set this so checks after report the non file backed region in the path column + vad_path = "" + + yield ( + vad_path, + f"This thread started execution in the VAD starting at base address ({vad_base:#x}), which is not backed by a file", + ) + + # All threads should point to PAGE_EXECUTE_WRITECOPY mapped regions + if prot != "PAGE_EXECUTE_WRITECOPY": + yield ( + vad_path, + f"VAD at base address ({vad_base:#x}) hosting this thread has an unexpected starting protection {prot}", + ) + + # check for process hollowing type techniques that mapped in a second, malicious exe file + if ( + exe_path + and vad_path.lower().endswith(".exe") + and (vad_path.lower() != exe_path.lower()) + ): + yield ( + vad_path, + "VAD at base address ({vad_base:#x}) hosting this thread maps an application executable that is not the process exectuable", + ) + + def _enumerate_processes( + self, kernel: interfaces.context.ModuleInterface, all_ranges + ): + filter_func = pslist.PsList.create_pid_filter(self.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, + filter_func=filter_func, + ): + ranges = self._get_ranges(kernel, all_ranges, proc) + + # smeared vads or process is terminating + if len(all_ranges[proc.vol.offset]) < 5: + continue + + pid = proc.UniqueProcessId + proc_name = utility.array_to_string(proc.ImageFileName) + + _, __, exe_path = self._get_range(ranges, proc.SectionBaseAddress) + if not isinstance(exe_path, str): + exe_path = None + + yield proc, pid, proc_name, exe_path, ranges + + def _generator(self): + kernel = self.context.modules[self.config["kernel"]] + + all_ranges = {} + + for proc, pid, proc_name, exe_path, ranges in self._enumerate_processes( + kernel, all_ranges + ): + # processes often create multiple threads at the same address + # there is no benefit to checking the same address more than once per process + checked = set() + + for thread in threads.Threads.list_threads(kernel, proc): + # do not process if a thread is exited or terminated (4 = Terminated) + if thread.ExitTime.QuadPart > 0 or thread.Tcb.State == 4: + continue + + # bail if accessing the threads members causes a page fault + info = thrdscan.ThrdScan.gather_thread_info(thread) + if not info: + continue + + _, _, tid, start_address, _, _ = info + + addresses = [ + (start_address, "Start"), + (thread.Win32StartAddress, "Win32Start"), + ] + + for address, context in addresses: + if address in checked: + continue + checked.add(address) + + for vad_path, note in self._check_thread_address( + exe_path, ranges, address + ): + yield 0, ( + proc_name, + pid, + tid, + context, + format_hints.Hex(address), + vad_path, + note, + ) + + def run(self): + return renderers.TreeGrid( + [ + ("Process", str), + ("PID", int), + ("TID", int), + ("Context", str), + ("Address", format_hints.Hex), + ("VAD Path", str), + ("Note", str), + ], + self._generator(), + ) diff --git a/volatility3/framework/plugins/windows/svcdiff.py b/volatility3/framework/plugins/windows/svcdiff.py new file mode 100644 index 0000000000..84d06a6951 --- /dev/null +++ b/volatility3/framework/plugins/windows/svcdiff.py @@ -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] diff --git a/volatility3/framework/plugins/windows/svclist.py b/volatility3/framework/plugins/windows/svclist.py new file mode 100644 index 0000000000..a59581063b --- /dev/null +++ b/volatility3/framework/plugins/windows/svclist.py @@ -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 diff --git a/volatility3/framework/plugins/windows/svcscan.py b/volatility3/framework/plugins/windows/svcscan.py index 60562915ec..52ed5e759e 100644 --- a/volatility3/framework/plugins/windows/svcscan.py +++ b/volatility3/framework/plugins/windows/svcscan.py @@ -4,25 +4,46 @@ import logging import os -from typing import List +from typing import Dict, List, NamedTuple, Optional, Tuple, Union, cast -from volatility3.framework import interfaces, renderers, constants, symbols, exceptions +from volatility3.framework import ( + constants, + exceptions, + interfaces, + objects, + renderers, + symbols, +) from volatility3.framework.configuration import requirements from volatility3.framework.layers import scanners from volatility3.framework.renderers import format_hints from volatility3.framework.symbols import intermed from volatility3.framework.symbols.windows import versions -from volatility3.framework.symbols.windows.extensions import services -from volatility3.plugins.windows import poolscanner, vadyarascan, pslist +from volatility3.framework.symbols.windows.extensions import services as services_types +from volatility3.plugins.windows import poolscanner, pslist, vadyarascan +from volatility3.plugins.windows.registry import hivelist vollog = logging.getLogger(__name__) +ServiceBinaryInfo = NamedTuple( + "ServiceBinaryInfo", + [ + ("dll", Union[str, interfaces.renderers.BaseAbsentValue]), + ("binary", Union[str, interfaces.renderers.BaseAbsentValue]), + ], +) + + class SvcScan(interfaces.plugins.PluginInterface): """Scans for windows services.""" _required_framework_version = (2, 0, 0) - _version = (1, 0, 0) + _version = (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]: @@ -42,10 +63,16 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] requirements.PluginRequirement( name="vadyarascan", plugin=vadyarascan.VadYaraScan, version=(1, 0, 0) ), + requirements.PluginRequirement( + name="hivelist", plugin=hivelist.HiveList, version=(1, 0, 0) + ), ] @staticmethod - def get_record_tuple(service_record: interfaces.objects.ObjectInterface): + def get_record_tuple( + service_record: interfaces.objects.ObjectInterface, + binary_info: ServiceBinaryInfo, + ): return ( format_hints.Hex(service_record.vol.offset), service_record.Order, @@ -56,10 +83,34 @@ def get_record_tuple(service_record: interfaces.objects.ObjectInterface): service_record.get_name(), service_record.get_display(), service_record.get_binary(), + binary_info.binary, + binary_info.dll, ) + # These checks must be completed from newest -> oldest OS version. + _win_version_file_map: List[Tuple[versions.OsDistinguisher, bool, str]] = [ + (versions.is_win10_25398_or_later, True, "services-win10-25398-x64"), + (versions.is_win10_19041_or_later, True, "services-win10-19041-x64"), + (versions.is_win10_19041_or_later, False, "services-win10-19041-x86"), + (versions.is_win10_18362_or_later, True, "services-win10-18362-x64"), + (versions.is_win10_18362_or_later, False, "services-win10-18362-x86"), + (versions.is_win10_17763_or_later, False, "services-win10-17763-x86"), + (versions.is_win10_16299_or_later, True, "services-win10-16299-x64"), + (versions.is_win10_16299_or_later, False, "services-win10-16299-x86"), + (versions.is_win10_15063, True, "services-win10-15063-x64"), + (versions.is_win10_15063, False, "services-win10-15063-x86"), + (versions.is_win10_up_to_15063, True, "services-win8-x64"), + (versions.is_win10_up_to_15063, False, "services-win8-x86"), + (versions.is_windows_8_or_later, True, "services-win8-x64"), + (versions.is_windows_8_or_later, True, "services-win8-x86"), + (versions.is_vista_or_later, True, "services-vista-x64"), + (versions.is_vista_or_later, False, "services-vista-x86"), + (versions.is_windows_xp, False, "services-xp-x86"), + (versions.is_xp_or_2003, True, "services-xp-2003-x64"), + ] + @staticmethod - def create_service_table( + def _create_service_table( context: interfaces.context.ContextInterface, symbol_table: str, config_path: str, @@ -78,67 +129,14 @@ def create_service_table( native_types = context.symbol_space[symbol_table].natives is_64bit = symbols.symbol_table_is_64bit(context, symbol_table) - if ( - versions.is_windows_xp(context=context, symbol_table=symbol_table) - and not is_64bit - ): - symbol_filename = "services-xp-x86" - elif ( - versions.is_xp_or_2003(context=context, symbol_table=symbol_table) - and is_64bit - ): - symbol_filename = "services-xp-2003-x64" - elif ( - versions.is_win10_16299_or_later(context=context, symbol_table=symbol_table) - and is_64bit - ): - symbol_filename = "services-win10-16299-x64" - elif ( - versions.is_win10_16299_or_later(context=context, symbol_table=symbol_table) - and not is_64bit - ): - symbol_filename = "services-win10-16299-x86" - elif ( - versions.is_win10_up_to_15063(context=context, symbol_table=symbol_table) - and is_64bit - ): - symbol_filename = "services-win8-x64" - elif ( - versions.is_win10_up_to_15063(context=context, symbol_table=symbol_table) - and not is_64bit - ): - symbol_filename = "services-win8-x86" - elif ( - versions.is_win10_15063(context=context, symbol_table=symbol_table) - and is_64bit - ): - symbol_filename = "services-win10-15063-x64" - elif ( - versions.is_win10_15063(context=context, symbol_table=symbol_table) - and not is_64bit - ): - symbol_filename = "services-win10-15063-x86" - elif ( - versions.is_windows_8_or_later(context=context, symbol_table=symbol_table) - and is_64bit - ): - symbol_filename = "services-win8-x64" - elif ( - versions.is_windows_8_or_later(context=context, symbol_table=symbol_table) - and not is_64bit - ): - symbol_filename = "services-win8-x86" - elif ( - versions.is_vista_or_later(context=context, symbol_table=symbol_table) - and is_64bit - ): - symbol_filename = "services-vista-x64" - elif ( - versions.is_vista_or_later(context=context, symbol_table=symbol_table) - and not is_64bit - ): - symbol_filename = "services-vista-x86" - else: + try: + symbol_filename = next( + filename + for version_check, for_64bit, filename in SvcScan._win_version_file_map + if is_64bit == for_64bit + and version_check(context=context, symbol_table=symbol_table) + ) + except StopIteration: raise NotImplementedError("This version of Windows is not supported!") return intermed.IntermediateSymbolTable.create( @@ -146,25 +144,150 @@ def create_service_table( 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 _generator(self): - kernel = self.context.modules[self.config["kernel"]] + @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=context, + base_config_path=interfaces.configuration.path_join( + config_path, "hivelist" + ), + layer_name=layer_name, + symbol_table=symbol_table, + filter_string="machine\\system", + ): + # Get ControlSet\Services. + try: + return cast( + objects.StructType, hive.get_key(r"CurrentControlSet\Services") + ) + except (KeyError, exceptions.InvalidAddressException): + try: + return cast( + objects.StructType, hive.get_key(r"ControlSet001\Services") + ) + except (KeyError, exceptions.InvalidAddressException): + vollog.log( + constants.LOGLEVEL_VVVV, + "Could not retrieve any control set from SYSTEM hive", + ) + + return None + + @staticmethod + def _get_service_dll( + service_key, + ) -> Union[str, interfaces.renderers.BaseAbsentValue]: + try: + param_key = next( + key + for key in service_key.get_subkeys() + if key.get_name() == "Parameters" + ) + return ( + next( + val + for val in param_key.get_values() + if val.get_name() == "ServiceDll" + ) + .decode_data() + .decode("utf-16") + .rstrip("\x00") + ) + + except UnicodeDecodeError: + return renderers.UnparsableValue() + except StopIteration: + return renderers.UnreadableValue() + + @staticmethod + def _get_service_binary( + service_key, + ) -> Union[str, interfaces.renderers.BaseAbsentValue]: + try: + return ( + next( + val + for val in service_key.get_values() + if val.get_name() == "ImagePath" + ) + .decode_data() + .decode("utf-16") + .rstrip("\x00") + ) + + except UnicodeDecodeError: + return renderers.UnparsableValue() + except StopIteration: + return renderers.UnreadableValue() - service_table_name = self.create_service_table( - self.context, kernel.symbol_table_name, self.config_path + @staticmethod + def _get_service_binary_map( + services_key: interfaces.objects.ObjectInterface, + ) -> Dict[str, ServiceBinaryInfo]: + services = services_key.get_subkeys() + return { + service_key.get_name(): ServiceBinaryInfo( + SvcScan._get_service_dll(service_key), + SvcScan._get_service_binary(service_key), + ) + for service_key in services + } + + @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_header = context.object( + service_table_name + constants.BANG + "_SERVICE_HEADER", + offset=offset, + layer_name=proc_layer_name, ) - relative_tag_offset = self.context.symbol_space.get_type( + if not service_header.is_valid(): + return + + # 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: @@ -175,9 +298,9 @@ def _generator(self): 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" @@ -192,15 +315,15 @@ def _generator(self): ) 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, @@ -209,25 +332,66 @@ def _generator(self): if not service_record.is_valid(): continue - yield (0, self.get_record_tuple(service_record)) - else: - service_header = self.context.object( - service_table_name + constants.BANG + "_SERVICE_HEADER", - offset=offset, - layer_name=proc_layer_name, + service_info = service_binary_dll_map.get( + service_record.get_name(), + ServiceBinaryInfo( + renderers.UnreadableValue(), renderers.UnreadableValue() + ), ) - - 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(): + yield cls.get_record_tuple(service_record, service_info) + else: + 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) - yield (0, self.get_record_tuple(service_record)) + 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( @@ -241,6 +405,8 @@ def run(self): ("Name", str), ("Display", str), ("Binary", str), + ("Binary (Registry)", str), + ("Dll", str), ], self._generator(), ) diff --git a/volatility3/framework/plugins/windows/thrdscan.py b/volatility3/framework/plugins/windows/thrdscan.py new file mode 100644 index 0000000000..b812a15ffb --- /dev/null +++ b/volatility3/framework/plugins/windows/thrdscan.py @@ -0,0 +1,154 @@ +## +## plugin for testing addition of threads scan support to poolscanner.py +## +import logging +import datetime +from typing import Callable, Iterable + +from volatility3.framework import renderers, interfaces, exceptions +from volatility3.framework.configuration import requirements +from volatility3.framework.renderers import format_hints +from volatility3.plugins.windows import poolscanner +from volatility3.plugins import timeliner + +vollog = logging.getLogger(__name__) + + +class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): + """Scans for windows threads.""" + + # version 2.6.0 adds support for scanning for 'Ethread' structures by pool tags + _required_framework_version = (2, 6, 0) + _version = (1, 1, 0) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.implementation = self.scan_threads + + @classmethod + def get_requirements(cls): + return [ + requirements.ModuleRequirement( + name="kernel", + description="Windows kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.PluginRequirement( + name="poolscanner", plugin=poolscanner.PoolScanner, version=(1, 0, 0) + ), + ] + + @classmethod + def scan_threads( + cls, + context: interfaces.context.ContextInterface, + 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 + 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"] + ) + + for result in poolscanner.PoolScanner.generate_pool_scan( + context, layer_name, symbol_table, constraints + ): + _constraint, mem_object, _header = result + yield mem_object + + @classmethod + def gather_thread_info(cls, ethread): + try: + thread_offset = ethread.vol.offset + owner_proc_pid = ethread.Cid.UniqueProcess + thread_tid = ethread.Cid.UniqueThread + thread_start_addr = ethread.StartAddress + thread_create_time = ( + ethread.get_create_time() + ) # datetime.datetime object / volatility3.framework.renderers.UnparsableValue object + thread_exit_time = ( + ethread.get_exit_time() + ) # datetime.datetime object / volatility3.framework.renderers.UnparsableValue object + except exceptions.InvalidAddressException: + vollog.debug("Thread invalid address {:#x}".format(ethread.vol.offset)) + return None + + return ( + format_hints.Hex(thread_offset), + owner_proc_pid, + thread_tid, + format_hints.Hex(thread_start_addr), + thread_create_time, + thread_exit_time, + ) + + def _generator(self, filter_func: Callable): + kernel_name = self.config["kernel"] + + for ethread in self.implementation(self.context, kernel_name): + info = self.gather_thread_info(ethread) + + if info: + yield (0, info) + + def generate_timeline(self): + filt_func = self.filter_func(self.config) + + for row in self._generator(filt_func): + _depth, row_data = row + row_dict = {} + ( + row_dict["Offset"], + row_dict["PID"], + row_dict["TID"], + row_dict["StartAddress"], + row_dict["CreateTime"], + row_dict["ExitTime"], + ) = row_data + + # Skip threads with no creation time + # - mainly system process threads + if not isinstance(row_dict["CreateTime"], datetime.datetime): + continue + description = f"Thread: Tid {row_dict['TID']} in Pid {row_dict['PID']} (Offset {row_dict['Offset']})" + + # yield created time, and if there is exit time, yield it too. + yield (description, timeliner.TimeLinerType.CREATED, row_dict["CreateTime"]) + if isinstance(row_dict["ExitTime"], datetime.datetime): + yield ( + description, + timeliner.TimeLinerType.MODIFIED, + row_dict["ExitTime"], + ) + + @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), + ("PID", int), + ("TID", int), + ("StartAddress", format_hints.Hex), + ("CreateTime", datetime.datetime), + ("ExitTime", datetime.datetime), + ], + self._generator(filt_func), + ) diff --git a/volatility3/framework/plugins/windows/threads.py b/volatility3/framework/plugins/windows/threads.py new file mode 100644 index 0000000000..98a3169a5b --- /dev/null +++ b/volatility3/framework/plugins/windows/threads.py @@ -0,0 +1,86 @@ +# 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 Callable, Iterable, List, Generator + +from volatility3.framework import interfaces, constants +from volatility3.framework.configuration import requirements +from volatility3.plugins.windows import pslist, thrdscan + +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_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 + return [ + requirements.ModuleRequirement( + name="kernel", + description="Windows kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.ListRequirement( + name="pid", + description="Filter on specific process IDs", + element_type=int, + optional=True, + ), + requirements.PluginRequirement( + name="thrdscan", plugin=thrdscan.ThrdScan, version=(1, 1, 0) + ), + ] + + @classmethod + def list_threads( + cls, kernel, proc: interfaces.objects.ObjectInterface + ) -> Generator[interfaces.objects.ObjectInterface, None, None]: + """Lists the Threads of a specific process. + + Args: + proc: _EPROCESS object from which to list the VADs + + Returns: + A list of threads based on the process and filtered based on the filter function + """ + seen = set() + for thread in proc.ThreadListHead.to_list( + f"{kernel.symbol_table_name}{constants.BANG}_ETHREAD", "ThreadListEntry" + ): + if thread.vol.offset in seen: + break + seen.add(thread.vol.offset) + yield thread + + @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(context.config.get("pid", None)) + + for proc in pslist.PsList.list_processes( + context=context, + layer_name=layer_name, + symbol_table=symbol_table_name, + filter_func=filter_func, + ): + for thread in cls.list_threads(module, proc): + yield thread diff --git a/volatility3/framework/plugins/windows/timers.py b/volatility3/framework/plugins/windows/timers.py new file mode 100644 index 0000000000..d49c287842 --- /dev/null +++ b/volatility3/framework/plugins/windows/timers.py @@ -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(), + ) diff --git a/volatility3/framework/plugins/windows/truecrypt.py b/volatility3/framework/plugins/windows/truecrypt.py new file mode 100644 index 0000000000..7fd26cb4e6 --- /dev/null +++ b/volatility3/framework/plugins/windows/truecrypt.py @@ -0,0 +1,144 @@ +# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# + +from typing import Iterable, Generator, List, Tuple + +from volatility3.framework import constants, interfaces, renderers +from volatility3.framework.configuration import requirements +from volatility3.framework.interfaces.configuration import RequirementInterface +from volatility3.framework.interfaces.objects import ObjectInterface +from volatility3.framework.objects import Bytes, DataFormatInfo, Integer, StructType +from volatility3.framework.objects.templates import ObjectTemplate +from volatility3.framework.objects.utility import array_to_string +from volatility3.framework.renderers import format_hints +from volatility3.framework.symbols import intermed +from volatility3.framework.symbols.windows.extensions import pe + +from volatility3.plugins.windows import modules + + +class Passphrase(interfaces.plugins.PluginInterface): + """TrueCrypt Cached Passphrase Finder""" + + _version = (0, 1, 0) + _required_framework_version = (2, 5, 2) + + @classmethod + def get_requirements(cls) -> List[RequirementInterface]: + return [ + requirements.ModuleRequirement( + "kernel", + description="Windows kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.VersionRequirement( + name="modules", component=modules.Modules, version=(2, 0, 0) + ), + requirements.IntRequirement( + name="min-length", + description="Minimum length of passphrases to identify", + default=5, + optional=True, + ), + ] + + def scan_module( + self, module_base: int, layer_name: str + ) -> Generator[Tuple[int, str], None, None]: + """Scans the TrueCrypt kernel module for cached passphrases. + + Args: + module_base: the module's DLL base + layer_name: the name of the layer in which the module resides + + Generates: + A tuple of the offset at which a password is found, and the password + """ + pe_table_name = intermed.IntermediateSymbolTable.create( + self.context, self.config_path, "windows", "pe", class_types=pe.class_types + ) + dos_header: pe.IMAGE_DOS_HEADER = self.context.object( + pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER", + layer_name, + module_base, + ) + data_section: StructType = next( + sec + for sec in dos_header.get_nt_header().get_sections() + if array_to_string(sec.Name) == ".data" + ) + base: int = data_section.VirtualAddress + module_base + size: int = data_section.Misc.VirtualSize + # Looking at `Length` in TrueCrypt/Common/Password.h::Password struct + DWORD_SIZE_BYTES: int = 4 + format = DataFormatInfo( + length=DWORD_SIZE_BYTES, byteorder="little", signed=True + ) + int32 = ObjectTemplate( + Integer, pe_table_name + constants.BANG + "int", data_format=format + ) + count, not_aligned = divmod(size, DWORD_SIZE_BYTES) + if not_aligned: + raise ValueError("PE data section not DWORD-aligned!") + lengths = self.context.object( + pe_table_name + constants.BANG + "array", + layer_name, + base, + count=count, + subtype=int32, + ) + min_length = self.config.get("min-length") + for length in lengths: + # TrueCrypt maximum password length is 64 + # (see TrueCrypt/Common/Password.h) + if not min_length <= length <= 64: + continue + offset = length.vol["offset"] + DWORD_SIZE_BYTES + passphrase: Bytes = self.context.object( + pe_table_name + constants.BANG + "bytes", + layer_name, + offset, + length=length, + ) + # TrueCrypt/Common/Password.c permits chars in the range + # [0x20, 0x7F). + if not all(0x20 <= c < 0x7F for c in passphrase): + continue + # TrueCrypt/Common/Password.h::Password struct is padded with + # 3 zero bytes to keep 64-byte alignment. + buf: Bytes = self.context.object( + pe_table_name + constants.BANG + "bytes", + layer_name, + offset + length + 1, # +1 for '\0'-terminated password string + length=3, + ) + if any(buf): + continue + # Password found. + yield offset, passphrase.decode(encoding="ascii") + + def _generator(self): + kernel = self.context.modules[self.config["kernel"]] + mods: Iterable[ObjectInterface] = modules.Modules.list_modules( + self.context, kernel.layer_name, kernel.symbol_table_name + ) + truecrypt_module_base = next( + mod.DllBase + for mod in mods + if mod.BaseDllName.get_string().lower() == "truecrypt.sys" + ) + for offset, password in self.scan_module( + truecrypt_module_base, kernel.layer_name + ): + yield (0, (format_hints.Hex(offset), len(password), password)) + + def run(self) -> renderers.TreeGrid: + return renderers.TreeGrid( + [ + ("Offset", format_hints.Hex), + ("Length", int), + ("Password", str), + ], + self._generator(), + ) diff --git a/volatility3/framework/plugins/windows/unhooked_system_calls.py b/volatility3/framework/plugins/windows/unhooked_system_calls.py new file mode 100644 index 0000000000..1a1e599407 --- /dev/null +++ b/volatility3/framework/plugins/windows/unhooked_system_calls.py @@ -0,0 +1,208 @@ +# 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 + +# Full details on the techniques used in these plugins to detect EDR-evading malware +# can be found in our 20 page whitepaper submitted to DEFCON along with the presentation +# https://www.volexity.com/wp-content/uploads/2024/08/Defcon24_EDR_Evasion_Detection_White-Paper_Andrew-Case.pdf + +import logging + +from typing import Dict, Tuple, List, Generator + +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.plugins.windows import pslist, pe_symbols + +vollog = logging.getLogger(__name__) + + +class unhooked_system_calls(interfaces.plugins.PluginInterface): + """Looks for signs of Skeleton Key malware""" + + _required_framework_version = (2, 4, 0) + + system_calls = { + "ntdll.dll": { + pe_symbols.wanted_names_identifier: [ + "NtCreateThread", + "NtProtectVirtualMemory", + "NtReadVirtualMemory", + "NtOpenProcess", + "NtWriteFile", + "NtQueryVirtualMemory", + "NtAllocateVirtualMemory", + "NtWorkerFactoryWorkerReady", + "NtAcceptConnectPort", + "NtAddDriverEntry", + "NtAdjustPrivilegesToken", + "NtAlpcCreatePort", + "NtClose", + "NtCreateFile", + "NtCreateMutant", + "NtOpenFile", + "NtOpenIoCompletion", + "NtOpenJobObject", + "NtOpenKey", + "NtOpenKeyEx", + "NtOpenThread", + "NtOpenThreadToken", + "NtOpenThreadTokenEx", + "NtWriteVirtualMemory", + "NtTraceEvent", + "NtTranslateFilePath", + "NtUmsThreadYield", + "NtUnloadDriver", + "NtUnloadKey", + "NtUnloadKey2", + "NtUnloadKeyEx", + "NtCreateKey", + "NtCreateSection", + "NtDeleteKey", + "NtDeleteValueKey", + "NtDuplicateObject", + "NtQueryValueKey", + "NtReplaceKey", + "NtRequestWaitReplyPort", + "NtRestoreKey", + "NtSetContextThread", + "NtSetSecurityObject", + "NtSetValueKey", + "NtSystemDebugControl", + "NtTerminateProcess", + ] + } + } + + # This data structure is used to track unique implementations of functions across processes + # The outer dictionary holds the module name (e.g., ntdll.dll) + # The next dictionary holds the function names (NtTerminateProcess, NtSetValueKey, etc.) inside a module + # The innermost dictionary holds the unique implementation (bytes) of a function across processes + # Each implementation is tracked along with the process(es) that host it + # For systems without malware, all functions should have the same implementation + # When API hooking/module unhooking is done, the victim (infected) processes will have unique implementations + _code_bytes_type = Dict[str, Dict[str, Dict[bytes, List[Tuple[int, str]]]]] + + @classmethod + def get_requirements(cls) -> List: + # 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.PluginRequirement( + name="pe_symbols", plugin=pe_symbols.PESymbols, version=(1, 0, 0) + ), + ] + + def _gather_code_bytes( + self, + kernel: interfaces.context.ModuleInterface, + found_symbols: pe_symbols.found_symbols_type, + ) -> _code_bytes_type: + """ + Enumerates the desired DLLs and function implementations in each process + Groups based on unique implementations of each DLLs' functions + The purpose is to detect when a function has different implementations (code) + in different processes. + This very effectively detects code injection. + """ + code_bytes: unhooked_system_calls._code_bytes_type = {} + + procs = pslist.PsList.list_processes( + context=self.context, + layer_name=kernel.layer_name, + symbol_table=kernel.symbol_table_name, + ) + + for proc in procs: + try: + proc_id = proc.UniqueProcessId + proc_name = utility.array_to_string(proc.ImageFileName) + proc_layer_name = proc.add_process_layer() + except exceptions.InvalidAddressException: + continue + + for dll_name, functions in found_symbols.items(): + for func_name, func_addr in functions: + try: + fbytes = self.context.layers[proc_layer_name].read( + func_addr, 0x20 + ) + except exceptions.InvalidAddressException: + continue + + # see the definition of _code_bytes_type for details of this data structure + if dll_name not in code_bytes: + code_bytes[dll_name] = {} + + if func_name not in code_bytes[dll_name]: + code_bytes[dll_name][func_name] = {} + + if fbytes not in code_bytes[dll_name][func_name]: + code_bytes[dll_name][func_name][fbytes] = [] + + code_bytes[dll_name][func_name][fbytes].append((proc_id, proc_name)) + + return code_bytes + + def _generator(self) -> Generator[Tuple[int, Tuple[str, str, int]], None, None]: + kernel = self.context.modules[self.config["kernel"]] + + found_symbols = pe_symbols.PESymbols.addresses_for_process_symbols( + self.context, + self.config_path, + kernel.layer_name, + kernel.symbol_table_name, + unhooked_system_calls.system_calls, + ) + + # code_bytes[dll_name][func_name][func_bytes] + code_bytes = self._gather_code_bytes(kernel, found_symbols) + + # walk the functions that were evaluated + for functions in code_bytes.values(): + # cbb is the distinct groups of bytes (instructions) + # for this function across processes + for func_name, cbb in functions.items(): + # the dict key here is the raw instructions, which is not helpful to look at + # the values are the list of tuples for the (proc_id, proc_name) pairs for this set of bytes (instructions) + cb = list(cbb.values()) + + # if all processes map to the same implementation, then no malware is present + if len(cb) == 1: + yield 0, (func_name, "", len(cb[0])) + else: + # if there are differing implementations then it means + # that malware has overwritten system call(s) in infected processes + # max_idx and small_idx find which implementation of a system call has the least processes + # as all observed malware and open source projects only infected a few targets, leaving the + # rest with the original EDR hooks in place + max_idx = 0 if len(cb[0]) > len(cb[1]) else 1 + small_idx = (~max_idx) & 1 + + ps = [] + + # gather processes on small_idx since these are the malware infected ones + for pid, pname in cb[small_idx]: + ps.append("{:d}:{}".format(pid, pname)) + + proc_names = ", ".join(ps) + + yield 0, (func_name, proc_names, len(cb[max_idx])) + + def run(self) -> renderers.TreeGrid: + return renderers.TreeGrid( + [ + ("Function", str), + ("Distinct Implementations", str), + ("Total Implementations", int), + ], + self._generator(), + ) diff --git a/volatility3/framework/plugins/windows/unloadedmodules.py b/volatility3/framework/plugins/windows/unloadedmodules.py new file mode 100644 index 0000000000..0d88e96ffb --- /dev/null +++ b/volatility3/framework/plugins/windows/unloadedmodules.py @@ -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(), + ) diff --git a/volatility3/framework/plugins/windows/vadinfo.py b/volatility3/framework/plugins/windows/vadinfo.py index abc6142fe0..2c6ed4dafc 100644 --- a/volatility3/framework/plugins/windows/vadinfo.py +++ b/volatility3/framework/plugins/windows/vadinfo.py @@ -3,7 +3,7 @@ # import logging -from typing import Callable, List, Generator, Iterable, Type, Optional +from typing import Callable, List, Generator, Iterable, Type, Optional, Tuple from volatility3.framework import renderers, interfaces, exceptions from volatility3.framework.configuration import requirements @@ -196,11 +196,31 @@ def vad_dump( return file_handle - def _generator(self, procs): + def _generator(self, procs: List[interfaces.objects.ObjectInterface]) -> Generator[ + Tuple[ + int, + Tuple[ + int, + str, + format_hints.Hex, + format_hints.Hex, + format_hints.Hex, + str, + str, + int, + int, + format_hints.Hex, + str, + str, + ], + ], + None, + None, + ]: kernel = self.context.modules[self.config["kernel"]] kernel_layer = self.context.layers[kernel.layer_name] - def passthrough(_: interfaces.objects.ObjectInterface) -> bool: + def passthrough(x: interfaces.objects.ObjectInterface) -> bool: return False filter_func = passthrough @@ -250,7 +270,7 @@ def filter_function(x: interfaces.objects.ObjectInterface) -> bool: ), ) - def run(self): + def run(self) -> renderers.TreeGrid: kernel = self.context.modules[self.config["kernel"]] filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) diff --git a/volatility3/framework/plugins/windows/vadyarascan.py b/volatility3/framework/plugins/windows/vadyarascan.py index 4b30a9d8b4..efcc70d07f 100644 --- a/volatility3/framework/plugins/windows/vadyarascan.py +++ b/volatility3/framework/plugins/windows/vadyarascan.py @@ -18,46 +18,22 @@ class VadYaraScan(interfaces.plugins.PluginInterface): """Scans all the Virtual Address Descriptor memory maps using yara.""" _required_framework_version = (2, 4, 0) - _version = (1, 0, 0) + _version = (1, 1, 1) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: - return [ + # create a list of requirements for vadyarascan + vadyarascan_requirements = [ requirements.ModuleRequirement( name="kernel", description="Windows kernel", architectures=["Intel32", "Intel64"], ), - requirements.BooleanRequirement( - name="wide", - description="Match wide (unicode) strings", - default=False, - optional=True, - ), - requirements.StringRequirement( - name="yara_rules", description="Yara rules (as a string)", optional=True - ), - requirements.URIRequirement( - 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 - requirements.URIRequirement( - name="yara_compiled_file", - description="Yara compiled rules (as a file)", - optional=True, - ), - requirements.IntRequirement( - name="max_size", - default=0x40000000, - description="Set the maximum size (default is 1GB)", - optional=True, - ), requirements.PluginRequirement( name="pslist", plugin=pslist.PsList, version=(2, 0, 0) ), - requirements.VersionRequirement( - name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0) + requirements.PluginRequirement( + name="yarascan", plugin=yarascan.YaraScan, version=(2, 0, 0) ), requirements.ListRequirement( name="pid", @@ -67,6 +43,12 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] ), ] + # get base yarascan requirements for command line options + yarascan_requirements = yarascan.YaraScan.get_yarascan_option_requirements() + + # return the combined requirements + return yarascan_requirements + vadyarascan_requirements + def _generator(self): kernel = self.context.modules[self.config["kernel"]] @@ -74,6 +56,8 @@ def _generator(self): filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None)) + sanity_check = 1024 * 1024 * 1024 # 1 GB + for task in pslist.PsList.list_processes( context=self.context, layer_name=kernel.layer_name, @@ -82,18 +66,49 @@ def _generator(self): ): layer_name = task.add_process_layer() layer = self.context.layers[layer_name] - for offset, rule_name, name, value in layer.scan( - context=self.context, - scanner=yarascan.YaraScanner(rules=rules), - sections=self.get_vad_maps(task), - ): - yield 0, ( - format_hints.Hex(offset), - task.UniqueProcessId, - rule_name, - name, - value, - ) + for start, size in self.get_vad_maps(task): + if size > sanity_check: + vollog.debug( + f"VAD at 0x{start:x} over sanity-check size, not scanning" + ) + continue + + 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, + f"{match.namespace}.{match.identifier}", + match_string.identifier, + data[ + instance.offset : instance.offset + + instance.length + ], + ) @staticmethod def get_vad_maps( @@ -106,7 +121,7 @@ def get_vad_maps( 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(): diff --git a/volatility3/framework/plugins/windows/verinfo.py b/volatility3/framework/plugins/windows/verinfo.py index 1c6615804a..4a06ed0c95 100644 --- a/volatility3/framework/plugins/windows/verinfo.py +++ b/volatility3/framework/plugins/windows/verinfo.py @@ -13,7 +13,7 @@ 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, modules, dlllist +from volatility3.plugins.windows import pslist, modules vollog = logging.getLogger(__name__) @@ -46,10 +46,7 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] name="pslist", plugin=pslist.PsList, version=(2, 0, 0) ), requirements.PluginRequirement( - name="modules", plugin=modules.Modules, version=(1, 0, 0) - ), - requirements.VersionRequirement( - name="dlllist", component=dlllist.DllList, version=(2, 0, 0) + name="modules", plugin=modules.Modules, version=(2, 0, 0) ), requirements.BooleanRequirement( name="extensive", diff --git a/volatility3/framework/plugins/windows/virtmap.py b/volatility3/framework/plugins/windows/virtmap.py index 5190bec8d2..3f3f270e2e 100644 --- a/volatility3/framework/plugins/windows/virtmap.py +++ b/volatility3/framework/plugins/windows/virtmap.py @@ -78,7 +78,7 @@ def determine_map( ) else: raise exceptions.SymbolError( - None, module.name, "Required structures not found" + "SystemVaRegions", module.name, "Required structures not found" ) elif module.has_symbol("MiSystemVaType"): system_range_start = module.object( @@ -99,7 +99,7 @@ def determine_map( ) else: raise exceptions.SymbolError( - None, module.name, "Required structures not found" + "MiVisibleState", module.name, "Required structures not found" ) return result diff --git a/volatility3/framework/plugins/yarascan.py b/volatility3/framework/plugins/yarascan.py index 1c84676895..310bbd0725 100644 --- a/volatility3/framework/plugins/yarascan.py +++ b/volatility3/framework/plugins/yarascan.py @@ -13,20 +13,31 @@ vollog = logging.getLogger(__name__) +USE_YARA_X = False + try: - import yara + 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,46 +45,90 @@ def __init__(self, rules) -> None: 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 self.st_object: - 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, 1, 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]: - return [ + """Returns the requirements needed to run yarascan directly, combining the TranslationLayerRequirement + and the requirements from get_yarascan_option_requirements.""" + return cls.get_yarascan_option_requirements() + [ requirements.TranslationLayerRequirement( name="primary", description="Memory layer for the kernel", architectures=["Intel32", "Intel64"], - ), + ) + ] + + @classmethod + def get_yarascan_option_requirements( + cls, + ) -> List[interfaces.configuration.RequirementInterface]: + """Returns the requirements needed for the command lines options used by yarascan. This can + then also be used by other plugins that are using yarascan. This does not include a + TranslationLayerRequirement or a ModuleRequirement.""" + return [ requirements.BooleanRequirement( name="insensitive", description="Makes the search case insensitive", @@ -87,10 +142,14 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] 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 @@ -107,32 +166,30 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface] ), ] + @classmethod + def yara_returns_instances(cls) -> bool: + 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 diff --git a/volatility3/framework/renderers/__init__.py b/volatility3/framework/renderers/__init__.py index 534686022c..43bb59a210 100644 --- a/volatility3/framework/renderers/__init__.py +++ b/volatility3/framework/renderers/__init__.py @@ -10,7 +10,7 @@ import collections.abc import datetime import logging -from typing import Any, Callable, Iterable, List, Optional, Tuple, TypeVar, Union +from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, TypeVar, Union from volatility3.framework import interfaces from volatility3.framework.interfaces import renderers @@ -96,6 +96,10 @@ def _validate_values(self, values: List[interfaces.renderers.BaseTypes]) -> None # if isinstance(val, datetime.datetime): # tznaive = val.tzinfo is None or val.tzinfo.utcoffset(val) is None + def asdict(self) -> Dict[str, Any]: + """Returns the contents of the node as a dictionary""" + return self._values._asdict() + @property def values(self) -> List[interfaces.renderers.BaseTypes]: """Returns the list of values from the particular node, based on column diff --git a/volatility3/framework/renderers/conversion.py b/volatility3/framework/renderers/conversion.py index bf7da9ecb2..c8ddc19fdd 100644 --- a/volatility3/framework/renderers/conversion.py +++ b/volatility3/framework/renderers/conversion.py @@ -11,6 +11,7 @@ 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,22 +20,26 @@ 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() def unixtime_to_datetime( unixtime: int, ) -> Union[interfaces.renderers.BaseAbsentValue, datetime.datetime]: - ret: Union[ - interfaces.renderers.BaseAbsentValue, datetime.datetime - ] = renderers.UnparsableValue() + ret: Union[interfaces.renderers.BaseAbsentValue, datetime.datetime] = ( + renderers.UnparsableValue() + ) 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 diff --git a/volatility3/framework/renderers/format_hints.py b/volatility3/framework/renderers/format_hints.py index 6ec9ebab97..194e38099c 100644 --- a/volatility3/framework/renderers/format_hints.py +++ b/volatility3/framework/renderers/format_hints.py @@ -10,6 +10,8 @@ """ 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 @@ -59,9 +61,24 @@ def __init__( def __eq__(self, other): return ( - super(self) == super(other) + isinstance(other, self.__class__) + and super() == super(self.__class__, other) and self.converted_int == other.converted_int and self.encoding == other.encoding 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 +) diff --git a/volatility3/framework/symbols/__init__.py b/volatility3/framework/symbols/__init__.py index 10cf39cf19..a8753bd4db 100644 --- a/volatility3/framework/symbols/__init__.py +++ b/volatility3/framework/symbols/__init__.py @@ -4,9 +4,20 @@ import collections import collections.abc +import datetime import enum import logging -from typing import Any, Dict, Iterable, Iterator, TypeVar, List +from typing import ( + Any, + Callable, + Dict, + Iterable, + Iterator, + Optional, + Tuple, + TypeVar, + List, +) from volatility3.framework import constants, exceptions, interfaces, objects @@ -35,9 +46,9 @@ class SymbolSpace(interfaces.symbols.SymbolSpaceInterface): def __init__(self) -> None: super().__init__() - self._dict: Dict[ - str, interfaces.symbols.BaseSymbolTableInterface - ] = collections.OrderedDict() + self._dict: Dict[str, interfaces.symbols.BaseSymbolTableInterface] = ( + collections.OrderedDict() + ) # Permanently cache all resolved symbols self._resolved: Dict[str, interfaces.objects.Template] = {} self._resolved_symbols: Dict[str, interfaces.objects.Template] = {} @@ -73,9 +84,9 @@ def get_symbols_by_location( self, offset: int, size: int = 0, table_name: str = None ) -> Iterable[str]: """Returns all symbols that exist at a specific relative address.""" - table_list: Iterable[ - interfaces.symbols.BaseSymbolTableInterface - ] = self._dict.values() + table_list: Iterable[interfaces.symbols.BaseSymbolTableInterface] = ( + self._dict.values() + ) if table_name is not None: if table_name in self._dict: table_list = [self._dict[table_name]] @@ -113,6 +124,42 @@ def remove(self, key: str) -> None: self._resolved = {} del self._dict[key] + def verify_table_versions( + self, + producer: str, + validator: Callable[[Optional[Tuple], Optional[datetime.datetime]], bool], + tables: List[str] = None, + ) -> bool: + """Verifies the producer metadata and version of tables + + Args: + producer: String name of a table producer to have validation performed + validator: callable that takes an optional version and an optional datetime that returns False if table is invalid + + Returns: + False if an invalid table was found or True if no invalid table was found + """ + if tables is None: + tables = self._dict.keys() + for table_name in tables: + table = self._dict[table_name] + if not table.producer: + vollog.debug( + f"Symbol table {table_name} could not be validated because no producer metadata was found" + ) + continue + if table.producer.name == producer: + # Run the verification + if not validator( + table.producer.version, + table.producer.datetime, + ): + vollog.debug(f"Symbol table {table_name} does not pass validator") + return False + else: + continue + return True + ### Resolution functions class UnresolvedTemplate(objects.templates.ReferenceTemplate): @@ -179,15 +226,15 @@ def _iterative_resolve(self, traverse_list): if child.vol.type_name not in self._resolved: traverse_list.append(child.vol.type_name) try: - self._resolved[ - child.vol.type_name - ] = self._weak_resolve( - SymbolType.TYPE, child.vol.type_name + self._resolved[child.vol.type_name] = ( + self._weak_resolve( + SymbolType.TYPE, child.vol.type_name + ) ) except exceptions.SymbolError: - self._resolved[ - child.vol.type_name - ] = self.UnresolvedTemplate(child.vol.type_name) + self._resolved[child.vol.type_name] = ( + self.UnresolvedTemplate(child.vol.type_name) + ) # Stash the replacement replacements.add((traverser, child)) elif child.children: diff --git a/volatility3/framework/symbols/intermed.py b/volatility3/framework/symbols/intermed.py index 24e00bbd83..751f88e39c 100644 --- a/volatility3/framework/symbols/intermed.py +++ b/volatility3/framework/symbols/intermed.py @@ -183,6 +183,7 @@ def _closest_version( types = _construct_delegate_function("types", True) enumerations = _construct_delegate_function("enumerations", True) metadata = _construct_delegate_function("metadata", True) + producer = _construct_delegate_function("producer", True) clear_symbol_cache = _construct_delegate_function("clear_symbol_cache") get_type = _construct_delegate_function("get_type") get_symbol = _construct_delegate_function("get_symbol") @@ -281,7 +282,7 @@ def create( urls = list(cls.file_symbol_url(sub_path, filename)) if not urls: raise FileNotFoundError( - "No symbol files found at provided filename: {}", filename + f"No symbol files found at provided filename: {filename}", ) table_name = context.symbol_space.free_table_name(filename) table = cls( @@ -372,6 +373,14 @@ def metadata(self) -> Optional[interfaces.symbols.MetadataInterface]: table.""" return None + @property + def producer(self) -> Optional["metadata.ProducerMetadata"]: + """Returns a metadata object containing information about the symbol + table.""" + return metadata.ProducerMetadata( + self._json_object.get("metadata", {}).get("producer", {}) + ) + def clear_symbol_cache(self) -> None: """Clears the symbol cache of the symbol table.""" self._symbol_cache.clear() diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 5c42a436d6..219f120ee7 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -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,10 +32,21 @@ def __init__(self, *args, **kwargs) -> None: 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("kernel_cap_struct", extensions.kernel_cap_struct) + 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) @@ -60,7 +74,7 @@ def __init__(self, *args, **kwargs) -> None: 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) @@ -155,13 +169,30 @@ def _get_new_sock_pipe_path(cls, context, task, filp) -> str: Returns: str: Sock pipe pathname relative to the task's root directory. """ + # FIXME: This function must be moved to the 'dentry' object extension + # Also, the scope of this function went beyond the sock pipe path, so we need to rename this. + # Once https://github.com/volatilityfoundation/volatility3/pull/1263 is merged, replace the + # dentry inode getters + + if not (filp and filp.is_readable()): + return f" {filp:x}" + dentry = filp.get_dentry() + if not (dentry and dentry.is_readable()): + return f" {dentry:x}" kernel_module = cls.get_module_from_volobj_type(context, dentry) sym_addr = dentry.d_op.d_dname + if not (sym_addr and sym_addr.is_readable()): + return f" {sym_addr:x}" + symbs = list(kernel_module.get_symbols_by_absolute_location(sym_addr)) + inode = dentry.d_inode + if not (inode and inode.is_readable() and inode.is_valid()): + return f" {inode:x}" + if len(symbs) == 1: sym = symbs[0].split(constants.BANG)[1] @@ -175,17 +206,50 @@ def _get_new_sock_pipe_path(cls, context, task, filp) -> str: pre_name = "pipe" elif sym == "simple_dname": - pre_name = cls._get_path_file(task, filp) - + name = dentry.d_name.name + if name: + pre_name = name.dereference().cast( + "string", max_length=255, errors="replace" + ) + return "/" + pre_name + " (deleted)" + else: + pre_name = "" + + elif sym == "ns_dname": + # From Kernels 3.19 + + # In Kernels >= 6.9, see Linux kernel commit 1fa08aece42512be072351f482096d5796edf7ca + # ns_common->stashed change from 'atomic64_t' to 'dentry*' + try: + ns_common_type = kernel_module.get_type("ns_common") + stashed_template = ns_common_type.child_template("stashed") + stashed_type_full_name = stashed_template.vol.type_name + stashed_type_name = stashed_type_full_name.split(constants.BANG)[1] + if stashed_type_name == "atomic64_t": + # 3.19 <= Kernels < 6.9 + fsdata_ptr = dentry.d_fsdata + if not (fsdata_ptr and fsdata_ptr.is_readable()): + raise IndexError + + ns_ops = fsdata_ptr.dereference().cast("proc_ns_operations") + else: + # Kernels >= 6.9 + private_ptr = inode.i_private + if not (private_ptr and private_ptr.is_readable()): + raise IndexError + + ns_common = private_ptr.dereference().cast("ns_common") + ns_ops = ns_common.ops + + pre_name = utility.pointer_to_string(ns_ops.name, 255) + except IndexError: + pre_name = "" else: - pre_name = f"" - - ret = f"{pre_name}:[{dentry.d_inode.i_ino:d}]" - + pre_name = f" {sym}" else: - ret = f" {sym_addr:x}" + pre_name = f" {sym_addr:x}" - return ret + return f"{pre_name}:[{inode.i_ino:d}]" @classmethod def path_for_file(cls, context, task, filp) -> str: @@ -242,18 +306,18 @@ def files_descriptors_for_process( task: interfaces.objects.ObjectInterface, ): # task.files can be null - if not task.files: - return + if not (task.files and task.files.is_readable()): + return None fd_table = task.files.get_fds() if fd_table == 0: - return + return None max_fds = task.files.get_max_fds() # corruption check if max_fds > 500000: - return + return None file_type = symbol_table + constants.BANG + "file" @@ -262,7 +326,7 @@ def files_descriptors_for_process( ) for fd_num, filp in enumerate(fds): - if filp != 0: + if filp and filp.is_readable(): full_path = LinuxUtilities.path_for_file(context, task, filp) yield fd_num, filp, full_path @@ -378,7 +442,7 @@ def container_of( """ if not addr: - return + return None type_dec = vmlinux.get_type(type_name) member_offset = type_dec.relative_child_offset(member_name) @@ -405,9 +469,7 @@ def get_module_from_volobj_type( 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) @@ -418,3 +480,353 @@ def get_module_from_volobj_type( 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 diff --git a/volatility3/framework/symbols/linux/elf.json b/volatility3/framework/symbols/linux/elf.json index 76cd8a2eca..79a96e07a3 100644 --- a/volatility3/framework/symbols/linux/elf.json +++ b/volatility3/framework/symbols/linux/elf.json @@ -270,8 +270,8 @@ "d_tag": { "offset": 0, "type": { - "kind": "base", - "name": "long long" + "kind": "enum", + "name": "DtypeEnum64" } }, "d_ptr": { @@ -699,8 +699,8 @@ "d_tag": { "offset": 0, "type": { - "kind": "base", - "name": "long" + "kind": "enum", + "name": "DtypeEnum32" } }, "d_ptr": { @@ -905,11 +905,139 @@ "PT_PHDR": 6, "PT_TLS": 7, "PT_LOOS": 1610612736, + "PT_GNU_EH_FRAME": 1685382480, + "PT_GNU_STACK": 1685382481, + "PT_GNU_RELRO": 1685382482, + "PT_GNU_PROPERTY": 1685382483, "PT_HIOS": 1879048191, "PT_LOWPROC": 1879048192, "PT_HIPROC": 2147483647 }, "size": 4 + }, + "DtypeEnum32": { + "base": "long", + "constants": { + "DT_NULL": 0, + "DT_NEEDED": 1, + "DT_PLTRELSZ": 2, + "DT_PLTGOT": 3, + "DT_HASH": 4, + "DT_STRTAB": 5, + "DT_SYMTAB": 6, + "DT_RELA": 7, + "DT_RELASZ": 8, + "DT_RELAENT": 9, + "DT_STRSZ": 10, + "DT_SYMENT": 11, + "DT_INIT": 12, + "DT_FINI": 13, + "DT_SONAME": 14, + "DT_RPATH": 15, + "DT_SYMBOLIC": 16, + "DT_REL": 17, + "DT_RELSZ": 18, + "DT_RELENT": 19, + "DT_PLTREL": 20, + "DT_DEBUG": 21, + "DT_TEXTREL": 22, + "DT_JMPREL": 23, + "DT_BIND_NOW": 24, + "DT_INIT_ARRAY": 25, + "DT_FINI_ARRAY": 26, + "DT_INIT_ARRAYSZ": 27, + "DT_FINI_ARRAYSZ": 28, + "DT_RUNPATH": 29, + "DT_FLAGS": 30, + "DT_ENCODING": 32, + "DT_PREINIT_ARRAYSZ": 33, + "DT_SYMTAB_SHNDX": 34, + "DT_RELRSZ": 35, + "DT_RELR": 36, + "DT_RELRENT": 37, + "DT_NUM": 38, + "OLD_DT_LOOS": 1610612736, + "DT_LOOS": 1610612749, + "DT_HIOS": 1879044096, + "DT_VALRNGLO": 1879047424, + "DT_VALRNGHI": 1879047679, + "DT_ADDRRNGLO": 1879047680, + "DT_GNU_HASH": 1879047925, + "DT_ADDRRNGHI": 1879047935, + "DT_VERSYM": 1879048176, + "DT_RELACOUNT": 1879048185, + "DT_RELCOUNT": 1879048186, + "DT_FLAGS_1": 1879048187, + "DT_VERDEF": 1879048188, + "DT_VERDEFNUM": 1879048189, + "DT_VERNEED": 1879048190, + "DT_VERNEEDNUM": 1879048191, + "DT_LOPROC": 1879048192, + "DT_HIPROC": 2147483647 + }, + "size": 4 + }, + "DtypeEnum64": { + "base": "long long", + "constants": { + "DT_NULL": 0, + "DT_NEEDED": 1, + "DT_PLTRELSZ": 2, + "DT_PLTGOT": 3, + "DT_HASH": 4, + "DT_STRTAB": 5, + "DT_SYMTAB": 6, + "DT_RELA": 7, + "DT_RELASZ": 8, + "DT_RELAENT": 9, + "DT_STRSZ": 10, + "DT_SYMENT": 11, + "DT_INIT": 12, + "DT_FINI": 13, + "DT_SONAME": 14, + "DT_RPATH": 15, + "DT_SYMBOLIC": 16, + "DT_REL": 17, + "DT_RELSZ": 18, + "DT_RELENT": 19, + "DT_PLTREL": 20, + "DT_DEBUG": 21, + "DT_TEXTREL": 22, + "DT_JMPREL": 23, + "DT_BIND_NOW": 24, + "DT_INIT_ARRAY": 25, + "DT_FINI_ARRAY": 26, + "DT_INIT_ARRAYSZ": 27, + "DT_FINI_ARRAYSZ": 28, + "DT_RUNPATH": 29, + "DT_FLAGS": 30, + "DT_ENCODING": 32, + "DT_PREINIT_ARRAYSZ": 33, + "DT_SYMTAB_SHNDX": 34, + "DT_RELRSZ": 35, + "DT_RELR": 36, + "DT_RELRENT": 37, + "DT_NUM": 38, + "OLD_DT_LOOS": 1610612736, + "DT_LOOS": 1610612749, + "DT_HIOS": 1879044096, + "DT_VALRNGLO": 1879047424, + "DT_VALRNGHI": 1879047679, + "DT_ADDRRNGLO": 1879047680, + "DT_GNU_HASH": 1879047925, + "DT_ADDRRNGHI": 1879047935, + "DT_VERSYM": 1879048176, + "DT_RELACOUNT": 1879048185, + "DT_RELCOUNT": 1879048186, + "DT_FLAGS_1": 1879048187, + "DT_VERDEF": 1879048188, + "DT_VERDEFNUM": 1879048189, + "DT_VERNEED": 1879048190, + "DT_VERNEEDNUM": 1879048191, + "DT_LOPROC": 1879048192, + "DT_HIPROC": 2147483647 + }, + "size": 8 } }, "base_types": { @@ -958,9 +1086,9 @@ }, "metadata": { "producer": { - "version": "0.0.1", - "name": "ikelos-by-hand", - "datetime": "2019-10-21T22:52:00" + "version": "0.0.2", + "name": "gcmoreira-by-hand", + "datetime": "2024-02-19T14:37:00" }, "format": "6.1.0" } diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index acf3bc1ef5..655c6d1d31 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -4,17 +4,22 @@ 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 +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 from volatility3.framework.constants.linux import ETH_PROTOCOLS, BLUETOOTH_STATES from volatility3.framework.constants.linux import BLUETOOTH_PROTOCOLS, SOCKET_STATES from volatility3.framework.constants.linux import CAPABILITIES -from volatility3.framework import exceptions, objects, interfaces, symbols from volatility3.framework.layers import linear from volatility3.framework.objects import utility from volatility3.framework.symbols import generic, linux, intermed @@ -26,14 +31,58 @@ class module(generic.GenericIntelProcess): + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self._mod_mem_type = None # Initialize _mod_mem_type to None for memoization + + @property + def mod_mem_type(self): + """Return the mod_mem_type enum choices if available or an empty dict if not""" + # mod_mem_type and module_memory were added in kernel 6.4 which replaces + # module_layout for storing the information around core_layout etc. + # see commit ac3b43283923440900b4f36ca5f9f0b1ca43b70e for more information + + if self._mod_mem_type is None: + try: + self._mod_mem_type = self._context.symbol_space.get_enumeration( + self.get_symbol_table_name() + constants.BANG + "mod_mem_type" + ).choices + except exceptions.SymbolError: + vollog.debug( + "Unable to find mod_mem_type enum. This message can be ignored for kernels < 6.4" + ) + # set to empty dict to show that the enum was not found, and so shouldn't be searched for again + self._mod_mem_type = {} + return self._mod_mem_type + def get_module_base(self): - if self.has_member("core_layout"): + if self.has_member("mem"): # kernels 6.4+ + try: + return self.mem[self.mod_mem_type["MOD_TEXT"]].base + except KeyError: + raise AttributeError( + "module -> get_module_base: Unable to get module base. Cannot read base from MOD_TEXT." + ) + elif self.has_member("core_layout"): return self.core_layout.base - else: + elif self.has_member("module_core"): return self.module_core + raise AttributeError("module -> get_module_base: Unable to get module base") def get_init_size(self): - if self.has_member("init_layout"): + if self.has_member("mem"): # kernels 6.4+ + try: + return ( + self.mem[self.mod_mem_type["MOD_INIT_TEXT"]].size + + self.mem[self.mod_mem_type["MOD_INIT_DATA"]].size + + self.mem[self.mod_mem_type["MOD_INIT_RODATA"]].size + ) + except KeyError: + raise AttributeError( + "module -> get_init_size: Unable to determine .init section size of module. Cannot read size of MOD_INIT_TEXT, MOD_INIT_DATA, and MOD_INIT_RODATA" + ) + elif self.has_member("init_layout"): return self.init_layout.size elif self.has_member("init_size"): return self.init_size @@ -42,7 +91,19 @@ def get_init_size(self): ) def get_core_size(self): - if self.has_member("core_layout"): + if self.has_member("mem"): # kernels 6.4+ + try: + return ( + self.mem[self.mod_mem_type["MOD_TEXT"]].size + + self.mem[self.mod_mem_type["MOD_DATA"]].size + + self.mem[self.mod_mem_type["MOD_RODATA"]].size + + self.mem[self.mod_mem_type["MOD_RO_AFTER_INIT"]].size + ) + except KeyError: + raise AttributeError( + "module -> get_core_size: Unable to determine core size of module. Cannot read size of MOD_TEXT, MOD_DATA, MOD_RODATA, and MOD_RO_AFTER_INIT." + ) + elif self.has_member("core_layout"): return self.core_layout.size elif self.has_member("core_size"): return self.core_size @@ -51,18 +112,32 @@ def get_core_size(self): ) def get_module_core(self): - if self.has_member("core_layout"): + if self.has_member("mem"): # kernels 6.4+ + try: + return self.mem[self.mod_mem_type["MOD_TEXT"]].base + except KeyError: + raise AttributeError( + "module -> get_module_core: Unable to get module core. Cannot read base from MOD_TEXT." + ) + elif self.has_member("core_layout"): return self.core_layout.base elif self.has_member("module_core"): return self.module_core raise AttributeError("module -> get_module_core: Unable to get module core") def get_module_init(self): - if self.has_member("init_layout"): + if self.has_member("mem"): # kernels 6.4+ + try: + return self.mem[self.mod_mem_type["MOD_INIT_TEXT"]].base + except KeyError: + raise AttributeError( + "module -> get_module_core: Unable to get module init. Cannot read base from MOD_INIT_TEXT." + ) + elif self.has_member("init_layout"): return self.init_layout.base elif self.has_member("module_init"): return self.module_init - raise AttributeError("module -> get_module_core: Unable to get module init") + raise AttributeError("module -> get_module_init: Unable to get module init") def get_name(self): """Get the name of the module as a string""" @@ -71,11 +146,11 @@ def get_name(self): def _get_sect_count(self, grp): """Try to determine the number of valid sections""" arr = self._context.object( - self.get_symbol_table().name + constants.BANG + "array", + self.get_symbol_table_name() + constants.BANG + "array", layer_name=self.vol.layer_name, offset=grp.attrs, subtype=self._context.symbol_space.get_type( - self.get_symbol_table().name + constants.BANG + "pointer" + self.get_symbol_table_name() + constants.BANG + "pointer" ), count=25, ) @@ -92,11 +167,11 @@ def get_sections(self): else: num_sects = self._get_sect_count(self.sect_attrs.grp) arr = self._context.object( - self.get_symbol_table().name + constants.BANG + "array", + self.get_symbol_table_name() + constants.BANG + "array", layer_name=self.vol.layer_name, offset=self.sect_attrs.attrs.vol.offset, subtype=self._context.symbol_space.get_type( - self.get_symbol_table().name + constants.BANG + "module_sect_attr" + self.get_symbol_table_name() + constants.BANG + "module_sect_attr" ), count=num_sects, ) @@ -104,41 +179,82 @@ def get_sections(self): for attr in arr: yield attr - def get_symbols(self): - if symbols.symbol_table_is_64bit(self._context, self.get_symbol_table().name): - prefix = "Elf64_" - else: - prefix = "Elf32_" + def get_elf_table_name(self): elf_table_name = intermed.IntermediateSymbolTable.create( - self.context, - self.config_path, + self._context, + "elf_symbol_table", "linux", "elf", native_types=None, class_types=elf.class_types, ) + return elf_table_name + + def get_symbols(self): + """Get symbols of the module + Yields: + A symbol object + """ + + if not hasattr(self, "_elf_table_name"): + self._elf_table_name = self.get_elf_table_name() + if symbols.symbol_table_is_64bit(self._context, self.get_symbol_table_name()): + prefix = "Elf64_" + else: + prefix = "Elf32_" syms = self._context.object( - self.get_symbol_table().name + constants.BANG + "array", + self.get_symbol_table_name() + constants.BANG + "array", layer_name=self.vol.layer_name, offset=self.section_symtab, subtype=self._context.symbol_space.get_type( - elf_table_name + constants.BANG + prefix + "Sym" + self._elf_table_name + constants.BANG + prefix + "Sym" ), count=self.num_symtab + 1, ) if self.section_strtab: for sym in syms: - sym.set_cached_strtab(self.section_strtab) yield sym - def get_symbol(self, wanted_sym_name): - """Get value for a given symbol name""" + def get_symbols_names_and_addresses(self) -> Tuple[str, int]: + """Get names and addresses for each symbol of the module + + Yields: + A tuple for each symbol containing the symbol name and its corresponding value + """ + for sym in self.get_symbols(): - sym_name = sym.get_name() - sym_addr = sym.st_value + sym_arr = self._context.object( + self.get_symbol_table_name() + constants.BANG + "array", + layer_name=self.vol.native_layer_name, + offset=self.section_strtab + sym.st_name, + ) + try: + sym_name = utility.array_to_string( + sym_arr, 512 + ) # 512 is the value of KSYM_NAME_LEN kernel constant + except exceptions.InvalidAddressException: + continue + if sym_name != "": + # Normalize sym.st_value offset, which is an address pointing to the symbol value + mask = self._context.layers[self.vol.layer_name].address_mask + sym_address = sym.st_value & mask + yield (sym_name, sym_address) + + def get_symbol(self, wanted_sym_name): + """Get symbol value for a given symbol name""" + for sym_name, sym_address in self.get_symbols_names_and_addresses(): if wanted_sym_name == sym_name: - return sym_addr + return sym_address + + return None + + def get_symbol_by_address(self, wanted_sym_address): + """Get symbol name for a given symbol address""" + for sym_name, sym_address in self.get_symbols_names_and_addresses(): + if wanted_sym_address == sym_address: + return sym_name + return None @property @@ -321,19 +437,30 @@ def _parse_maple_tree_node( maple_tree_entry, parent, expected_maple_tree_depth, - seen=set(), + seen=None, current_depth=1, ): """Recursively parse Maple Tree Nodes and yield all non empty slots""" + # Create seen set if it does not exist, e.g. on the first call into this recursive function. This + # must be None or an existing set of addresses for MTEs that have already been processed or that + # should otherwise be ignored. If parsing from the root node for example this should be None on the + # first call. If you needed to parse all nodes downwards from part of the tree this should still be + # None. If however you wanted to parse from a node, but ignore some parts of the tree below it then + # this could be populated with the addresses of the nodes you wish to ignore. + + if seen is None: + seen = set() + # protect against unlikely loop if maple_tree_entry in seen: vollog.warning( f"The mte {hex(maple_tree_entry)} has all ready been seen, no further results will be produced for this node." ) - return + return None else: seen.add(maple_tree_entry) + # check if we have exceeded the expected depth of this maple tree. # e.g. when current_depth is larger than expected_maple_tree_depth there may be an issue. # it is normal that expected_maple_tree_depth is equal to current_depth. @@ -342,6 +469,7 @@ def _parse_maple_tree_node( f"The depth for the maple tree at {hex(self.vol.offset)} is {expected_maple_tree_depth}, however when parsing the nodes " f"a depth of {current_depth} was reached. This is unexpected and may lead to incorrect results." ) + # parse the mte to extract the pointer value, node type, and leaf status pointer = maple_tree_entry & ~(self.MAPLE_NODE_POINTER_MASK) node_type = ( @@ -406,15 +534,28 @@ def _parse_maple_tree_node( class mm_struct(objects.StructType): + + # TODO: As of version 3.0.0 this method should be removed def get_mmap_iter(self) -> Iterable[interfaces.objects.ObjectInterface]: - """Returns an iterator for the mmap list member of an mm_struct.""" + """ + Deprecated: Use either get_vma_iter() or _get_mmap_iter(). + """ + vollog.warning( + "This method has been deprecated in favour of using the get_vma_iter() method." + ) + yield from self.get_vma_iter() + + def _get_mmap_iter(self) -> Iterable[interfaces.objects.ObjectInterface]: + """Returns an iterator for the mmap list member of an mm_struct. Use this only if + required, get_vma_iter() will choose the correct _get_maple_tree_iter() or + _get_mmap_iter() automatically as required.""" if not self.has_member("mmap"): raise AttributeError( - "get_mmap_iter called on mm_struct where no mmap member exists." + "_get_mmap_iter called on mm_struct where no mmap member exists." ) if not self.mmap: - return + return None yield self.mmap seen = {self.mmap.vol.offset} @@ -425,12 +566,24 @@ def get_mmap_iter(self) -> Iterable[interfaces.objects.ObjectInterface]: seen.add(link.vol.offset) link = link.vm_next + # TODO: As of version 3.0.0 this method should be removed def get_maple_tree_iter(self) -> Iterable[interfaces.objects.ObjectInterface]: - """Returns an iterator for the mm_mt member of an mm_struct.""" + """ + Deprecated: Use either get_vma_iter() or _get_maple_tree_iter(). + """ + vollog.warning( + "This method has been deprecated in favour of using the get_vma_iter() method." + ) + yield from self.get_vma_iter() + + def _get_maple_tree_iter(self) -> Iterable[interfaces.objects.ObjectInterface]: + """Returns an iterator for the mm_mt member of an mm_struct. Use this only if + required, get_vma_iter() will choose the correct _get_maple_tree_iter() or + get_mmap_iter() automatically as required.""" if not self.has_member("mm_mt"): raise AttributeError( - "get_maple_tree_iter called on mm_struct where no mm_mt member exists." + "_get_maple_tree_iter called on mm_struct where no mm_mt member exists." ) symbol_table_name = self.get_symbol_table_name() for vma_pointer in self.mm_mt.get_slot_iter(): @@ -446,9 +599,9 @@ def get_vma_iter(self) -> Iterable[interfaces.objects.ObjectInterface]: """Returns an iterator for the VMAs in an mm_struct. Automatically choosing the mmap or mm_mt as required.""" if self.has_member("mmap"): - yield from self.get_mmap_iter() + yield from self._get_mmap_iter() elif self.has_member("mm_mt"): - yield from self.get_maple_tree_iter() + yield from self._get_maple_tree_iter() else: raise AttributeError("Unable to find mmap or mm_mt in mm_struct") @@ -497,11 +650,23 @@ def get_flags_opts(self) -> Iterable[str]: ] return sb_opts - def get_type(self): - mnt_sb_type = utility.pointer_to_string(self.s_type.name, count=255) - if self.s_subtype: - mnt_sb_subtype = utility.pointer_to_string(self.s_subtype, count=255) + def get_type(self) -> Optional[str]: + """Gets the superblock filesystem type string""" + + s_type_ptr = self.s_type + if not (s_type_ptr and s_type_ptr.is_readable()): + return None + + s_type_name_ptr = s_type_ptr.name + if not (s_type_name_ptr and s_type_name_ptr.is_readable()): + return None + + mnt_sb_type = utility.pointer_to_string(s_type_name_ptr, count=255) + s_subtype_ptr = self.s_subtype + if s_subtype_ptr and s_subtype_ptr.is_readable(): + mnt_sb_subtype = utility.pointer_to_string(s_subtype_ptr, count=255) mnt_sb_type += "." + mnt_sb_subtype + return mnt_sb_type @@ -571,7 +736,8 @@ def get_flags(self) -> str: def get_page_offset(self) -> int: if self.vm_file == 0: return 0 - return self.vm_pgoff << constants.linux.PAGE_SHIFT + parent_layer = self._context.layers[self.vol.layer_name] + return self.vm_pgoff << parent_layer.page_shift def get_name(self, context, task): if self.vm_file != 0: @@ -600,7 +766,7 @@ def is_suspicious(self, proclayer=None): elif flags_str == "r-x" and self.vm_file.dereference().vol.offset == 0: ret = True elif proclayer and "x" in flags_str: - for i in range(self.vm_start, self.vm_end, 1 << constants.linux.PAGE_SHIFT): + for i in range(self.vm_start, self.vm_end, proclayer.page_size): try: if proclayer.is_dirty(i): vollog.warning( @@ -681,25 +847,75 @@ def d_ancestor(self, ancestor_dentry): 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) + + def get_inode(self) -> interfaces.objects.ObjectInterface: + """Returns the inode associated with this dentry""" + + inode_ptr = self.d_inode + if not (inode_ptr and inode_ptr.is_readable() and inode_ptr.is_valid()): + return None + + return inode_ptr.dereference() + class struct_file(objects.StructType): def get_dentry(self) -> interfaces.objects.ObjectInterface: - if self.has_member("f_dentry"): - return self.f_dentry - elif self.has_member("f_path"): + """Returns a pointer to the dentry associated with this file""" + if self.has_member("f_path"): return self.f_path.dentry + elif self.has_member("f_dentry"): + return self.f_dentry else: raise AttributeError("Unable to find file -> dentry") def get_vfsmnt(self) -> interfaces.objects.ObjectInterface: """Returns the fs (vfsmount) where this file is mounted""" - if self.has_member("f_vfsmnt"): - return self.f_vfsmnt - elif self.has_member("f_path"): + if self.has_member("f_path"): return self.f_path.mnt + elif self.has_member("f_vfsmnt"): + return self.f_vfsmnt else: raise AttributeError("Unable to find file -> vfs mount") + def get_inode(self) -> interfaces.objects.ObjectInterface: + """Returns an inode associated with this file""" + + inode_ptr = None + if self.has_member("f_inode") and self.f_inode and self.f_inode.is_readable(): + # Try first the cached value, kernels +3.9 + inode_ptr = self.f_inode + + if not (inode_ptr and inode_ptr.is_readable() and inode_ptr.is_valid()): + dentry_ptr = self.get_dentry() + if not (dentry_ptr and dentry_ptr.is_readable()): + return None + + inode_ptr = dentry_ptr.d_inode + + if not (inode_ptr and inode_ptr.is_readable() and inode_ptr.is_valid()): + return None + + return inode_ptr.dereference() + class list_head(objects.StructType, collections.abc.Iterable): def to_list( @@ -735,7 +951,7 @@ def to_list( try: link = getattr(self, direction).dereference() except exceptions.InvalidAddressException: - return + return None if not sentinel: yield self._context.object( symbol_type, layer, offset=self.vol.offset - relative_offset @@ -797,7 +1013,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"): @@ -1001,17 +1218,17 @@ def is_equal(self, vfsmount_ptr) -> bool: """Helper to make sure it is comparing two pointers to 'vfsmount'. Depending on the kernel version, the calling object (self) could be - a 'vfsmount \*' (<3.3.8) or a 'vfsmount' (>=3.3.8). This way we trust + a 'vfsmount \\*' (<3.3.8) or a 'vfsmount' (>=3.3.8). This way we trust in the framework "auto" dereferencing ability to assure that when we reach this point 'self' will be a 'vfsmount' already and self.vol.offset - a 'vfsmount \*' and not a 'vfsmount \*\*'. The argument must be a 'vfsmount \*'. + a 'vfsmount \\*' and not a 'vfsmount \\*\\*'. The argument must be a 'vfsmount \\*'. Typically, it's called from do_get_path(). Args: - vfsmount_ptr (vfsmount \*): A pointer to a 'vfsmount' + vfsmount_ptr (vfsmount *): A pointer to a 'vfsmount' Raises: - exceptions.VolatilityException: If vfsmount_ptr is not a 'vfsmount \*' + exceptions.VolatilityException: If vfsmount_ptr is not a 'vfsmount \\*' Returns: bool: 'True' if the given argument points to the the same 'vfsmount' @@ -1112,6 +1329,7 @@ def has_parent(self) -> bool: 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: @@ -1144,7 +1362,7 @@ def get_devname(self) -> str: class kobject(objects.StructType): def reference_count(self): refcnt = self.kref.refcount - if self.has_member("counter"): + if refcnt.has_member("counter"): ret = refcnt.counter else: ret = refcnt.refs.counter @@ -1230,7 +1448,7 @@ def get_inode(self): return self.sk_socket.get_inode() def get_protocol(self): - return + return None def get_state(self): # Return the generic socket state @@ -1242,13 +1460,13 @@ def get_state(self): class unix_sock(objects.StructType): def get_name(self): if not self.addr: - return + return None sockaddr_un = self.addr.name.cast("sockaddr_un") saddr = str(utility.array_to_string(sockaddr_un.sun_path)) return saddr def get_protocol(self): - return + return None def get_state(self): """Return a string representing the sock state.""" @@ -1307,7 +1525,7 @@ def get_dst_port(self): elif hasattr(sk_common, "skc_dport"): dport_le = sk_common.skc_dport else: - return + return None return socket_module.htons(dport_le) def get_src_addr(self): @@ -1325,7 +1543,7 @@ def get_src_addr(self): addr_size = 16 saddr = self.pinet6.saddr else: - return + return None parent_layer = self._context.layers[self.vol.layer_name] try: addr_bytes = parent_layer.read(saddr.vol.offset, addr_size) @@ -1333,7 +1551,7 @@ def get_src_addr(self): vollog.debug( f"Unable to read socket src address from {saddr.vol.offset:#x}" ) - return + return None return socket_module.inet_ntop(family, addr_bytes) def get_dst_addr(self): @@ -1354,7 +1572,7 @@ def get_dst_addr(self): daddr = sk_common.skc_v6_daddr addr_size = 16 else: - return + return None parent_layer = self._context.layers[self.vol.layer_name] try: addr_bytes = parent_layer.read(daddr.vol.offset, addr_size) @@ -1362,7 +1580,7 @@ def get_dst_addr(self): vollog.debug( f"Unable to read socket dst address from {daddr.vol.offset:#x}" ) - return + return None return socket_module.inet_ntop(family, addr_bytes) @@ -1400,7 +1618,7 @@ def get_dst_portid(self): class vsock_sock(objects.StructType): def get_protocol(self): # The protocol should always be 0 for vsocks - return + return None def get_state(self): # Return the generic socket state @@ -1411,7 +1629,7 @@ class packet_sock(objects.StructType): def get_protocol(self): eth_proto = socket_module.htons(self.num) if eth_proto == 0: - return + return None elif eth_proto in ETH_PROTOCOLS: return ETH_PROTOCOLS[eth_proto] else: @@ -1437,7 +1655,7 @@ def get_state(self): class xdp_sock(objects.StructType): def get_protocol(self): # The protocol should always be 0 for xdp_sock - return + return None def get_state(self): # xdp_sock.state is an enum @@ -1445,20 +1663,59 @@ def get_state(self): 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): @@ -1502,13 +1759,13 @@ def euid(self): class kernel_cap_struct(objects.StructType): - # struct kernel_cap_struct was added in kernels 2.5.0 + # struct kernel_cap_struct exists from 2.1.92 <= kernels < 6.3 @classmethod def get_last_cap_value(cls) -> int: """Returns the latest capability ID supported by the framework. Returns: - int: The latest supported capability ID supported by the framework. + int: The latest capability ID supported by the framework. """ return len(CAPABILITIES) - 1 @@ -1516,7 +1773,7 @@ def get_kernel_cap_full(self) -> int: """Return the maximum value allowed for this kernel for a capability Returns: - int: _description_ + int: The capability full bitfield mask """ vmlinux = linux.LinuxUtilities.get_module_from_volobj_type(self._context, self) try: @@ -1552,17 +1809,29 @@ def get_capabilities(self) -> int: int: The capability bitfield value. """ + if not self.has_member("cap"): + raise exceptions.VolatilityException( + "Unsupported kernel capabilities implementation" + ) + if isinstance(self.cap, objects.Array): - # In 2.6.25.x <= kernels < 6.3 kernel_cap_struct::cap is a two - # elements __u32 array that constitutes a 64bit bitfield. - # Technically, it can also be an array of 1 element if - # _KERNEL_CAPABILITY_U32S = _LINUX_CAPABILITY_U32S_1 - # However, in the source code, that never happens. - # From 2.6.24 to 2.6.25 cap became an array of 2 elements. - cap_value = (self.cap[1] << 32) | self.cap[0] + if len(self.cap) == 1: + # At least in the vanilla kernel, from 2.6.24 to 2.6.25 + # kernel_cap_struct::cap become a two elements array. + # However, in some distros or custom kernel can technically + # be _KERNEL_CAPABILITY_U32S = _LINUX_CAPABILITY_U32S_1 + # Leaving this code here for the sake of ensuring completeness. + cap_value = self.cap[0] + elif len(self.cap) == 2: + # In 2.6.25.x <= kernels < 6.3 kernel_cap_struct::cap is a two + # elements __u32 array that constitutes a 64bit bitfield. + cap_value = (self.cap[1] << 32) | self.cap[0] + else: + raise exceptions.VolatilityException( + "Unsupported kernel capabilities implementation" + ) else: - # In kernels < 2.6.25.x kernel_cap_struct::cap was a __u32 - # In kernels >= 6.3 kernel_cap_struct::cap is a u64 + # In kernels < 2.6.25.x kernel_cap_struct::cap is a __u32 cap_value = self.cap return cap_value & self.get_kernel_cap_full() @@ -1593,3 +1862,403 @@ def has_capability(self, capability: str) -> bool: cap_value = 1 << CAPABILITIES.index(capability) return cap_value & self.get_capabilities() != 0 + + +class kernel_cap_t(kernel_cap_struct): + # In kernels 6.3 kernel_cap_struct became the kernel_cap_t typedef + def get_capabilities(self) -> int: + """Returns the capability bitfield value + + Returns: + int: The capability bitfield value. + """ + + if self.has_member("val"): + # In kernels >= 6.3 kernel_cap_t::val is a u64 + cap_value = self.val + else: + raise exceptions.VolatilityException( + "Unsupported kernel capabilities implementation" + ) + + 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 diff --git a/volatility3/framework/symbols/linux/extensions/elf.py b/volatility3/framework/symbols/linux/extensions/elf.py index 416a7e4d28..eadcbbae01 100644 --- a/volatility3/framework/symbols/linux/extensions/elf.py +++ b/volatility3/framework/symbols/linux/extensions/elf.py @@ -3,9 +3,16 @@ # from typing import Dict, Tuple +import logging from volatility3.framework import constants -from volatility3.framework import objects, interfaces +from volatility3.framework.constants.linux import ( + ELF_IDENT, + ELF_CLASS, +) +from volatility3.framework import objects, interfaces, exceptions + +vollog = logging.getLogger(__name__) class elf(objects.StructType): @@ -33,27 +40,38 @@ def __init__( layer_name = self.vol.layer_name symbol_table_name = self.get_symbol_table_name() # We read the MAGIC: (0x0 to 0x4) 0x7f 0x45 0x4c 0x46 - magic = self._context.object( - symbol_table_name + constants.BANG + "unsigned long", - layer_name=layer_name, - offset=object_info.offset, - ) + try: + magic = self._context.object( + symbol_table_name + constants.BANG + "unsigned long", + layer_name=layer_name, + offset=object_info.offset, + ) + except ( + exceptions.PagedInvalidAddressException, + exceptions.InvalidAddressException, + ) as excp: + vollog.debug( + f"Unable to check magic bytes for ELF file at offset {hex(object_info.offset)} in layer {layer_name}: {excp}" + ) + return None # Check validity - if magic != 0x464C457F: + if magic != 0x464C457F: # e.g. ELF return None # We need to read the EI_CLASS (0x4 offset) ei_class = self._context.object( symbol_table_name + constants.BANG + "unsigned char", layer_name=layer_name, - offset=object_info.offset + 0x4, + offset=object_info.offset + ELF_IDENT.EI_CLASS, ) - if ei_class == 1: + if ei_class == ELF_CLASS.ELFCLASS32: self._type_prefix = "Elf32_" - elif ei_class == 2: + self._ei_class_size = 32 + elif ei_class == ELF_CLASS.ELFCLASS64: self._type_prefix = "Elf64_" + self._ei_class_size = 64 else: raise ValueError(f"Unsupported ei_class value {ei_class}") @@ -72,7 +90,10 @@ def is_valid(self): """ Determine whether it is a valid object """ - return self._type_prefix is not None and self._hdr is not None + if hasattr(self, "_type_prefix") and hasattr(self, "_hdr"): + return self._type_prefix is not None and self._hdr is not None + else: + return False def __getattr__(self, name): # Just redirect to the corresponding header @@ -125,36 +146,137 @@ def get_section_headers(self): ) return section_headers + def get_link_maps(self, kernel_symbol_table_name): + """Get the ELF link map objects for the given VMA address + + Args: + kernel_symbol_table_name (str): Kernel symbol table name + + Yields: + The ELF link map objects + """ + got_entry_size = self._ei_class_size // 8 + + elf_symbol_table = self.get_symbol_table_name() + + link_maps_seen = set() + for phdr in self.get_program_headers(): + try: + if phdr.p_type.description != "PT_DYNAMIC": + continue + except ValueError: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping unknown ELF program header type: {phdr.p_type}", + ) + continue + + for dsec in phdr.dynamic_sections(): + try: + if dsec.d_tag.description != "DT_PLTGOT": + continue + except ValueError: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping unknown ELF dynamic section type: {dsec.d_tag}", + ) + continue + + got_start = dsec.d_ptr + + # link_map is stored at the second GOT entry + link_map_addr = got_start + got_entry_size + + # It needs the kernel symbol table to create a pointer + link_map_ptr = self._context.object( + kernel_symbol_table_name + constants.BANG + "pointer", + offset=link_map_addr, + layer_name=self.vol.layer_name, + ) + if not link_map_ptr: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Invalid ELF link map pointer at 0x{link_map_addr:x}", + ) + continue + + linkmap_symname = ( + elf_symbol_table + constants.BANG + self._type_prefix + "LinkMap" + ) + try: + link_map = self._context.object( + object_type=linkmap_symname, + offset=link_map_ptr, + layer_name=self.vol.layer_name, + ) + except exceptions.InvalidAddressException: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Invalid ELF link map address at 0x{link_map_ptr:x}", + ) + continue + + while link_map and link_map.vol.offset != 0: + if link_map.vol.offset in link_maps_seen: + break + link_maps_seen.add(link_map.vol.offset) + + yield link_map + + try: + link_map = self._context.object( + object_type=linkmap_symname, + offset=link_map.l_next, + layer_name=self.vol.layer_name, + ) + except exceptions.InvalidAddressException: + vollog.log( + constants.LOGLEVEL_VVVV, + f"ELF link map linked list is corrupt at 0x{self.vol.offset:x}", + ) + break + def _find_symbols(self): dt_strtab = None dt_symtab = None dt_strent = None for phdr in self.get_program_headers(): + # Find PT_DYNAMIC segment try: - # Find PT_DYNAMIC segment - if str(phdr.p_type.description) != "PT_DYNAMIC": + if phdr.p_type.description != "PT_DYNAMIC": continue except ValueError: - # If the p_type value is outside the ones declared in the enumeration, an - # exception is raised - return None + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping unknown ELF program header type: {phdr.p_type}", + ) + continue # This section contains pointers to the strtab, symtab, and strent sections for dsec in phdr.dynamic_sections(): - if dsec.d_tag == 5: + try: + dtag = dsec.d_tag.description + except ValueError: + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping unknown ELF dynamic section type: {dsec.d_tag}", + ) + continue + + if dtag == "DT_STRTAB": dt_strtab = dsec.d_ptr - elif dsec.d_tag == 6: + elif dtag == "DT_SYMTAB": dt_symtab = dsec.d_ptr - elif dsec.d_tag == 11: + elif dtag == "DT_SYMENT": # Size of the symtab symbol entry dt_strent = dsec.d_ptr break - if dt_strtab is None or dt_symtab is None or dt_strent is None: + if not (dt_strtab and dt_symtab and dt_strent): return None self._cached_symtab = dt_symtab @@ -171,7 +293,7 @@ def get_symbols(self): self._find_symbols() if self._cached_symtab is None: - return + return None symtab_arr = self._context.object( self.get_symbol_table_name() + constants.BANG + "array", @@ -259,19 +381,31 @@ def type_prefix(self, prefix): def get_vaddr(self): offset = self.__getattr__("p_vaddr") - if self._parent_e_type == 3: # ET_DYN - offset = self._parent_offset + offset + try: + if self._parent_e_type.description == "ET_DYN": + offset = self._parent_offset + offset + except ValueError: + # Unknown ELF object file type. Anyway, if the ELF object file type is not a + # shared object (ET_DYN), the virtual address is 'p_vaddr'. + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping unknown ELF object type: {self._parent_e_type}", + ) return offset def dynamic_sections(self): # sanity check try: - if str(self.p_type.description) != "PT_DYNAMIC": + if self.p_type.description != "PT_DYNAMIC": return None except ValueError: # If the value is outside the ones declared in the enumeration, an # exception is raised + vollog.log( + constants.LOGLEVEL_VVVV, + f"Skipping unknown ELF program header type: {self.p_type}", + ) return None # the buffer of array starts at elf_base + our virtual address ( offset ) @@ -299,10 +433,30 @@ def dynamic_sections(self): break +class elf_linkmap(objects.StructType): + def get_name(self): + try: + buf = self._context.layers.read(self.vol.layer_name, self.l_name, 256) + except exceptions.PagedInvalidAddressException: + # Protection against memory smear + vollog.log( + constants.LOGLEVEL_VVVV, + f"Invalid l_name address for ELF link map at 0x{self.vol.offset:x}", + ) + return None + + idx = buf.find(b"\x00") + if idx != -1: + buf = buf[:idx] + return buf.decode() + + class_types = { "Elf": elf, "Elf64_Phdr": elf_phdr, "Elf32_Phdr": elf_phdr, "Elf32_Sym": elf_sym, "Elf64_Sym": elf_sym, + "Elf32_LinkMap": elf_linkmap, + "Elf64_LinkMap": elf_linkmap, } diff --git a/volatility3/framework/symbols/mac/__init__.py b/volatility3/framework/symbols/mac/__init__.py index 56ac96633f..c695ca77a7 100644 --- a/volatility3/framework/symbols/mac/__init__.py +++ b/volatility3/framework/symbols/mac/__init__.py @@ -21,12 +21,14 @@ def __init__(self, *args, **kwargs) -> None: self.set_type_class("vm_map_object", extensions.vm_map_object) self.set_type_class("socket", extensions.socket) self.set_type_class("inpcb", extensions.inpcb) - self.set_type_class("queue_entry", extensions.queue_entry) self.set_type_class("ifnet", extensions.ifnet) self.set_type_class("sockaddr_dl", extensions.sockaddr_dl) self.set_type_class("sockaddr", extensions.sockaddr) self.set_type_class("sysctl_oid", extensions.sysctl_oid) self.set_type_class("kauth_scope", extensions.kauth_scope) + # https://developer.apple.com/documentation/kernel/queue_head_t + self.set_type_class("queue_entry", extensions.queue_entry) + self.optional_set_type_class("queue_head_t", extensions.queue_entry) class MacUtilities(interfaces.configuration.VersionableInterface): @@ -169,7 +171,7 @@ def files_descriptors_for_process( try: table_addr = task.p_fd.fd_ofiles.dereference() except exceptions.InvalidAddressException: - return + return None fds = objects.utility.array_of_pointers( table_addr, count=num_fds, subtype=file_type, context=context @@ -204,7 +206,7 @@ def _walk_iterable( try: current = queue.member(attr=list_head_member) except exceptions.InvalidAddressException: - return + return None while current: if current.vol.offset in seen: diff --git a/volatility3/framework/symbols/mac/extensions/__init__.py b/volatility3/framework/symbols/mac/extensions/__init__.py index 9b4ceee556..fc609f9886 100644 --- a/volatility3/framework/symbols/mac/extensions/__init__.py +++ b/volatility3/framework/symbols/mac/extensions/__init__.py @@ -51,7 +51,7 @@ def get_map_iter(self) -> Iterable[interfaces.objects.ObjectInterface]: task = self.get_task() current_map = task.map.hdr.links.next except exceptions.InvalidAddressException: - return + return None seen: Set[int] = set() @@ -155,13 +155,13 @@ def get_map_object(self): class vnode(objects.StructType): def _do_calc_path(self, ret, vnodeobj, vname): if vnodeobj is None: - return + return None if vname: try: ret.append(utility.pointer_to_string(vname, 255)) except exceptions.InvalidAddressException: - return + return None if int(vnodeobj.v_flag) & 0x000001 != 0 and int(vnodeobj.v_mount) != 0: if int(vnodeobj.v_mount.mnt_vnodecovered) != 0: @@ -175,7 +175,7 @@ def _do_calc_path(self, ret, vnodeobj, vname): parent = vnodeobj.v_parent parent_name = parent.v_name except exceptions.InvalidAddressException: - return + return None self._do_calc_path(ret, parent, parent_name) @@ -507,22 +507,24 @@ def walk_list( for attr in ["next", "prev"]: with contextlib.suppress(exceptions.InvalidAddressException): - n = getattr(self, attr).dereference().cast(type_name) - - while n is not None and n.vol.offset != list_head: - if n.vol.offset in seen: + queue_element = getattr(self, attr).dereference().cast(type_name) + while ( + queue_element is not None + and queue_element.vol.offset != list_head.vol.offset + ): + if queue_element.vol.offset in seen: break - yield n + yield queue_element - seen.add(n.vol.offset) + seen.add(queue_element.vol.offset) yielded = yielded + 1 if yielded == max_size: - return + return None - n = ( - getattr(n.member(attr=member_name), attr) + queue_element = ( + getattr(queue_element.member(attr=member_name), attr) .dereference() .cast(type_name) ) diff --git a/volatility3/framework/symbols/metadata.py b/volatility3/framework/symbols/metadata.py index 61947be698..95f542f079 100644 --- a/volatility3/framework/symbols/metadata.py +++ b/volatility3/framework/symbols/metadata.py @@ -2,9 +2,49 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # +import datetime +import logging from typing import Optional, Tuple, Union -from volatility3.framework import interfaces +from volatility3.framework import constants, interfaces + +vollog = logging.getLogger(__name__) + + +class ProducerMetadata(interfaces.symbols.MetadataInterface): + """Class to handle the Producer metadata from an ISF""" + + @property + def name(self) -> Optional[str]: + return self._json_data.get("name", None) + + @property + def version(self) -> Optional[Tuple[int]]: + """Returns the version of the ISF file producer""" + version = self._json_data.get("version", None) + if not version: + return None + if all(x in "0123456789." for x in version): + return tuple(int(x) for x in version.split(".")) + vollog.log( + constants.LOGLEVEL_VVVV, + f"Metadata version contains unexpected characters: '{version}'", + ) + return None + + @property + def datetime(self) -> Optional[datetime.datetime]: + """Returns a timestamp for when the file was produced""" + if "datetime" not in self._json_data: + return None + try: + timestamp = datetime.datetime.strptime( + self._json_data["datetime"], "YYYY-MM-DD" + ) + except (TypeError, ValueError): + vollog.debug("Invalid timestamp in producer information of symbol table") + return None + return timestamp class WindowsMetadata(interfaces.symbols.MetadataInterface): diff --git a/volatility3/framework/symbols/windows/__init__.py b/volatility3/framework/symbols/windows/__init__.py index abf9f6da32..c1b7894ffa 100755 --- a/volatility3/framework/symbols/windows/__init__.py +++ b/volatility3/framework/symbols/windows/__init__.py @@ -17,6 +17,7 @@ def __init__(self, *args, **kwargs) -> None: 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 @@ def __init__(self, *args, **kwargs) -> None: 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) diff --git a/volatility3/framework/symbols/windows/callbacks-x64.json b/volatility3/framework/symbols/windows/callbacks-x64.json index 87682cb923..705f2361d5 100644 --- a/volatility3/framework/symbols/windows/callbacks-x64.json +++ b/volatility3/framework/symbols/windows/callbacks-x64.json @@ -1,6 +1,18 @@ { "symbols": {}, - "enums": {}, + "enums": { + "EventCategory": { + "base": "long", + "constants": { + "EventCategoryReserved": 0, + "EventCategoryHardwareProfileChange": 1, + "EventCategoryDeviceInterfaceChange": 2, + "EventCategoryTargetDeviceChange": 3, + "EventCategoryKernelSoftRestart": 4 + }, + "size": 4 + } + }, "base_types": { "unsigned long": { "kind": "int", @@ -48,19 +60,164 @@ "user_types": { "_GENERIC_CALLBACK": { "fields": { - "Callback": { - "type": { - "kind": "pointer", - "subtype": { - "kind": "base", - "name": "void" - } + "Callback": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 8 }, - "offset": 8 - } + "Associated": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_NOTIFICATION_PACKET": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "DriverObject": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_DRIVER_OBJECT" + } + }, + "offset": 16 + }, + "NotificationRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 24 + } + }, + "kind": "struct", + "size": 48 + }, + "_SHUTDOWN_PACKET": { + "fields": { + "Entry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "DeviceObject": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_DEVICE_OBJECT" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_DBGPRINT_CALLBACK": { + "fields": { + "Function": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_NOTIFY_ENTRY_HEADER": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "EventCategory": { + "type": { + "kind": "enum", + "name": "EventCategory" + }, + "offset": 16 + }, + "CallbackRoutine": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 32 + }, + "DriverObject": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_DRIVER_OBJECT" + } + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 64 + }, + "_REGISTRY_CALLBACK": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "Function": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 40 + } }, "kind": "struct", - "size": 16 + "size": 48 }, "_KBUGCHECK_CALLBACK_RECORD": { "fields": { @@ -184,10 +341,10 @@ }, "metadata": { "producer": { - "version": "0.0.1", - "name": "mhl by hand", - "datetime": "2019-08-27T18:17:16.417006" + "version": "0.0.2", + "name": "dgmcdona by hand", + "datetime": "2024-02-02T19:32:00.000000" }, "format": "4.0.0" } -} \ No newline at end of file +} diff --git a/volatility3/framework/symbols/windows/callbacks-x86.json b/volatility3/framework/symbols/windows/callbacks-x86.json index 702b68a650..63b8c69b4f 100644 --- a/volatility3/framework/symbols/windows/callbacks-x86.json +++ b/volatility3/framework/symbols/windows/callbacks-x86.json @@ -1,6 +1,18 @@ { "symbols": {}, - "enums": {}, + "enums": { + "EventCategory": { + "base": "long", + "constants": { + "EventCategoryReserved": 0, + "EventCategoryHardwareProfileChange": 1, + "EventCategoryDeviceInterfaceChange": 2, + "EventCategoryTargetDeviceChange": 3, + "EventCategoryKernelSoftRestart": 4 + }, + "size": 4 + } + }, "base_types": { "unsigned long": { "kind": "int", @@ -46,6 +58,151 @@ } }, "user_types": { + "_NOTIFICATION_PACKET": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "DriverObject": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_DRIVER_OBJECT" + } + }, + "offset": 8 + }, + "NotificationRoutine": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 16 + }, + "_SHUTDOWN_PACKET": { + "fields": { + "Entry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "DeviceObject": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_DEVICE_OBJECT" + } + }, + "offset": 8 + } + }, + "kind": "struct", + "size": 12 + }, + "_REGISTRY_CALLBACK": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "Function": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 28 + } + }, + "kind": "struct", + "size": 32 + }, + "_REGISTRY_CALLBACK_LEGACY": { + "fields": { + "CreateTime": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 28 + } + }, + "kind": "struct", + "size": 32 + }, + "_DBGPRINT_CALLBACK": { + "fields": { + "Function": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 8 + } + }, + "kind": "struct", + "size": 20 + }, + "_NOTIFY_ENTRY_HEADER": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "EventCategory": { + "type": { + "kind": "enum", + "name": "EventCategory" + }, + "offset": 8 + }, + "CallbackRoutine": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 20 + }, + "DriverObject": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_DRIVER_OBJECT" + } + }, + "offset": 28 + } + }, + "kind": "struct", + "size": 32 + }, "_GENERIC_CALLBACK": { "fields": { "Callback": { @@ -57,10 +214,20 @@ } }, "offset": 4 + }, + "Associated": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 8 } }, "kind": "struct", - "size": 8 + "size": 12 }, "_KBUGCHECK_CALLBACK_RECORD": { "fields": { @@ -184,10 +351,10 @@ }, "metadata": { "producer": { - "version": "0.0.1", - "name": "mhl by hand", - "datetime": "2019-08-27T18:17:16.417006" + "version": "0.0.2", + "name": "dgmcdona by hand", + "datetime": "2024-02-02T19:32:00.000000" }, "format": "4.0.0" } -} \ No newline at end of file +} diff --git a/volatility3/framework/symbols/windows/consoles/consoles-win10-17763-3232-x64.json b/volatility3/framework/symbols/windows/consoles/consoles-win10-17763-3232-x64.json new file mode 100644 index 0000000000..13382c7ee5 --- /dev/null +++ b/volatility3/framework/symbols/windows/consoles/consoles-win10-17763-3232-x64.json @@ -0,0 +1,659 @@ +{ + "symbols": {}, + "enums": {}, + "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" + }, + "short": { + "kind": "int", + "size": 2, + "signed": true, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_CONSOLE_INFORMATION": { + "fields": { + "ScreenX": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 32 + }, + "ScreenY": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 34 + }, + "CommandHistorySize": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 144 + }, + "HistoryBufferMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 148 + }, + "OriginalTitle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 1736 + }, + "Title": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 1672 + }, + "GetScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 1800 + }, + "CurrentScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 1384 + }, + "ConsoleProcessList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 1360 + }, + "ProcessCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 1368 + }, + "HistoryList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": -760 + }, + "HistoryBufferCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": -752 + }, + "ExeAliasList": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 1392 + } + }, + "kind": "struct", + "size": 140 + }, + "_VECTOR": { + "fields": { + "Begin": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 0 + }, + "End": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 8 + }, + "EndCapacity": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned long" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 0 + }, + "Pointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 0 + }, + "Length": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 16 + }, + "Allocated": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 24 + } + }, + "kind": "struct", + "size": 32 + }, + "_CONSOLE_PROCESS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ConsoleProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_CONSOLE_PROCESS_HANDLE": { + "fields": { + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 52 + }, + "_CONSOLE_PROCESS": { + "fields": { + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 28 + }, + "ThreadId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 32 + }, + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND_HISTORY": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "CommandBucket": { + "type": { + "kind": "struct", + "name": "_VECTOR" + }, + "offset": 16 + }, + "CommandCountMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 40 + }, + "Application": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + }, + "ConsoleProcessHandle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS_HANDLE" + } + }, + "offset": 80 + }, + "Flags": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 88 + }, + "LastDisplayed": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 92 + } + }, + "kind": "struct", + "size": 96 + }, + "_SCREEN_INFORMATION": { + "fields": { + "TextBufferInfo": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 56 + }, + "Next": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 64 + } + }, + "kind": "struct", + "size": 72 + }, + "_ROW_POINTER": { + "fields": { + "Row": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + + }, + "_ROWS_ARRAY": { + "fields": { + "Rows": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_ROW_POINTER" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_TEXT_BUFFER_INFO": { + "fields": { + "BufferDeque": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_DEQUE" + } + }, + "offset": 0 + }, + "BufferRows": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROWS_ARRAY" + } + }, + "offset": 8 + }, + "BufferCapacity": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 32 + }, + "ThisBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 48 + }, + "BufferEnd": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 56 + }, + "BufferLastIndex": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 58 + }, + "BufferStart": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 112 + } + }, + "kind": "struct", + "size": 72 + }, + "_CHAR_ROW_CELL": { + "fields": { + "Text": { + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + }, + "offset": 0 + }, + "DbcsAttribute": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 2 + } + }, + "kind": "struct", + "size": 3 + }, + "_CHAR_ROW_CELL_ARRAY": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_ROW": { + "fields": { + "CharRow": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL_ARRAY" + } + }, + "offset": 8 + }, + "RowLength": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 64 + }, + "Index": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 72 + }, + "RowLength2": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 80 + }, + "TextBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 88 + } + }, + "kind": "struct", + "size": 96 + }, + "_DEQUE": { + "fields": { + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_EXE_ALIAS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ExeLength": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 8 + }, + "ExeName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 24 + }, + "AliasList": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 32 + } + }, + "kind": "struct", + "size": 48 + }, + "_ALIAS": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "Source": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 16 + }, + "Target": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 32 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "Dave Lassalle", + "datetime": "2024-07-31T15:05:35-06:00" + }, + "format": "4.1.0" + } +} diff --git a/volatility3/framework/symbols/windows/consoles/consoles-win10-17763-x64.json b/volatility3/framework/symbols/windows/consoles/consoles-win10-17763-x64.json new file mode 100644 index 0000000000..f813691bc5 --- /dev/null +++ b/volatility3/framework/symbols/windows/consoles/consoles-win10-17763-x64.json @@ -0,0 +1,659 @@ +{ + "symbols": {}, + "enums": {}, + "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" + }, + "short": { + "kind": "int", + "size": 2, + "signed": true, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_CONSOLE_INFORMATION": { + "fields": { + "ScreenX": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 32 + }, + "ScreenY": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 34 + }, + "CommandHistorySize": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 144 + }, + "HistoryBufferMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 148 + }, + "OriginalTitle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 1736 + }, + "Title": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 1672 + }, + "GetScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 1800 + }, + "CurrentScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 1384 + }, + "ConsoleProcessList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 1360 + }, + "ProcessCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 1368 + }, + "HistoryList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": -760 + }, + "HistoryBufferCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": -752 + }, + "ExeAliasList": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 1392 + } + }, + "kind": "struct", + "size": 140 + }, + "_VECTOR": { + "fields": { + "Begin": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 0 + }, + "End": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 8 + }, + "EndCapacity": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned long" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 0 + }, + "Pointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 0 + }, + "Length": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 16 + }, + "Allocated": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 24 + } + }, + "kind": "struct", + "size": 32 + }, + "_CONSOLE_PROCESS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ConsoleProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_CONSOLE_PROCESS_HANDLE": { + "fields": { + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 52 + }, + "_CONSOLE_PROCESS": { + "fields": { + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 28 + }, + "ThreadId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 32 + }, + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND_HISTORY": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "CommandBucket": { + "type": { + "kind": "struct", + "name": "_VECTOR" + }, + "offset": 16 + }, + "CommandCountMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 40 + }, + "Application": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + }, + "ConsoleProcessHandle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS_HANDLE" + } + }, + "offset": 80 + }, + "Flags": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 88 + }, + "LastDisplayed": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 92 + } + }, + "kind": "struct", + "size": 96 + }, + "_SCREEN_INFORMATION": { + "fields": { + "TextBufferInfo": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 32 + }, + "Next": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 36 + } + }, + "kind": "struct", + "size": 72 + }, + "_ROW_POINTER": { + "fields": { + "Row": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + + }, + "_ROWS_ARRAY": { + "fields": { + "Rows": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_ROW_POINTER" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_TEXT_BUFFER_INFO": { + "fields": { + "BufferDeque": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_DEQUE" + } + }, + "offset": 0 + }, + "BufferRows": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROWS_ARRAY" + } + }, + "offset": 8 + }, + "BufferCapacity": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 32 + }, + "ThisBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 48 + }, + "BufferEnd": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 56 + }, + "BufferLastIndex": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 58 + }, + "BufferStart": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 112 + } + }, + "kind": "struct", + "size": 72 + }, + "_CHAR_ROW_CELL": { + "fields": { + "Text": { + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + }, + "offset": 0 + }, + "DbcsAttribute": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 2 + } + }, + "kind": "struct", + "size": 3 + }, + "_CHAR_ROW_CELL_ARRAY": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_ROW": { + "fields": { + "CharRow": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL_ARRAY" + } + }, + "offset": 8 + }, + "RowLength": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 64 + }, + "Index": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 72 + }, + "RowLength2": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 80 + }, + "TextBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 88 + } + }, + "kind": "struct", + "size": 96 + }, + "_DEQUE": { + "fields": { + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_EXE_ALIAS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ExeLength": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 16 + }, + "ExeName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 24 + }, + "AliasList": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 32 + } + }, + "kind": "struct", + "size": 48 + }, + "_ALIAS": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "Source": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 16 + }, + "Target": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 32 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "Dave Lassalle", + "datetime": "2024-07-31T15:05:35-06:00" + }, + "format": "4.1.0" + } +} diff --git a/volatility3/framework/symbols/windows/consoles/consoles-win10-18362-x64.json b/volatility3/framework/symbols/windows/consoles/consoles-win10-18362-x64.json new file mode 100644 index 0000000000..85ef9d7180 --- /dev/null +++ b/volatility3/framework/symbols/windows/consoles/consoles-win10-18362-x64.json @@ -0,0 +1,649 @@ +{ + "symbols": {}, + "enums": {}, + "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" + }, + "short": { + "kind": "int", + "size": 2, + "signed": true, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_CONSOLE_INFORMATION": { + "fields": { + "ScreenX": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 32 + }, + "ScreenY": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 34 + }, + "CommandHistorySize": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 144 + }, + "HistoryBufferMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 148 + }, + "OriginalTitle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 1744 + }, + "Title": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 1680 + }, + "GetScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 1808 + }, + "CurrentScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 1392 + }, + "ConsoleProcessList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 1368 + }, + "ProcessCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 1376 + }, + "HistoryList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": -768 + }, + "HistoryBufferCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": -760 + }, + "ExeAliasList": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": -856 + } + }, + "kind": "struct", + "size": 140 + }, + "_VECTOR": { + "fields": { + "Begin": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 0 + }, + "End": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 8 + }, + "EndCapacity": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned long" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 0 + }, + "Pointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 0 + }, + "Length": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 16 + }, + "Allocated": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 24 + } + }, + "kind": "struct", + "size": 32 + }, + "_CONSOLE_PROCESS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ConsoleProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_CONSOLE_PROCESS_HANDLE": { + "fields": { + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 52 + }, + "_CONSOLE_PROCESS": { + "fields": { + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 28 + }, + "ThreadId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 32 + }, + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND_HISTORY": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "CommandBucket": { + "type": { + "kind": "struct", + "name": "_VECTOR" + }, + "offset": 16 + }, + "CommandCountMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 40 + }, + "Application": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + }, + "ConsoleProcessHandle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS_HANDLE" + } + }, + "offset": 80 + }, + "Flags": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 88 + }, + "LastDisplayed": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 92 + } + }, + "kind": "struct", + "size": 96 + }, + "_SCREEN_INFORMATION": { + "fields": { + "TextBufferInfo": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 56 + }, + "Next": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 64 + } + }, + "kind": "struct", + "size": 72 + }, + "_ROW_POINTER": { + "fields": { + "Row": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + + }, + "_ROWS_ARRAY": { + "fields": { + "Rows": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_ROW_POINTER" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_TEXT_BUFFER_INFO": { + "fields": { + "BufferDeque": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_DEQUE" + } + }, + "offset": 0 + }, + "BufferRows": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROWS_ARRAY" + } + }, + "offset": 8 + }, + "BufferCapacity": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 32 + }, + "BufferEnd": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 56 + }, + "BufferLastIndex": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 58 + }, + "BufferStart": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 88 + } + }, + "kind": "struct", + "size": 72 + }, + "_CHAR_ROW_CELL": { + "fields": { + "Text": { + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + }, + "offset": 0 + }, + "DbcsAttribute": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 2 + } + }, + "kind": "struct", + "size": 3 + }, + "_CHAR_ROW_CELL_ARRAY": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_ROW": { + "fields": { + "CharRow": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL_ARRAY" + } + }, + "offset": 8 + }, + "RowLength": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 64 + }, + "Index": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 72 + }, + "RowLength2": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 80 + }, + "TextBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 88 + } + }, + "kind": "struct", + "size": 96 + }, + "_DEQUE": { + "fields": { + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_EXE_ALIAS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ExeLength": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 8 + }, + "ExeName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 24 + }, + "AliasList": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 32 + } + }, + "kind": "struct", + "size": 48 + }, + "_ALIAS": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "Source": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 16 + }, + "Target": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 32 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "Dave Lassalle", + "datetime": "2024-07-31T15:05:35-06:00" + }, + "format": "4.1.0" + } +} diff --git a/volatility3/framework/symbols/windows/consoles/consoles-win10-19041-x64.json b/volatility3/framework/symbols/windows/consoles/consoles-win10-19041-x64.json new file mode 100644 index 0000000000..8f6dd8c6a8 --- /dev/null +++ b/volatility3/framework/symbols/windows/consoles/consoles-win10-19041-x64.json @@ -0,0 +1,649 @@ +{ + "symbols": {}, + "enums": {}, + "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" + }, + "short": { + "kind": "int", + "size": 2, + "signed": true, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_CONSOLE_INFORMATION": { + "fields": { + "ScreenX": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 32 + }, + "ScreenY": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 34 + }, + "CommandHistorySize": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 144 + }, + "HistoryBufferMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 148 + }, + "OriginalTitle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 1752 + }, + "Title": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 1688 + }, + "GetScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 1816 + }, + "CurrentScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 1392 + }, + "ConsoleProcessList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 1368 + }, + "ProcessCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 1376 + }, + "HistoryList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": -352 + }, + "HistoryBufferCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": -344 + }, + "ExeAliasList": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 1436 + } + }, + "kind": "struct", + "size": 140 + }, + "_VECTOR": { + "fields": { + "Begin": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 0 + }, + "End": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 8 + }, + "EndCapacity": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned long" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 0 + }, + "Pointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 0 + }, + "Length": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 16 + }, + "Allocated": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 24 + } + }, + "kind": "struct", + "size": 32 + }, + "_CONSOLE_PROCESS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ConsoleProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_CONSOLE_PROCESS_HANDLE": { + "fields": { + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 52 + }, + "_CONSOLE_PROCESS": { + "fields": { + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 28 + }, + "ThreadId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 32 + }, + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND_HISTORY": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "CommandBucket": { + "type": { + "kind": "struct", + "name": "_VECTOR" + }, + "offset": 16 + }, + "CommandCountMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 40 + }, + "Application": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + }, + "ConsoleProcessHandle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS_HANDLE" + } + }, + "offset": 80 + }, + "Flags": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 88 + }, + "LastDisplayed": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 92 + } + }, + "kind": "struct", + "size": 96 + }, + "_SCREEN_INFORMATION": { + "fields": { + "TextBufferInfo": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 56 + }, + "Next": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 64 + } + }, + "kind": "struct", + "size": 72 + }, + "_ROW_POINTER": { + "fields": { + "Row": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + + }, + "_ROWS_ARRAY": { + "fields": { + "Rows": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_ROW_POINTER" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_TEXT_BUFFER_INFO": { + "fields": { + "BufferDeque": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_DEQUE" + } + }, + "offset": 0 + }, + "BufferRows": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROWS_ARRAY" + } + }, + "offset": 8 + }, + "BufferCapacity": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 32 + }, + "BufferEnd": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 56 + }, + "BufferLastIndex": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 58 + }, + "BufferStart": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 88 + } + }, + "kind": "struct", + "size": 72 + }, + "_CHAR_ROW_CELL": { + "fields": { + "Text": { + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + }, + "offset": 0 + }, + "DbcsAttribute": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 2 + } + }, + "kind": "struct", + "size": 3 + }, + "_CHAR_ROW_CELL_ARRAY": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_ROW": { + "fields": { + "CharRow": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL_ARRAY" + } + }, + "offset": 8 + }, + "RowLength": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 64 + }, + "Index": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 72 + }, + "RowLength2": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 80 + }, + "TextBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 88 + } + }, + "kind": "struct", + "size": 96 + }, + "_DEQUE": { + "fields": { + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_EXE_ALIAS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ExeLength": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 8 + }, + "ExeName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 24 + }, + "AliasList": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 32 + } + }, + "kind": "struct", + "size": 48 + }, + "_ALIAS": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "Source": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 16 + }, + "Target": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 32 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "Dave Lassalle", + "datetime": "2024-07-31T15:05:35-06:00" + }, + "format": "4.1.0" + } +} diff --git a/volatility3/framework/symbols/windows/consoles/consoles-win10-20348-1970-x64.json b/volatility3/framework/symbols/windows/consoles/consoles-win10-20348-1970-x64.json new file mode 100644 index 0000000000..8357ece4af --- /dev/null +++ b/volatility3/framework/symbols/windows/consoles/consoles-win10-20348-1970-x64.json @@ -0,0 +1,655 @@ +{ + "symbols": {}, + "enums": {}, + "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" + }, + "short": { + "kind": "int", + "size": 2, + "signed": true, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_CONSOLE_INFORMATION": { + "fields": { + "ScreenX": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 24 + }, + "ScreenY": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 26 + }, + "CommandHistorySize": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 136 + }, + "HistoryBufferMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 140 + }, + "OriginalTitle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 1616 + }, + "Title": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 1552 + }, + "GetScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 1680 + }, + "CurrentScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 1296 + }, + "ConsoleProcessList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 1272 + }, + "ProcessCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 1280 + }, + "HistoryList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 9320 + }, + "HistoryBufferCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 9328 + }, + "ExeAliasList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 2410 + } + }, + "kind": "struct", + "size": 140 + }, + "_VECTOR": { + "fields": { + "Begin": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 0 + }, + "End": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 8 + }, + "EndCapacity": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned long" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 0 + }, + "Pointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 0 + }, + "Length": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 16 + }, + "Allocated": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 24 + } + }, + "kind": "struct", + "size": 32 + }, + "_CONSOLE_PROCESS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ConsoleProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_CONSOLE_PROCESS_HANDLE": { + "fields": { + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 52 + }, + "_CONSOLE_PROCESS": { + "fields": { + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 28 + }, + "ThreadId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 32 + }, + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND_HISTORY": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "CommandBucket": { + "type": { + "kind": "struct", + "name": "_VECTOR" + }, + "offset": 16 + }, + "CommandCountMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 40 + }, + "Application": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + }, + "ConsoleProcessHandle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS_HANDLE" + } + }, + "offset": 80 + }, + "Flags": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 88 + }, + "LastDisplayed": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 92 + } + }, + "kind": "struct", + "size": 96 + }, + "_SCREEN_INFORMATION": { + "fields": { + "TextBufferInfo": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 56 + }, + "Next": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 64 + } + }, + "kind": "struct", + "size": 72 + }, + "_ROW_POINTER": { + "fields": { + "Row": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + + }, + "_ROWS_ARRAY": { + "fields": { + "Rows": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_ROW_POINTER" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_TEXT_BUFFER_INFO": { + "fields": { + "BufferDeque": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_DEQUE" + } + }, + "offset": 0 + }, + "BufferRows": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROWS_ARRAY" + } + }, + "offset": 16 + }, + "BufferCapacity": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 40 + }, + "ThisBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 48 + }, + "BufferEnd": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 54 + }, + "BufferLastIndex": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 56 + }, + "BufferStart": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 96 + } + }, + "kind": "struct", + "size": 72 + }, + "_CHAR_ROW_CELL": { + "fields": { + "Text": { + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + }, + "offset": 0 + }, + "DbcsAttribute": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 2 + } + }, + "kind": "struct", + "size": 3 + }, + "_CHAR_ROW_CELL_ARRAY": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_ROW": { + "fields": { + "CharRow": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL_ARRAY" + } + }, + "offset": 8 + }, + "RowLength": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 64 + }, + "Index": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 72 + }, + "RowLength2": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 80 + }, + "TextBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 88 + } + }, + "kind": "struct", + "size": 96 + }, + "_DEQUE": { + "fields": { + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_EXE_ALIAS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ExeName": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 16 + }, + "AliasList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 56 + } + }, + "kind": "struct", + "size": 64 + }, + "_ALIAS": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "Source": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 16 + }, + "Target": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 32 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "Dave Lassalle", + "datetime": "2024-07-31T15:05:35-06:00" + }, + "format": "4.1.0" + } +} diff --git a/volatility3/framework/symbols/windows/consoles/consoles-win10-20348-2461-x64.json b/volatility3/framework/symbols/windows/consoles/consoles-win10-20348-2461-x64.json new file mode 100644 index 0000000000..f80a991480 --- /dev/null +++ b/volatility3/framework/symbols/windows/consoles/consoles-win10-20348-2461-x64.json @@ -0,0 +1,655 @@ +{ + "symbols": {}, + "enums": {}, + "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" + }, + "short": { + "kind": "int", + "size": 2, + "signed": true, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_CONSOLE_INFORMATION": { + "fields": { + "ScreenX": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 24 + }, + "ScreenY": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 26 + }, + "CommandHistorySize": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 136 + }, + "HistoryBufferMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 140 + }, + "OriginalTitle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 1616 + }, + "Title": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 1552 + }, + "GetScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 1680 + }, + "CurrentScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 1296 + }, + "ConsoleProcessList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 1272 + }, + "ProcessCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 1280 + }, + "HistoryList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 9176 + }, + "HistoryBufferCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 9184 + }, + "ExeAliasList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 9232 + } + }, + "kind": "struct", + "size": 140 + }, + "_VECTOR": { + "fields": { + "Begin": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 0 + }, + "End": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 8 + }, + "EndCapacity": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned long" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 0 + }, + "Pointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 0 + }, + "Length": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 16 + }, + "Allocated": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 24 + } + }, + "kind": "struct", + "size": 32 + }, + "_CONSOLE_PROCESS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ConsoleProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_CONSOLE_PROCESS_HANDLE": { + "fields": { + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 52 + }, + "_CONSOLE_PROCESS": { + "fields": { + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 28 + }, + "ThreadId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 32 + }, + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND_HISTORY": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "CommandBucket": { + "type": { + "kind": "struct", + "name": "_VECTOR" + }, + "offset": 16 + }, + "CommandCountMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 40 + }, + "Application": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + }, + "ConsoleProcessHandle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS_HANDLE" + } + }, + "offset": 80 + }, + "Flags": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 88 + }, + "LastDisplayed": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 92 + } + }, + "kind": "struct", + "size": 96 + }, + "_SCREEN_INFORMATION": { + "fields": { + "TextBufferInfo": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 56 + }, + "Next": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 64 + } + }, + "kind": "struct", + "size": 72 + }, + "_ROW_POINTER": { + "fields": { + "Row": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + + }, + "_ROWS_ARRAY": { + "fields": { + "Rows": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_ROW_POINTER" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_TEXT_BUFFER_INFO": { + "fields": { + "BufferDeque": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_DEQUE" + } + }, + "offset": 0 + }, + "BufferRows": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROWS_ARRAY" + } + }, + "offset": 16 + }, + "BufferCapacity": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 40 + }, + "ThisBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 48 + }, + "BufferEnd": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 54 + }, + "BufferLastIndex": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 56 + }, + "BufferStart": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 96 + } + }, + "kind": "struct", + "size": 72 + }, + "_CHAR_ROW_CELL": { + "fields": { + "Text": { + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + }, + "offset": 0 + }, + "DbcsAttribute": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 2 + } + }, + "kind": "struct", + "size": 3 + }, + "_CHAR_ROW_CELL_ARRAY": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_ROW": { + "fields": { + "CharRow": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL_ARRAY" + } + }, + "offset": 8 + }, + "RowLength": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 64 + }, + "Index": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 72 + }, + "RowLength2": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 80 + }, + "TextBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 88 + } + }, + "kind": "struct", + "size": 96 + }, + "_DEQUE": { + "fields": { + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_EXE_ALIAS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ExeName": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 16 + }, + "AliasList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 56 + } + }, + "kind": "struct", + "size": 64 + }, + "_ALIAS": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "Source": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 16 + }, + "Target": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 32 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "Dave Lassalle", + "datetime": "2024-07-31T15:05:35-06:00" + }, + "format": "4.1.0" + } +} diff --git a/volatility3/framework/symbols/windows/consoles/consoles-win10-20348-x64.json b/volatility3/framework/symbols/windows/consoles/consoles-win10-20348-x64.json new file mode 100644 index 0000000000..5c7fc84733 --- /dev/null +++ b/volatility3/framework/symbols/windows/consoles/consoles-win10-20348-x64.json @@ -0,0 +1,655 @@ +{ + "symbols": {}, + "enums": {}, + "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" + }, + "short": { + "kind": "int", + "size": 2, + "signed": true, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_CONSOLE_INFORMATION": { + "fields": { + "ScreenX": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 24 + }, + "ScreenY": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 26 + }, + "CommandHistorySize": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 136 + }, + "HistoryBufferMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 140 + }, + "OriginalTitle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 1616 + }, + "Title": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 1552 + }, + "GetScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 1680 + }, + "CurrentScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 1296 + }, + "ConsoleProcessList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 1272 + }, + "ProcessCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 1280 + }, + "HistoryList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": -288 + }, + "HistoryBufferCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": -280 + }, + "ExeAliasList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": -376 + } + }, + "kind": "struct", + "size": 140 + }, + "_VECTOR": { + "fields": { + "Begin": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 0 + }, + "End": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 8 + }, + "EndCapacity": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned long" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 0 + }, + "Pointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 0 + }, + "Length": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 16 + }, + "Allocated": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 24 + } + }, + "kind": "struct", + "size": 32 + }, + "_CONSOLE_PROCESS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ConsoleProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_CONSOLE_PROCESS_HANDLE": { + "fields": { + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 52 + }, + "_CONSOLE_PROCESS": { + "fields": { + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 28 + }, + "ThreadId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 32 + }, + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND_HISTORY": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "CommandBucket": { + "type": { + "kind": "struct", + "name": "_VECTOR" + }, + "offset": 16 + }, + "CommandCountMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 40 + }, + "Application": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + }, + "ConsoleProcessHandle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS_HANDLE" + } + }, + "offset": 80 + }, + "Flags": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 88 + }, + "LastDisplayed": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 92 + } + }, + "kind": "struct", + "size": 96 + }, + "_SCREEN_INFORMATION": { + "fields": { + "TextBufferInfo": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 56 + }, + "Next": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 64 + } + }, + "kind": "struct", + "size": 72 + }, + "_ROW_POINTER": { + "fields": { + "Row": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + + }, + "_ROWS_ARRAY": { + "fields": { + "Rows": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_ROW_POINTER" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_TEXT_BUFFER_INFO": { + "fields": { + "BufferDeque": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_DEQUE" + } + }, + "offset": 0 + }, + "BufferRows": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROWS_ARRAY" + } + }, + "offset": 16 + }, + "BufferCapacity": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 40 + }, + "ThisBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 48 + }, + "BufferEnd": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 54 + }, + "BufferLastIndex": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 56 + }, + "BufferStart": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 96 + } + }, + "kind": "struct", + "size": 72 + }, + "_CHAR_ROW_CELL": { + "fields": { + "Text": { + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + }, + "offset": 0 + }, + "DbcsAttribute": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 2 + } + }, + "kind": "struct", + "size": 3 + }, + "_CHAR_ROW_CELL_ARRAY": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_ROW": { + "fields": { + "CharRow": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL_ARRAY" + } + }, + "offset": 8 + }, + "RowLength": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 64 + }, + "Index": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 72 + }, + "RowLength2": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 80 + }, + "TextBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 88 + } + }, + "kind": "struct", + "size": 96 + }, + "_DEQUE": { + "fields": { + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_EXE_ALIAS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ExeName": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 16 + }, + "AliasList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 56 + } + }, + "kind": "struct", + "size": 64 + }, + "_ALIAS": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "Source": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 16 + }, + "Target": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 32 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "Dave Lassalle", + "datetime": "2024-07-31T15:05:35-06:00" + }, + "format": "4.1.0" + } +} diff --git a/volatility3/framework/symbols/windows/consoles/consoles-win10-22000-x64.json b/volatility3/framework/symbols/windows/consoles/consoles-win10-22000-x64.json new file mode 100644 index 0000000000..cf38d3e14c --- /dev/null +++ b/volatility3/framework/symbols/windows/consoles/consoles-win10-22000-x64.json @@ -0,0 +1,681 @@ +{ + "symbols": {}, + "enums": {}, + "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" + }, + "short": { + "kind": "int", + "size": 2, + "signed": true, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_CONSOLE_INFORMATION": { + "fields": { + "ScreenX": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 24 + }, + "ScreenY": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 26 + }, + "CommandHistorySize": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 136 + }, + "HistoryBufferMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 140 + }, + "OriginalTitle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 1648 + }, + "Title": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 1616 + }, + "GetScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 1664 + }, + "CurrentScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 1296 + }, + "ConsoleProcessList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 1272 + }, + "ProcessCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 1280 + }, + "HistoryList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": -920 + }, + "HistoryBufferCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": -912 + }, + "ExeAliasList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": -1008 + } + }, + "kind": "struct", + "size": 140 + }, + "_VECTOR": { + "fields": { + "Begin": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 0 + }, + "End": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 8 + }, + "EndCapacity": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned long" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 0 + }, + "Pointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 0 + }, + "Length": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 16 + }, + "Allocated": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 24 + } + }, + "kind": "struct", + "size": 32 + }, + "_CONSOLE_PROCESS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ConsoleProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_CONSOLE_PROCESS_HANDLE": { + "fields": { + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 52 + }, + "_CONSOLE_PROCESS": { + "fields": { + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 28 + }, + "ThreadId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 32 + }, + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND_HISTORY": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "CommandBucket": { + "type": { + "kind": "struct", + "name": "_VECTOR" + }, + "offset": 16 + }, + "CommandCountMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 40 + }, + "Application": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + }, + "ConsoleProcessHandle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS_HANDLE" + } + }, + "offset": 80 + }, + "Flags": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 88 + }, + "LastDisplayed": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 92 + } + }, + "kind": "struct", + "size": 96 + }, + "_SCREEN_INFORMATION": { + "fields": { + "TextBufferInfo": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 56 + }, + "Next": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 64 + } + }, + "kind": "struct", + "size": 72 + }, + "_ROW_POINTER": { + "fields": { + "Row": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_ROWS_ARRAY": { + "fields": { + "Rows": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_TEXT_BUFFER_INFO": { + "fields": { + "ScreenX": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 4 + }, + "ScreenY": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 6 + }, + "BufferRows": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROWS_ARRAY" + } + }, + "offset": 8 + }, + "BufferCapacity": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 6 + }, + "ThisBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 32 + }, + "FirstRow": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 8 + }, + "LastRow": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 16 + }, + "BufferStart": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 80 + } + }, + "kind": "struct", + "size": 72 + }, + "_CHAR_ROW_CELL": { + "fields": { + "Text": { + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + }, + "offset": 0 + }, + "DbcsAttribute": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 2 + } + }, + "kind": "struct", + "size": 3 + }, + "_CHAR_ROW_CELL_ARRAY": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_ROW": { + "fields": { + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": -88 + }, + "CharRow": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL_ARRAY" + } + }, + "offset": 0 + }, + "RowLength": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": -18 + }, + "Index": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": -20 + }, + "RowLength2": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 8 + }, + "Allocated": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 16 + }, + "TextBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": -8 + } + }, + "kind": "struct", + "size": 480 + }, + "_DEQUE": { + "fields": { + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_EXE_ALIAS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ExeName": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 16 + }, + "AliasList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 56 + } + }, + "kind": "struct", + "size": 64 + }, + "_ALIAS": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "Source": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 16 + }, + "Target": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 32 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "Dave Lassalle", + "datetime": "2024-07-31T15:05:35-06:00" + }, + "format": "4.1.0" + } +} diff --git a/volatility3/framework/symbols/windows/consoles/consoles-win10-22621-3527-x64.json b/volatility3/framework/symbols/windows/consoles/consoles-win10-22621-3527-x64.json new file mode 100644 index 0000000000..a5bda0f350 --- /dev/null +++ b/volatility3/framework/symbols/windows/consoles/consoles-win10-22621-3527-x64.json @@ -0,0 +1,681 @@ +{ + "symbols": {}, + "enums": {}, + "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" + }, + "short": { + "kind": "int", + "size": 2, + "signed": true, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_CONSOLE_INFORMATION": { + "fields": { + "ScreenX": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 2400 + }, + "ScreenY": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 2402 + }, + "CommandHistorySize": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 2512 + }, + "HistoryBufferMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 2516 + }, + "OriginalTitle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 2944 + }, + "Title": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 2912 + }, + "GetScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 3008 + }, + "CurrentScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 2632 + }, + "ConsoleProcessList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 2608 + }, + "ProcessCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 2616 + }, + "HistoryList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 10640 + }, + "HistoryBufferCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 10648 + }, + "ExeAliasList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 10552 + } + }, + "kind": "struct", + "size": 140 + }, + "_VECTOR": { + "fields": { + "Begin": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 0 + }, + "End": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 8 + }, + "EndCapacity": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned long" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 0 + }, + "Pointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 0 + }, + "Length": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 16 + }, + "Allocated": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 24 + } + }, + "kind": "struct", + "size": 32 + }, + "_CONSOLE_PROCESS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ConsoleProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_CONSOLE_PROCESS_HANDLE": { + "fields": { + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 52 + }, + "_CONSOLE_PROCESS": { + "fields": { + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 28 + }, + "ThreadId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 32 + }, + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND_HISTORY": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "CommandBucket": { + "type": { + "kind": "struct", + "name": "_VECTOR" + }, + "offset": 16 + }, + "CommandCountMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 40 + }, + "Application": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + }, + "ConsoleProcessHandle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS_HANDLE" + } + }, + "offset": 80 + }, + "Flags": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 88 + }, + "LastDisplayed": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 92 + } + }, + "kind": "struct", + "size": 96 + }, + "_SCREEN_INFORMATION": { + "fields": { + "TextBufferInfo": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 56 + }, + "Next": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 64 + } + }, + "kind": "struct", + "size": 72 + }, + "_ROW_POINTER": { + "fields": { + "Row": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_ROWS_ARRAY": { + "fields": { + "Rows": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_TEXT_BUFFER_INFO": { + "fields": { + "ScreenX": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 4 + }, + "ScreenY": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 6 + }, + "BufferRows": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROWS_ARRAY" + } + }, + "offset": 8 + }, + "BufferCapacity": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 6 + }, + "ThisBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 32 + }, + "FirstRow": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 8 + }, + "LastRow": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 16 + }, + "BufferStart": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 72 + } + }, + "kind": "struct", + "size": 72 + }, + "_CHAR_ROW_CELL": { + "fields": { + "Text": { + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + }, + "offset": 0 + }, + "DbcsAttribute": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 2 + } + }, + "kind": "struct", + "size": 3 + }, + "_CHAR_ROW_CELL_ARRAY": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_ROW": { + "fields": { + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": -80 + }, + "CharRow": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL_ARRAY" + } + }, + "offset": 0 + }, + "RowLength": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 8 + }, + "Index": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": -20 + }, + "RowLength2": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 8 + }, + "Allocated": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 16 + }, + "TextBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": -16 + } + }, + "kind": "struct", + "size": 472 + }, + "_DEQUE": { + "fields": { + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_EXE_ALIAS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ExeName": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 16 + }, + "AliasList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 56 + } + }, + "kind": "struct", + "size": 64 + }, + "_ALIAS": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "Source": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 16 + }, + "Target": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 32 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "Dave Lassalle", + "datetime": "2024-07-31T15:05:35-06:00" + }, + "format": "4.1.0" + } +} diff --git a/volatility3/framework/symbols/windows/consoles/consoles-win10-22621-x64.json b/volatility3/framework/symbols/windows/consoles/consoles-win10-22621-x64.json new file mode 100644 index 0000000000..44f926a87a --- /dev/null +++ b/volatility3/framework/symbols/windows/consoles/consoles-win10-22621-x64.json @@ -0,0 +1,681 @@ +{ + "symbols": {}, + "enums": {}, + "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" + }, + "short": { + "kind": "int", + "size": 2, + "signed": true, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_CONSOLE_INFORMATION": { + "fields": { + "ScreenX": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 2400 + }, + "ScreenY": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 2402 + }, + "CommandHistorySize": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 2512 + }, + "HistoryBufferMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 2516 + }, + "OriginalTitle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 2944 + }, + "Title": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 2912 + }, + "GetScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 3008 + }, + "CurrentScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 2632 + }, + "ConsoleProcessList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 2608 + }, + "ProcessCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 2616 + }, + "HistoryList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 10664 + }, + "HistoryBufferCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 10672 + }, + "ExeAliasList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 10576 + } + }, + "kind": "struct", + "size": 140 + }, + "_VECTOR": { + "fields": { + "Begin": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 0 + }, + "End": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 8 + }, + "EndCapacity": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned long" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 0 + }, + "Pointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 0 + }, + "Length": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 16 + }, + "Allocated": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 24 + } + }, + "kind": "struct", + "size": 32 + }, + "_CONSOLE_PROCESS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ConsoleProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_CONSOLE_PROCESS_HANDLE": { + "fields": { + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 52 + }, + "_CONSOLE_PROCESS": { + "fields": { + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 28 + }, + "ThreadId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 32 + }, + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND_HISTORY": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "CommandBucket": { + "type": { + "kind": "struct", + "name": "_VECTOR" + }, + "offset": 16 + }, + "CommandCountMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 40 + }, + "Application": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + }, + "ConsoleProcessHandle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS_HANDLE" + } + }, + "offset": 80 + }, + "Flags": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 88 + }, + "LastDisplayed": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 92 + } + }, + "kind": "struct", + "size": 96 + }, + "_SCREEN_INFORMATION": { + "fields": { + "TextBufferInfo": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 56 + }, + "Next": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 64 + } + }, + "kind": "struct", + "size": 72 + }, + "_ROW_POINTER": { + "fields": { + "Row": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_ROWS_ARRAY": { + "fields": { + "Rows": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_TEXT_BUFFER_INFO": { + "fields": { + "ScreenX": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 4 + }, + "ScreenY": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 6 + }, + "BufferRows": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROWS_ARRAY" + } + }, + "offset": 8 + }, + "BufferCapacity": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 6 + }, + "ThisBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 32 + }, + "FirstRow": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 8 + }, + "LastRow": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 16 + }, + "BufferStart": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 72 + } + }, + "kind": "struct", + "size": 72 + }, + "_CHAR_ROW_CELL": { + "fields": { + "Text": { + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + }, + "offset": 0 + }, + "DbcsAttribute": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 2 + } + }, + "kind": "struct", + "size": 3 + }, + "_CHAR_ROW_CELL_ARRAY": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_ROW": { + "fields": { + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": -96 + }, + "CharRow": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL_ARRAY" + } + }, + "offset": 0 + }, + "RowLength": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 8 + }, + "Index": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": -20 + }, + "RowLength2": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 8 + }, + "Allocated": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 16 + }, + "TextBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": -8 + } + }, + "kind": "struct", + "size": 480 + }, + "_DEQUE": { + "fields": { + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_EXE_ALIAS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ExeName": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 16 + }, + "AliasList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 56 + } + }, + "kind": "struct", + "size": 64 + }, + "_ALIAS": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "Source": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 16 + }, + "Target": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 32 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "Dave Lassalle", + "datetime": "2024-07-31T15:05:35-06:00" + }, + "format": "4.1.0" + } +} diff --git a/volatility3/framework/symbols/windows/consoles/consoles-win10-25398-x64.json b/volatility3/framework/symbols/windows/consoles/consoles-win10-25398-x64.json new file mode 100644 index 0000000000..5e91d88247 --- /dev/null +++ b/volatility3/framework/symbols/windows/consoles/consoles-win10-25398-x64.json @@ -0,0 +1,682 @@ +{ + "symbols": {}, + "enums": {}, + "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" + }, + "short": { + "kind": "int", + "size": 2, + "signed": true, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_CONSOLE_INFORMATION": { + "fields": { + "ScreenX": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 2592 + }, + "ScreenY": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 2594 + }, + "CommandHistorySize": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 2704 + }, + "HistoryBufferMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 2708 + }, + "OriginalTitle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 3056 + }, + "Title": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 3120 + }, + "GetScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 3216 + }, + "CurrentScreenBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 2824 + }, + "ConsoleProcessList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 2800 + }, + "ProcessCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 2808 + }, + "HistoryList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": -360 + }, + "HistoryBufferCount": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": -352 + }, + "ExeAliasList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 3776 + } + }, + "kind": "struct", + "size": 140 + }, + "_VECTOR": { + "fields": { + "Begin": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 0 + }, + "End": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_COMMAND" + } + }, + "offset": 8 + }, + "EndCapacity": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned long" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "base", + "name": "unsigned char" + } + }, + "offset": 0 + }, + "Pointer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "string" + } + }, + "offset": 0 + }, + "Length": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 16 + }, + "Allocated": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 24 + } + }, + "kind": "struct", + "size": 32 + }, + "_CONSOLE_PROCESS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ConsoleProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 24 + }, + "_CONSOLE_PROCESS_HANDLE": { + "fields": { + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 52 + }, + "_CONSOLE_PROCESS": { + "fields": { + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 28 + }, + "ThreadId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 32 + }, + "ProcessHandle": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 24 + }, + "_COMMAND_HISTORY": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "CommandBucket": { + "type": { + "kind": "struct", + "name": "_VECTOR" + }, + "offset": 16 + }, + "CommandCountMax": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 40 + }, + "Application": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + }, + "ConsoleProcessHandle": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CONSOLE_PROCESS_HANDLE" + } + }, + "offset": 80 + }, + "Flags": { + "type": { + "kind": "base", + "name": "unsigned short" + }, + "offset": 88 + }, + "LastDisplayed": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 92 + } + }, + "kind": "struct", + "size": 96 + }, + "_SCREEN_INFORMATION": { + "fields": { + "TextBufferInfo": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 56 + }, + "Next": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SCREEN_INFORMATION" + } + }, + "offset": 64 + } + }, + "kind": "struct", + "size": 72 + }, + "_ROW_POINTER": { + "fields": { + "Row": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + + }, + "_ROWS_ARRAY": { + "fields": { + "Rows": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_ROW_POINTER" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_TEXT_BUFFER_INFO": { + "fields": { + "ScreenX": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 8 + }, + "ScreenY": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 10 + }, + "BufferRows": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROWS_ARRAY" + } + }, + "offset": 16 + }, + "BufferCapacity": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 10 + }, + "ThisBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": 40 + }, + "FirstRow": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 16 + }, + "LastRow": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": 24 + }, + "BufferStart": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 88 + } + }, + "kind": "struct", + "size": 72 + }, + "_CHAR_ROW_CELL": { + "fields": { + "Text": { + "type": { + "kind": "struct", + "name": "nt_symbols!_UNICODE_STRING" + }, + "offset": 0 + }, + "DbcsAttribute": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 2 + } + }, + "kind": "struct", + "size": 3 + }, + "_CHAR_ROW_CELL_ARRAY": { + "fields": { + "Chars": { + "type": { + "count": 1, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_ROW": { + "fields": { + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_ROW" + } + }, + "offset": -88 + }, + "CharRow": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_CHAR_ROW_CELL_ARRAY" + } + }, + "offset": 0 + }, + "RowLength": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 8 + }, + "Index": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": -20 + }, + "RowLength2": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 8 + }, + "Allocated": { + "type": { + "kind": "base", + "name": "short" + }, + "offset": 16 + }, + "TextBuffer": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_TEXT_BUFFER_INFO" + } + }, + "offset": -8 + } + }, + "kind": "struct", + "size": 464 + }, + "_DEQUE": { + "fields": { + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_EXE_ALIAS_LIST": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "ExeName": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 16 + }, + "AliasList": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + } + }, + "offset": 56 + } + }, + "kind": "struct", + "size": 64 + }, + "_ALIAS": { + "fields": { + "ListEntry": { + "type": { + "kind": "struct", + "name": "nt_symbols!_LIST_ENTRY" + }, + "offset": 0 + }, + "Source": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 16 + }, + "Target": { + "type": { + "kind": "struct", + "name": "_COMMAND" + }, + "offset": 48 + } + }, + "kind": "struct", + "size": 32 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "Dave Lassalle", + "datetime": "2024-07-31T15:05:35-06:00" + }, + "format": "4.1.0" + } +} diff --git a/volatility3/framework/symbols/windows/extensions/__init__.py b/volatility3/framework/symbols/windows/extensions/__init__.py index cb5e408ac5..1d729d8483 100755 --- a/volatility3/framework/symbols/windows/extensions/__init__.py +++ b/volatility3/framework/symbols/windows/extensions/__init__.py @@ -20,9 +20,10 @@ ) 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__) @@ -91,7 +92,7 @@ def traverse(self, visited=None, depth=0): if vad_address in visited: vollog.log(constants.LOGLEVEL_VVV, "VAD node already seen!") - return + return None visited.add(vad_address) tag = self.get_tag() @@ -111,7 +112,7 @@ def traverse(self, visited=None, depth=0): constants.LOGLEVEL_VVV, f"Skipping VAD at {self.vol.offset} depth {depth} with tag {tag}", ) - return + return None if target: vad_object = self.cast(target) @@ -306,16 +307,19 @@ def get_private_memory(self): 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] @@ -452,9 +456,9 @@ def is_valid(self) -> bool: ].is_valid(self.FileName.Buffer) def file_name_with_device(self) -> Union[str, interfaces.renderers.BaseAbsentValue]: - name: Union[ - str, interfaces.renderers.BaseAbsentValue - ] = renderers.UnreadableValue() + name: Union[str, interfaces.renderers.BaseAbsentValue] = ( + renderers.UnreadableValue() + ) # this pointer needs to be checked against native_layer_name because the object may # be instantiated from a primary (virtual) layer or a memory (physical) layer. @@ -492,9 +496,47 @@ def get_name(self) -> str: return header.NameInfo.Name.String # type: ignore -class ETHREAD(objects.StructType): +class ETHREAD(objects.StructType, pool.ExecutiveObject): """A class for executive thread objects.""" + def is_valid(self) -> bool: + """Determine if the object is valid.""" + + try: + # validation by TID: + if self.Cid.UniqueThread % 4 != 0: # NT tids are divisible by 4 + return False + + # validation by PID of parent process: + if self.Cid.UniqueProcess % 4 != 0: + return False + + # validation by thread creation time: + if ( + self.Cid.UniqueProcess != 4 + ): # The System process (PID 4) has no create time + ctime = self.get_create_time() + if not isinstance(ctime, datetime.datetime): + return False + + if not (1998 < ctime.year < 2030): + return False + + except exceptions.InvalidAddressException: + return False + + # passed all validations + return True + + def get_create_time(self): + # For Windows XPs + if self.has_member("ThreadsProcess"): + return conversion.wintime_to_datetime(self.CreateTime.QuadPart >> 3) + return conversion.wintime_to_datetime(self.CreateTime.QuadPart) + + def get_exit_time(self): + return conversion.wintime_to_datetime(self.ExitTime.QuadPart) + def owning_process(self) -> interfaces.objects.ObjectInterface: """Return the EPROCESS that owns this thread.""" @@ -555,6 +597,38 @@ def get_string(self) -> interfaces.objects.ObjectInterface: 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.""" @@ -573,11 +647,23 @@ def is_valid(self) -> bool: 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 @@ -595,7 +681,11 @@ def is_valid(self) -> bool: 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 @@ -665,7 +755,7 @@ def load_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]: ): yield entry except exceptions.InvalidAddressException: - return + return None def init_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]: """Generator for DLLs in the order that they were initialized""" @@ -678,7 +768,7 @@ def init_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]: ): yield entry except exceptions.InvalidAddressException: - return + return None def mem_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]: """Generator for DLLs in the order that they appear in memory""" @@ -691,7 +781,7 @@ def mem_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]: ): yield entry except exceptions.InvalidAddressException: - return + return None def get_handle_count(self): try: @@ -855,11 +945,11 @@ def to_list( try: is_valid = trans_layer.is_valid(self.vol.offset) if not is_valid: - return + return None link = getattr(self, direction).dereference() except exceptions.InvalidAddressException: - return + return None if not sentinel: yield self._context.object( @@ -874,7 +964,7 @@ def to_list( obj_offset = link.vol.offset - relative_offset if not trans_layer.is_valid(obj_offset): - return + return None obj = self._context.object( symbol_type, @@ -889,7 +979,7 @@ def to_list( try: link = getattr(link, direction).dereference() except exceptions.InvalidAddressException: - return + return None def __iter__(self) -> Iterator[interfaces.objects.ObjectInterface]: return self.to_list(self.vol.parent.vol.type_name, self.vol.member_name) @@ -919,10 +1009,10 @@ def get_sids(self) -> Iterable[str]: sid = sid_and_attr.Sid.dereference().cast("_SID") # catch invalid pointers (UserAndGroupCount is too high) if sid is None: - return + return None # this mimics the windows API IsValidSid if sid.Revision & 0xF != 1 or sid.SubAuthorityCount > 15: - return + return None id_auth = "" for i in sid.IdentifierAuthority.Value: id_auth = i @@ -970,6 +1060,83 @@ def privileges(self): 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.""" @@ -1094,6 +1261,15 @@ def get_available_pages(self) -> Iterable[Tuple[int, int, int]]: is_64bit = symbols.symbol_table_is_64bit(self._context, symbol_table_name) is_pae = self._context.layers[self.vol.layer_name].metadata.get("pae", False) + # the sector_size is used as a multiplier to the StartingSector + # within each _SUBSECTION. ImageSectionObjects use a multiplier + # of 0x200 corresponding to sector alignment on disk, + # while DataSectionObjects use a multiplier of 0x1000 corresponding + # to the size of a page + sector_size = 0x200 + if self.u.Flags.Image != 1: + sector_size = 0x1000 + # This is a null-terminated single-linked list. while subsection != 0: try: @@ -1104,7 +1280,7 @@ def get_available_pages(self) -> Iterable[Tuple[int, int, int]]: # The offset into the file is stored implicitly based on the PTE location within the Subsection. starting_sector = subsection.StartingSector - subsection_offset = starting_sector * 0x200 + subsection_offset = starting_sector * sector_size # Similar to the check in is_valid(), make sure the SubsectionBase is not page aligned. # if subsection.SubsectionBase & self.PAGE_MASK == 0: diff --git a/volatility3/framework/symbols/windows/extensions/callbacks.py b/volatility3/framework/symbols/windows/extensions/callbacks.py new file mode 100644 index 0000000000..f54db39f20 --- /dev/null +++ b/volatility3/framework/symbols/windows/extensions/callbacks.py @@ -0,0 +1,79 @@ +import logging +from typing import Dict + +from volatility3.framework import exceptions, objects +from volatility3.framework.symbols.windows.extensions import pool + +vollog = logging.getLogger(__name__) + + +class _SHUTDOWN_PACKET(objects.StructType, pool.ExecutiveObject): + """Class for _SHUTDOWN_PACKET objects found in IoSh pools. + + This class serves as a base class for all pooled shutdown callback packets. + + It exposes a function which sanity-checks structure members. + """ + + def is_valid(self) -> bool: + """ + Perform some checks. + """ + try: + if not ( + self.Entry.Flink.is_readable() + and self.Entry.Blink.is_readable() + and self.DeviceObject.is_readable() + ): + vollog.debug( + f"Callback obj 0x{self.vol.offset:x} invalid due to unreadable structure members" + ) + return False + + except exceptions.InvalidAddressException: + vollog.debug( + f"callback obj 0x{self.vol.offset:x} invalid due to invalid address access" + ) + return False + + return True + + def is_parseable(self, type_map: Dict[int, str]) -> bool: + """ + Determines whether or not this `_SHUTDOWN_PACKET` callback can be reliably parsed. + Requires a `type_map` that maps NT executive object type indices to string representations. + This type map can be acquired via the `handles.Handles.get_type_map` classmethod. + """ + if not self.is_valid(): + return False + + try: + + device = self.DeviceObject + if not device or not (device.DriverObject.DriverStart % 0x1000 == 0): + vollog.debug( + f"callback obj 0x{self.vol.offset:x} invalid due to invalid device object" + ) + return False + + header = device.get_object_header() + object_type = header.get_object_type(type_map) + is_valid = object_type == "Device" + if not is_valid: + vollog.debug( + f"Callback obj 0x{self.vol.offset:x} invalid due to invalid device type: wanted 'Device', found '{object_type}'" + ) + return is_valid + except exceptions.InvalidAddressException: + vollog.debug( + f"callback obj 0x{self.vol.offset:x} invalid due to invalid address access" + ) + return False + except ValueError: + vollog.debug( + f"Could not get object type for object at 0x{self.vol.offset:x}" + ) + return False + + +class_types_x86 = {"_SHUTDOWN_PACKET": _SHUTDOWN_PACKET} diff --git a/volatility3/framework/symbols/windows/extensions/consoles.py b/volatility3/framework/symbols/windows/extensions/consoles.py new file mode 100644 index 0000000000..2312149c71 --- /dev/null +++ b/volatility3/framework/symbols/windows/extensions/consoles.py @@ -0,0 +1,415 @@ +# 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 Generator, List, Union, Tuple +from volatility3.framework import objects, interfaces +from volatility3.framework import constants + +vollog = logging.getLogger(__name__) + + +class ROW(objects.StructType): + """A Row Structure.""" + + def _valid_dbcs(self, dbcs_attr: int, text_attr_msb: int) -> bool: + # TODO this need more research and testing + # https://github.com/search?q=repo%3Amicrosoft%2Fterminal+DbcsAttr&type=code + valid = text_attr_msb == 0 and dbcs_attr in ( + 0x0, + 0x1, + 0x2, + 0x8, + 0x10, + 0x18, + 0x20, + 0x28, + 0x30, + 0x48, + 0x50, + 0x58, + 0x60, + 0x68, + 0x70, + 0x78, + 0x80, + 0x88, + 0xA8, + 0xB8, + 0xC0, + 0xC8, + 0x98, + 0xD8, + 0xE0, + 0xE8, + 0xF8, + 0xF0, + 0xA0, + ) + if text_attr_msb == 0 and not valid: + vollog.debug(f"Bad Dbcs Attribute {dbcs_attr:#x}") + return valid + + def get_text(self, truncate: bool = True) -> str: + """A convenience method to extract the text from the _ROW. The _ROW + contains a pointer CharRow to an array of CharRowCell objects. Each + CharRowCell contains the wide character and an attribute. Enumerating + self.CharRow.Chars and casting each character to unicode takes too long, + so this reads the whole row into a buffer, then extracts the text characters.""" + + layer = self._context.layers[self.vol.layer_name] + offset = self.CharRow.Chars.vol.offset + length = self.RowLength * 3 + char_row = layer.read(offset, length) + line = "" + try: + if char_row: + line = "".join( + ( + char_row[i : i + 2].decode("utf-16le", errors="replace") + if self._valid_dbcs(char_row[i + 2], char_row[i + 1]) + else "" + ) + for i in range(0, len(char_row), 3) + ) + except Exception as e: + line = "" + + if truncate: + return line.rstrip() + else: + return line + + +class ALIAS(objects.StructType): + """An Alias Structure""" + + def get_source(self) -> Union[str, None]: + return self.Source.get_command_string() + + def get_target(self) -> Union[str, None]: + return self.Target.get_command_string() + + +class EXE_ALIAS_LIST(objects.StructType): + """An Exe Alias List Structure""" + + def get_exename(self) -> Union[str, None]: + exe_name = self.ExeName + # Windows 10 22000 and Server 20348 removed the Pointer + if isinstance(exe_name, objects.Pointer): + exe_name = exe_name.dereference() + return exe_name.get_string() + + return exe_name.get_command_string() + + def get_aliases(self) -> Generator[interfaces.objects.ObjectInterface, None, None]: + """Generator for the individual aliases for a + particular executable.""" + for alias in self.AliasList.to_list( + f"{self.get_symbol_table_name()}{constants.BANG}_ALIAS", + "ListEntry", + ): + yield alias + + +class SCREEN_INFORMATION(objects.StructType): + """A Screen Information Structure.""" + + @property + def ScreenX(self) -> int: + # 22000 change from an array of pointers to _ROW to an array of _ROW + row = self.TextBufferInfo.BufferRows.Rows[0] + if hasattr(row, "Row"): + return row.Row.RowLength2 + else: + return row.RowLength2 + + @property + def ScreenY(self) -> int: + return self.TextBufferInfo.BufferCapacity + + def _truncate_rows(self, rows: List[str]) -> List[str]: + """To truncate empty rows at the end, walk the list + backwards and get the last non-empty row. Use that + row index to splice. Rows are created based on the + length given in the ROW structure, so empty rows will + be ''.""" + + non_empty_index = 0 + rows_traversed = False + + for index, row in enumerate(reversed(rows)): + # the string was created based on the length in the ROW structure so it shouldn't have any bad data + if len(row.rstrip()) > 0: + non_empty_index = index + break + rows_traversed = True + + if non_empty_index == 0 and rows_traversed: + rows = [] + else: + rows = rows[0 : len(rows) - non_empty_index] + + return rows + + def get_buffer( + self, truncate_rows: bool = True, truncate_lines: bool = True + ) -> List[str]: + """Get the screen buffer. + + The screen buffer is comprised of the screen's Y + coordinate which tells us the number of rows and + the X coordinate which tells us the width of each + row in characters. Windows 10 17763 changed from + a large text buffer to a grid of cells, with each + cell containing a single wide character in that + cell, stored in a CharRowCell object. + + @param truncate: True if the empty rows at the + end (i.e. bottom) of the screen buffer should be + supressed. + """ + rows = [] + + capacity = self.TextBufferInfo.BufferCapacity + start = self.TextBufferInfo.BufferStart + buffer_rows = self.TextBufferInfo.BufferRows + buffer_rows.Rows.count = self.TextBufferInfo.BufferCapacity + + for i in range(capacity): + index = (start + i) % capacity + row = buffer_rows.Rows[index] + if hasattr(row, "Row"): + row = row.Row + try: + text = row.get_text(truncate_lines) + rows.append(text) + except Exception: + break + + if truncate_rows: + rows = self._truncate_rows(rows) + + return rows + + +class CONSOLE_INFORMATION(objects.StructType): + """A Console Information Structure.""" + + @property + def ScreenBuffer(self) -> interfaces.objects.ObjectInterface: + return self.GetScreenBuffer + + def is_valid(self, max_buffers: int = 4) -> bool: + """Determine if the structure is valid.""" + + # Last displayed must be between -1 and max + if self.HistoryBufferCount < 1 or self.HistoryBufferCount > max_buffers: + return False + + if not self.get_title() and not self.get_original_title(): + return False + + return True + + def get_screens(self) -> Generator[interfaces.objects.ObjectInterface, None, None]: + """Generator for screens in the console. + + A console can have multiple screen buffers at a time, + but only the current/active one is displayed. + + Multiple screens are tracked using the singly-linked + list _SCREEN_INFORMATION.Next. + + See CreateConsoleScreenBuffer + """ + screens = [self.CurrentScreenBuffer] + + if self.ScreenBuffer not in screens: + screens.append(self.ScreenBuffer) + + seen = set() + + for screen in screens: + cur = screen + while cur and cur.vol.offset != 0 and cur.vol.offset not in seen: + cur.TextBufferInfo.BufferRows.Rows.count = ( + cur.TextBufferInfo.BufferCapacity + ) + yield cur + seen.add(cur.vol.offset) + cur = cur.Next + + def get_histories( + self, + ) -> Generator[interfaces.objects.ObjectInterface, None, None]: + for cmd_hist in self.HistoryList.to_list( + f"{self.get_symbol_table_name()}{constants.BANG}_COMMAND_HISTORY", + "ListEntry", + ): + yield cmd_hist + + def get_exe_aliases( + self, + ) -> Generator[interfaces.objects.ObjectInterface, None, None]: + exe_alias_list = self.ExeAliasList + # Windows 10 22000 and Server 20348 made this a Pointer + if isinstance(exe_alias_list, objects.Pointer): + exe_alias_list = exe_alias_list.dereference() + for exe_alias_list_item in exe_alias_list.to_list( + f"{self.get_symbol_table_name()}{constants.BANG}_EXE_ALIAS_LIST", + "ListEntry", + ): + yield exe_alias_list_item + + def get_processes( + self, + ) -> Generator[interfaces.objects.ObjectInterface, None, None]: + for proc in self.ConsoleProcessList.to_list( + f"{self.get_symbol_table_name()}{constants.BANG}_CONSOLE_PROCESS_LIST", + "ListEntry", + ): + yield proc + + def get_title(self) -> Union[str, None]: + try: + return self.Title.dereference().cast( + "string", encoding="utf-16", errors="replace", max_length=512 + ) + except Exception: + return "" + + def get_original_title(self) -> Union[str, None]: + try: + return self.OriginalTitle.dereference().cast( + "string", encoding="utf-16", errors="replace", max_length=512 + ) + except Exception: + return "" + + +class COMMAND(objects.StructType): + """A Command Structure""" + + def is_valid(self) -> bool: + if ( + self.Length < 1 + or self.Allocated < 1 + or self.Length > 1024 + or self.Allocated > 1024 + ): + return False + + return True + + def get_command_string(self) -> Union[str, None]: + if self.Length < 8: + return self.Chars.cast( + "string", + encoding="utf-16", + errors="replace", + max_length=self.Length * 2, + ) + elif self.Length < 1024: + return self.Pointer.dereference().cast( + "string", + encoding="utf-16", + errors="replace", + max_length=self.Length * 2, + ) + + return None + + +class COMMAND_HISTORY(objects.StructType): + """A Command History Structure.""" + + @property + def CommandCount(self) -> int: + command_type = self.get_symbol_table_name() + constants.BANG + "_COMMAND" + command_size = self._context.symbol_space.get_type(command_type).size + return int((self.CommandBucket.End - self.CommandBucket.Begin) / command_size) + + @property + def ProcessHandle(self) -> int: + """Allow ProcessHandle to be referenced regardless of OS version""" + return self.ConsoleProcessHandle.ProcessHandle + + def is_valid(self, max_history: int = 50) -> bool: + # The count must be between zero and max + if self.CommandCount < 0 or self.CommandCount > max_history: + return False + + # Last displayed must be between -1 and max + if self.LastDisplayed < -1 or self.LastDisplayed > max_history: + return False + + # Process handle must be a valid pid + if ( + self.ProcessHandle <= 0 + or self.ProcessHandle > 0xFFFF + or self.ProcessHandle % 4 != 0 + ): + return False + + return True + + def get_application(self) -> Union[str, None]: + return self.Application.get_command_string() + + def scan_command_bucket( + self, end: Union[int, None] = None + ) -> Generator[Tuple[int, interfaces.objects.ObjectInterface], None, None]: + """Brute force print all strings pointed to by the CommandBucket entries by + going to greater of EndCapacity or CommandCountMax*sizeof(_COMMAND)""" + + command_type = self.get_symbol_table_name() + constants.BANG + "_COMMAND" + command_history_size = self._context.symbol_space.get_type( + self.vol.type_name + ).size + command_size = self._context.symbol_space.get_type(command_type).size + + if end is None: + end = max( + self.CommandBucket.EndCapacity, + self.CommandBucket.Begin + command_history_size * self.CommandCountMax, + ) + + for i, pointer in enumerate(range(self.CommandBucket.Begin, end, command_size)): + cmd = self._context.object(command_type, self.vol.layer_name, pointer) + if cmd.is_valid(): + yield i, cmd + + def get_commands( + self, + ) -> Generator[Tuple[int, interfaces.objects.ObjectInterface], None, None]: + """Generator for commands in the history buffer. + + The CommandBucket is an array of pointers to _COMMAND + structures. The array size is CommandCount. Once CommandCount + is reached, the oldest commands are cycled out and the + rest are coalesced. + """ + + for i, cmd in self.scan_command_bucket(self.CommandBucket.End): + yield i, cmd + + +win10_x64_class_types = { + "_EXE_ALIAS_LIST": EXE_ALIAS_LIST, + "_ALIAS": ALIAS, + "_ROW": ROW, + "_SCREEN_INFORMATION": SCREEN_INFORMATION, + "_CONSOLE_INFORMATION": CONSOLE_INFORMATION, + "_COMMAND_HISTORY": COMMAND_HISTORY, + "_COMMAND": COMMAND, +} +class_types = { + "_ROW": ROW, + "_SCREEN_INFORMATION": SCREEN_INFORMATION, + "_CONSOLE_INFORMATION": CONSOLE_INFORMATION, + "_COMMAND_HISTORY": COMMAND_HISTORY, + "_COMMAND": COMMAND, +} diff --git a/volatility3/framework/symbols/windows/extensions/mft.py b/volatility3/framework/symbols/windows/extensions/mft.py index 17b6c83253..ebba882c0b 100644 --- a/volatility3/framework/symbols/windows/extensions/mft.py +++ b/volatility3/framework/symbols/windows/extensions/mft.py @@ -2,7 +2,9 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # -from volatility3.framework import objects +from typing import Optional + +from volatility3.framework import objects, constants, exceptions class MFTEntry(objects.StructType): @@ -21,3 +23,52 @@ def get_full_name(self) -> str: "string", encoding="utf16", max_length=self.NameLength * 2, errors="replace" ) return output + + +class MFTAttribute(objects.StructType): + """This represents an MFT ATTRIBUTE""" + + 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( + self.vol.type_name.split(constants.BANG)[0] + constants.BANG + "string", + layer_name=self.vol.layer_name, + offset=self.vol.offset + self.Attr_Header.NameOffset, + max_length=self.Attr_Header.NameLength * 2, + errors="replace", + encoding="utf16", + ) + return name + except exceptions.InvalidAddressException: + return None + + 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( + self.vol.type_name.split(constants.BANG)[0] + constants.BANG + "bytes", + layer_name=self.vol.layer_name, + offset=self.vol.offset + self.Attr_Header.ContentOffset, + native_layer_name=self.vol.native_layer_name, + length=self.Attr_Header.ContentLength, + ) + return bytesobj + except exceptions.InvalidAddressException: + return None diff --git a/volatility3/framework/symbols/windows/extensions/registry.py b/volatility3/framework/symbols/windows/extensions/registry.py index fbd3ead8e9..bebfaea891 100644 --- a/volatility3/framework/symbols/windows/extensions/registry.py +++ b/volatility3/framework/symbols/windows/extensions/registry.py @@ -5,7 +5,7 @@ import enum import logging import struct -from typing import Iterable, Optional, Union +from typing import Iterator, Optional, Union, cast from volatility3.framework import constants, exceptions, interfaces, objects from volatility3.framework.layers.registry import ( @@ -39,6 +39,29 @@ def _missing_(cls, value): return cls(RegValueTypes.REG_UNKNOWN) +INTEGER_TYPES = [ + RegValueTypes.REG_DWORD, + RegValueTypes.REG_QWORD, + RegValueTypes.REG_DWORD_BIG_ENDIAN, + RegValueTypes.REG_DWORD_BIG_ENDIAN, +] + +STRING_TYPES = [ + RegValueTypes.REG_SZ, + RegValueTypes.REG_MULTI_SZ, + RegValueTypes.REG_EXPAND_SZ, + RegValueTypes.REG_LINK, +] + +BINARY_TYPES = [ + RegValueTypes.REG_RESOURCE_LIST, + RegValueTypes.REG_BINARY, + RegValueTypes.REG_FULL_RESOURCE_DESCRIPTOR, + RegValueTypes.REG_RESOURCE_REQUIREMENTS_LIST, + RegValueTypes.REG_NONE, +] + + class RegKeyFlags(enum.IntEnum): KEY_IS_VOLATILE = 0x01 KEY_HIVE_EXIT = 0x02 @@ -142,7 +165,7 @@ def get_volatile(self) -> bool: ) return bool(self.vol.offset & 0x80000000) - def get_subkeys(self) -> Iterable[interfaces.objects.ObjectInterface]: + def get_subkeys(self) -> Iterator["CM_KEY_NODE"]: """Returns a list of the key nodes.""" hive = self._context.layers[self.vol.layer_name] if not isinstance(hive, RegistryHive): @@ -154,7 +177,7 @@ def get_subkeys(self) -> Iterable[interfaces.objects.ObjectInterface]: def _get_subkeys_recursive( self, hive: RegistryHive, node: interfaces.objects.ObjectInterface - ) -> Iterable[interfaces.objects.ObjectInterface]: + ) -> Iterator["CM_KEY_NODE"]: """Recursively descend a node returning subkeys.""" # The keylist appears to include 4 bytes of key name after each value # We can either double the list and only use the even items, or @@ -162,7 +185,7 @@ def _get_subkeys_recursive( try: signature = node.cast("string", max_length=2, encoding="latin-1") except (exceptions.InvalidAddressException, RegistryFormatException): - return + return None listjump = None if signature == "ri": @@ -170,7 +193,7 @@ def _get_subkeys_recursive( elif signature == "lh" or signature == "lf": listjump = 2 elif node.vol.type_name.endswith(constants.BANG + "_CM_KEY_NODE"): - yield node + yield cast("CM_KEY_NODE", node) else: vollog.debug( "Unexpected node type encountered when traversing subkeys: {}, signature: {}".format( @@ -200,7 +223,7 @@ def _get_subkeys_recursive( continue yield from self._get_subkeys_recursive(hive, subnode) - def get_values(self) -> Iterable[interfaces.objects.ObjectInterface]: + def get_values(self) -> Iterator["CM_KEY_VALUE"]: """Returns a list of the Value nodes for a key.""" hive = self._context.layers[self.vol.layer_name] if not isinstance(hive, RegistryHive): @@ -216,11 +239,12 @@ def get_values(self) -> Iterable[interfaces.objects.ObjectInterface]: except (RegistryInvalidIndex, RegistryFormatException) as excp: vollog.debug(f"Invalid address {excp}") continue - if node.vol.type_name.endswith(constants.BANG + "_CM_KEY_VALUE"): + if isinstance(node, CM_KEY_VALUE): yield node + except (exceptions.InvalidAddressException, RegistryFormatException) as excp: vollog.debug(f"Invalid address in get_values iteration: {excp}") - return + return None def get_name(self) -> interfaces.objects.ObjectInterface: """Gets the name for the current key node""" @@ -249,6 +273,10 @@ def get_name(self) -> interfaces.objects.ObjectInterface: self.Name.count = namelength return self.Name.cast("string", max_length=namelength, encoding="latin-1") + def get_type(self) -> RegValueTypes: + """Get the type of the registry value""" + return RegValueTypes(self.Type) + def decode_data(self) -> Union[int, bytes]: """Properly decodes the data associated with the value node""" # Determine if the data is stored inline @@ -293,29 +321,28 @@ def decode_data(self) -> Union[int, bytes]: # but the length at the start could be negative so just adding 4 to jump past it data = layer.read(self.Data + 4, datalen) - self_type = RegValueTypes(self.Type) - if self_type == RegValueTypes.REG_DWORD: + if self.get_type() == RegValueTypes.REG_DWORD: if len(data) != struct.calcsize("L"): raise ValueError( f"Size of data does not match the type of registry value {self.get_name()}" ) (res,) = struct.unpack(">L", data) return res - if self_type == RegValueTypes.REG_QWORD: + if self.get_type() == RegValueTypes.REG_QWORD: if len(data) != struct.calcsize(" Union[int, bytes]: RegValueTypes.REG_RESOURCE_REQUIREMENTS_LIST, ]: return data - if self_type == RegValueTypes.REG_NONE: + if self.get_type() == RegValueTypes.REG_NONE: return b"" # Fall back if it's something weird diff --git a/volatility3/framework/symbols/windows/extensions/services.py b/volatility3/framework/symbols/windows/extensions/services.py index 00fb1cc4ec..e14de761d8 100644 --- a/volatility3/framework/symbols/windows/extensions/services.py +++ b/volatility3/framework/symbols/windows/extensions/services.py @@ -110,7 +110,7 @@ def traverse(self): yield rec rec = rec.ServiceList.Blink.dereference() except exceptions.InvalidAddressException: - return + return None class SERVICE_HEADER(objects.StructType): diff --git a/volatility3/framework/symbols/windows/extensions/shimcache.py b/volatility3/framework/symbols/windows/extensions/shimcache.py new file mode 100644 index 0000000000..b84a7df6fd --- /dev/null +++ b/volatility3/framework/symbols/windows/extensions/shimcache.py @@ -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(" 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, +} diff --git a/volatility3/framework/symbols/windows/mft.json b/volatility3/framework/symbols/windows/mft.json index e5de8f3fa7..d4f2aef7a9 100644 --- a/volatility3/framework/symbols/windows/mft.json +++ b/volatility3/framework/symbols/windows/mft.json @@ -230,21 +230,21 @@ "offset": 0, "type": { "kind": "struct", - "name": "mft!ATTR_HEADER" + "name": "ATTR_HEADER" } }, "Resident_Header": { "offset": 16, "type": { "kind": "struct", - "name": "mft!RESIDENT_HEADER" + "name": "RESIDENT_HEADER" } }, "Attr_Data": { "offset": 24, "type": { "kind": "struct", - "name": "mft!ATTR_HEADER" + "name": "ATTR_HEADER" } } }, @@ -300,10 +300,24 @@ "kind": "base", "name": "unsigned short" } + }, + "ContentLength": { + "offset": 16, + "type": { + "kind": "base", + "name": "unsigned int" + } + }, + "ContentOffset": { + "offset": 20, + "type": { + "kind": "base", + "name": "unsigned short" + } } }, "kind": "struct", - "size": 16 + "size": 24 },"RESIDENT_HEADER": { "fields": { "AttrSize": { diff --git a/volatility3/framework/symbols/windows/netscan/netscan-win10-20348-x64.json b/volatility3/framework/symbols/windows/netscan/netscan-win10-20348-x64.json new file mode 100644 index 0000000000..bd574b2b7c --- /dev/null +++ b/volatility3/framework/symbols/windows/netscan/netscan-win10-20348-x64.json @@ -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" + } +} diff --git a/volatility3/framework/symbols/windows/pdbutil.py b/volatility3/framework/symbols/windows/pdbutil.py index a43933ccfb..3816312cd5 100644 --- a/volatility3/framework/symbols/windows/pdbutil.py +++ b/volatility3/framework/symbols/windows/pdbutil.py @@ -2,13 +2,13 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # -import binascii import json import logging import lzma import os import re import struct +from pathlib import PureWindowsPath from typing import Any, Dict, Generator, List, Optional, Tuple, Union from urllib import parse, request @@ -226,13 +226,12 @@ def get_guid_from_mz( return None pdb_name = debug_entry.PdbFileName.decode("utf-8").strip("\x00") + + # Let pathlib do the filename extraction. This will likely always be a Windows path though. + pdb_name = PureWindowsPath(pdb_name).name + age = debug_entry.Age - guid = "{:08x}{:04x}{:04x}{}".format( - debug_entry.Signature_Data1, - debug_entry.Signature_Data2, - debug_entry.Signature_Data3, - binascii.hexlify(debug_entry.Signature_Data4).decode("utf-8"), - ) + guid = debug_entry.Signature_String[:32] # Removes the Age from the GUID return guid, age, pdb_name @classmethod diff --git a/volatility3/framework/symbols/windows/services/services-win10-17763-x86.json b/volatility3/framework/symbols/windows/services/services-win10-17763-x86.json new file mode 100644 index 0000000000..8f28547213 --- /dev/null +++ b/volatility3/framework/symbols/windows/services/services-win10-17763-x86.json @@ -0,0 +1,248 @@ +{ + "symbols": {}, + "enums": { + "StateEnum": { + "base": "long", + "constants": { + "SERVICE_START_PENDING": 2, + "SERVICE_STOP_PENDING": 3, + "SERVICE_STOPPED": 1, + "SERVICE_CONTINUE_PENDING": 5, + "SERVICE_PAUSE_PENDING": 6, + "SERVICE_PAUSED": 7, + "SERVICE_RUNNING": 4 + }, + "size": 4 + }, + "StartEnum": { + "base": "long", + "constants": { + "SERVICE_DEMAND_START": 3, + "SERVICE_AUTO_START": 2, + "SERVICE_BOOT_START": 0, + "SERVICE_DISABLED": 4, + "SERVICE_SYSTEM_START": 1 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_SERVICE_LIST_ENTRY": { + "fields": { + "Flink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 4 + }, + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_SERVICE_PROCESS": { + "fields": { + "BinaryPath": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 12 + }, + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 20 + } + }, + "kind": "struct", + "size": 20 + }, + "_SERVICE_HEADER": { + "fields": { + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "ServiceRecord": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 12 + }, + "_SERVICE_RECORD": { + "fields": { + "DisplayName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 48 + }, + "ServiceProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_PROCESS" + } + }, + "offset": 160 + }, + "PrevEntry": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 12 + }, + "Start": { + "type": { + "kind": "enum", + "name": "StartEnum" + }, + "offset": 24 + }, + "State": { + "type": { + "kind": "enum", + "name": "StateEnum" + }, + "offset": 56 + }, + "ServiceName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 44 + }, + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "DriverName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 160 + }, + "Type": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 52 + }, + "Order": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 20 + } + }, + "kind": "struct", + "size": 156 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "vtypes_to_json.py", + "datetime": "2019-04-17T13:45:16.417006" + }, + "format": "4.1.0" + } +} diff --git a/volatility3/framework/symbols/windows/services/services-win10-18362-x64.json b/volatility3/framework/symbols/windows/services/services-win10-18362-x64.json new file mode 100644 index 0000000000..a6a80c1d3a --- /dev/null +++ b/volatility3/framework/symbols/windows/services/services-win10-18362-x64.json @@ -0,0 +1,255 @@ +{ + "symbols": {}, + "enums": { + "StateEnum": { + "base": "long", + "constants": { + "SERVICE_START_PENDING": 2, + "SERVICE_STOP_PENDING": 3, + "SERVICE_STOPPED": 1, + "SERVICE_CONTINUE_PENDING": 5, + "SERVICE_PAUSE_PENDING": 6, + "SERVICE_PAUSED": 7, + "SERVICE_RUNNING": 4 + }, + "size": 4 + }, + "StartEnum": { + "base": "long", + "constants": { + "SERVICE_DEMAND_START": 3, + "SERVICE_AUTO_START": 2, + "SERVICE_BOOT_START": 0, + "SERVICE_DISABLED": 4, + "SERVICE_SYSTEM_START": 1 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_SERVICE_LIST_ENTRY": { + "fields": { + "Flink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 16 + }, + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 16 + }, + "_SERVICE_PROCESS": { + "fields": { + "BinaryPath": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 24 + }, + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 40 + } + }, + "kind": "struct", + "size": 40 + }, + "_SERVICE_HEADER": { + "fields": { + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "ServiceRecord": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 16 + }, + "_SERVICE_RECORD": { + "fields": { + "ServiceList": { + "type": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + }, + "offset": 0 + }, + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 32 + }, + "DisplayName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 64 + }, + "ServiceProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_PROCESS" + } + }, + "offset": 240 + }, + "PrevEntry": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 16 + }, + "Start": { + "type": { + "kind": "enum", + "name": "StartEnum" + }, + "offset": 36 + }, + "State": { + "type": { + "kind": "enum", + "name": "StateEnum" + }, + "offset": 76 + }, + "ServiceName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 56 + }, + "DriverName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 240 + }, + "Type": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 72 + }, + "Order": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 32 + } + }, + "kind": "struct", + "size": 248 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "vtypes_to_json.py", + "datetime": "2019-04-17T13:45:16.417006" + }, + "format": "4.1.0" + } +} diff --git a/volatility3/framework/symbols/windows/services/services-win10-18362-x86.json b/volatility3/framework/symbols/windows/services/services-win10-18362-x86.json new file mode 100644 index 0000000000..4684dfe5b5 --- /dev/null +++ b/volatility3/framework/symbols/windows/services/services-win10-18362-x86.json @@ -0,0 +1,248 @@ +{ + "symbols": {}, + "enums": { + "StateEnum": { + "base": "long", + "constants": { + "SERVICE_START_PENDING": 2, + "SERVICE_STOP_PENDING": 3, + "SERVICE_STOPPED": 1, + "SERVICE_CONTINUE_PENDING": 5, + "SERVICE_PAUSE_PENDING": 6, + "SERVICE_PAUSED": 7, + "SERVICE_RUNNING": 4 + }, + "size": 4 + }, + "StartEnum": { + "base": "long", + "constants": { + "SERVICE_DEMAND_START": 3, + "SERVICE_AUTO_START": 2, + "SERVICE_BOOT_START": 0, + "SERVICE_DISABLED": 4, + "SERVICE_SYSTEM_START": 1 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_SERVICE_LIST_ENTRY": { + "fields": { + "Flink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 4 + }, + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_SERVICE_PROCESS": { + "fields": { + "BinaryPath": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 12 + }, + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 20 + } + }, + "kind": "struct", + "size": 20 + }, + "_SERVICE_HEADER": { + "fields": { + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "ServiceRecord": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 12 + } + }, + "kind": "struct", + "size": 12 + }, + "_SERVICE_RECORD": { + "fields": { + "DisplayName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 48 + }, + "ServiceProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_PROCESS" + } + }, + "offset": 164 + }, + "PrevEntry": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 12 + }, + "Start": { + "type": { + "kind": "enum", + "name": "StartEnum" + }, + "offset": 24 + }, + "State": { + "type": { + "kind": "enum", + "name": "StateEnum" + }, + "offset": 56 + }, + "ServiceName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 44 + }, + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "DriverName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 164 + }, + "Type": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 52 + }, + "Order": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 20 + } + }, + "kind": "struct", + "size": 156 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "vtypes_to_json.py", + "datetime": "2019-04-17T13:45:16.417006" + }, + "format": "4.1.0" + } +} diff --git a/volatility3/framework/symbols/windows/services/services-win10-19041-x64.json b/volatility3/framework/symbols/windows/services/services-win10-19041-x64.json new file mode 100644 index 0000000000..e44dbbd37c --- /dev/null +++ b/volatility3/framework/symbols/windows/services/services-win10-19041-x64.json @@ -0,0 +1,255 @@ +{ + "symbols": {}, + "enums": { + "StateEnum": { + "base": "long", + "constants": { + "SERVICE_START_PENDING": 2, + "SERVICE_STOP_PENDING": 3, + "SERVICE_STOPPED": 1, + "SERVICE_CONTINUE_PENDING": 5, + "SERVICE_PAUSE_PENDING": 6, + "SERVICE_PAUSED": 7, + "SERVICE_RUNNING": 4 + }, + "size": 4 + }, + "StartEnum": { + "base": "long", + "constants": { + "SERVICE_DEMAND_START": 3, + "SERVICE_AUTO_START": 2, + "SERVICE_BOOT_START": 0, + "SERVICE_DISABLED": 4, + "SERVICE_SYSTEM_START": 1 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_SERVICE_LIST_ENTRY": { + "fields": { + "Flink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 16 + }, + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 16 + }, + "_SERVICE_PROCESS": { + "fields": { + "BinaryPath": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 24 + }, + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 40 + } + }, + "kind": "struct", + "size": 40 + }, + "_SERVICE_HEADER": { + "fields": { + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "ServiceRecord": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 16 + }, + "_SERVICE_RECORD": { + "fields": { + "ServiceList": { + "type": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + }, + "offset": 0 + }, + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 32 + }, + "DisplayName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 64 + }, + "ServiceProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_PROCESS" + } + }, + "offset": 296 + }, + "PrevEntry": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 16 + }, + "Start": { + "type": { + "kind": "enum", + "name": "StartEnum" + }, + "offset": 36 + }, + "State": { + "type": { + "kind": "enum", + "name": "StateEnum" + }, + "offset": 76 + }, + "ServiceName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 56 + }, + "DriverName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 296 + }, + "Type": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 72 + }, + "Order": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 32 + } + }, + "kind": "struct", + "size": 296 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "David McDonald", + "datetime": "2023-11-16T15:05:35-06:00" + }, + "format": "4.1.0" + } +} diff --git a/volatility3/framework/symbols/windows/services/services-win10-19041-x86.json b/volatility3/framework/symbols/windows/services/services-win10-19041-x86.json new file mode 100644 index 0000000000..cc5ed9a73b --- /dev/null +++ b/volatility3/framework/symbols/windows/services/services-win10-19041-x86.json @@ -0,0 +1,248 @@ +{ + "symbols": {}, + "enums": { + "StateEnum": { + "base": "long", + "constants": { + "SERVICE_START_PENDING": 2, + "SERVICE_STOP_PENDING": 3, + "SERVICE_STOPPED": 1, + "SERVICE_CONTINUE_PENDING": 5, + "SERVICE_PAUSE_PENDING": 6, + "SERVICE_PAUSED": 7, + "SERVICE_RUNNING": 4 + }, + "size": 4 + }, + "StartEnum": { + "base": "long", + "constants": { + "SERVICE_DEMAND_START": 3, + "SERVICE_AUTO_START": 2, + "SERVICE_BOOT_START": 0, + "SERVICE_DISABLED": 4, + "SERVICE_SYSTEM_START": 1 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_SERVICE_LIST_ENTRY": { + "fields": { + "Flink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 4 + }, + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 8 + }, + "_SERVICE_PROCESS": { + "fields": { + "BinaryPath": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 12 + }, + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 20 + } + }, + "kind": "struct", + "size": 20 + }, + "_SERVICE_HEADER": { + "fields": { + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "ServiceRecord": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 12 + }, + "_SERVICE_RECORD": { + "fields": { + "DisplayName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 48 + }, + "ServiceProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_PROCESS" + } + }, + "offset": 192 + }, + "PrevEntry": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 12 + }, + "Start": { + "type": { + "kind": "enum", + "name": "StartEnum" + }, + "offset": 24 + }, + "State": { + "type": { + "kind": "enum", + "name": "StateEnum" + }, + "offset": 56 + }, + "ServiceName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 44 + }, + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "DriverName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 192 + }, + "Type": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 52 + }, + "Order": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 20 + } + }, + "kind": "struct", + "size": 192 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "vtypes_to_json.py", + "datetime": "2019-04-17T13:45:16.417006" + }, + "format": "4.1.0" + } +} diff --git a/volatility3/framework/symbols/windows/services/services-win10-25398-x64.json b/volatility3/framework/symbols/windows/services/services-win10-25398-x64.json new file mode 100644 index 0000000000..cd29abc430 --- /dev/null +++ b/volatility3/framework/symbols/windows/services/services-win10-25398-x64.json @@ -0,0 +1,255 @@ +{ + "symbols": {}, + "enums": { + "StateEnum": { + "base": "long", + "constants": { + "SERVICE_START_PENDING": 2, + "SERVICE_STOP_PENDING": 3, + "SERVICE_STOPPED": 1, + "SERVICE_CONTINUE_PENDING": 5, + "SERVICE_PAUSE_PENDING": 6, + "SERVICE_PAUSED": 7, + "SERVICE_RUNNING": 4 + }, + "size": 4 + }, + "StartEnum": { + "base": "long", + "constants": { + "SERVICE_DEMAND_START": 3, + "SERVICE_AUTO_START": 2, + "SERVICE_BOOT_START": 0, + "SERVICE_DISABLED": 4, + "SERVICE_SYSTEM_START": 1 + }, + "size": 4 + } + }, + "base_types": { + "unsigned long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned char": { + "kind": "char", + "size": 1, + "signed": false, + "endian": "little" + }, + "pointer": { + "kind": "int", + "size": 8, + "signed": false, + "endian": "little" + }, + "unsigned int": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + }, + "unsigned short": { + "kind": "int", + "size": 2, + "signed": false, + "endian": "little" + }, + "long": { + "kind": "int", + "size": 4, + "signed": false, + "endian": "little" + } + }, + "user_types": { + "_SERVICE_LIST_ENTRY": { + "fields": { + "Flink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 16 + }, + "Blink": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + } + }, + "offset": 0 + } + }, + "kind": "struct", + "size": 16 + }, + "_SERVICE_PROCESS": { + "fields": { + "BinaryPath": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 24 + }, + "ProcessId": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 40 + } + }, + "kind": "struct", + "size": 40 + }, + "_SERVICE_HEADER": { + "fields": { + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 0 + }, + "ServiceRecord": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 16 + } + }, + "kind": "struct", + "size": 16 + }, + "_SERVICE_RECORD": { + "fields": { + "ServiceList": { + "type": { + "kind": "struct", + "name": "_SERVICE_LIST_ENTRY" + }, + "offset": 0 + }, + "Tag": { + "type": { + "count": 4, + "subtype": { + "kind": "base", + "name": "unsigned char" + }, + "kind": "array" + }, + "offset": 32 + }, + "DisplayName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 64 + }, + "ServiceProcess": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_PROCESS" + } + }, + "offset": 336 + }, + "PrevEntry": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "struct", + "name": "_SERVICE_RECORD" + } + }, + "offset": 16 + }, + "Start": { + "type": { + "kind": "enum", + "name": "StartEnum" + }, + "offset": 36 + }, + "State": { + "type": { + "kind": "enum", + "name": "StateEnum" + }, + "offset": 84 + }, + "ServiceName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 56 + }, + "DriverName": { + "type": { + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "unsigned short" + } + }, + "offset": 296 + }, + "Type": { + "type": { + "kind": "base", + "name": "unsigned long" + }, + "offset": 80 + }, + "Order": { + "type": { + "kind": "base", + "name": "unsigned int" + }, + "offset": 32 + } + }, + "kind": "struct", + "size": 336 + } + }, + "metadata": { + "producer": { + "version": "0.0.1", + "name": "David McDonald", + "datetime": "2023-11-16T15:05:35-06:00" + }, + "format": "4.1.0" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-2003-x64.json b/volatility3/framework/symbols/windows/shimcache/shimcache-2003-x64.json new file mode 100644 index 0000000000..d1540f12f0 --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-2003-x64.json @@ -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" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-2003-x86.json b/volatility3/framework/symbols/windows/shimcache/shimcache-2003-x86.json new file mode 100644 index 0000000000..e4739c4a0a --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-2003-x86.json @@ -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" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-vista-x64.json b/volatility3/framework/symbols/windows/shimcache/shimcache-vista-x64.json new file mode 100644 index 0000000000..0c5183a4a5 --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-vista-x64.json @@ -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" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-vista-x86.json b/volatility3/framework/symbols/windows/shimcache/shimcache-vista-x86.json new file mode 100644 index 0000000000..91290b1ba1 --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-vista-x86.json @@ -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" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-win10-x64.json b/volatility3/framework/symbols/windows/shimcache/shimcache-win10-x64.json new file mode 100644 index 0000000000..fe9593af37 --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-win10-x64.json @@ -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" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-win10-x86.json b/volatility3/framework/symbols/windows/shimcache/shimcache-win10-x86.json new file mode 100644 index 0000000000..26d493c37a --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-win10-x86.json @@ -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" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-win7-x64.json b/volatility3/framework/symbols/windows/shimcache/shimcache-win7-x64.json new file mode 100644 index 0000000000..eac5407ba3 --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-win7-x64.json @@ -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" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-win7-x86.json b/volatility3/framework/symbols/windows/shimcache/shimcache-win7-x86.json new file mode 100644 index 0000000000..423f7e2551 --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-win7-x86.json @@ -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" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-win8-x64.json b/volatility3/framework/symbols/windows/shimcache/shimcache-win8-x64.json new file mode 100644 index 0000000000..a40c8d6804 --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-win8-x64.json @@ -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" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-win8-x86.json b/volatility3/framework/symbols/windows/shimcache/shimcache-win8-x86.json new file mode 100644 index 0000000000..c3cee5febe --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-win8-x86.json @@ -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" + } +} + diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-xp-sp2-x86.json b/volatility3/framework/symbols/windows/shimcache/shimcache-xp-sp2-x86.json new file mode 100644 index 0000000000..6114e6c856 --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-xp-sp2-x86.json @@ -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" + } +} diff --git a/volatility3/framework/symbols/windows/shimcache/shimcache-xp-sp3-x86.json b/volatility3/framework/symbols/windows/shimcache/shimcache-xp-sp3-x86.json new file mode 100644 index 0000000000..a9b86d93cd --- /dev/null +++ b/volatility3/framework/symbols/windows/shimcache/shimcache-xp-sp3-x86.json @@ -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" + } +} diff --git a/volatility3/framework/symbols/windows/unloadedmodules-x64.json b/volatility3/framework/symbols/windows/unloadedmodules-x64.json new file mode 100644 index 0000000000..75ab7c690c --- /dev/null +++ b/volatility3/framework/symbols/windows/unloadedmodules-x64.json @@ -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" + } +} \ No newline at end of file diff --git a/volatility3/framework/symbols/windows/unloadedmodules-x86.json b/volatility3/framework/symbols/windows/unloadedmodules-x86.json new file mode 100644 index 0000000000..ff9e78965b --- /dev/null +++ b/volatility3/framework/symbols/windows/unloadedmodules-x86.json @@ -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" + } +} \ No newline at end of file diff --git a/volatility3/framework/symbols/windows/versions.py b/volatility3/framework/symbols/windows/versions.py index 84ce654325..495655681b 100644 --- a/volatility3/framework/symbols/windows/versions.py +++ b/volatility3/framework/symbols/windows/versions.py @@ -114,6 +114,24 @@ def __call__( ], ) +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 @@ def __call__( ], ) +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 @@ def __call__( ], ) +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=[ @@ -151,11 +187,45 @@ def __call__( ], ) +is_win10_17763_or_later = OsDistinguisher( + version_check=lambda x: x >= (10, 0, 17763), + fallback_checks=[ + ("_EPROCESS", "TrustletIdentity", False), + ("ParentSecurityDomain", None, True), + ], +) + +is_win10_18362_or_later = OsDistinguisher( + version_check=lambda x: x >= (10, 0, 18362), + fallback_checks=[ + ("ObHeaderCookie", None, True), + ("_CM_CACHED_VALUE_INDEX", None, False), + ("_WNF_PROCESS_CONTEXT", None, True), + ], +) + is_win10_18363_or_later = OsDistinguisher( version_check=lambda x: x >= (10, 0, 18363), fallback_checks=[("_KQOS_GROUPING_SETS", None, True)], ) +is_win10_19041_or_later = OsDistinguisher( + version_check=lambda x: x >= (10, 0, 19041), + fallback_checks=[ + ("_EPROCESS", "TimerResolutionIgnore", True), + ("_EPROCESS", "VmProcessorHostTransition", True), + ("_KQOS_GROUPING_SETS", None, True), + ], +) + +is_win10_25398_or_later = OsDistinguisher( + version_check=lambda x: x >= (10, 0, 25398), + fallback_checks=[ + ("_EPROCESS", "MmSlabIdentity", True), + ("_EPROCESS", "EnableProcessImpersonationLogging", True), + ], +) + is_windows_10 = OsDistinguisher( version_check=lambda x: x >= (10, 0), fallback_checks=[("ObHeaderCookie", None, True)], diff --git a/volatility3/plugins/windows/statistics.py b/volatility3/plugins/windows/statistics.py index 9915312e39..7f56b75f8a 100644 --- a/volatility3/plugins/windows/statistics.py +++ b/volatility3/plugins/windows/statistics.py @@ -31,13 +31,9 @@ def _generator(self): # Do mass mapping and determine the number of different layers and how many pages go to each one layer = self.context.layers[self.config["primary"]] - page_count = ( - swap_count - ) = ( - invalid_page_count - ) = ( - large_page_count - ) = large_swap_count = large_invalid_count = other_invalid = 0 + page_count = swap_count = invalid_page_count = large_page_count = ( + large_swap_count + ) = large_invalid_count = other_invalid = 0 if isinstance(layer, intel.Intel): page_addr = 0 diff --git a/volatility3/schemas/schema-6.3.0.json b/volatility3/schemas/schema-6.3.0.json new file mode 100644 index 0000000000..ba6e970bb1 --- /dev/null +++ b/volatility3/schemas/schema-6.3.0.json @@ -0,0 +1,507 @@ +{ + "$schema": "http://json-schema.org/schema#", + "id": "http://volatilityfoundation.org/intermediate-format/schema", + "title": "Symbol Container", + "type": "object", + "definitions": { + "metadata_producer": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string", + "pattern": "^[0-9]+.[0-9]+.[0-9]+$" + }, + "datetime": { + "type": "string", + "format": "date-time" + } + }, + "required":[ + "name", + "version" + ] + }, + "metadata_windows_pe": { + "type": "object", + "properties": { + "major": { + "type": "integer" + }, + "minor": { + "type": "integer" + }, + "revision": { + "type": "integer" + }, + "build": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "major", + "minor", + "revision" + ] + }, + "metadata_windows_pdb": { + "type": "object", + "properties": { + "GUID": { + "type": "string" + }, + "age": { + "type": "integer" + }, + "database": { + "type": "string" + }, + "machine_type": { + "type": "integer" + } + }, + "additionalProperties": false, + "required": [ + "GUID", + "age", + "database", + "machine_type" + ] + }, + "metadata_windows": { + "type": "object", + "properties": { + "pe": { + "$ref": "#/definitions/metadata_windows_pe" + }, + "pdb": { + "$ref": "#/definitions/metadata_windows_pdb" + } + }, + "additionalProperties": false + }, + "metadata_nix": { + "type": "object", + "properties": { + "symbols": { + "type": "array", + "items": { + "$ref": "#/definitions/metadata_nix_item" + } + }, + "types": { + "type": "array", + "items": { + "$ref": "#/definitions/metadata_nix_item" + } + } + }, + "additionalProperties": false + }, + "metadata_format": { + "type": "string", + "pattern": "^6.[0-9]+.[0-9]+$" + }, + "metadata_nix_item": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "pattern": "^(dwarf|symtab|system-map)$" + }, + "name": { + "type": "string" + }, + "hash_type": { + "type": "string", + "pattern": "^(sha256)$" + }, + "hash_value": { + "type": "string", + "pattern": "^[a-fA-F0-9]+$" + } + }, + "additionalProperties": false + }, + "element_metadata": { + "type": "object", + "oneOf": [ + { + "properties": { + "format": { + "$ref": "#/definitions/metadata_format" + }, + "producer": { + "$ref": "#/definitions/metadata_producer" + } + }, + "required": [ + "format", + "producer" + ], + "additionalProperties": false + }, + { + "properties": { + "format": { + "$ref": "#/definitions/metadata_format" + }, + "producer": { + "$ref": "#/definitions/metadata_producer" + }, + "windows": { + "$ref": "#/definitions/metadata_windows" + } + }, + "required": [ + "format", + "producer", + "windows" + ], + "additionalProperties": false + }, + { + "properties": { + "format": { + "$ref": "#/definitions/metadata_format" + }, + "producer": { + "$ref": "#/definitions/metadata_producer" + }, + "linux": { + "$ref": "#/definitions/metadata_nix" + } + }, + "required": [ + "format", + "producer", + "linux" + ], + "additionalProperties": false + }, + { + "properties": { + "format": { + "$ref": "#/definitions/metadata_format" + }, + "producer": { + "$ref": "#/definitions/metadata_producer" + }, + "mac": { + "$ref": "#/definitions/metadata_nix" + } + }, + "required": [ + "format", + "producer", + "mac" + ], + "additionalProperties": false + } + ] + }, + "element_enum": { + "properties": { + "size": { + "type": "integer" + }, + "base": { + "type": "string" + }, + "constants": { + "type": "object", + "additionalProperties": { + "type": "integer" + } + } + }, + "required": [ + "size", + "base", + "constants" + ], + "additionalProperties": false + }, + "element_symbol": { + "properties": { + "address": { + "type": "number" + }, + "linkage_name": { + "type": "string" + }, + "type": { + "$ref": "#/definitions/type_descriptor" + }, + "constant_data": { + "type": "string", + "media": { + "binaryEncoding": "base64", + "readOnly": true + } + } + }, + "required": [ + "address" + ], + "additionalProperties": false + }, + "element_base_type": { + "properties": { + "size": { + "type": "integer" + }, + "signed": { + "type": "boolean" + }, + "kind": { + "type": "string", + "pattern": "^(void|int|float|char|bool)$" + }, + "endian": { + "type": "string", + "pattern": "^(little|big)$" + } + }, + "required": [ + "size", + "kind", + "signed", + "endian" + ], + "additionalProperties": false + }, + "element_user_type": { + "properties": { + "kind": { + "type": "string", + "pattern": "^(struct|union|class)$" + }, + "size": { + "type": "integer" + }, + "fields": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/field" + } + } + }, + "required": [ + "kind", + "size", + "fields" + ], + "additionalProperties": false + }, + "field": { + "properties": { + "type": { + "$ref": "#/definitions/type_descriptor" + }, + "offset": { + "type": "integer" + }, + "anonymous": { + "type": "boolean" + } + }, + "required": [ + "type", + "offset" + ], + "additionalProperties": false + }, + "type_descriptor": { + "oneOf": [ + { + "$ref": "#/definitions/type_pointer" + }, + { + "$ref": "#/definitions/type_base" + }, + { + "$ref": "#/definitions/type_array" + }, + { + "$ref": "#/definitions/type_struct" + }, + { + "$ref": "#/definitions/type_enum" + }, + { + "$ref": "#/definitions/type_function" + }, + { + "$ref": "#/definitions/type_bitfield" + } + ] + }, + "type_pointer": { + "properties": { + "kind": { + "type": "string", + "pattern": "^pointer$" + }, + "base": { + "type": "string" + }, + "subtype": { + "$ref": "#/definitions/type_descriptor" + } + }, + "required": [ + "kind", + "subtype" + ], + "additionalProperties": false + }, + "type_base": { + "properties": { + "kind": { + "type": "string", + "pattern": "^base$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_array": { + "properties": { + "kind": { + "type": "string", + "pattern": "^array$" + }, + "subtype": { + "$ref": "#/definitions/type_descriptor" + }, + "count": { + "type": "integer" + } + }, + "required": [ + "kind", + "subtype", + "count" + ], + "additionalProperties": false + }, + "type_struct": { + "properties": { + "kind": { + "type": "string", + "pattern": "^(struct|class|union)$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_enum": { + "properties": { + "kind": { + "type": "string", + "pattern": "^enum$" + }, + "name": { + "type": "string" + } + }, + "required": [ + "kind", + "name" + ], + "additionalProperties": false + }, + "type_function": { + "properties": { + "kind": { + "type": "string", + "pattern": "^function$" + } + }, + "required": [ + "kind" + ], + "additionalProperties": false + }, + "type_bitfield": { + "properties": { + "kind": { + "type": "string", + "pattern": "^bitfield$" + }, + "bit_position": { + "type": "integer" + }, + "bit_length": { + "type": "integer" + }, + "type": { + "oneOf": [ + { + "$ref": "#/definitions/type_base" + }, + { + "$ref": "#/definitions/type_enum" + } + ] + } + }, + "required": [ + "kind", + "bit_position", + "bit_length", + "type" + ], + "additionalProperties": false + } + }, + "properties": { + "metadata": { + "$ref": "#/definitions/element_metadata" + }, + "base_types": { + "additionalProperties": { + "$ref": "#/definitions/element_base_type" + } + }, + "user_types": { + "additionalProperties": { + "$ref": "#/definitions/element_user_type" + } + }, + "enums": { + "additionalProperties": { + "$ref": "#/definitions/element_enum" + } + }, + "symbols": { + "additionalProperties": { + "$ref": "#/definitions/element_symbol" + } + } + }, + "required": [ + "metadata", + "base_types", + "user_types", + "enums", + "symbols" + ], + "additionalProperties": false +} diff --git a/volatility3/symbols/generic/vmcs/haswell-architecture.json b/volatility3/symbols/generic/vmcs/haswell-architecture.json new file mode 100644 index 0000000000..33b2e55d88 --- /dev/null +++ b/volatility3/symbols/generic/vmcs/haswell-architecture.json @@ -0,0 +1,131 @@ +{ + "base_types": { + "pointer": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 8 + }, + "unsigned char": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 1 + }, + "unsigned long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 4 + }, + "unsigned long long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 8 + }, + "unsigned short": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 2 + } + }, + "enums": {}, + "metadata": { + "format": "6.1.0", + "producer": { + "datetime": "2021-07-31T17:37:28.313255", + "name": "vmextract-by-hand", + "version": "0.0.1" + } + }, + "symbols": { + "revision_id": { + "address": 0, + "constant_data": "MTg=" + } + }, + "user_types": { + "_VMCS": { + "fields": { + "ept": { + "offset": 320, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "executive_vmcs_ptr": { + "offset": 208, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "guest_cr3": { + "offset": 528, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "guest_cr4": { + "offset": 536, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "guest_pdpte": { + "offset": 544, + "type": { + "count": 4, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "unsigned long long" + } + } + }, + "guest_physical_addr": { + "offset": 328, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "host_cr3": { + "offset": 816, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "host_cr4": { + "offset": 824, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "vmcs_link_ptr": { + "offset": 248, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "vpid": { + "offset": 206, + "type": { + "kind": "struct", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 4096 + } + } +} \ No newline at end of file diff --git a/volatility3/symbols/generic/vmcs/nehalem-architecture.json b/volatility3/symbols/generic/vmcs/nehalem-architecture.json new file mode 100644 index 0000000000..ae0ab28633 --- /dev/null +++ b/volatility3/symbols/generic/vmcs/nehalem-architecture.json @@ -0,0 +1,131 @@ +{ + "base_types": { + "pointer": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 8 + }, + "unsigned char": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 1 + }, + "unsigned long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 4 + }, + "unsigned long long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 8 + }, + "unsigned short": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 2 + } + }, + "enums": {}, + "metadata": { + "format": "6.1.0", + "producer": { + "datetime": "2021-07-31T17:37:28.302702", + "name": "vmextract-by-hand", + "version": "0.0.1" + } + }, + "symbols": { + "revision_id": { + "address": 0, + "constant_data": "MTQ=" + } + }, + "user_types": { + "_VMCS": { + "fields": { + "ept": { + "offset": 232, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "executive_vmcs_ptr": { + "offset": 208, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "guest_cr3": { + "offset": 736, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "guest_cr4": { + "offset": 744, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "guest_pdpte": { + "offset": 928, + "type": { + "count": 4, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "unsigned long long" + } + } + }, + "guest_physical_addr": { + "offset": 240, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "host_cr3": { + "offset": 832, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "host_cr4": { + "offset": 840, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "vmcs_link_ptr": { + "offset": 248, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "vpid": { + "offset": 752, + "type": { + "kind": "struct", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 4096 + } + } +} \ No newline at end of file diff --git a/volatility3/symbols/generic/vmcs/sandybridge-architecture.json b/volatility3/symbols/generic/vmcs/sandybridge-architecture.json new file mode 100644 index 0000000000..b2b3cfebfe --- /dev/null +++ b/volatility3/symbols/generic/vmcs/sandybridge-architecture.json @@ -0,0 +1,131 @@ +{ + "base_types": { + "pointer": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 8 + }, + "unsigned char": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 1 + }, + "unsigned long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 4 + }, + "unsigned long long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 8 + }, + "unsigned short": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 2 + } + }, + "enums": {}, + "metadata": { + "format": "6.1.0", + "producer": { + "datetime": "2021-07-31T17:37:28.311608", + "name": "vmextract-by-hand", + "version": "0.0.1" + } + }, + "symbols": { + "revision_id": { + "address": 0, + "constant_data": "MTY=" + } + }, + "user_types": { + "_VMCS": { + "fields": { + "ept": { + "offset": 232, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "executive_vmcs_ptr": { + "offset": 208, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "guest_cr3": { + "offset": 736, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "guest_cr4": { + "offset": 744, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "guest_pdpte": { + "offset": 928, + "type": { + "count": 4, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "unsigned long long" + } + } + }, + "guest_physical_addr": { + "offset": 240, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "host_cr3": { + "offset": 832, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "host_cr4": { + "offset": 840, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "vmcs_link_ptr": { + "offset": 248, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "vpid": { + "offset": 752, + "type": { + "kind": "struct", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 4096 + } + } +} \ No newline at end of file diff --git a/volatility3/symbols/generic/vmcs/skylake-architecture.json b/volatility3/symbols/generic/vmcs/skylake-architecture.json new file mode 100644 index 0000000000..78b110d94e --- /dev/null +++ b/volatility3/symbols/generic/vmcs/skylake-architecture.json @@ -0,0 +1,131 @@ +{ + "base_types": { + "pointer": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 8 + }, + "unsigned char": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 1 + }, + "unsigned long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 4 + }, + "unsigned long long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 8 + }, + "unsigned short": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 2 + } + }, + "enums": {}, + "metadata": { + "format": "6.1.0", + "producer": { + "datetime": "2021-07-16T16:21:01.062423", + "name": "vmextract-by-hand", + "version": "0.0.1" + } + }, + "symbols": { + "revision_id": { + "address": 0, + "constant_data": "NA==" + } + }, + "user_types": { + "_VMCS": { + "fields": { + "ept": { + "offset": 320, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "executive_vmcs_ptr": { + "offset": 208, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "guest_cr3": { + "offset": 528, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "guest_cr4": { + "offset": 536, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "guest_pdpte": { + "offset": 544, + "type": { + "count": 4, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "unsigned long long" + } + } + }, + "guest_physical_addr": { + "offset": 328, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "host_cr3": { + "offset": 816, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "host_cr4": { + "offset": 824, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "vmcs_link_ptr": { + "offset": 248, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "vpid": { + "offset": 206, + "type": { + "kind": "struct", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 4096 + } + } +} diff --git a/volatility3/symbols/generic/vmcs/westmere-architecture.json b/volatility3/symbols/generic/vmcs/westmere-architecture.json new file mode 100644 index 0000000000..2769f70812 --- /dev/null +++ b/volatility3/symbols/generic/vmcs/westmere-architecture.json @@ -0,0 +1,131 @@ +{ + "base_types": { + "pointer": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 8 + }, + "unsigned char": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 1 + }, + "unsigned long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 4 + }, + "unsigned long long": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 8 + }, + "unsigned short": { + "endian": "little", + "kind": "int", + "signed": false, + "size": 2 + } + }, + "enums": {}, + "metadata": { + "format": "6.1.0", + "producer": { + "datetime": "2021-07-31T17:37:28.314801", + "name": "vmextract-by-hand", + "version": "0.0.1" + } + }, + "symbols": { + "revision_id": { + "address": 0, + "constant_data": "MTU=" + } + }, + "user_types": { + "_VMCS": { + "fields": { + "ept": { + "offset": 320, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "executive_vmcs_ptr": { + "offset": 208, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "guest_cr3": { + "offset": 736, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "guest_cr4": { + "offset": 744, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "guest_pdpte": { + "offset": 928, + "type": { + "count": 4, + "kind": "array", + "subtype": { + "kind": "struct", + "name": "unsigned long long" + } + } + }, + "guest_physical_addr": { + "offset": 328, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "host_cr3": { + "offset": 832, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "host_cr4": { + "offset": 840, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "vmcs_link_ptr": { + "offset": 248, + "type": { + "kind": "struct", + "name": "unsigned long long" + } + }, + "vpid": { + "offset": 220, + "type": { + "kind": "struct", + "name": "unsigned short" + } + } + }, + "kind": "struct", + "size": 4096 + } + } +} \ No newline at end of file diff --git a/volshell.py b/volshell.py index 71d35a47c9..65b11885e1 100755 --- a/volshell.py +++ b/volshell.py @@ -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