mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-08-17 20:35:40 +02:00
Merge branch 'develop' into issues/issue1255
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
name: Black python linter
|
||||
name: Black python formatter
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
name: build-pyinstaller
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- stable
|
||||
- develop
|
||||
- "release/**"
|
||||
pull_request:
|
||||
branches:
|
||||
- stable
|
||||
- "release/**"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
exe:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["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: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install pyinstaller
|
||||
pip install -e .[full,cloud]
|
||||
|
||||
- name: Pyinstall executable
|
||||
run: |
|
||||
pyinstaller --clean -y vol.spec
|
||||
pyinstaller --clean -y volshell.spec
|
||||
|
||||
- name: Move files
|
||||
run: |
|
||||
mv dist/vol.exe vol.exe
|
||||
mv dist/volshell.exe volshell.exe
|
||||
|
||||
- name: Archive
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: volatility3-pyinstaller
|
||||
path: |
|
||||
vol.exe
|
||||
volshell.exe
|
||||
README.md
|
||||
LICENSE.txt
|
||||
@@ -13,10 +13,10 @@ name: "CodeQL"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "develop" ]
|
||||
branches: ["develop"]
|
||||
pull_request:
|
||||
# The branches below must be a subset of the branches above
|
||||
branches: [ "develop" ]
|
||||
branches: ["develop"]
|
||||
# schedule:
|
||||
# - cron: '16 8 * * 0'
|
||||
|
||||
@@ -32,43 +32,42 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
language: [ 'python' ]
|
||||
language: ["python"]
|
||||
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
|
||||
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v2
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||
# By default, queries listed here will override any specified in a config file.
|
||||
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||
|
||||
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
queries: security-and-quality # ,security-extended
|
||||
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
|
||||
queries: security-and-quality # ,security-extended
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v2
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
|
||||
# If the Autobuild fails above, remove it and uncomment the following three lines.
|
||||
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
# - run: |
|
||||
# echo "Run, Build Application using script"
|
||||
# ./location_of_script_within_repo/buildscript.sh
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v2
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
with:
|
||||
category: "/language:${{matrix.language}}"
|
||||
|
||||
@@ -20,12 +20,8 @@ jobs:
|
||||
- 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
|
||||
run: vol --help
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
---
|
||||
name: Ruff
|
||||
|
||||
on: [push, pull_request]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: astral-sh/ruff-action@v3.2.1
|
||||
with:
|
||||
args: check
|
||||
src: "."
|
||||
+55
-39
@@ -1,54 +1,70 @@
|
||||
name: Test Volatility3
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.8"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install Cmake
|
||||
pip install build
|
||||
pip install -r ./test/requirements-testing.txt
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip Cmake build
|
||||
pip install .[test]
|
||||
|
||||
- name: Build PyPi packages
|
||||
run: |
|
||||
python -m build
|
||||
- name: Build PyPi packages
|
||||
run: |
|
||||
python -m build
|
||||
|
||||
- name: Download images
|
||||
run: |
|
||||
curl -sLO "https://downloads.volatilityfoundation.org/volatility3/images/linux-sample-1.bin.gz"
|
||||
gunzip linux-sample-1.bin.gz
|
||||
curl -sLO "https://downloads.volatilityfoundation.org/volatility3/images/win-xp-laptop-2005-06-25.img.gz"
|
||||
gunzip win-xp-laptop-2005-06-25.img.gz
|
||||
- name: Download images
|
||||
run: |
|
||||
mkdir test_images
|
||||
cd test_images
|
||||
curl -sLO "https://downloads.volatilityfoundation.org/volatility3/images/linux-sample-1.bin.gz"
|
||||
gunzip linux-sample-1.bin.gz
|
||||
curl -sLO "https://downloads.volatilityfoundation.org/volatility3/images/win-xp-laptop-2005-06-25.img.gz"
|
||||
gunzip win-xp-laptop-2005-06-25.img.gz
|
||||
curl -sLO "https://downloads.volatilityfoundation.org/volatility3/images/win-10_19041-2025_03.dmp.gz"
|
||||
gunzip win-10_19041-2025_03.dmp.gz
|
||||
cd -
|
||||
|
||||
- name: Download and Extract symbols
|
||||
run: |
|
||||
cd ./volatility3/symbols
|
||||
curl -sLO https://downloads.volatilityfoundation.org/volatility3/symbols/linux.zip
|
||||
unzip linux.zip
|
||||
cd -
|
||||
- name: Download and Extract symbols
|
||||
run: |
|
||||
cd ./volatility3/symbols
|
||||
curl -sLO https://downloads.volatilityfoundation.org/volatility3/symbols/linux.zip
|
||||
curl -sLO https://downloads.volatilityfoundation.org/volatility3/symbols/symbols_win-10_19041-2025_03.zip
|
||||
unzip linux.zip
|
||||
unzip symbols_win-10_19041-2025_03.zip
|
||||
cd -
|
||||
|
||||
- name: Testing...
|
||||
run: |
|
||||
py.test ./test/test_volatility.py --volatility=vol.py --image win-xp-laptop-2005-06-25.img -k test_windows -v
|
||||
py.test ./test/test_volatility.py --volatility=vol.py --image linux-sample-1.bin -k test_linux -v
|
||||
- name: Testing...
|
||||
run: |
|
||||
# VolShell
|
||||
pytest --cov-append --cov-report=html --cov= ./test/plugins/windows/windows.py --volatility=volshell.py --image-dir=./test_images -k test_windows_volshell -v
|
||||
pytest --cov-append --cov-report=html --cov= ./test/plugins/linux/linux.py --volatility=volshell.py --image-dir=./test_images -k test_linux_volshell -v
|
||||
|
||||
- name: Clean up post-test
|
||||
run: |
|
||||
rm -rf *.bin
|
||||
rm -rf *.img
|
||||
cd volatility3/symbols
|
||||
rm -rf linux
|
||||
rm -rf linux.zip
|
||||
cd -
|
||||
# Volatility
|
||||
pytest --cov-append --cov-report=html --cov= ./test/plugins/windows/windows.py --volatility=vol.py --image=./test_images/win-10_19041-2025_03.dmp -k "test_windows and not test_windows_volshell" -v --durations=0
|
||||
pytest --cov-append --cov-report=html --cov= ./test/plugins/linux/linux.py --volatility=vol.py --image-dir=./test_images -k "test_linux and not test_linux_volshell" -v --durations=0
|
||||
|
||||
- name: Create coverage artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: code-coverage-report
|
||||
path: htmlcov
|
||||
overwrite: true
|
||||
retention-days: 7
|
||||
|
||||
- name: Clean up post-test
|
||||
run: |
|
||||
rm -rf test_images
|
||||
cd volatility3/symbols
|
||||
rm -rf linux
|
||||
rm -rf linux.zip
|
||||
cd -
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
name: Volatility3 Code Analysis
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-22.04
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.8"]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install .[test]
|
||||
|
||||
- name: Testing...
|
||||
run: |
|
||||
python ./test/volatility3_code_analysis.py
|
||||
@@ -43,3 +43,6 @@ ENV/
|
||||
|
||||
# PyTest cache files
|
||||
.pytest_cache/
|
||||
|
||||
# Coverage cache
|
||||
.coverage
|
||||
|
||||
+4
-1
@@ -20,4 +20,7 @@ build:
|
||||
# Optionally set the version of Python and requirements required to build your docs
|
||||
python:
|
||||
install:
|
||||
- requirements: doc/requirements.txt
|
||||
- method: pip
|
||||
path: .
|
||||
extra_requirements:
|
||||
- docs
|
||||
|
||||
+94
-2
@@ -4,6 +4,100 @@ API Changes
|
||||
When an addition to the existing API is made, the minor version is bumped.
|
||||
When an API feature or function is removed or changed, the major version is bumped.
|
||||
|
||||
2.25.0
|
||||
======
|
||||
Pointer class now supports `get_raw_value()`.
|
||||
`KTIMER` no longer supports `get_raw_dpc()`.
|
||||
|
||||
2.24.0
|
||||
======
|
||||
Support `encoding` parameter for `objects.utility.array_to_string`
|
||||
|
||||
2.23.0
|
||||
======
|
||||
Add support for windows GUI classes and OS distinguishers.
|
||||
Add a symbol_table_name for `ExecutiveObject.get_object_header()`/
|
||||
|
||||
2.22.0
|
||||
======
|
||||
Linux net constants added.
|
||||
Network objects moved to separate versionable module.
|
||||
|
||||
2.21.0
|
||||
======
|
||||
`uuid` method added to `linux.extensions`.
|
||||
|
||||
2.20.0
|
||||
======
|
||||
NM_TYPES_DESC constants added to linux.
|
||||
`latch_tree_root` and `kernel_symbol` added to linux extensions.
|
||||
Linux `module` class additions:
|
||||
* `get_module_address_boundaries`
|
||||
* `section_typetab`
|
||||
Linux `task_struct` class additions:
|
||||
* `get_address_space_layer`
|
||||
* `state`
|
||||
Linux `bpf_prog` class additions:
|
||||
* `bpf_jit_binary_hdr_address`
|
||||
|
||||
2.19.0
|
||||
======
|
||||
Introduction of `Modules` versionable linux extension module.
|
||||
Deprecation of some `LinuxUtilities` functions relating to modules.
|
||||
|
||||
2.18.0
|
||||
======
|
||||
Addition of `scatterlist` linux extension.
|
||||
|
||||
2.17.0
|
||||
======
|
||||
The addition of a `types` member to `SymbolInterface`
|
||||
|
||||
2.16.0
|
||||
======
|
||||
Addition of TAINT_FLAG constants, `TaintFlag` dataclass
|
||||
Addition of linux `tainting` versionable module
|
||||
|
||||
2.15.0
|
||||
======
|
||||
Addition of `convert_fourcc_code` to `LinuxUtilities` class
|
||||
|
||||
2.14.0
|
||||
======
|
||||
No significant changes (part of the 2.16.0 PR which took time in development)
|
||||
|
||||
2.13.0
|
||||
======
|
||||
Linux `task` object extension addition of `getppid`
|
||||
|
||||
2.12.0
|
||||
======
|
||||
Changes to the Intel layer to support `PROT_NONE` pages.
|
||||
|
||||
2.11.0
|
||||
======
|
||||
Addition of `get_type` method to windows `CM_KEY_NODE` registry structure
|
||||
|
||||
2.10.0
|
||||
======
|
||||
No significant API changes (CLI changes to the JSONL text renderer)
|
||||
|
||||
2.9.0
|
||||
=====
|
||||
No significant API changes (change to call `linux.LinuxUtilities.get_module_from_volobj_type` to get the kernel)
|
||||
|
||||
2.8.0
|
||||
=====
|
||||
Addition of the `BinOrAbsent`, `HexOrAbsent`, `HexBytesOrAbsent` and `MultiTypeDataOrAbsent` data type renderers
|
||||
|
||||
2.7.0
|
||||
=====
|
||||
Addition of `is_valid`, `get_create_time` and `get_exit_time` to ETHREAD structure
|
||||
|
||||
2.6.0
|
||||
=====
|
||||
No significant changes (again, the version got bump twice in the PR straight to 2.7.0)
|
||||
|
||||
2.5.0
|
||||
=====
|
||||
Add in support for specifying a type override for object_from_symbol
|
||||
@@ -50,5 +144,3 @@ an absolute offset. This can be done with `Module.get_absolute_symbol_address`
|
||||
* Added context.modules
|
||||
* Added ModuleRequirement
|
||||
* Added get\_symbols\_by\_absolute\_location
|
||||
|
||||
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
Coding Standards
|
||||
================
|
||||
|
||||
The coding standards for volatility are mostly by our linter and our code formatter.
|
||||
All code submissions will be vetted automatically through tests from both and the submission will not be accepted if either of these fail.
|
||||
|
||||
Code Linter: Ruff
|
||||
Code Formatter: Black
|
||||
|
||||
In addition, there are some coding practices that we employ to prevent specific failure cases and ensure consistency across the codebase. These are documented below along with the rationale for the decision.
|
||||
|
||||
This is heavily based upon https://google.github.io/styleguide/pyguide.html with minor modifications for volatility use.
|
||||
|
||||
Imports
|
||||
-------
|
||||
|
||||
Use import statements for packages and modules only, not for individual types, classes, or functions and ideally not aliased unless the imported name would cause confusion. This is to prevent people from importing something that was itself imported from elsewhere (which can lead to confusion and add in an unnecessary dependency in the import chain).
|
||||
|
||||
* Use `import x` for importing packages and modules.
|
||||
* Use `from x import y` where x is the package prefix and y is the module name with no prefix.
|
||||
* Use `from x import y as z` in any of the following circumstances:
|
||||
* Two modules named `y` are to be imported.
|
||||
* `y` conflicts with a top-level name defined in the current module.
|
||||
* `y` conflicts with a common parameter name that is part of the public API (e.g., `features`).
|
||||
* `y` is an inconveniently long name.
|
||||
* `y` is too generic in the context of your code (e.g., `from storage.file_system import options as fs_options`).
|
||||
|
||||
Exemptions from this rule:
|
||||
|
||||
* Symbols from the following modules are used to support static analysis and type checking:
|
||||
* `typing` module
|
||||
* `collections.abc` module
|
||||
* `typing_extensions` module
|
||||
|
||||
Function calls
|
||||
--------------
|
||||
|
||||
For longer function calls, where line length is no longer an issue, favour using keyword arguments for clarity over unnamed positional arguments.
|
||||
This helps coders learning the code from examples to know what parameters to pass in and avoids ordering mistakes.
|
||||
|
||||
Global Mutable State
|
||||
--------------------
|
||||
|
||||
Avoid mutable global state.
|
||||
|
||||
In those rare cases where using global state is warranted, mutable global entities should be declared at the module level or as a class attribute and made internal by prepending an _ to the name. If necessary, external access to mutable global state must be done through public functions or class methods. See Naming below. Please explain the design reasons why mutable global state is being used in a comment or a doc linked to from a comment.
|
||||
|
||||
Module-level constants are permitted and encouraged. For example: _MAX_HOLY_HANDGRENADE_COUNT = 3 for an internal use constant or SIR_LANCELOTS_FAVORITE_COLOR = "blue" for a public API constant. Constants must be named using all caps with underscores. See Naming below.
|
||||
|
||||
Exceptions
|
||||
----------
|
||||
|
||||
Never use catch-all except: statements, or catch Exception or StandardError, unless you are
|
||||
|
||||
* re-raising the exception, or
|
||||
* creating an isolation point in the program where exceptions are not propagated but are recorded and suppressed instead, such as protecting a thread from crashing by guarding its outermost block.
|
||||
|
||||
Python is very tolerant in this regard and except: will really catch everything including misspelled names, sys.exit() calls, Ctrl+C interrupts, unittest failures and all kinds of other exceptions that you simply don’t want to catch.
|
||||
|
||||
Versioning
|
||||
----------
|
||||
|
||||
Modules that inherit from `VersionableInterface` define a `_version` attribute which states their version. This is a tuple of `(MAJOR, MINOR, PATCH)` numbers, which can then be used for Semantic Versioning (where modifications that change the API in a non-backwards compatible way bump the `MAJOR` version (and set the `MINOR` and `PATCH` to 0) and additive changes increase the `MINOR` version (and set the `PATCH` to 0). Changes that have no effect on the external interface (either input or output form) should have their `PATCH` number incremented. This allows for callers of the interface to determine when changes have happened and whether their code will still work with it. Volatility carries out these checks through the requirements system, where a plugin can define what requirements it has.
|
||||
|
||||
Shared functionality
|
||||
--------------------
|
||||
|
||||
Within a plugin, there may be functions that are useful to other plugins. These are created as `classmethod`s so that the plugin can be depended upon by other plugins in their requirements section, without needing to instantiate a whole copy of the plugin. It is not a staticmethod, because the caller may wish to determine information about the class the method is defined in, and this is not easily accessible for staticmethods.
|
||||
A classmethod usually takes a `context` for its first method (and if it requires one, a configuration string for it second). All other parameters should generally be basic types (such as strings, numbers, etc) so that future work requiring parallelization does not have complex types to have to keep in sync. In particular, the idea was to ensure only one context was used per method (and each object brings its own context with it, meaning the function signature should not include objects to avoid discrepancies).
|
||||
|
||||
Comprehensions
|
||||
--------------
|
||||
|
||||
Comprehensions are allowed, however multiple for clauses or filter expressions are not permitted. Optimize for readability, not conciseness.
|
||||
|
||||
Lambda functions
|
||||
----------------
|
||||
|
||||
Okay for one-liners. Prefer generator expressions over map() or filter() with a lambda.
|
||||
|
||||
Default Arguments
|
||||
-----------------
|
||||
|
||||
Default arguments are fine, but not with mutable types (because they're constructed once at module load time and can lead to confusion/errors.)
|
||||
|
||||
Format strings
|
||||
--------------
|
||||
Generally f-strings are preferred, and where possible a format modifier should be used over a separate method call. As an example, hex output should be `f"0x{offset:x}"` rather than `f"{hex(offset)}"`.
|
||||
F-strings should be used over other formatting methods *except* in cases of logging where the f-string gets calculated/executed whether the log message is displayed or not (where as parameters are not evaluated if not needed).
|
||||
The ruff linter should alert about these situations and exceptions can be maded if needed.
|
||||
|
||||
True/False Evaluations
|
||||
----------------------
|
||||
|
||||
Use the “implicit” false if possible, e.g., if foo: rather than if foo != []:. There are a few caveats that you should keep in mind though:
|
||||
|
||||
* Always use `if foo is None:` (or `is not None`) to check for a `None` value. E.g., when testing whether a variable or argument that defaults to `None` was set to some other value. The other value might be a value that’s false in a boolean context!
|
||||
* Never compare a boolean variable to `False` using `==`. Use `if not x:` instead. If you need to distinguish `False` from `None` then chain the expressions, such as `if not x and x is not None:`.
|
||||
* For sequences (strings, lists, tuples), use the fact that empty sequences are false, so `if seq:` and `if not seq:` are preferable to `if len(seq):` and `if not len(seq):` respectively.
|
||||
|
||||
Logging
|
||||
-------
|
||||
|
||||
We do allow f-string usage in log messages, although technically it should be avoided since it will be evaluated even if the log message is never emitted.
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
prune development
|
||||
include * .*
|
||||
include doc/make.bat doc/Makefile doc/requirements.txt
|
||||
include pyproject.toml doc/make.bat doc/Makefile
|
||||
recursive-include doc/source *
|
||||
recursive-include volatility3 *.json
|
||||
recursive-exclude doc/source volatility3.*.rst
|
||||
|
||||
@@ -14,80 +14,70 @@ technical and performance challenges associated with the original
|
||||
code base that became apparent over the previous 10 years. Another benefit
|
||||
of the rewrite is that Volatility 3 could be released under a custom
|
||||
license that was more aligned with the goals of the Volatility community,
|
||||
the Volatility Software License (VSL). See the
|
||||
[LICENSE](https://www.volatilityfoundation.org/license/vsl-v1.0) file for
|
||||
the Volatility Software License (VSL). See the
|
||||
[LICENSE](https://www.volatilityfoundation.org/license/vsl-v1.0) file for
|
||||
more details.
|
||||
|
||||
## Requirements
|
||||
|
||||
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 pip. However, as noted in the Quick Start section below, Volatility 3 does not *need* to be installed prior to using it.
|
||||
|
||||
```shell
|
||||
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.
|
||||
|
||||
```shell
|
||||
pip3 install -r requirements.txt
|
||||
```
|
||||
|
||||
## Downloading Volatility
|
||||
|
||||
The latest stable version of Volatility will always be the stable branch of the GitHub repository. You can get the latest version of the code using the following command:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/volatilityfoundation/volatility3.git
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. Clone the latest version of Volatility from GitHub:
|
||||
1. Install the required dependencies:
|
||||
|
||||
```shell
|
||||
git clone https://github.com/volatilityfoundation/volatility3.git
|
||||
pip install --user -e ".[full]"
|
||||
```
|
||||
|
||||
2. See available options:
|
||||
|
||||
```shell
|
||||
python3 vol.py -h
|
||||
vol -h
|
||||
```
|
||||
|
||||
3. To get more information on a Windows memory sample and to make sure
|
||||
Volatility supports that sample type, run
|
||||
`python3 vol.py -f <imagepath> windows.info`
|
||||
|
||||
Example:
|
||||
3. To get more information on a Windows memory sample and to make sure Volatility supports that sample type, run `vol -f <imagepath> windows.info`:
|
||||
|
||||
```shell
|
||||
python3 vol.py -f /home/user/samples/stuxnet.vmem windows.info
|
||||
vol -f /home/user/samples/stuxnet.vmem windows.info
|
||||
```
|
||||
|
||||
4. Run some other plugins. The `-f` or `--single-location` is not strictly
|
||||
required, but most plugins expect a single sample. Some also
|
||||
require/accept other options. Run `python3 vol.py <plugin> -h`
|
||||
for more information on a particular command.
|
||||
4. Run some other plugins. The `-f` or `--single-location` is not strictly required, but most plugins expect a single sample.
|
||||
Some also require/accept other options. Run `vol <plugin> -h` for more information on a particular command.
|
||||
|
||||
## Installing
|
||||
|
||||
Volatility 3 requires Python 3.8.0 or later and is published on the [PyPi registry](https://pypi.org/project/volatility3).
|
||||
|
||||
```shell
|
||||
pip install volatility3
|
||||
```
|
||||
|
||||
If you want to use the latest development version of Volatility 3 we recommend you manually clone this repository and install an editable version of the project.
|
||||
We recommend you use a virtual environment to keep installed dependencies separate from system packages.
|
||||
|
||||
The latest stable version of Volatility will always be the `stable` branch of the GitHub repository. The default branch is `develop`.
|
||||
|
||||
```shell
|
||||
git clone https://github.com/volatilityfoundation/volatility3.git
|
||||
cd volatility3/
|
||||
python3 -m venv venv && . venv/bin/activate
|
||||
pip install -e ".[dev]"
|
||||
```
|
||||
|
||||
## Symbol Tables
|
||||
|
||||
Symbol table packs for the various operating systems are available for download at:
|
||||
|
||||
<https://downloads.volatilityfoundation.org/volatility3/symbols/windows.zip>
|
||||
<https://downloads.volatilityfoundation.org/volatility3/symbols/mac.zip>
|
||||
<https://downloads.volatilityfoundation.org/volatility3/symbols/linux.zip>
|
||||
<https://downloads.volatilityfoundation.org/volatility3/symbols/windows.zip>
|
||||
|
||||
<https://downloads.volatilityfoundation.org/volatility3/symbols/mac.zip>
|
||||
|
||||
<https://downloads.volatilityfoundation.org/volatility3/symbols/linux.zip>
|
||||
|
||||
The hashes to verify whether any of the symbol pack files have downloaded successfully or have changed can be found at:
|
||||
|
||||
<https://downloads.volatilityfoundation.org/volatility3/symbols/SHA256SUMS>
|
||||
<https://downloads.volatilityfoundation.org/volatility3/symbols/SHA1SUMS>
|
||||
<https://downloads.volatilityfoundation.org/volatility3/symbols/MD5SUMS>
|
||||
<https://downloads.volatilityfoundation.org/volatility3/symbols/SHA256SUMS>
|
||||
|
||||
<https://downloads.volatilityfoundation.org/volatility3/symbols/SHA1SUMS>
|
||||
|
||||
<https://downloads.volatilityfoundation.org/volatility3/symbols/MD5SUMS>
|
||||
|
||||
Symbol tables zip files must be placed, as named, into the `volatility3/symbols` directory (or just the symbols directory next to the executable file).
|
||||
|
||||
@@ -106,7 +96,7 @@ The latest generated copy of the documentation can be found at: <https://volatil
|
||||
|
||||
## Licensing and Copyright
|
||||
|
||||
Copyright (C) 2007-2024 Volatility Foundation
|
||||
Copyright (C) 2007-2025 Volatility Foundation
|
||||
|
||||
All Rights Reserved
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BannerCacheGenerator:
|
||||
|
||||
def __init__(self, path: str, url_prefix: str):
|
||||
self._path = path
|
||||
self._url_prefix = url_prefix
|
||||
@@ -28,10 +27,10 @@ class BannerCacheGenerator:
|
||||
|
||||
def run(self):
|
||||
context = contexts.Context()
|
||||
json_output = {'version': 1}
|
||||
json_output = {"version": 1}
|
||||
|
||||
path = self._path
|
||||
filename = '*'
|
||||
filename = "*"
|
||||
|
||||
for banner_cache in [linux.LinuxBannerCache, mac.MacBannerCache]:
|
||||
sub_path = banner_cache.os
|
||||
@@ -39,37 +38,53 @@ class BannerCacheGenerator:
|
||||
for extension in constants.ISF_EXTENSIONS:
|
||||
# Hopefully these will not be large lists, otherwise this might be slow
|
||||
try:
|
||||
for found in pathlib.Path(path).joinpath(sub_path).resolve().rglob(filename + extension):
|
||||
for found in (
|
||||
pathlib.Path(path)
|
||||
.joinpath(sub_path)
|
||||
.resolve()
|
||||
.rglob(filename + extension)
|
||||
):
|
||||
potentials.append(found.as_uri())
|
||||
except FileNotFoundError:
|
||||
# If there's no linux symbols, don't cry about it
|
||||
pass
|
||||
|
||||
new_banners = banner_cache.read_new_banners(context, 'BannerServer', potentials, banner_cache.symbol_name,
|
||||
banner_cache.os, progress_callback = PrintedProgress())
|
||||
new_banners = banner_cache.read_new_banners(
|
||||
context,
|
||||
"BannerServer",
|
||||
potentials,
|
||||
banner_cache.symbol_name,
|
||||
banner_cache.os,
|
||||
progress_callback=PrintedProgress(),
|
||||
)
|
||||
result_banners = {}
|
||||
for new_banner in new_banners:
|
||||
# Only accept file schemes
|
||||
value = [self.convert_url(url) for url in new_banners[new_banner] if
|
||||
urllib.parse.urlparse(url).scheme == 'file']
|
||||
value = [
|
||||
self.convert_url(url)
|
||||
for url in new_banners[new_banner]
|
||||
if urllib.parse.urlparse(url).scheme == "file"
|
||||
]
|
||||
if value and new_banner:
|
||||
# Convert files into URLs
|
||||
result_banners[str(base64.b64encode(new_banner), 'latin-1')] = value
|
||||
result_banners[str(base64.b64encode(new_banner), "latin-1")] = value
|
||||
|
||||
json_output[banner_cache.os] = result_banners
|
||||
|
||||
output_path = os.path.join(self._path, 'banners.json')
|
||||
with open(output_path, 'w') as fp:
|
||||
output_path = os.path.join(self._path, "banners.json")
|
||||
with open(output_path, "w") as fp:
|
||||
vollog.warning(f"Banners file written to {output_path}")
|
||||
json.dump(json_output, fp)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--path', default = os.path.dirname(__file__))
|
||||
parser.add_argument('--urlprefix', help = 'Web prefix that will eventually serve the ISF files',
|
||||
default = 'http://localhost/symbols')
|
||||
parser.add_argument("--path", default=os.path.dirname(__file__))
|
||||
parser.add_argument(
|
||||
"--urlprefix",
|
||||
help="Web prefix that will eventually serve the ISF files",
|
||||
default="http://localhost/symbols",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
+224
-117
@@ -15,17 +15,17 @@ class VolatilityImage:
|
||||
filepath: str = ""
|
||||
vol2_profile: str = ""
|
||||
vol2_imageinfo_time: float = None
|
||||
vol2_plugin_parameters: Dict[str, List[str]] = field(default_factory = dict)
|
||||
vol3_plugin_parameters: Dict[str, List[str]] = field(default_factory = dict)
|
||||
rekall_plugin_parameters: Dict[str, List[str]] = field(default_factory = dict)
|
||||
vol2_plugin_parameters: Dict[str, List[str]] = field(default_factory=dict)
|
||||
vol3_plugin_parameters: Dict[str, List[str]] = field(default_factory=dict)
|
||||
rekall_plugin_parameters: Dict[str, List[str]] = field(default_factory=dict)
|
||||
|
||||
|
||||
@dataclass
|
||||
class VolatilityPlugin:
|
||||
name: str = ""
|
||||
vol2_plugin_parameters: List[str] = field(default_factory = list)
|
||||
vol3_plugin_parameters: List[str] = field(default_factory = list)
|
||||
rekall_plugin_parameters: List[str] = field(default_factory = list)
|
||||
vol2_plugin_parameters: List[str] = field(default_factory=list)
|
||||
vol3_plugin_parameters: List[str] = field(default_factory=list)
|
||||
rekall_plugin_parameters: List[str] = field(default_factory=list)
|
||||
|
||||
|
||||
class VolatilityTest:
|
||||
@@ -39,32 +39,50 @@ class VolatilityTest:
|
||||
def result_titles(self) -> List[str]:
|
||||
return [self.long_name]
|
||||
|
||||
def create_prerequisites(self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash: str) -> None:
|
||||
def create_prerequisites(
|
||||
self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash: str
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
def create_results(self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash: str) -> List[float]:
|
||||
def create_results(
|
||||
self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash: str
|
||||
) -> List[float]:
|
||||
self.create_prerequisites(plugin, image, image_hash)
|
||||
|
||||
# Volatility 2 Test
|
||||
print(f"[*] Testing {self.short_name} {plugin.name} with image {image.filepath}")
|
||||
print(
|
||||
f"[*] Testing {self.short_name} {plugin.name} with image {image.filepath}"
|
||||
)
|
||||
os.chdir(self.path)
|
||||
cmd = self.plugin_cmd(plugin, image)
|
||||
start_time = time.perf_counter()
|
||||
try:
|
||||
completed = subprocess.run(cmd, cwd = self.path, capture_output = True, timeout = 420)
|
||||
completed = subprocess.run(
|
||||
cmd, cwd=self.path, capture_output=True, timeout=420
|
||||
)
|
||||
except subprocess.TimeoutExpired as excp:
|
||||
completed = excp
|
||||
end_time = time.perf_counter()
|
||||
total_time = end_time - start_time
|
||||
print(f" Tested {self.short_name} {plugin.name} with image {image.filepath}: {total_time}")
|
||||
print(
|
||||
f" Tested {self.short_name} {plugin.name} with image {image.filepath}: {total_time}"
|
||||
)
|
||||
with open(
|
||||
os.path.join(self.output_directory, f'{self.short_name}_{plugin.name}_{image_hash}_stdout'),
|
||||
"wb") as f:
|
||||
os.path.join(
|
||||
self.output_directory,
|
||||
f"{self.short_name}_{plugin.name}_{image_hash}_stdout",
|
||||
),
|
||||
"wb",
|
||||
) as f:
|
||||
f.write(completed.stdout)
|
||||
if completed.stderr:
|
||||
with open(
|
||||
os.path.join(self.output_directory, f'{self.short_name}_{plugin.name}_{image_hash}_stderr'),
|
||||
"wb") as f:
|
||||
os.path.join(
|
||||
self.output_directory,
|
||||
f"{self.short_name}_{plugin.name}_{image_hash}_stderr",
|
||||
),
|
||||
"wb",
|
||||
) as f:
|
||||
f.write(completed.stderr)
|
||||
return [total_time]
|
||||
|
||||
@@ -77,31 +95,57 @@ class Volatility2Test(VolatilityTest):
|
||||
long_name = "Volatility 2"
|
||||
|
||||
def plugin_cmd(self, plugin: VolatilityPlugin, image: VolatilityImage):
|
||||
return ["python2", "-u", "vol.py", "-f", image.filepath, "--profile", image.vol2_profile
|
||||
] + plugin.vol2_plugin_parameters + image.vol2_plugin_parameters.get(plugin.name, [])
|
||||
return (
|
||||
[
|
||||
"python2",
|
||||
"-u",
|
||||
"vol.py",
|
||||
"-f",
|
||||
image.filepath,
|
||||
"--profile",
|
||||
image.vol2_profile,
|
||||
]
|
||||
+ plugin.vol2_plugin_parameters
|
||||
+ image.vol2_plugin_parameters.get(plugin.name, [])
|
||||
)
|
||||
|
||||
def result_titles(self):
|
||||
return [self.long_name, "Imageinfo", f"{self.long_name} + Imageinfo"]
|
||||
|
||||
def create_results(self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash) -> List[float]:
|
||||
def create_results(
|
||||
self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash
|
||||
) -> List[float]:
|
||||
result = super().create_results(plugin, image, image_hash)
|
||||
result += [image.vol2_imageinfo_time, result[0] + image.vol2_imageinfo_time]
|
||||
return result
|
||||
|
||||
def create_prerequisites(self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash):
|
||||
def create_prerequisites(
|
||||
self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash
|
||||
):
|
||||
# Volatility 2 image info
|
||||
if not image.vol2_profile:
|
||||
print(f"[*] Testing {self.short_name} imageinfo with image {image.filepath}")
|
||||
print(
|
||||
f"[*] Testing {self.short_name} imageinfo with image {image.filepath}"
|
||||
)
|
||||
os.chdir(self.path)
|
||||
cmd = ["python2", "-u", "vol.py", "-f", image.filepath, "imageinfo"]
|
||||
start_time = time.perf_counter()
|
||||
vol2_completed = subprocess.run(cmd, cwd = self.path, capture_output = True)
|
||||
vol2_completed = subprocess.run(cmd, cwd=self.path, capture_output=True)
|
||||
end_time = time.perf_counter()
|
||||
image.vol2_imageinfo_time = end_time - start_time
|
||||
print(f" Tested volatility2 imageinfo with image {image.filepath}: {end_time - start_time}")
|
||||
with open(os.path.join(self.output_directory, f'vol2_imageinfo_{image_hash}_stdout'), "wb") as f:
|
||||
print(
|
||||
f" Tested volatility2 imageinfo with image {image.filepath}: {end_time - start_time}"
|
||||
)
|
||||
with open(
|
||||
os.path.join(
|
||||
self.output_directory, f"vol2_imageinfo_{image_hash}_stdout"
|
||||
),
|
||||
"wb",
|
||||
) as f:
|
||||
f.write(vol2_completed.stdout)
|
||||
image.vol2_profile = re.search(b"Suggested Profile\(s\) : ([^,]+)", vol2_completed.stdout)[1]
|
||||
image.vol2_profile = re.search(
|
||||
rb"Suggested Profile\(s\) : ([^,]+)", vol2_completed.stdout
|
||||
)[1]
|
||||
|
||||
|
||||
class RekallTest(VolatilityTest):
|
||||
@@ -113,11 +157,16 @@ class RekallTest(VolatilityTest):
|
||||
plugin.rekall_plugin_parameters = plugin.vol2_plugin_parameters
|
||||
if not image.rekall_plugin_parameters:
|
||||
image.rekall_plugin_parameters = image.vol2_plugin_parameters
|
||||
return ["rekall", "-f", image.filepath] + plugin.rekall_plugin_parameters + image.rekall_plugin_parameters.get(
|
||||
plugin.name, [])
|
||||
return (
|
||||
["rekall", "-f", image.filepath]
|
||||
+ plugin.rekall_plugin_parameters
|
||||
+ image.rekall_plugin_parameters.get(plugin.name, [])
|
||||
)
|
||||
|
||||
def create_prerequisites(self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash: str) -> None:
|
||||
shutil.rmtree('/home/mike/.rekall_cache/sessions')
|
||||
def create_prerequisites(
|
||||
self, plugin: VolatilityPlugin, image: VolatilityImage, image_hash: str
|
||||
) -> None:
|
||||
shutil.rmtree("/home/mike/.rekall_cache/sessions")
|
||||
|
||||
|
||||
class Volatility3Test(VolatilityTest):
|
||||
@@ -125,14 +174,18 @@ class Volatility3Test(VolatilityTest):
|
||||
long_name = "Volatility 3"
|
||||
|
||||
def plugin_cmd(self, plugin: VolatilityPlugin, image: VolatilityImage) -> List[str]:
|
||||
return [
|
||||
"python",
|
||||
"-u",
|
||||
"vol.py",
|
||||
"-q",
|
||||
"-f",
|
||||
image.filepath,
|
||||
] + plugin.vol3_plugin_parameters + image.vol3_plugin_parameters.get(plugin.name, [])
|
||||
return (
|
||||
[
|
||||
"python",
|
||||
"-u",
|
||||
"vol.py",
|
||||
"-q",
|
||||
"-f",
|
||||
image.filepath,
|
||||
]
|
||||
+ plugin.vol3_plugin_parameters
|
||||
+ image.vol3_plugin_parameters.get(plugin.name, [])
|
||||
)
|
||||
|
||||
|
||||
class Volatility3PyPyTest(VolatilityTest):
|
||||
@@ -140,26 +193,31 @@ class Volatility3PyPyTest(VolatilityTest):
|
||||
long_name = "Volatility 3 (PyPy)"
|
||||
|
||||
def plugin_cmd(self, plugin: VolatilityPlugin, image: VolatilityImage) -> List[str]:
|
||||
return [
|
||||
"pypy3",
|
||||
"-u",
|
||||
"vol.py",
|
||||
"-q",
|
||||
"-f",
|
||||
image.filepath,
|
||||
] + plugin.vol3_plugin_parameters + image.vol3_plugin_parameters.get(plugin.name, [])
|
||||
return (
|
||||
[
|
||||
"pypy3",
|
||||
"-u",
|
||||
"vol.py",
|
||||
"-q",
|
||||
"-f",
|
||||
image.filepath,
|
||||
]
|
||||
+ plugin.vol3_plugin_parameters
|
||||
+ image.vol3_plugin_parameters.get(plugin.name, [])
|
||||
)
|
||||
|
||||
|
||||
class VolatilityTester:
|
||||
|
||||
def __init__(self,
|
||||
images: List[VolatilityImage],
|
||||
plugins: List[VolatilityPlugin],
|
||||
frameworks: List[str],
|
||||
output_dir: str,
|
||||
vol2_path: str = None,
|
||||
vol3_path: str = None,
|
||||
rekall_path = None):
|
||||
def __init__(
|
||||
self,
|
||||
images: List[VolatilityImage],
|
||||
plugins: List[VolatilityPlugin],
|
||||
frameworks: List[str],
|
||||
output_dir: str,
|
||||
vol2_path: str = None,
|
||||
vol3_path: str = None,
|
||||
rekall_path=None,
|
||||
):
|
||||
self.images = images
|
||||
self.plugins = plugins
|
||||
if not vol2_path:
|
||||
@@ -172,7 +230,7 @@ class VolatilityTester:
|
||||
Volatility3Test(vol3_path, output_dir),
|
||||
Volatility3PyPyTest(vol3_path, output_dir),
|
||||
Volatility2Test(vol2_path, output_dir),
|
||||
RekallTest(rekall_path, output_dir)
|
||||
RekallTest(rekall_path, output_dir),
|
||||
]
|
||||
self.tests = [x for x in available_tests if x.short_name.lower() in frameworks]
|
||||
self.csv_writer = None
|
||||
@@ -183,7 +241,7 @@ class VolatilityTester:
|
||||
print(f"[?] Frameworks: {[x.long_name for x in self.tests]}")
|
||||
|
||||
def run_tests(self):
|
||||
with open("volatility-timings.csv", 'w') as csvfile:
|
||||
with open("volatility-timings.csv", "w") as csvfile:
|
||||
self.csv_writer = csv.writer(csvfile)
|
||||
titles = ["Image Hash", "Image Path", "Plugin Name"]
|
||||
for test in self.tests:
|
||||
@@ -203,72 +261,121 @@ class VolatilityTester:
|
||||
self.csv_writer.writerow([image_hash, image.filepath, plugin.name] + results)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
plugins = [
|
||||
VolatilityPlugin(name = "pslist",
|
||||
vol2_plugin_parameters = ["pslist"],
|
||||
vol3_plugin_parameters = ["windows.pslist"]),
|
||||
VolatilityPlugin(name = "psscan",
|
||||
vol2_plugin_parameters = ["psscan"],
|
||||
vol3_plugin_parameters = ["windows.psscan"],
|
||||
rekall_plugin_parameters = ["psscan", "--scan_kernel"]),
|
||||
VolatilityPlugin(name = "driverscan",
|
||||
vol2_plugin_parameters = ["driverscan"],
|
||||
vol3_plugin_parameters = ["windows.driverscan"],
|
||||
rekall_plugin_parameters = ["driverscan", "--scan_kernel"]),
|
||||
VolatilityPlugin(name = "handles",
|
||||
vol2_plugin_parameters = ["handles"],
|
||||
vol3_plugin_parameters = ["windows.handles"]),
|
||||
VolatilityPlugin(name = "modules",
|
||||
vol2_plugin_parameters = ["modules"],
|
||||
vol3_plugin_parameters = ["windows.modules"]),
|
||||
VolatilityPlugin(name = "hivelist",
|
||||
vol2_plugin_parameters = ["hivelist"],
|
||||
vol3_plugin_parameters = ["registry.hivelist"],
|
||||
rekall_plugin_parameters = ["hives"]),
|
||||
VolatilityPlugin(name = "vadinfo",
|
||||
vol2_plugin_parameters = ["vadinfo"],
|
||||
vol3_plugin_parameters = ["windows.vadinfo"],
|
||||
rekall_plugin_parameters = ["vad"]),
|
||||
VolatilityPlugin(name = "modscan",
|
||||
vol2_plugin_parameters = ["modscan"],
|
||||
vol3_plugin_parameters = ["windows.modscan"],
|
||||
rekall_plugin_parameters = ["modscan", "--scan_kernel"]),
|
||||
VolatilityPlugin(name = "svcscan",
|
||||
vol2_plugin_parameters = ["svcscan"],
|
||||
vol3_plugin_parameters = ["windows.svcscan"],
|
||||
rekall_plugin_parameters = ["svcscan"]),
|
||||
VolatilityPlugin(name = "ssdt", vol2_plugin_parameters = ["ssdt"], vol3_plugin_parameters = ["windows.ssdt"]),
|
||||
VolatilityPlugin(name = "printkey",
|
||||
vol2_plugin_parameters = ["printkey", "-K", "Classes"],
|
||||
vol3_plugin_parameters = ["registry.printkey", "--key", "Classes"],
|
||||
rekall_plugin_parameters = ["printkey", "--key", "Classes"])
|
||||
VolatilityPlugin(
|
||||
name="pslist",
|
||||
vol2_plugin_parameters=["pslist"],
|
||||
vol3_plugin_parameters=["windows.pslist"],
|
||||
),
|
||||
VolatilityPlugin(
|
||||
name="psscan",
|
||||
vol2_plugin_parameters=["psscan"],
|
||||
vol3_plugin_parameters=["windows.psscan"],
|
||||
rekall_plugin_parameters=["psscan", "--scan_kernel"],
|
||||
),
|
||||
VolatilityPlugin(
|
||||
name="driverscan",
|
||||
vol2_plugin_parameters=["driverscan"],
|
||||
vol3_plugin_parameters=["windows.driverscan"],
|
||||
rekall_plugin_parameters=["driverscan", "--scan_kernel"],
|
||||
),
|
||||
VolatilityPlugin(
|
||||
name="handles",
|
||||
vol2_plugin_parameters=["handles"],
|
||||
vol3_plugin_parameters=["windows.handles"],
|
||||
),
|
||||
VolatilityPlugin(
|
||||
name="modules",
|
||||
vol2_plugin_parameters=["modules"],
|
||||
vol3_plugin_parameters=["windows.modules"],
|
||||
),
|
||||
VolatilityPlugin(
|
||||
name="hivelist",
|
||||
vol2_plugin_parameters=["hivelist"],
|
||||
vol3_plugin_parameters=["registry.hivelist"],
|
||||
rekall_plugin_parameters=["hives"],
|
||||
),
|
||||
VolatilityPlugin(
|
||||
name="vadinfo",
|
||||
vol2_plugin_parameters=["vadinfo"],
|
||||
vol3_plugin_parameters=["windows.vadinfo"],
|
||||
rekall_plugin_parameters=["vad"],
|
||||
),
|
||||
VolatilityPlugin(
|
||||
name="modscan",
|
||||
vol2_plugin_parameters=["modscan"],
|
||||
vol3_plugin_parameters=["windows.modscan"],
|
||||
rekall_plugin_parameters=["modscan", "--scan_kernel"],
|
||||
),
|
||||
VolatilityPlugin(
|
||||
name="svcscan",
|
||||
vol2_plugin_parameters=["svcscan"],
|
||||
vol3_plugin_parameters=["windows.svcscan"],
|
||||
rekall_plugin_parameters=["svcscan"],
|
||||
),
|
||||
VolatilityPlugin(
|
||||
name="ssdt",
|
||||
vol2_plugin_parameters=["ssdt"],
|
||||
vol3_plugin_parameters=["windows.ssdt"],
|
||||
),
|
||||
VolatilityPlugin(
|
||||
name="printkey",
|
||||
vol2_plugin_parameters=["printkey", "-K", "Classes"],
|
||||
vol3_plugin_parameters=["registry.printkey", "--key", "Classes"],
|
||||
rekall_plugin_parameters=["printkey", "--key", "Classes"],
|
||||
),
|
||||
]
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--output-dir", type = str, default = os.getcwd(), help = "Directory to store all results")
|
||||
parser.add_argument("--vol3path",
|
||||
type = str,
|
||||
default = os.path.join(os.getcwd(), 'volatility3'),
|
||||
help = "Path ot the volatility 3 directory")
|
||||
parser.add_argument("--vol2path",
|
||||
type = str,
|
||||
default = os.path.join(os.getcwd(), 'volatility'),
|
||||
help = "Path to the volatility 2 directory")
|
||||
parser.add_argument("--rekallpath",
|
||||
type = str,
|
||||
default = os.path.join(os.getcwd(), 'rekall'),
|
||||
help = "Path to the rekall directory")
|
||||
parser.add_argument("--frameworks",
|
||||
nargs = "+",
|
||||
type = str,
|
||||
choices = [x.short_name.lower() for x in VolatilityTest.__subclasses__()],
|
||||
default = [x.short_name.lower() for x in VolatilityTest.__subclasses__()],
|
||||
help = "A comma separated list of frameworks to test")
|
||||
parser.add_argument('images', metavar = 'IMAGE', type = str, nargs = '+', help = 'The list of images to compare')
|
||||
parser.add_argument(
|
||||
"--output-dir",
|
||||
type=str,
|
||||
default=os.getcwd(),
|
||||
help="Directory to store all results",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--vol3path",
|
||||
type=str,
|
||||
default=os.path.join(os.getcwd(), "volatility3"),
|
||||
help="Path to the volatility 3 directory",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--vol2path",
|
||||
type=str,
|
||||
default=os.path.join(os.getcwd(), "volatility"),
|
||||
help="Path to the volatility 2 directory",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--rekallpath",
|
||||
type=str,
|
||||
default=os.path.join(os.getcwd(), "rekall"),
|
||||
help="Path to the rekall directory",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--frameworks",
|
||||
nargs="+",
|
||||
type=str,
|
||||
choices=[x.short_name.lower() for x in VolatilityTest.__subclasses__()],
|
||||
default=[x.short_name.lower() for x in VolatilityTest.__subclasses__()],
|
||||
help="A comma separated list of frameworks to test",
|
||||
)
|
||||
parser.add_argument(
|
||||
"images",
|
||||
metavar="IMAGE",
|
||||
type=str,
|
||||
nargs="+",
|
||||
help="The list of images to compare",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
vt = VolatilityTester([VolatilityImage(filepath = x) for x in args.images], plugins,
|
||||
[x.lower() for x in args.frameworks], args.output_dir, args.vol2path, args.vol3path,
|
||||
args.rekallpath)
|
||||
vt = VolatilityTester(
|
||||
[VolatilityImage(filepath=x) for x in args.images],
|
||||
plugins,
|
||||
[x.lower() for x in args.frameworks],
|
||||
args.output_dir,
|
||||
args.vol2path,
|
||||
args.vol3path,
|
||||
args.rekallpath,
|
||||
)
|
||||
vt.run_tests()
|
||||
|
||||
@@ -7,11 +7,12 @@
|
||||
# Cleaned up C version (as the basis for my code) here, thanks to Pepijn Bruienne / @bruienne
|
||||
# https://gist.github.com/bruienne/029494bbcfb358098b41
|
||||
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
|
||||
|
||||
def seekread(f, offset = None, length = 0, relative = True):
|
||||
def seekread(f, offset=None, length=0, relative=True):
|
||||
if offset is not None:
|
||||
# offset provided, let's seek
|
||||
f.seek(offset, [0, 1, 2][relative])
|
||||
@@ -22,55 +23,57 @@ def seekread(f, offset = None, length = 0, relative = True):
|
||||
|
||||
def parse_pbzx(pbzx_path):
|
||||
section = 0
|
||||
xar_out_path = '%s.part%02d.cpio.xz' % (pbzx_path, section)
|
||||
with open(pbzx_path, 'rb') as f:
|
||||
xar_out_path = f"{pbzx_path}.part{section:02d}.cpio.xz"
|
||||
with open(pbzx_path, "rb") as f:
|
||||
# pbzx = f.read()
|
||||
# f.close()
|
||||
magic = seekread(f, length = 4)
|
||||
if magic != 'pbzx':
|
||||
magic = seekread(f, length=4)
|
||||
if magic != "pbzx":
|
||||
raise RuntimeError("Error: Not a pbzx file")
|
||||
# Read 8 bytes for initial flags
|
||||
flags = seekread(f, length = 8)
|
||||
flags = seekread(f, length=8)
|
||||
# Interpret the flags as a 64-bit big-endian unsigned int
|
||||
flags = struct.unpack('>Q', flags)[0]
|
||||
flags = struct.unpack(">Q", flags)[0]
|
||||
while flags & (1 << 24):
|
||||
with open(xar_out_path, 'wb') as xar_f:
|
||||
with open(xar_out_path, "wb") as xar_f:
|
||||
xar_f.seek(0, os.SEEK_END)
|
||||
# Read in more flags
|
||||
flags = seekread(f, length = 8)
|
||||
flags = struct.unpack('>Q', flags)[0]
|
||||
flags = seekread(f, length=8)
|
||||
flags = struct.unpack(">Q", flags)[0]
|
||||
# Read in length
|
||||
f_length = seekread(f, length = 8)
|
||||
f_length = struct.unpack('>Q', f_length)[0]
|
||||
xzmagic = seekread(f, length = 6)
|
||||
if xzmagic != '\xfd7zXZ\x00':
|
||||
f_length = seekread(f, length=8)
|
||||
f_length = struct.unpack(">Q", f_length)[0]
|
||||
xzmagic = seekread(f, length=6)
|
||||
if xzmagic != "\xfd7zXZ\x00":
|
||||
# This isn't xz content, this is actually _raw decompressed cpio_ chunk of 16MB in size...
|
||||
# Let's back up ...
|
||||
seekread(f, offset = -6, length = 0)
|
||||
seekread(f, offset=-6, length=0)
|
||||
# ... and split it out ...
|
||||
f_content = seekread(f, length = f_length)
|
||||
f_content = seekread(f, length=f_length)
|
||||
section += 1
|
||||
decomp_out = '%s.part%02d.cpio' % (pbzx_path, section)
|
||||
with open(decomp_out, 'wb') as g:
|
||||
decomp_out = f"{pbzx_path}.part{section:02d}.cpio"
|
||||
with open(decomp_out, "wb") as g:
|
||||
g.write(f_content)
|
||||
# Now to start the next section, which should hopefully be .xz (we'll just assume it is ...)
|
||||
section += 1
|
||||
xar_out_path = '%s.part%02d.cpio.xz' % (pbzx_path, section)
|
||||
xar_out_path = f"{pbzx_path}.part{section:02d}.cpio.xz"
|
||||
else:
|
||||
f_length -= 6
|
||||
# This part needs buffering
|
||||
f_content = seekread(f, length = f_length)
|
||||
tail = seekread(f, offset = -2, length = 2)
|
||||
f_content = seekread(f, length=f_length)
|
||||
tail = seekread(f, offset=-2, length=2)
|
||||
xar_f.write(xzmagic)
|
||||
xar_f.write(f_content)
|
||||
if tail != 'YZ':
|
||||
if tail != "YZ":
|
||||
raise RuntimeError("Error: Footer is not xar file footer")
|
||||
|
||||
|
||||
def main():
|
||||
parse_pbzx(sys.argv[1])
|
||||
print("Now xz decompress the .xz chunks, then 'cat' them all together in order into a single new.cpio file")
|
||||
print(
|
||||
"Now xz decompress the .xz chunks, then 'cat' them all together in order into a single new.cpio file"
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
+122
-76
@@ -13,31 +13,30 @@ import pdbparse.undecorate
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(1)
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
console = logging.StreamHandler()
|
||||
console.setLevel(1)
|
||||
formatter = logging.Formatter('%(levelname)-8s %(name)-12s: %(message)s')
|
||||
formatter = logging.Formatter("%(levelname)-8s %(name)-12s: %(message)s")
|
||||
console.setFormatter(formatter)
|
||||
logger.addHandler(console)
|
||||
|
||||
|
||||
class PDBRetreiver:
|
||||
|
||||
def retreive_pdb(self, guid: str, file_name: str) -> Optional[str]:
|
||||
logger.info("Download PDB file...")
|
||||
file_name = ".".join(file_name.split(".")[:-1] + ['pdb'])
|
||||
for sym_url in ['http://msdl.microsoft.com/download/symbols']:
|
||||
file_name = ".".join(file_name.split(".")[:-1] + ["pdb"])
|
||||
for sym_url in ["http://msdl.microsoft.com/download/symbols"]:
|
||||
url = sym_url + f"/{file_name}/{guid}/"
|
||||
|
||||
result = None
|
||||
for suffix in [file_name[:-1] + '_', file_name]:
|
||||
for suffix in [file_name[:-1] + "_", file_name]:
|
||||
try:
|
||||
logger.debug(f"Attempting to retrieve {url + suffix}")
|
||||
logger.debug("Attempting to retrieve %s", url + suffix)
|
||||
result, _ = request.urlretrieve(url + suffix)
|
||||
except request.HTTPError as excp:
|
||||
logger.debug(f"Failed with {excp}")
|
||||
logger.debug("Failed with %s", excp)
|
||||
if result:
|
||||
logger.debug(f"Successfully written to {result}")
|
||||
logger.debug("Successfully written to %s", result)
|
||||
break
|
||||
return result
|
||||
|
||||
@@ -69,7 +68,7 @@ class PDBConvertor:
|
||||
"float": "float",
|
||||
"double": "float",
|
||||
"long double": "float",
|
||||
"void": "void"
|
||||
"void": "void",
|
||||
}
|
||||
|
||||
base_type_size = {
|
||||
@@ -122,13 +121,18 @@ class PDBConvertor:
|
||||
self._seen_ctypes.add(ctype)
|
||||
return self.ctype[ctype]
|
||||
|
||||
def lookup_ctype_pointers(self, ctype_pointer: str) -> Dict[str, Union[str, Dict[str, str]]]:
|
||||
base_type = ctype_pointer.replace('32P', '').replace('64P', '')
|
||||
def lookup_ctype_pointers(
|
||||
self, ctype_pointer: str
|
||||
) -> Dict[str, Union[str, Dict[str, str]]]:
|
||||
base_type = ctype_pointer.replace("32P", "").replace("64P", "")
|
||||
if base_type == ctype_pointer:
|
||||
# We raise a KeyError, because we've been asked about a type that isn't a pointer
|
||||
raise KeyError
|
||||
self._seen_ctypes.add(base_type)
|
||||
return {"kind": "pointer", "subtype": {"kind": "base", "name": self.ctype[base_type]}}
|
||||
return {
|
||||
"kind": "pointer",
|
||||
"subtype": {"kind": "base", "name": self.ctype[base_type]},
|
||||
}
|
||||
|
||||
def read_pdb(self) -> Dict:
|
||||
"""Reads in the PDB file and forms essentially a python dictionary of necessary data"""
|
||||
@@ -137,32 +141,31 @@ class PDBConvertor:
|
||||
"enums": self.read_enums(),
|
||||
"metadata": self.generate_metadata(),
|
||||
"symbols": self.read_symbols(),
|
||||
"base_types": self.read_basetypes()
|
||||
"base_types": self.read_basetypes(),
|
||||
}
|
||||
return output
|
||||
|
||||
def generate_metadata(self) -> Dict[str, Any]:
|
||||
"""Generates the metadata necessary for this object"""
|
||||
dbg = self._pdb.STREAM_DBI
|
||||
last_bytes = str(binascii.hexlify(self._pdb.STREAM_PDB.GUID.Data4), 'ascii')[-16:]
|
||||
guidstr = u'{:08x}{:04x}{:04x}{}'.format(self._pdb.STREAM_PDB.GUID.Data1, self._pdb.STREAM_PDB.GUID.Data2,
|
||||
self._pdb.STREAM_PDB.GUID.Data3, last_bytes)
|
||||
last_bytes = str(binascii.hexlify(self._pdb.STREAM_PDB.GUID.Data4), "ascii")[
|
||||
-16:
|
||||
]
|
||||
guidstr = f"{self._pdb.STREAM_PDB.GUID.Data1:08x}{self._pdb.STREAM_PDB.GUID.Data2:04x}{self._pdb.STREAM_PDB.GUID.Data3:04x}{last_bytes}"
|
||||
pdb_data = {
|
||||
"GUID": guidstr.upper(),
|
||||
"age": self._pdb.STREAM_PDB.Age,
|
||||
"database": "ntkrnlmp.pdb",
|
||||
"machine_type": int(dbg.machine)
|
||||
"machine_type": int(dbg.machine),
|
||||
}
|
||||
result = {
|
||||
"format": "6.0.0",
|
||||
"producer": {
|
||||
"datetime": datetime.datetime.now().isoformat(),
|
||||
"name": "pdbconv",
|
||||
"version": "0.1.0"
|
||||
"version": "0.1.0",
|
||||
},
|
||||
"windows": {
|
||||
"pdb": pdb_data
|
||||
}
|
||||
"windows": {"pdb": pdb_data},
|
||||
}
|
||||
return result
|
||||
|
||||
@@ -173,16 +176,21 @@ class PDBConvertor:
|
||||
stream = self._pdb.STREAM_TPI
|
||||
for type_index in stream.types:
|
||||
user_type = stream.types[type_index]
|
||||
if (user_type.leaf_type == "LF_ENUM" and not user_type.prop.fwdref):
|
||||
if user_type.leaf_type == "LF_ENUM" and not user_type.prop.fwdref:
|
||||
output.update(self._format_enum(user_type))
|
||||
return output
|
||||
|
||||
def _format_enum(self, user_enum):
|
||||
output = {
|
||||
user_enum.name: {
|
||||
'base': self.lookup_ctype(user_enum.utype),
|
||||
'size': self._determine_size(user_enum.utype),
|
||||
'constants': dict([(enum.name, enum.enum_value) for enum in user_enum.fieldlist.substructs])
|
||||
"base": self.lookup_ctype(user_enum.utype),
|
||||
"size": self._determine_size(user_enum.utype),
|
||||
"constants": dict(
|
||||
[
|
||||
(enum.name, enum.enum_value)
|
||||
for enum in user_enum.fieldlist.substructs
|
||||
]
|
||||
),
|
||||
}
|
||||
}
|
||||
return output
|
||||
@@ -195,14 +203,14 @@ class PDBConvertor:
|
||||
try:
|
||||
sects = self._pdb.STREAM_SECT_HDR_ORIG.sections
|
||||
omap = self._pdb.STREAM_OMAP_FROM_SRC
|
||||
except AttributeError as e:
|
||||
except AttributeError:
|
||||
# In this case there is no OMAP, so we use the given section
|
||||
# headers and use the identity function for omap.remap
|
||||
sects = self._pdb.STREAM_SECT_HDR.sections
|
||||
omap = None
|
||||
|
||||
for sym in self._pdb.STREAM_GSYM.globals:
|
||||
if not hasattr(sym, 'offset'):
|
||||
if not hasattr(sym, "offset"):
|
||||
continue
|
||||
try:
|
||||
virt_base = sects[sym.segment - 1].VirtualAddress
|
||||
@@ -223,9 +231,9 @@ class PDBConvertor:
|
||||
stream = self._pdb.STREAM_TPI
|
||||
for type_index in stream.types:
|
||||
user_type = stream.types[type_index]
|
||||
if (user_type.leaf_type == "LF_STRUCTURE" and not user_type.prop.fwdref):
|
||||
if user_type.leaf_type == "LF_STRUCTURE" and not user_type.prop.fwdref:
|
||||
output.update(self._format_usertype(user_type, "struct"))
|
||||
elif (user_type.leaf_type == "LF_UNION" and not user_type.prop.fwdref):
|
||||
elif user_type.leaf_type == "LF_UNION" and not user_type.prop.fwdref:
|
||||
output.update(self._format_usertype(user_type, "union"))
|
||||
return output
|
||||
|
||||
@@ -233,16 +241,22 @@ class PDBConvertor:
|
||||
"""Produces a single usertype"""
|
||||
fields: Dict[str, Dict[str, Any]] = {}
|
||||
[fields.update(self._format_field(s)) for s in usertype.fieldlist.substructs]
|
||||
return {usertype.name: {'fields': fields, 'kind': kind, 'size': usertype.size}}
|
||||
return {usertype.name: {"fields": fields, "kind": kind, "size": usertype.size}}
|
||||
|
||||
def _format_field(self, field) -> Dict[str, Dict[str, Any]]:
|
||||
return {field.name: {"offset": field.offset, "type": self._format_kind(field.index)}}
|
||||
return {
|
||||
field.name: {"offset": field.offset, "type": self._format_kind(field.index)}
|
||||
}
|
||||
|
||||
def _determine_size(self, field):
|
||||
output = None
|
||||
if isinstance(field, str):
|
||||
output = self.base_type_size[field]
|
||||
elif (field.leaf_type == "LF_STRUCTURE" or field.leaf_type == "LF_ARRAY" or field.leaf_type == "LF_UNION"):
|
||||
elif (
|
||||
field.leaf_type == "LF_STRUCTURE"
|
||||
or field.leaf_type == "LF_ARRAY"
|
||||
or field.leaf_type == "LF_UNION"
|
||||
):
|
||||
output = field.size
|
||||
elif field.leaf_type == "LF_POINTER":
|
||||
output = self.base_type_size[field.ptr_attr.type]
|
||||
@@ -256,6 +270,7 @@ class PDBConvertor:
|
||||
output = self._determine_size(field.index)
|
||||
if output is None:
|
||||
import pdb
|
||||
|
||||
pdb.set_trace()
|
||||
raise ValueError(f"Unknown size for field: {field.name}")
|
||||
return output
|
||||
@@ -267,36 +282,37 @@ class PDBConvertor:
|
||||
output = self.lookup_ctype_pointers(kind)
|
||||
except KeyError:
|
||||
try:
|
||||
output = {'kind': 'base', 'name': self.lookup_ctype(kind)}
|
||||
output = {"kind": "base", "name": self.lookup_ctype(kind)}
|
||||
except KeyError:
|
||||
output = {'kind': 'base', 'name': kind}
|
||||
elif kind.leaf_type == 'LF_MODIFIER':
|
||||
output = {"kind": "base", "name": kind}
|
||||
elif kind.leaf_type == "LF_MODIFIER":
|
||||
output = self._format_kind(kind.modified_type)
|
||||
elif kind.leaf_type == 'LF_STRUCTURE':
|
||||
output = {'kind': 'struct', 'name': kind.name}
|
||||
elif kind.leaf_type == 'LF_UNION':
|
||||
output = {'kind': 'union', 'name': kind.name}
|
||||
elif kind.leaf_type == 'LF_BITFIELD':
|
||||
elif kind.leaf_type == "LF_STRUCTURE":
|
||||
output = {"kind": "struct", "name": kind.name}
|
||||
elif kind.leaf_type == "LF_UNION":
|
||||
output = {"kind": "union", "name": kind.name}
|
||||
elif kind.leaf_type == "LF_BITFIELD":
|
||||
output = {
|
||||
'kind': 'bitfield',
|
||||
'type': self._format_kind(kind.base_type),
|
||||
'bit_length': kind.length,
|
||||
'bit_position': kind.position
|
||||
"kind": "bitfield",
|
||||
"type": self._format_kind(kind.base_type),
|
||||
"bit_length": kind.length,
|
||||
"bit_position": kind.position,
|
||||
}
|
||||
elif kind.leaf_type == 'LF_POINTER':
|
||||
output = {'kind': 'pointer', 'subtype': self._format_kind(kind.utype)}
|
||||
elif kind.leaf_type == 'LF_ARRAY':
|
||||
elif kind.leaf_type == "LF_POINTER":
|
||||
output = {"kind": "pointer", "subtype": self._format_kind(kind.utype)}
|
||||
elif kind.leaf_type == "LF_ARRAY":
|
||||
output = {
|
||||
'kind': 'array',
|
||||
'count': kind.size // self._determine_size(kind.element_type),
|
||||
'subtype': self._format_kind(kind.element_type)
|
||||
"kind": "array",
|
||||
"count": kind.size // self._determine_size(kind.element_type),
|
||||
"subtype": self._format_kind(kind.element_type),
|
||||
}
|
||||
elif kind.leaf_type == 'LF_ENUM':
|
||||
output = {'kind': 'enum', 'name': kind.name}
|
||||
elif kind.leaf_type == 'LF_PROCEDURE':
|
||||
output = {'kind': "function"}
|
||||
elif kind.leaf_type == "LF_ENUM":
|
||||
output = {"kind": "enum", "name": kind.name}
|
||||
elif kind.leaf_type == "LF_PROCEDURE":
|
||||
output = {"kind": "function"}
|
||||
else:
|
||||
import pdb
|
||||
|
||||
pdb.set_trace()
|
||||
return output
|
||||
|
||||
@@ -306,40 +322,70 @@ class PDBConvertor:
|
||||
if "64" in self._pdb.STREAM_DBI.machine:
|
||||
ptr_size = 8
|
||||
|
||||
output = {"pointer": {"endian": "little", "kind": "int", "signed": False, "size": ptr_size}}
|
||||
output = {
|
||||
"pointer": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": False,
|
||||
"size": ptr_size,
|
||||
}
|
||||
}
|
||||
for index in self._seen_ctypes:
|
||||
output[self.ctype[index]] = {
|
||||
"endian": "little",
|
||||
"kind": self.ctype_python_types.get(self.ctype[index], "int"),
|
||||
"signed": False if "_U" in index else True,
|
||||
"size": self.base_type_size[index]
|
||||
"size": self.base_type_size[index],
|
||||
}
|
||||
return output
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description = "Convertor for PDB files to Volatility 3 Intermediate Symbol Format")
|
||||
parser.add_argument("-o", "--output", metavar = "OUTPUT", help = "Filename for data output", required = True)
|
||||
file_group = parser.add_argument_group("file", description = "File-based conversion of PDB to ISF")
|
||||
file_group.add_argument("-f", "--file", metavar = "FILE", help = "PDB file to translate to ISF")
|
||||
data_group = parser.add_argument_group("data", description = "Convert based on a GUID and filename pattern")
|
||||
data_group.add_argument("-p", "--pattern", metavar = "PATTERN", help = "Filename pattern to recover PDB file")
|
||||
data_group.add_argument("-g",
|
||||
"--guid",
|
||||
metavar = "GUID",
|
||||
help = "GUID + Age string for the required PDB file",
|
||||
default = None)
|
||||
data_group.add_argument("-k",
|
||||
"--keep",
|
||||
action = "store_true",
|
||||
default = False,
|
||||
help = "Keep the downloaded PDB file")
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Convertor for PDB files to Volatility 3 Intermediate Symbol Format"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-o",
|
||||
"--output",
|
||||
metavar="OUTPUT",
|
||||
help="Filename for data output",
|
||||
required=True,
|
||||
)
|
||||
file_group = parser.add_argument_group(
|
||||
"file", description="File-based conversion of PDB to ISF"
|
||||
)
|
||||
file_group.add_argument(
|
||||
"-f", "--file", metavar="FILE", help="PDB file to translate to ISF"
|
||||
)
|
||||
data_group = parser.add_argument_group(
|
||||
"data", description="Convert based on a GUID and filename pattern"
|
||||
)
|
||||
data_group.add_argument(
|
||||
"-p",
|
||||
"--pattern",
|
||||
metavar="PATTERN",
|
||||
help="Filename pattern to recover PDB file",
|
||||
)
|
||||
data_group.add_argument(
|
||||
"-g",
|
||||
"--guid",
|
||||
metavar="GUID",
|
||||
help="GUID + Age string for the required PDB file",
|
||||
default=None,
|
||||
)
|
||||
data_group.add_argument(
|
||||
"-k",
|
||||
"--keep",
|
||||
action="store_true",
|
||||
default=False,
|
||||
help="Keep the downloaded PDB file",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
delfile = False
|
||||
filename = None
|
||||
if args.guid is not None and args.pattern is not None:
|
||||
filename = PDBRetreiver().retreive_pdb(guid = args.guid, file_name = args.pattern)
|
||||
filename = PDBRetreiver().retreive_pdb(guid=args.guid, file_name=args.pattern)
|
||||
delfile = True
|
||||
elif args.file:
|
||||
filename = args.file
|
||||
@@ -352,7 +398,7 @@ if __name__ == '__main__':
|
||||
convertor = PDBConvertor(filename)
|
||||
|
||||
with open(args.output, "w") as f:
|
||||
json.dump(convertor.read_pdb(), f, indent = 2, sort_keys = True)
|
||||
json.dump(convertor.read_pdb(), f, indent=2, sort_keys=True)
|
||||
|
||||
if args.keep:
|
||||
print(f"Temporary PDB file: {filename}")
|
||||
|
||||
@@ -1,34 +1,33 @@
|
||||
import argparse
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
|
||||
# TODO: Rather nasty hack, when volatility's actually installed this would be unnecessary
|
||||
sys.path += ".."
|
||||
|
||||
import logging
|
||||
|
||||
console = logging.StreamHandler()
|
||||
console.setLevel(logging.DEBUG)
|
||||
formatter = logging.Formatter('%(levelname)-8s %(name)-12s: %(message)s')
|
||||
formatter = logging.Formatter("%(levelname)-8s %(name)-12s: %(message)s")
|
||||
console.setFormatter(formatter)
|
||||
|
||||
logger = logging.getLogger("")
|
||||
logger.addHandler(console)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
from volatility3 import schemas
|
||||
from volatility3 import schemas # noqa: E402
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser("Validates ")
|
||||
parser.add_argument("-s", "--schema", dest = "schema", default = None)
|
||||
parser.add_argument("filenames", metavar = "FILE", nargs = '+')
|
||||
parser.add_argument("-s", "--schema", dest="schema", default=None)
|
||||
parser.add_argument("filenames", metavar="FILE", nargs="+")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
schema = None
|
||||
if args.schema:
|
||||
with open(os.path.abspath(args.schema), 'r') as s:
|
||||
with open(os.path.abspath(args.schema)) as s:
|
||||
schema = json.load(s)
|
||||
|
||||
failures = []
|
||||
@@ -36,7 +35,7 @@ if __name__ == '__main__':
|
||||
try:
|
||||
if os.path.exists(filename):
|
||||
print(f"[?] Validating file: {filename}")
|
||||
with open(filename, 'r') as t:
|
||||
with open(filename) as t:
|
||||
test = json.load(t)
|
||||
|
||||
if args.schema:
|
||||
|
||||
@@ -9,15 +9,14 @@ import requests
|
||||
import rpmfile
|
||||
from debian import debfile
|
||||
|
||||
DWARF2JSON = './dwarf2json'
|
||||
DWARF2JSON = "./dwarf2json"
|
||||
|
||||
|
||||
class Downloader:
|
||||
|
||||
def __init__(self, url_lists: List[List[str]]) -> None:
|
||||
self.url_lists = url_lists
|
||||
|
||||
def download_lists(self, keep = False):
|
||||
def download_lists(self, keep=False):
|
||||
for url_list in self.url_lists:
|
||||
print("Downloading files...")
|
||||
files_for_processing = self.download_list(url_list)
|
||||
@@ -35,43 +34,45 @@ class Downloader:
|
||||
with tempfile.NamedTemporaryFile() as archivedata:
|
||||
archivedata.write(data.content)
|
||||
archivedata.seek(0)
|
||||
if url.endswith('.rpm'):
|
||||
if url.endswith(".rpm"):
|
||||
processed_files[url] = self.process_rpm(archivedata)
|
||||
elif url.endswith('.deb'):
|
||||
elif url.endswith(".deb"):
|
||||
processed_files[url] = self.process_deb(archivedata)
|
||||
|
||||
return processed_files
|
||||
|
||||
def process_rpm(self, archivedata) -> Optional[str]:
|
||||
rpm = rpmfile.RPMFile(fileobj = archivedata)
|
||||
rpm = rpmfile.RPMFile(fileobj=archivedata)
|
||||
member = None
|
||||
extracted = None
|
||||
for member in rpm.getmembers():
|
||||
if 'vmlinux' in member.name or 'System.map' in member.name:
|
||||
if "vmlinux" in member.name or "System.map" in member.name:
|
||||
print(f" - Extracting {member.name}")
|
||||
extracted = rpm.extractfile(member)
|
||||
break
|
||||
if not member or not extracted:
|
||||
return None
|
||||
with tempfile.NamedTemporaryFile(delete = False,
|
||||
prefix = 'vmlinux' if 'vmlinux' in member.name else 'System.map') as output:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
delete=False, prefix="vmlinux" if "vmlinux" in member.name else "System.map"
|
||||
) as output:
|
||||
print(f" - Writing to {output.name}")
|
||||
output.write(extracted.read())
|
||||
return output.name
|
||||
|
||||
def process_deb(self, archivedata) -> Optional[str]:
|
||||
deb = debfile.DebFile(fileobj = archivedata)
|
||||
deb = debfile.DebFile(fileobj=archivedata)
|
||||
member = None
|
||||
extracted = None
|
||||
for member in deb.data.tgz().getmembers():
|
||||
if member.name.endswith('vmlinux') or 'System.map' in member.name:
|
||||
if member.name.endswith("vmlinux") or "System.map" in member.name:
|
||||
print(f" - Extracting {member.name}")
|
||||
extracted = deb.data.get_file(member.name)
|
||||
break
|
||||
if not member or not extracted:
|
||||
return None
|
||||
with tempfile.NamedTemporaryFile(delete = False,
|
||||
prefix = 'vmlinux' if 'vmlinux' in member.name else 'System.map') as output:
|
||||
with tempfile.NamedTemporaryFile(
|
||||
delete=False, prefix="vmlinux" if "vmlinux" in member.name else "System.map"
|
||||
) as output:
|
||||
print(f" - Writing to {output.name}")
|
||||
output.write(extracted.read())
|
||||
return output.name
|
||||
@@ -83,43 +84,55 @@ class Downloader:
|
||||
if named_files[i] is None:
|
||||
print(f"FAILURE: None encountered for {i}")
|
||||
return
|
||||
args = [DWARF2JSON, 'linux']
|
||||
output_filename = 'unknown-kernel.json'
|
||||
args = [DWARF2JSON, "linux"]
|
||||
output_filename = "unknown-kernel.json"
|
||||
for named_file in named_files:
|
||||
prefix = '--system-map'
|
||||
if 'System' not in named_files[named_file]:
|
||||
prefix = '--elf'
|
||||
output_filename = './' + '-'.join((named_file.split('/')[-1]).split('-')[2:])[:-4] + '.json.xz'
|
||||
prefix = "--system-map"
|
||||
if "System" not in named_files[named_file]:
|
||||
prefix = "--elf"
|
||||
output_filename = (
|
||||
"./"
|
||||
+ "-".join((named_file.split("/")[-1]).split("-")[2:])[:-4]
|
||||
+ ".json.xz"
|
||||
)
|
||||
args += [prefix, named_files[named_file]]
|
||||
print(f" - Running {args}")
|
||||
proc = subprocess.run(args, capture_output = True)
|
||||
proc = subprocess.run(args, capture_output=True)
|
||||
|
||||
print(f" - Writing to {output_filename}")
|
||||
with lzma.open(output_filename, 'w') as f:
|
||||
with lzma.open(output_filename, "w") as f:
|
||||
f.write(proc.stdout)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = argparse.ArgumentParser(description = "Takes a list of URLs for Centos and downloads them")
|
||||
parser.add_argument("-f",
|
||||
"--file",
|
||||
dest = 'filename',
|
||||
metavar = "FILENAME",
|
||||
help = "Filename to be read",
|
||||
required = True)
|
||||
parser.add_argument("-d",
|
||||
"--dwarf2json",
|
||||
dest = 'dwarfpath',
|
||||
metavar = "PATH",
|
||||
default = DWARF2JSON,
|
||||
help = "Path to the dwarf2json binary",
|
||||
required = True)
|
||||
parser.add_argument("-k",
|
||||
"--keep",
|
||||
dest = 'keep',
|
||||
action = 'store_true',
|
||||
help = 'Keep extracted temporary files after completion',
|
||||
default = False)
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Takes a list of URLs for Centos and downloads them"
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--file",
|
||||
dest="filename",
|
||||
metavar="FILENAME",
|
||||
help="Filename to be read",
|
||||
required=True,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-d",
|
||||
"--dwarf2json",
|
||||
dest="dwarfpath",
|
||||
metavar="PATH",
|
||||
default=DWARF2JSON,
|
||||
help="Path to the dwarf2json binary",
|
||||
required=True,
|
||||
)
|
||||
parser.add_argument(
|
||||
"-k",
|
||||
"--keep",
|
||||
dest="keep",
|
||||
action="store_true",
|
||||
help="Keep extracted temporary files after completion",
|
||||
default=False,
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
DWARF2JSON = args.dwarfpath
|
||||
@@ -132,4 +145,4 @@ if __name__ == '__main__':
|
||||
urls += [[lines[2 * i].strip(), lines[(2 * i) + 1].strip()]]
|
||||
|
||||
d = Downloader(urls)
|
||||
d.download_lists(keep = args.keep)
|
||||
d.download_lists(keep=args.keep)
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# These packages are required for building the documentation.
|
||||
sphinx>=4.0.0,<7
|
||||
sphinx_autodoc_typehints>=1.4.0
|
||||
sphinx-rtd-theme>=0.4.3
|
||||
|
||||
yara-python
|
||||
yara-x
|
||||
pycryptodome
|
||||
pefile
|
||||
@@ -14,7 +14,7 @@ Memory layers
|
||||
-------------
|
||||
|
||||
A memory layer is a body of data that can be accessed by requesting data at a specific address. At its lowest level
|
||||
this data is stored on a phyiscal medium (RAM) and very early computers addresses locations in memory directly. However,
|
||||
this data is stored on a phyiscal medium (RAM) and very early computers addressed locations in memory directly. However,
|
||||
as the size of memory increased and it became more difficult to manage memory most architectures moved to a "paged" model
|
||||
of memory, where the available memory is cut into specific fixed-sized pages. To help further, programs can ask for any address
|
||||
and the processor will look up their (virtual) address in a map, to find out where the (physical) address that it lives at is,
|
||||
@@ -25,8 +25,8 @@ address `9`). The automagic that runs at the start of every volatility session
|
||||
a kernel virtual layer, which allows for kernel addresses to be looked up and the correct data returned. There can, however, be
|
||||
several maps, and in general there is a different map for each process (although a portion of the operating system's memory is
|
||||
usually mapped to the same location across all processes). The maps may take the same address but point to a different part of
|
||||
physical memory. It also means that two processes could theoretically share memory, but having an virtual address mapped to the
|
||||
same physical address as another process. See the worked example below for more information.
|
||||
physical memory. It also means that two processes could theoretically share memory, both having a virtual address mapped to the
|
||||
same physical address. See the worked example below for more information.
|
||||
|
||||
To translate an address on a layer, call :py:meth:`layer.mapping(offset, length, ignore_errors) <volatility3.framework.interfaces.layers.TranslationLayerInterface.mapping>` and it will return a list of chunks without overlap, in order,
|
||||
for the requested range. If a portion cannot be mapped, an exception will be thrown unless `ignore_errors` is true. Each
|
||||
@@ -61,7 +61,7 @@ mean they each see something different:
|
||||
4 -> 2 16 - Free
|
||||
|
||||
In this example, part of the operating system is visible across all processes (although not all processes can write to the memory, there
|
||||
is a permissions model for intel addressing which is not discussed further here).)
|
||||
is a permissions model for Intel addressing which is not discussed further here).
|
||||
|
||||
In Volatility 3 mappings are represented by a directed graph of layers, whose end nodes are
|
||||
:py:class:`DataLayers <volatility3.framework.interfaces.layers.DataLayerInterface>` and whose internal nodes are :py:class:`TranslationLayers <volatility3.framework.interfaces.layers.TranslationLayerInterface>`.
|
||||
@@ -69,13 +69,13 @@ In this way, a raw memory image in the LiME file format and a page file can be c
|
||||
memory layer. When requesting addresses from the Intel layer, it will use the Intel memory mapping algorithm, along
|
||||
with the address of the directory table base or page table map, to translate that
|
||||
address into a physical address, which will then either be directed towards the swap layer or the LiME layer. Should it
|
||||
be directed towards the LiME layer, the LiME file format algorithm will be translate the new address to determine where
|
||||
be directed towards the LiME layer, the LiME file format algorithm will translate the new address to determine where
|
||||
within the file the data is stored. When the :py:meth:`layer.read() <volatility3.framework.interfaces.layers.TranslationLayerInterface.read>`
|
||||
method is called, the translation is done automatically and the correct data gathered and combined.
|
||||
|
||||
.. note:: Volatility 2 had a similar concept, called address spaces, but these could only stack linearly one on top of another.
|
||||
|
||||
The list of layers supported by volatility can be determined by running the `frameworkinfo` plugin.
|
||||
The list of layers supported by Volatility can be determined by running the `frameworkinfo` plugin.
|
||||
|
||||
Templates and Objects
|
||||
---------------------
|
||||
@@ -167,8 +167,7 @@ There are certain setup tasks that establish the context in a way favorable to a
|
||||
several tasks that are repetitive and also easy to get wrong. These are called
|
||||
:py:class:`Automagic <volatility3.framework.interfaces.automagic.AutomagicInterface>`, since they do things like magically
|
||||
taking a raw memory image and automatically providing the plugin with an appropriate Intel translation layer and an
|
||||
accurate symbol table without either the plugin or the calling program having to specify all the necessary details.
|
||||
accurate symbol table without either the plugin or the calling program having to specify all the necessary details. Automagics are a core component which consumers of the library can call or not at their discretion.
|
||||
|
||||
.. note:: Volatility 2 used to do this as well, but it wasn't a particularly modular mechanism, and was used only for
|
||||
stacking address spaces (rather than identifying profiles), and it couldn't really be disabled/configured easily.
|
||||
Automagics in Volatility 3 are a core component which consumers of the library can call or not at their discretion.
|
||||
|
||||
@@ -13,7 +13,7 @@ There is scope for this, in order to run multiple plugins (see `Writing plugins
|
||||
is to provide a parameterized `classmethod` within the plugin, which will allow the method to yield whatever kind of output it will
|
||||
generate and take whatever parameters it might need.
|
||||
|
||||
This is how processes are listed, which is an often used function. The code lives within the
|
||||
As an example, an often used function is listing processes. The code lives within the
|
||||
:py:class:`~volatility3.plugins.windows.pslist.PsList` plugin but can be used by other plugins by providing the
|
||||
appropriate parameters (see
|
||||
:py:meth:`~volatility3.plugins.windows.pslist.PsList.list_processes`).
|
||||
@@ -36,8 +36,8 @@ each plugin in order to populate the context's configuration correctly based on
|
||||
between plugins). Once the automagics have been constructed, the plugin can be instantiated using the helper function
|
||||
:py:func:`~volatility3.framework.plugins.construct_plugin` providing:
|
||||
|
||||
* the base context (containing the configuration and any already loaded layers or symbol tables),
|
||||
* the plugin class to run,
|
||||
* the base context (containing the configuration and any already loaded layers or symbol tables)
|
||||
* the plugin class to run
|
||||
* the configuration path within the context for the plugin
|
||||
* any callback to determine progress in lengthy operations
|
||||
* an open method for the plugin to create files during the run
|
||||
@@ -58,7 +58,7 @@ ContextManager, so it can be used by the python `with` keyword). This is set on
|
||||
that can be set on the filename, and a :py:class:`~volatility3.framework.interfaces.plugins.FileHandlerInterface` is the result.
|
||||
This mimics an `IO[bytes]` object, which closely mimics a standard python file-like object.
|
||||
|
||||
As such code for outputting to a file would be expected to look something like:
|
||||
As such, code for outputting to a file would be expected to look something like:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@@ -77,7 +77,7 @@ Scanners are objects that adhere to the :py:class:`~volatility3.framework.interf
|
||||
passed to the :py:meth:`~volatility3.framework.interfaces.layers.TranslationLayerInterface.scan` method on layers which will
|
||||
divide the provided range of sections (or the entire layer
|
||||
if none are provided) and call the :py:meth:`~volatility3.framework.interfaces.layers.ScannerInterface`'s call method
|
||||
method with each chunk as a parameter, ensuring a suitable amount of overlap (as defined by the scanner).
|
||||
with each chunk as a parameter, ensuring a suitable amount of overlap (as defined by the scanner).
|
||||
The offset of the chunk, within the layer, is also provided as a parameter.
|
||||
|
||||
Scanners can technically maintain state, but it is not recommended since the ordering that the chunks are scanned is
|
||||
@@ -92,18 +92,18 @@ Empirically it was found that scanners are typically not the most time intensive
|
||||
extensive scanning) and so parallelism does not offer significant gains. As such, parallelism is not enabled by default
|
||||
but interfaces can easily enable parallelism when desired.
|
||||
|
||||
Writing/Using Intermediate Symbol Format Files
|
||||
----------------------------------------------
|
||||
Writing / Using Intermediate Symbol Format Files
|
||||
------------------------------------------------
|
||||
|
||||
It can occasionally be useful to create a data file containing the static structures that can create a
|
||||
:py:class:`~volatility3.framework.interfaces.objects.Template` to be instantiated on a layer.
|
||||
Volatility has all the machinery necessary to construct these for you from properly formatted JSON data.
|
||||
|
||||
The JSON format is documented by the JSON schema files located in schemas. These are versioned using standard .so
|
||||
The JSON format is documented by the JSON schema files located in the schemas directory. These are versioned using standard .so
|
||||
library versioning, so they may not increment as expected. Each schema lists an available version that can be used,
|
||||
which specifies five different sections:
|
||||
|
||||
* Base_types - These are the basic type names that will make up the native/primitive types
|
||||
* Base_types - These are the basic type names that will make up the native / primitive types
|
||||
* User_types - These are the standard definitions of type structures, most will go here
|
||||
* Symbols - These list offsets that are associated with specific names (and can be associated with specific type names)
|
||||
* Enums - Enumerations that offer a number of choices
|
||||
@@ -180,7 +180,7 @@ of data. Each chunk contains the following information, in order:
|
||||
**layer_name**
|
||||
the layer that this data comes from
|
||||
|
||||
An example (and the most common layer encountered in memory forensics) would be an Intel layer, which models the intel
|
||||
An example (and the most common layer encountered in memory forensics) would be an Intel layer, which models the Intel
|
||||
page mapping system. Based on a series of tables stored within the layer itself, an intel layer can convert a virtual
|
||||
address to a physical address. It should be noted that intel layers allow multiple virtual addresses to map to the
|
||||
same physical address (but a single virtual address cannot ever map to more than one physical address).
|
||||
@@ -195,7 +195,7 @@ like `abcdr`, requesting `mapping(5, 4)` would return:
|
||||
(7,2,0,2, 'physical_layer')
|
||||
]
|
||||
|
||||
This mapping mechanism allows for great flexibility in that chunks making up a virtual layer can come from multiple
|
||||
This mapping mechanism allows for great flexibility because chunks making up a virtual layer can come from multiple
|
||||
different range layers, allowing for swap space to be used to construct the virtual layer, for example. Also, by
|
||||
defining the mapping method, the read and write methods (which read and write into the domain layer) are defined for you
|
||||
to write to the lower layers (which in turn can write to layers even lower than that) until eventually they arrive at a
|
||||
@@ -264,7 +264,7 @@ so it therefore populates the `metadata` property. This is defined as a read-on
|
||||
includes data from every underlying layer. As such, CrashDumpLayer would actually specify this value by setting it
|
||||
in the protected dictionary by `self._direct_metadata['page_map_offset']`.
|
||||
|
||||
There is, unfortunately, no easy way to form consensus between a particular layer may want and what a particular layer
|
||||
There is, unfortunately, no easy way to form consensus between what a particular layer may want and what a particular layer
|
||||
may be able to provide. At the moment, the main information that layers may populate are:
|
||||
|
||||
* `os` with values of `Windows`, `Linux`, `Mac` or `unknown`
|
||||
|
||||
+6
-8
@@ -19,6 +19,8 @@ import sys
|
||||
|
||||
import sphinx.ext.apidoc
|
||||
|
||||
from importlib.util import find_spec
|
||||
|
||||
|
||||
def setup(app):
|
||||
volatility_directory = os.path.abspath(
|
||||
@@ -124,7 +126,7 @@ def setup(app):
|
||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||
sys.path.insert(0, os.path.abspath("../.."))
|
||||
|
||||
from volatility3.framework import constants
|
||||
from volatility3.framework import constants # noqa: E402
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
@@ -147,13 +149,9 @@ extensions = [
|
||||
|
||||
autosectionlabel_prefix_document = True
|
||||
|
||||
try:
|
||||
import sphinx_autodoc_typehints
|
||||
|
||||
if find_spec("sphinx_autodoc_typehints") is not None:
|
||||
extensions.append("sphinx_autodoc_typehints")
|
||||
except ImportError:
|
||||
# If the autodoc typehints extension isn't available, carry on regardless
|
||||
pass
|
||||
# If the autodoc typehints extension isn't available, carry on regardless
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
# templates_path = ['tools/templates']
|
||||
@@ -169,7 +167,7 @@ master_doc = "index"
|
||||
|
||||
# General information about the project.
|
||||
project = "Volatility 3"
|
||||
copyright = "2012-2024, Volatility Foundation"
|
||||
copyright = "2012-2025, 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
|
||||
|
||||
@@ -6,41 +6,47 @@ This guide will give you a brief overview of how volatility3 works as well as a
|
||||
Acquiring memory
|
||||
----------------
|
||||
|
||||
Volatility3 does not provide the ability to acquire memory. Below are some examples of tools that can be used to acquire memory, but more are available:
|
||||
Volatility3 does not provide the ability to acquire memory. Below is an example of a tool that can be used to acquire memory on Linux systems:
|
||||
|
||||
* `AVML - Acquire Volatile Memory for Linux <https://github.com/microsoft/avml>`_
|
||||
* `LiME - Linux Memory Extract <https://github.com/504ensicsLabs/LiME>`_
|
||||
|
||||
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 <https://github.com/504ensicsLabs/LiME/issues/111>`_.
|
||||
Other tools may exist, but please verify their maintenance status and compatibility with volatility3 before use.
|
||||
|
||||
Procedure to create symbol tables for linux
|
||||
--------------------------------------------
|
||||
Procedure to create symbol tables for Linux
|
||||
-------------------------------------------
|
||||
|
||||
To create a symbol table please refer to :ref:`symbol-tables:Mac or Linux symbol tables`.
|
||||
It is recommended to first check the repository `volatility3-symbols <https://github.com/Abyss-W4tcher/volatility3-symbols>`_ for pre-generated JSON.xz symbol table files.
|
||||
This repository provides files organized by kernel version for popular Linux distributions such as Debian, Ubuntu, and AlmaLinux.
|
||||
|
||||
If you cannot find a suitable symbol table for your kernel version there, please refer to :ref:`symbol-tables:Mac or Linux symbol tables` to create one manually.
|
||||
|
||||
After creating the file, place it under the directory ``volatility3/symbols``.
|
||||
Volatility3 will automatically detect and use symbol tables from this location.
|
||||
|
||||
.. tip:: It may be possible to locate pre-made ISF files from the `Linux ISF Server <https://isf-server.techanarchy.net/>`_ ,
|
||||
which is built and maintained by `kevthehermit <https://twitter.com/kevthehermit>`_.
|
||||
After creating the file or downloading it from the ISF server, place the file under the directory ``volatility3/symbols/linux``.
|
||||
If necessary create a linux directory under the symbols directory (this will become unnecessary in future versions).
|
||||
|
||||
|
||||
Listing plugins
|
||||
---------------
|
||||
|
||||
The following is a sample of the linux plugins available for volatility3, it is not complete and more more plugins may
|
||||
be added. For a complete reference, please see the volatility 3 :doc:`list of plugins <volatility3.plugins>`.
|
||||
For plugin requests, please create an issue with a description of the requested plugin.
|
||||
Volatility3 currently supports over 40 Linux-specific plugins covering a wide range of forensic analysis needs, such as process enumeration, memory-mapped file inspection, loaded modules, and kernel tracing features.
|
||||
|
||||
Some representative plugins include:
|
||||
|
||||
- ``linux.pslist``: Lists running processes with their PIDs and PPIDs.
|
||||
- ``linux.bash``: Recovers bash command history from memory.
|
||||
- ``linux.lsmod``: Displays loaded kernel modules.
|
||||
- ``linux.kmsg``: Reads messages from the kernel log buffer.
|
||||
- ``linux.elfs``: Lists all memory-mapped ELF files.
|
||||
- ``linux.check_creds``: Checks for suspicious credential structures.
|
||||
- ``linux.vmayarascan``: Scans process memory using YARA signatures.
|
||||
|
||||
For a full list of supported plugins, run the following command:
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
$ python3 vol.py --help | grep -i linux. | head -n 5
|
||||
banners.Banners Attempts to identify potential linux banners in an
|
||||
linux.bash.Bash Recovers bash command history from memory.
|
||||
linux.check_afinfo.Check_afinfo
|
||||
linux.check_creds.Check_creds
|
||||
linux.check_idt.Check_idt
|
||||
$ python3 vol.py --help | grep -i linux.
|
||||
|
||||
.. note:: Here the the command is piped to grep and head in-order to provide the start of the list of linux plugins.
|
||||
.. note:: You can also filter and inspect available plugins using more sophisticated patterns or tools like ``grep``, ``awk``, or simply explore the source under ``volatility3/framework/plugins/linux``.
|
||||
|
||||
|
||||
Using plugins
|
||||
@@ -60,14 +66,14 @@ banners
|
||||
~~~~~~~
|
||||
|
||||
In this example we will be using a memory dump from the Insomni'hack teaser 2020 CTF Challenge called Getdents. We will limit the discussion to memory forensics with volatility 3 and not extend it to other parts of the challenge.
|
||||
Thanks go to `stuxnet <https://github.com/stuxnet999/>`_ for providing this memory dump and `writeup <https://stuxnet999.github.io/insomnihack/2020/09/17/Insomihack-getdents.html>`_.
|
||||
Thanks go to `stuxnet <https://github.com/stuxnet999/>`_ for providing this memory dump and `writeup <https://stuxnet999.github.io/dfir/insomnihack-teaser-2020-getdents/>`_.
|
||||
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
$ python3 vol.py -f memory.vmem banners
|
||||
|
||||
Volatility 3 Framework 2.0.1
|
||||
Volatility 3 Framework 2.26.0
|
||||
|
||||
Progress: 100.00 PDB scanning finished
|
||||
Offset Banner
|
||||
@@ -79,85 +85,79 @@ Thanks go to `stuxnet <https://github.com/stuxnet999/>`_ for providing this memo
|
||||
0x7fde0010 Linux version 4.15.0-72-generic (buildd@lcy01-amd64-026) (gcc version 7.4.0 (Ubuntu 7.4.0-1ubuntu1~18.04.1)) #81-Ubuntu SMP Tue Nov 26 12:20:02 UTC 2019 (Ubuntu 4.15.0-72.81-generic 4.15.18)
|
||||
|
||||
|
||||
The above command helps us to find the memory dump's kernel version and the distribution version. Now using the above banner we can search for the needed ISF file from the ISF server.
|
||||
If ISF file cannot be found then, follow the instructions on :ref:`getting-started-linux-tutorial:Procedure to create symbol tables for linux`. After that, place the ISF file under the ``volatility3/symbols/linux`` directory.
|
||||
The above command helps us identify the kernel version and distribution from the memory dump.
|
||||
Using this information, follow the instructions in :ref:`getting-started-linux-tutorial:Procedure to create symbol tables for linux` to generate the required ISF file.
|
||||
Once created, place the file under the ``volatility3/symbols`` directory so that Volatility3 can recognize it automatically.
|
||||
|
||||
linux.boottime
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
This plugin provides the system boot time extracted from memory.
|
||||
It is useful for establishing a timeline, particularly when analyzing incident response scenarios or determining system uptime.
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
$ python3 vol.py -f memory.vmem linux.boottime
|
||||
|
||||
Volatility 3 Framework 2.26.0
|
||||
Progress: 100.00 Stacking attempts finished
|
||||
|
||||
TIME NS Boot Time
|
||||
|
||||
- 2022-02-10 06:50:16.450008 UTC
|
||||
|
||||
This timestamp can serve as a reference point for correlating system events, such as process start times, logs, or malicious activity.
|
||||
|
||||
.. tip:: Use the banner text which is most repeated to search from ISF Server.
|
||||
|
||||
linux.pslist
|
||||
~~~~~~~~~~~~
|
||||
|
||||
This plugin lists active processes by walking the task list from memory.
|
||||
It provides detailed metadata for each process, including identifiers and user/group information.
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
$ python3 vol.py -f memory.vmem linux.pslist
|
||||
|
||||
Volatility 3 Framework 2.0.1 Stacking attempts finished
|
||||
Volatility 3 Framework 2.26.0
|
||||
Progress: 100.00 Stacking attempts finished
|
||||
OFFSET (V) PID TID PPID COMM UID GID EUID EGID CREATION TIME File output
|
||||
|
||||
PID PPID COMM
|
||||
0x8ca6db1aac80 1 1 0 systemd 0 0 0 0 2022-02-10 06:50:16.364213 UTC Disabled
|
||||
0x8ca6db1a9640 2 2 0 kthreadd 0 0 0 0 2022-02-10 06:50:16.364213 UTC Disabled
|
||||
0x8ca6db1ac2c0 3 3 2 rcu_gp 0 0 0 0 2022-02-10 06:50:16.372213 UTC Disabled
|
||||
...
|
||||
|
||||
1 0 systemd
|
||||
2 0 kthreadd
|
||||
3 2 kworker/0:0
|
||||
4 2 kworker/0:0H
|
||||
5 2 kworker/u256:0
|
||||
6 2 mm_percpu_wq
|
||||
7 2 ksoftirqd/0
|
||||
8 2 rcu_sched
|
||||
9 2 rcu_bh
|
||||
10 2 migration/0
|
||||
11 2 watchdog/0
|
||||
12 2 cpuhp/0
|
||||
13 2 kdevtmpfs
|
||||
14 2 netns
|
||||
15 2 rcu_tasks_kthre
|
||||
16 2 kauditd
|
||||
.....
|
||||
This detailed view allows investigators to correlate user privileges, startup times, and relationships between processes more precisely than before.
|
||||
|
||||
``linux.pslist`` helps us to list the processes which are running, their PIDs and PPIDs.
|
||||
|
||||
linux.pstree
|
||||
~~~~~~~~~~~~
|
||||
This plugin presents the process hierarchy as a tree, clearly showing parent-child relationships between processes.
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
$ python3 vol.py -f memory.vmem linux.pstree
|
||||
Volatility 3 Framework 2.0.1
|
||||
|
||||
Volatility 3 Framework 2.26.0
|
||||
Progress: 100.00 Stacking attempts finished
|
||||
PID PPID COMM
|
||||
OFFSET (V) PID TID PPID COMM
|
||||
|
||||
0x8ca6db1aac80 1 1 0 systemd
|
||||
* 0x8ca6db3342c0 278 278 1 systemd-journal
|
||||
* 0x8ca6d005ac80 315 315 1 systemd-udevd
|
||||
* 0x8ca6d0eac2c0 478 478 1 systemd-resolve
|
||||
* ...
|
||||
*** 0x8ca67108c2c0 1507 1507 1438 gdm-x-session
|
||||
**** 0x8ca671215900 1527 1527 1507 Xorg
|
||||
**** 0x8ca671210000 1608 1608 1507 gnome-session-b
|
||||
***** 0x8ca66fba42c0 1765 1765 1608 ssh-agent
|
||||
|
||||
|
||||
It helps identify unusual or suspicious process structures such as orphaned child processes, injected children under legitimate parents, or long chains of shell execution.
|
||||
The tree view is particularly useful for spotting anomalies in process launch sequences or privilege escalations by inspecting unexpected parent-child relationships.
|
||||
|
||||
1 0 systemd
|
||||
* 636 1 polkitd
|
||||
* 514 1 acpid
|
||||
* 1411 1 pulseaudio
|
||||
* 517 1 rsyslogd
|
||||
* 637 1 cups-browsed
|
||||
* 903 1 whoopsie
|
||||
* 522 1 ModemManager
|
||||
* 525 1 cron
|
||||
* 526 1 avahi-daemon
|
||||
** 542 526 avahi-daemon
|
||||
* 657 1 unattended-upgr
|
||||
* 914 1 kerneloops
|
||||
* 532 1 dbus-daemon
|
||||
* 1429 1 ibus-x11
|
||||
* 929 1 kerneloops
|
||||
* 1572 1 gsd-printer
|
||||
* 933 1 upowerd
|
||||
* 1071 1 rtkit-daemon
|
||||
* 692 1 gdm3
|
||||
** 1234 692 gdm-session-wor
|
||||
*** 1255 1234 gdm-x-session
|
||||
**** 1257 1255 Xorg
|
||||
**** 1266 1255 gnome-session-b
|
||||
***** 1537 1266 gsd-clipboard
|
||||
***** 1539 1266 gsd-color
|
||||
***** 1542 1266 gsd-datetime
|
||||
***** 2950 1266 deja-dup-monito
|
||||
***** 1546 1266 gsd-housekeepin
|
||||
***** 1548 1266 gsd-keyboard
|
||||
***** 1550 1266 gsd-media-keys
|
||||
|
||||
``linux.pstree`` helps us to display the parent child relationships between processes.
|
||||
|
||||
linux.bash
|
||||
~~~~~~~~~~
|
||||
@@ -168,7 +168,7 @@ Now to find the commands that were run in the bash shell by using ``linux.bash``
|
||||
|
||||
$ python3 vol.py -f memory.vmem linux.bash
|
||||
|
||||
Volatility 3 Framework 2.0.1
|
||||
Volatility 3 Framework 2.26.0
|
||||
Progress: 100.00 Stacking attempts finished
|
||||
PID Process CommandTime Command
|
||||
|
||||
@@ -177,17 +177,85 @@ Now to find the commands that were run in the bash shell by using ``linux.bash``
|
||||
1733 bash 2020-01-16 14:00:36.000000 sudo apt upgrade
|
||||
1733 bash 2020-01-16 14:00:36.000000 sudo apt upgrade
|
||||
1733 bash 2020-01-16 14:00:36.000000 sudo reboot
|
||||
1733 bash 2020-01-16 14:00:36.000000 sudo apt update
|
||||
1733 bash 2020-01-16 14:00:36.000000 sudo apt update
|
||||
1733 bash 2020-01-16 14:00:36.000000 sudo reboot
|
||||
1733 bash 2020-01-16 14:00:36.000000 sudo apt upgrade
|
||||
1733 bash 2020-01-16 14:00:36.000000 sudo apt update
|
||||
1733 bash 2020-01-16 14:00:36.000000 rub
|
||||
1733 bash 2020-01-16 14:00:36.000000 sudo apt upgrade
|
||||
1733 bash 2020-01-16 14:00:36.000000 uname -a
|
||||
1733 bash 2020-01-16 14:00:36.000000 uname -a
|
||||
1733 bash 2020-01-16 14:00:36.000000 sudo apt autoclean
|
||||
1733 bash 2020-01-16 14:00:36.000000 sudo reboot
|
||||
1733 bash 2020-01-16 14:00:36.000000 sudo apt upgrade
|
||||
1733 bash 2020-01-16 14:00:41.000000 chmod +x meterpreter
|
||||
1733 bash 2020-01-16 14:00:42.000000 sudo ./meterpreter
|
||||
|
||||
|
||||
linux.ip.Addr and linux.ip.Link
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Network configuration is an essential aspect of memory forensics.
|
||||
Analyzing the network interfaces and their IP assignments can reveal active connections, misconfigured settings, or even artifacts of malicious activity.
|
||||
|
||||
Volatility3 provides the following two plugins to examine this information:
|
||||
|
||||
**linux.ip.Addr** displays IP-related metadata for each interface, including IPv4/IPv6 addresses, MAC, scope, and interface status.
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
$ python3 vol.py -f memory.vmem linux.ip.Addr
|
||||
|
||||
NetNS Index Interface MAC Promiscuous IP Prefix Scope Type State
|
||||
4026531992 2 enp0s3 08:00:27:8a:4d:eb False 10.0.2.15 24 global UP
|
||||
...
|
||||
|
||||
**linux.ip.Link** shows lower-level link information such as MTU, Qdisc, and interface flags.
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
$ python3 vol.py -f memory.vmem linux.ip.Link
|
||||
|
||||
NS Interface MAC State MTU Qdisc Qlen Flags
|
||||
4026531992 enp0s3 08:00:27:8a:4d:eb UP 1500 fq_codel 1000 BROADCAST,LOWER_UP,MULTICAST,UP
|
||||
|
||||
Together, these plugins help investigators assess the system’s network exposure and identify anomalies such as multiple network namespaces, unexpected IP addresses, or active interfaces in promiscuous mode.
|
||||
|
||||
linux.malfind
|
||||
~~~~~~~~~~~~~
|
||||
|
||||
This plugin scans process memory for suspicious executable regions that may indicate code injection or malicious payloads.
|
||||
It is particularly useful for detecting fileless malware, injected shellcode, or unpacked runtime payloads that do not correspond to legitimate binary files on disk.
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
$ python3 vol.py -f memory.vmem linux.malfind
|
||||
|
||||
Volatility 3 Framework 2.26.0
|
||||
Progress: 100.00 Stacking attempts finished
|
||||
PID Process Start End Path Protection Hexdump Disasm
|
||||
|
||||
540 networkd-dispat 0x7f1506482000 0x7f1506483000 Anonymous Mapping rwx
|
||||
00 00 00 00 00 00 00 00 43 00 00 00 00 00 00 00 ........C.......
|
||||
4c 8d 15 f9 ff ff ff ff 25 03 00 00 00 0f 1f 00 L.......%.......
|
||||
...
|
||||
0x7f1506482000: add byte ptr [rax], al
|
||||
0x7f1506482002: add byte ptr [rax], al
|
||||
...
|
||||
0x7f1506482013: stc
|
||||
|
||||
In this output:
|
||||
|
||||
- **PID / Process**: Identifies the target process (in this case, `networkd-dispat`, PID 540)
|
||||
- **Start / End**: The memory address range of the suspicious region
|
||||
- **Path**: Indicates that the region is an anonymous memory mapping (i.e., not backed by a file)
|
||||
- **Protection**: The region is marked `rwx` (read-write-execute), which is uncommon for legitimate memory regions
|
||||
- **Disasm**: Shows the disassembled machine code found in that memory region
|
||||
|
||||
**Key indicators to focus on:**
|
||||
|
||||
- **Anonymous Mapping + rwx**: Memory that is not backed by a file and has execute permissions is often used for injected code
|
||||
- **Disassembly patterns**: Repetitive `add` instructions, `nop`, or unusual instruction sequences can be artifacts of shellcode, packer stubs, or JIT-compiled code
|
||||
- **Process context**: The suspicious memory is found in `networkd-dispat`, a system service — if this service is not expected to have dynamic executable memory regions, it may be compromised
|
||||
|
||||
Use this plugin early in an investigation to flag processes for deeper inspection.
|
||||
|
||||
Further Exploration and Contribution
|
||||
------------------------------------
|
||||
|
||||
This guide has introduced several key Linux plugins available in Volatility 3 for memory forensics.
|
||||
However, many more plugins are available, covering topics such as kernel modules, page cache analysis, tracing frameworks, and malware detection.
|
||||
|
||||
If you identify gaps in plugin functionality or wish to extend support for a specific analysis use case, you are encouraged to contribute new plugins or enhancements.
|
||||
Your insights can help shape the future of Linux memory forensics.
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ For plugin requests, please create an issue with a description of the requested
|
||||
mac.check_sysctl.Check_sysctl
|
||||
mac.check_trap_table.Check_trap_table
|
||||
|
||||
.. note:: Here the the command is piped to grep and head in-order to provide the start of the list of macOS plugins.
|
||||
.. note:: Here the command is piped to grep and head to provide the start of the list of macOS plugins.
|
||||
|
||||
|
||||
Using plugins
|
||||
@@ -78,7 +78,7 @@ Thanks go to `stuxnet <https://github.com/stuxnet999/>`_ for providing this memo
|
||||
|
||||
|
||||
The above command helps us to find the memory dump's Darwin kernel version. Now using the above banner we can search for the needed ISF file.
|
||||
If ISF file cannot be found then, follow the instructions on :ref:`getting-started-mac-tutorial:Procedure to create symbol tables for macOS`. After that, place the ISF file under the ``volatility3/symbols`` directory.
|
||||
If an ISF file cannot be found then, follow the instructions on :ref:`getting-started-mac-tutorial:Procedure to create symbol tables for macOS`. After that, place the ISF file under the ``volatility3/symbols`` directory.
|
||||
|
||||
mac.pslist
|
||||
~~~~~~~~~~
|
||||
@@ -125,7 +125,7 @@ mac.pstree
|
||||
337 1 system_installd
|
||||
* 455 337 update_dyld_shar
|
||||
|
||||
``mac.pstree`` helps us to display the parent child relationships between processes.
|
||||
``mac.pstree`` helps us to display the parent-child relationships between processes.
|
||||
|
||||
mac.ifconfig
|
||||
~~~~~~~~~~~~
|
||||
@@ -150,4 +150,4 @@ mac.ifconfig
|
||||
utun0 False
|
||||
utun0 fe80:5::2a95:bb15:87e3:977c False
|
||||
|
||||
we can use the ``mac.ifconfig`` plugin to get information about the configuration of the network interfaces of the host under investigation.
|
||||
We can use the ``mac.ifconfig`` plugin to get information about the configuration of the network interfaces of the host under investigation.
|
||||
|
||||
@@ -15,19 +15,19 @@ Memory can be acquired using a number of tools, below are some examples but othe
|
||||
Listing Plugins
|
||||
---------------
|
||||
|
||||
The following is a sample of the windows plugins available for volatility3, it is not complete and more more plugins may
|
||||
The following is a sample of the windows plugins available for volatility3, it is not complete and more plugins may
|
||||
be added. For a complete reference, please see the volatility 3 :doc:`list of plugins <volatility3.plugins>`.
|
||||
For plugin requests, please create an issue with a description of the requested plugin.
|
||||
|
||||
.. code-block:: shell-session
|
||||
|
||||
$ python3 vol.py --help | grep windows | head -n 5
|
||||
$ python3 vol.py --help | grep windows | head -n 4
|
||||
windows.bigpools.BigPools
|
||||
windows.cmdline.CmdLine
|
||||
windows.crashinfo.Crashinfo
|
||||
windows.dlllist.DllList
|
||||
|
||||
.. note:: Here the the command is piped to grep and head in-order to provide the start of a list of the available windows plugins.
|
||||
.. note:: Here the command is piped to grep and head to provide the start of a list of the available windows plugins.
|
||||
|
||||
Using plugins
|
||||
-------------
|
||||
@@ -95,9 +95,9 @@ windows.pstree
|
||||
** 616 504 svchost.exe 0xfa8002b86ab0 13 314 0 False 2022-02-07 16:32:16.000000 N/A
|
||||
** 624 504 svchost.exe 0xfa8002410630 10 350 0 False 2022-02-07 16:30:14.000000 N/A
|
||||
|
||||
``windows.pstree`` helps to display the parent child relationships between processes.
|
||||
``windows.pstree`` helps to display the parent-child relationships between processes.
|
||||
|
||||
.. note:: Here the the command is piped to head in-order to provide smaller output, here listing only the first 20.
|
||||
.. note:: Here the command is piped to head to provide smaller output, here listing only the first 20.
|
||||
|
||||
windows.hashdump
|
||||
~~~~~~~~~~~~~~~~
|
||||
@@ -116,9 +116,3 @@ windows.hashdump
|
||||
Dennis 1003 aad3b435b51404eeaad3b435b51404ee cf96684bbc7877920adaa9663698bf54
|
||||
|
||||
``windows.hashdump`` helps to list the hashes of the users in the system.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+18
-8
@@ -23,7 +23,7 @@ Alignment
|
||||
.. _Array:
|
||||
|
||||
Array
|
||||
This represents a list of items, which can be access by an index, which is zero-based (meaning the first
|
||||
This represents a list of items, which can be accessed by an index, which is zero-based (meaning the first
|
||||
element has index 0). Items in arrays are almost always the same size (it is not a generic list, as in python)
|
||||
even if they are :ref:`pointers<pointer>` to different sized objects.
|
||||
|
||||
@@ -43,7 +43,14 @@ Dereference
|
||||
.. _Domain:
|
||||
|
||||
Domain
|
||||
This the grouping for input values for a mapping or mathematical function.
|
||||
The set of input values for a mapping or mathematical function.
|
||||
|
||||
I
|
||||
-
|
||||
.. _Intermediate Symbol File (ISF):
|
||||
|
||||
Intermediate Symbol File (ISF)
|
||||
They contain kernel structures and specific offsets formatted as JSON. For macOS and Linux analysis, the kernel needs to be added as an ISF file to the volatility 3 symbols directory. For Windows, the required ISF file can often be generated from PDB files automatically downloaded from Microsoft servers, and therefore does not require manual intervention.
|
||||
|
||||
M
|
||||
-
|
||||
@@ -54,9 +61,7 @@ Map, mapping
|
||||
of the :ref:`Range<range>`). Mappings can be seen as a mathematical function, and therefore volatility 3
|
||||
attempts to use mathematical functional notation where possible. Within volatility a mapping is most often
|
||||
used to refer to the function for translating addresses from a higher layer (domain) to a lower layer (range).
|
||||
For further information, please see
|
||||
`Function (mathematics) in wikipedia https://en.wikipedia.org/wiki/Function_(mathematics)`
|
||||
|
||||
For further information, please see `Function (mathematics) in Wikipedia<https://en.wikipedia.org/wiki/Function_(mathematics)>_`.
|
||||
|
||||
.. _Member:
|
||||
|
||||
@@ -69,7 +74,7 @@ O
|
||||
.. _Object:
|
||||
|
||||
Object
|
||||
This has a specific meaning within computer programming (as in Object Oriented Programming), but within the world
|
||||
This has a specific meaning within computer programming (as in object-oriented programming), but within the world
|
||||
of Volatility it is used to refer to a type that has been associated with a chunk of data, or a specific instance
|
||||
of a type. See also :ref:`Type<type>`.
|
||||
|
||||
@@ -116,6 +121,11 @@ Page Table
|
||||
possible to use them as a way to map a particular address within a (potentially larger, but sparsely populated)
|
||||
virtual space to a concrete (and usually contiguous) physical space, through the process of :ref:`mapping<map>`.
|
||||
|
||||
.. _Plugin:
|
||||
|
||||
Plugin
|
||||
Plugins are the "functions" of the volatility framework. They carry out algorithms on data stored in layers using objects constructed from symbols. Broadly, plugins take in a number of TranslationLayers (the data, which is a representation of part of an image, in a specified type described by templates) and outputs a TreeGrid.
|
||||
|
||||
.. _Pointer:
|
||||
|
||||
Pointer
|
||||
@@ -145,9 +155,9 @@ Struct, Structure
|
||||
|
||||
Symbol
|
||||
This is used in many different contexts, as a short term for many things. Within Volatility, a symbol is a
|
||||
construct that usually encompasses a specific type :ref:`type<Type>` at a specific :ref:`offset<Offset>`,
|
||||
construct that usually encompasses a specific :ref:`type<Type>` at a specific :ref:`offset<Offset>`,
|
||||
representing a particular instance of that type within the memory of a compiled and running program. An example
|
||||
would be the location in memory of a list of active tcp endpoints maintained by the networking stack
|
||||
would be the location in memory of a list of active TCP endpoints maintained by the networking stack
|
||||
within an operating system.
|
||||
|
||||
T
|
||||
|
||||
@@ -41,24 +41,36 @@ to be able to run properly. Any that are defined as optional need not necessari
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
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.PluginRequirement(name = 'pslist',
|
||||
plugin = pslist.PsList,
|
||||
version = (2, 0, 0))]
|
||||
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)
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
This is a classmethod, because it is called before the specific plugin object has been instantiated (in order to know how
|
||||
This is a classmethod, so it can be called before the specific plugin object has been instantiated (in order to know how
|
||||
to instantiate the plugin). At the moment these requirements are fairly straightforward:
|
||||
|
||||
::
|
||||
|
||||
requirements.ModuleRequirement(name = 'kernel', description = 'Windows kernel',
|
||||
architectures = ["Intel32", "Intel64"]),
|
||||
requirements.ModuleRequirement(
|
||||
name = 'kernel',
|
||||
description = 'Windows kernel',
|
||||
architectures = ["Intel32", "Intel64"]
|
||||
),
|
||||
|
||||
This requirement specifies the need for a particular submodule. Each module requires a
|
||||
:py:class:`TranslationLayer <volatility3.framework.interfaces.layers.TranslationLayerInterface>` and a
|
||||
@@ -85,9 +97,11 @@ not be requested directly from the user.
|
||||
|
||||
::
|
||||
|
||||
requirements.TranslationLayerRequirement(name = 'primary',
|
||||
description = 'Memory layer for the kernel',
|
||||
architectures = ["Intel32", "Intel64"]),
|
||||
requirements.TranslationLayerRequirement(
|
||||
name = 'primary',
|
||||
description = 'Memory layer for the kernel',
|
||||
architectures = ["Intel32", "Intel64"]
|
||||
),
|
||||
|
||||
This requirement indicates that the plugin will operate on a single
|
||||
:py:class:`TranslationLayer <volatility3.framework.interfaces.layers.TranslationLayerInterface>`. The name of the
|
||||
@@ -110,8 +124,10 @@ not be requested directly from the user.
|
||||
|
||||
::
|
||||
|
||||
requirements.SymbolTableRequirement(name = "nt_symbols",
|
||||
description = "Windows kernel symbols"),
|
||||
requirements.SymbolTableRequirement(
|
||||
name = "nt_symbols",
|
||||
description = "Windows kernel symbols"
|
||||
),
|
||||
|
||||
This requirement specifies the need for a particular
|
||||
:py:class:`SymbolTable <volatility3.framework.interfaces.symbols.SymbolTableInterface>`
|
||||
@@ -127,10 +143,12 @@ not be requested directly from the user.
|
||||
|
||||
::
|
||||
|
||||
requirements.ListRequirement(name = 'pid',
|
||||
description = 'Filter on specific process IDs',
|
||||
element_type = int,
|
||||
optional = True),
|
||||
requirements.ListRequirement(
|
||||
name = 'pid',
|
||||
description = 'Filter on specific process IDs',
|
||||
element_type = int,
|
||||
optional = True
|
||||
),
|
||||
|
||||
The next requirement is a List Requirement, populated by integers. The description will be presented to the user to
|
||||
describe what the value represents. The optional flag indicates that the plugin can function without the ``pid`` value
|
||||
@@ -138,9 +156,11 @@ being defined within the configuration tree at all.
|
||||
|
||||
::
|
||||
|
||||
requirements.PluginRequirement(name = 'pslist',
|
||||
plugin = pslist.PsList,
|
||||
version = (2, 0, 0))]
|
||||
requirements.PluginRequirement(
|
||||
name = 'pslist',
|
||||
plugin = pslist.PsList,
|
||||
version = (2, 0, 0)
|
||||
)
|
||||
|
||||
This requirement indicates that the plugin will make use of another plugin's code, and specifies the version requirements
|
||||
on that plugin. The version is specified in terms of Semantic Versioning meaning that, to be compatible, the major
|
||||
@@ -178,18 +198,24 @@ that will be output as part of the :py:class:`~volatility3.framework.interfaces.
|
||||
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),
|
||||
("Base", format_hints.Hex),
|
||||
("Size", format_hints.Hex),
|
||||
("Name", str),
|
||||
("Path", str)],
|
||||
self._generator(pslist.PsList.list_processes(self.context,
|
||||
kernel.layer_name,
|
||||
kernel.symbol_table_name,
|
||||
filter_func = filter_func)))
|
||||
return renderers.TreeGrid(
|
||||
[
|
||||
("PID", int),
|
||||
("Process", str),
|
||||
("Base", format_hints.Hex),
|
||||
("Size", format_hints.Hex),
|
||||
("Name", str),
|
||||
("Path", str),
|
||||
],
|
||||
self._generator(
|
||||
pslist.PsList.list_processes(
|
||||
context=self.context,
|
||||
kernel_module_name=self.config['kernel'],
|
||||
filter_func = filter_func
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
In this instance, the plugin constructs a filter (using the PsList plugin's *classmethod* for creating filters).
|
||||
It checks the plugin's configuration for the ``pid`` value, and passes it in as a list if it finds it, or None if
|
||||
@@ -207,7 +233,7 @@ the :py:class:`~volatility3.plugins.windows.pslist.PsList` plugin. That plugin
|
||||
so that other plugins can call it. As such, it takes all the necessary parameters rather than accessing them
|
||||
from a configuration. Since it must be portable code, it takes a context, as well as the layer name,
|
||||
symbol table and optionally a filter. In this instance we unconditionally
|
||||
pass it the values from the configuration for the layer and symbol table from the kernel module object, constructed from
|
||||
pass it the value from the configuration for the kernel module name, constructed from
|
||||
the ``kernel`` configuration requirement. This will generate a list
|
||||
of :py:class:`~volatility3.framework.symbols.windows.extensions.EPROCESS` objects, as provided by the :py:class:`~volatility.plugins.windows.pslist.PsList` plugin,
|
||||
and is not covered here but is used as an example for how to share code across plugins
|
||||
@@ -281,5 +307,3 @@ such as ``<table>!_UNICODE``) and the parameters to that type.
|
||||
Since the cast value must populate a string typed column, it had to be a Python string (such as being cast to the native
|
||||
type string) and could not have been a special Structure such as ``_UNICODE``. For the format hint columns, the format
|
||||
hint type must be used to ensure the error checking does not fail.
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ How Volatility finds symbol tables
|
||||
|
||||
All files are stored as JSON data, they can be in pure JSON files as ``.json``, or compressed as ``.json.gz`` or ``.json.xz``.
|
||||
Volatility will automatically decompress them on use. It will also cache their contents (compressed) when used, located
|
||||
under the user's home directory, in :file:`.cache/volatility3`, along with other useful data. The cache directory currently
|
||||
under the user's home directory, in :file:`.cache/volatility3` or when `XDG_CACHE_HOME` is set in :file:`${XDG_CACHE_HOME}/volatility3`, along with other useful data. The cache directory currently
|
||||
cannot be altered.
|
||||
|
||||
Symbol table JSON files live, by default, under the :file:`volatility3/symbols` directory. The symbols directory is
|
||||
@@ -25,9 +25,9 @@ as long as the symbol files stay in the same location.
|
||||
Windows symbol tables
|
||||
---------------------
|
||||
|
||||
For Windows systems, Volatility accepts a string made up of the GUID and Age of the required PDB file. It then
|
||||
For Windows systems, Volatility accepts a string made up of the GUID and age of the required PDB file. It then
|
||||
searches all files under the configured symbol directories under the windows subdirectory. Any that contain metadata
|
||||
which matches the pdb name and GUID/age (or any compressed variant) will be used. If such a symbol table cannot be found, then
|
||||
which matches the PDB name and GUID/age (or any compressed variant) will be used. If such a symbol table cannot be found, then
|
||||
the associated PDB file will be downloaded from Microsoft's Symbol Server and converted into the appropriate JSON
|
||||
format, and will be saved in the correct location.
|
||||
|
||||
@@ -54,8 +54,8 @@ most Volatility plugins. Note that in most linux distributions, the standard ke
|
||||
and the kernel with debugging information is stored in a package that must be acquired separately.
|
||||
|
||||
A generic table isn't guaranteed to produce accurate results, and would reduce the number of structures
|
||||
that all plugins could rely on. As such, and because linux kernels with different configurations can produce different structures,
|
||||
volatility 3 requires that the banners in the JSON file match the banners found in the image *exactly*, not just the version
|
||||
that all plugins could rely on. As such, and because Linux kernels with different configurations can produce different structures,
|
||||
Volatility 3 requires that the banners in the JSON file match the banners found in the image *exactly*, not just the version
|
||||
number. This can include elements such as the compilation time and even the version of gcc used for the compilation.
|
||||
The exact match is required to ensure that the results volatility returns are accurate, therefore there is no simple means
|
||||
provided to get the wrong JSON ISF file to easily match.
|
||||
@@ -63,8 +63,8 @@ provided to get the wrong JSON ISF file to easily match.
|
||||
To determine the string for a particular memory image, use the `banners` plugin. Once the specific banner is known,
|
||||
try to locate that exact kernel debugging package for the operating system. Unfortunately each distribution provides
|
||||
its debugging packages under different package names and there are so many that the distribution may not keep all old
|
||||
versions of the debugging symbols, and therefore **it may not be possible to find the right symbols to analyze a linux
|
||||
memory image with volatility**. With Macs there are far fewer kernels and only one distribution, making it easier to
|
||||
versions of the debugging symbols, and therefore **it may not be possible to find the right symbols to analyze a Linux
|
||||
memory image with Volatility**. With Macs there are far fewer kernels and only one distribution, making it easier to
|
||||
ensure that the right symbols can be found.
|
||||
|
||||
Once a kernel with debugging symbols/appropriate DWARF file has been located, `dwarf2json <https://github.com/volatilityfoundation/dwarf2json>`_ will convert it into an
|
||||
@@ -75,7 +75,7 @@ symbol offsets within the DWARF data, which dwarf2json can extract into the JSON
|
||||
|
||||
The banners available for volatility to use can be found using the `isfinfo` plugin, but this will potentially take a
|
||||
long time to run depending on the number of JSON files available. This will list all the JSON (ISF) files that
|
||||
volatility3 is aware of, and for linux/mac systems what banner string they search for. For volatility to use the JSON
|
||||
Volatility 3 is aware of, and for linux/mac systems what banner string they search for. For volatility to use the JSON
|
||||
file, the banners must match exactly (down to the compilation date).
|
||||
|
||||
.. note::
|
||||
|
||||
@@ -3,7 +3,7 @@ Using Volatility 3 as a Library
|
||||
|
||||
This portion of the documentation discusses how to access the Volatility 3 framework from an external application.
|
||||
|
||||
The general process of using volatility as a library is to as follows:
|
||||
The general process of using volatility as a library is as follows:
|
||||
|
||||
1. :ref:`create_context`
|
||||
2. (Optional) :ref:`available_plugins`
|
||||
@@ -21,7 +21,7 @@ Creating a context
|
||||
First we make sure the volatility framework works the way we expect it (and is the version we expect). The
|
||||
versioning used is semantic versioning, meaning any version with the same major number and a higher or equal
|
||||
minor number will satisfy the requirement. An example is below since the CLI doesn't need any of the features
|
||||
from versions 1.1 or 1.2:
|
||||
from version 1.1 or later:
|
||||
|
||||
::
|
||||
|
||||
@@ -86,7 +86,7 @@ List requirements are a list of simple types (integers, booleans, floats and str
|
||||
options, multiple requirements needs all their subrequirements fulfilled and the other types require the names of
|
||||
valid translation layers or symbol tables within the context, respectively. Luckily, each of these requirements can
|
||||
tell you whether they've been fulfilled or not later in the process. For now, they can be used to ask the user to
|
||||
fill in any parameters they made need to. Some requirements are optional, others are not.
|
||||
fill in any parameters they may need to. Some requirements are optional, others are not.
|
||||
|
||||
The plugin is essentially a multiple requirement. It should also be noted that automagic classes can have requirements
|
||||
(as can translation layers).
|
||||
@@ -100,7 +100,7 @@ Once you know what requirements the plugin will need, you can populate them with
|
||||
The configuration is essentially a hierarchical tree of values, much like the windows registry.
|
||||
Each plugin is instantiated at a particular branch within the hierarchy and will look for its configuration
|
||||
options under that hierarchy (if it holds any configurable items, it will likely instantiate those at a point
|
||||
underneaths its own branch). To set the hierarchy, you'll need to know where the configurables will be constructed.
|
||||
underneath its own branch). To set the hierarchy, you'll need to know where the configurables will be constructed.
|
||||
|
||||
For this example, we'll assume plugins' base_config_path is set as `plugins`, and that automagics are configured under
|
||||
the `automagic` tree. We'll see later how to ensure this matches up with the plugins and automagic when they're
|
||||
@@ -139,7 +139,7 @@ A suitable list of automagics for a particular plugin (based on operating system
|
||||
|
||||
This will take the plugin module, extract the operating system (first level of the hierarchy) and then return just
|
||||
the automagics which apply to the operating system. Each automagic can exclude itself from being used for specific
|
||||
operating systems, so that an automagic designed for linux is not used for windows or mac plugins.
|
||||
operating systems, such that an automagic designed for linux is not used for windows or mac plugins.
|
||||
|
||||
These automagics can then be run by providing the list, the context, the plugin to be run, the hierarchy name that
|
||||
the plugin will be constructed on ('plugins' by default) and a progress_callback. This is a callable which takes
|
||||
@@ -157,8 +157,8 @@ Any exceptions that occur during the execution of the automagic will be returned
|
||||
Run the plugin
|
||||
--------------
|
||||
|
||||
Firstly, we should check whether the plugin will be able to run (ie, whether the configuration options it needs
|
||||
have been successfully set). We do this as follow (where plugin_config_path is the base_config_path (which defaults
|
||||
Firstly, we should check whether the plugin will be able to run (i.e., whether the configuration options it needs
|
||||
have been successfully set). We do this as follows, where plugin_config_path is the base_config_path (which defaults
|
||||
to `plugins` and then the name of the class itself):
|
||||
|
||||
::
|
||||
@@ -166,7 +166,7 @@ to `plugins` and then the name of the class itself):
|
||||
unsatisfied = plugin.unsatisfied(context, plugin_config_path)
|
||||
|
||||
If unsatisfied is an empty list, then the plugin has been given everything it requires. If not, it will be a
|
||||
Dictionary of the hierarchy paths and their associated requirements that weren't satisfied.
|
||||
dict of the hierarchy paths and their associated requirements that weren't satisfied.
|
||||
|
||||
The plugin can then be instantiated with the context (containing the plugin's configuration) and the path that the
|
||||
plugin can find its configuration at. This configuration path only needs to be a unique value to identify where the
|
||||
|
||||
@@ -58,7 +58,7 @@ Options
|
||||
EXTEND. Extensions must be of the form **configuration.item.name=value**
|
||||
|
||||
-p PLUGIN_DIRS, --plugin-dirs PLUGIN_DIRS
|
||||
Specified a semi-colon separated list of paths that contain directories
|
||||
Specified as a semi-colon separated list of paths that contain directories
|
||||
where plugins may be found. These paths are searched before the default
|
||||
paths when loading python files for plugins. This can therefore be used
|
||||
to override built-in plugins. NOTE: All python code within this directory
|
||||
@@ -67,12 +67,12 @@ Options
|
||||
-s SYMBOL_DIRS, --symbol-dirs SYMBOL_DIRS
|
||||
SYMBOL_DIRS is a semi-colon separated list of paths that contain symbol
|
||||
files or symbol zip packs. Symbols must be within a particular directory
|
||||
structure if they depending on the operating system of the symbols,
|
||||
structure if they depend on the operating system of the symbols,
|
||||
whilst symbol packs must be in the root of the directory and named after
|
||||
the after the operating system to which they apply.
|
||||
the operating system to which they apply.
|
||||
|
||||
-v, --verbose
|
||||
A flag which can be used multiple times, each time increasing the level of
|
||||
A flag which can be used multiple times (up to six, -vvvvvv), each time increasing the level of
|
||||
detail in the logs produced.
|
||||
|
||||
-l LOG, --log LOG
|
||||
@@ -87,7 +87,7 @@ Options
|
||||
-q, --quiet
|
||||
When present, this flag mutes the progress feedback for operations. This
|
||||
can be beneficial when piping the output directly to a file or another
|
||||
tool. This also removes the
|
||||
tool.
|
||||
|
||||
-r RENDERER, --renderer RENDERER
|
||||
Specifies the output format in which to display results. The default is
|
||||
@@ -120,9 +120,7 @@ Options
|
||||
Change the default path used to store the cache.
|
||||
|
||||
--offline
|
||||
Do not search online for additional JSON files.
|
||||
Run offline mode (defaults to false) and for
|
||||
remote windows symbol tables, linux/mac banner repositories.
|
||||
Run offline mode (defaults to false). Do not search online for additional JSON files, remote windows symbol tables, nor linux/mac banner repositories.
|
||||
|
||||
--single-location SINGLE_LOCATION
|
||||
This specifies a URL which will be downloaded if necessary, and built
|
||||
@@ -152,7 +150,7 @@ but can be overridden by creating a JSON file (`%APPDATA%/volatility3/vol.json`
|
||||
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):
|
||||
It should be noted that the ordering is (`x < y` means `x` is overridden by `y`):
|
||||
|
||||
`in-built default value < config file value < command line parameter`
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ The object model has changed as well, objects now inherit directly from their Py
|
||||
object is actually a Python integer (and has all the associated methods, and can be used wherever a normal int could).
|
||||
In Volatility 2, a complex proxy object was constructed which tried to emulate all the methods of the host object, but
|
||||
ultimately it was a different type and could not be used in the same places (critically, it could make the ordering of
|
||||
operations important, since a + b might not work, but b + a might work fine).
|
||||
operations important, since x + y might not work, but y + x might work fine).
|
||||
|
||||
Volatility 3 has also had significant speed improvements, where Volatility 2 was designed to allow access to live memory
|
||||
images and situations in which the underlying data could change during the run of the plugin, in Volatility 3 the data
|
||||
@@ -36,11 +36,11 @@ This was because live memory analysis was barely ever used, and this feature cou
|
||||
re-read many times over for no benefit (particularly since each re-read could result in many additional image reads
|
||||
from following page table translations).
|
||||
|
||||
Finally, in order to provide Volatility specific information without impact on the ability for structures to have members
|
||||
Further, in order to provide Volatility specific information without impact on the ability for structures to have members
|
||||
with arbitrary names, all the metadata about the object (such as its layer or offset) have been moved to a read-only :py:meth:`~volatility3.framework.interfaces.objects.ObjectInterface.vol`
|
||||
dictionary.
|
||||
|
||||
Further the distinction between a :py:class:`~volatility3.framework.interfaces.objects.Template` (the thing that
|
||||
Finally, the distinction between a :py:class:`~volatility3.framework.interfaces.objects.Template` (the thing that
|
||||
constructs an object) and the :py:class:`Object <volatility3.framework.interfaces.objects.ObjectInterface>` itself has
|
||||
been made more explicit. In Volatility 2, some information (such as size) could only be determined from a constructed object,
|
||||
leading to instantiating a template on an empty buffer, just to determine the size. In Volatility 3, templates contain
|
||||
@@ -56,15 +56,14 @@ Volatility 2 were strictly limited to a stack, one on top of one other. In Vola
|
||||
|
||||
Automagic
|
||||
---------
|
||||
In Volatility 2, we often tried to make this simpler for both users and developers. This resulted in something was
|
||||
referred to as automagic, in that it was magic that happened automatically. We've now codified that more, so that the
|
||||
In Volatility 2, we often tried to make this simpler for both users and developers. This resulted in something referred to as automagic, in that it was magic that happened automatically. We've now codified that more, so that the
|
||||
automagic processes are clearly defined and can be enabled or disabled as necessary for any particular run. We also
|
||||
included a stacker automagic to emulate the most common feature of Volatility 2, automatically stacking address spaces
|
||||
(now translation layers) on top of each other.
|
||||
|
||||
By default the automagic chosen to be run are determined based on the plugin requested, so that linux plugins get linux
|
||||
specific automagic and windows plugins get windows specific automagic. This should reduce unnecessarily searching for
|
||||
linux kernels in a windows image, for example. At the moment this is not user configurableS.
|
||||
By default the automagic chosen to be run are determined based on the plugin requested, so that Linux plugins get Linux
|
||||
specific automagic and Windows plugins get Windows specific automagic. This should reduce unnecessarily searching for
|
||||
Linux kernels in a Windows image, for example. At the moment this is not user configurable.
|
||||
|
||||
Searching and Scanning
|
||||
----------------------
|
||||
|
||||
+59
-6
@@ -36,7 +36,7 @@ operating system mode for volshell, and the current layer available for use.
|
||||
|
||||
(primary) >>>
|
||||
|
||||
Volshell itself in essentially a plugin, but an interactive one. As such, most values are accessed through `self`
|
||||
Volshell itself is essentially a plugin, but an interactive one. As such, most values are accessed through `self`
|
||||
although there is also a `context` object whenever a context must be provided.
|
||||
|
||||
The prompt for the tool will indicate the name of the current layer (which can be accessed as `self.current_layer`
|
||||
@@ -92,7 +92,7 @@ It can also be provided with an object and will interpret the data for each in t
|
||||
0x2e8 : UniqueProcessId symbol_table_name1!pointer 4
|
||||
...
|
||||
|
||||
These values can be accessed directory as attributes
|
||||
These values can be accessed directly as attributes
|
||||
|
||||
::
|
||||
|
||||
@@ -144,12 +144,12 @@ We can provide arguments via the `dpo` method call:
|
||||
356 4 smss.exe 0x8c0bccf8d040 3 - N/A False 2021-03-13 17:25:33.000000 N/A Disabled
|
||||
...
|
||||
|
||||
Here's we've provided the kernel name that was requested by the volshell plugin itself (the generic volshell does not
|
||||
Here we've provided the kernel name that was requested by the volshell plugin itself (the generic volshell does not
|
||||
load a kernel module, and instead only has a TranslationLayerRequirement).
|
||||
A different module could be created and provided instead. The context used
|
||||
by the `dpo` method is always `context`.
|
||||
|
||||
Instead of print the results directly to screen, they can be gathered into a TreeGrid objects for direct access by
|
||||
Instead of printing the results directly to screen, they can be gathered into a TreeGrid objects for direct access by
|
||||
using the `generate_treegrid` or `gt` command.
|
||||
|
||||
::
|
||||
@@ -180,15 +180,68 @@ used:
|
||||
|
||||
layer = cc(mynewlayer.MyNewLayer, on_top_of = 'primary', other_parameter = 'important')
|
||||
with open('output.dmp', 'wb') as fp:
|
||||
for i in range(0, 1073741824, 0x1000):
|
||||
for i in range(0, 0x4000000, 0x1000):
|
||||
data = layer.read(i, 0x1000, pad = True)
|
||||
fp.write(data)
|
||||
|
||||
As this demonstrates, all of the python is accessible, as are the volshell built in functions (such as `cc` which
|
||||
creates a constructable, like a layer or a symbol table).
|
||||
|
||||
User Convenience
|
||||
----------------
|
||||
|
||||
There are functions available that make often-done tasks easier, and generally provide a shell-like experience. These can be listed using `help()` which, as already mentioned, is advertised when volshell starts.
|
||||
|
||||
Loading files
|
||||
-------------
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
Files can be loaded as physical layers using the `load_file` or `lf` command, which takes a filename or a URI. This will be added
|
||||
to `context.layers` and can be accessed by the name returned by `lf`.
|
||||
|
||||
Regex
|
||||
^^^^^
|
||||
|
||||
It is easy to scan for some bytes or a pattern using `regex_scan` or `rx`.
|
||||
|
||||
::
|
||||
|
||||
(layer_name) >>> rx(rb"(Linux version|Darwin Kernel Version) [0-9]+\.[0-9]+\.[0-9]+")
|
||||
0x880001400070 4c 69 6e 75 78 20 76 65 72 73 69 6f 6e 20 33 2e Linux.version.3.
|
||||
0x880001400080 32 2e 30 2d 34 2d 61 6d 64 36 34 20 28 64 65 62 2.0-4-amd64.(deb
|
||||
0x880001400090 69 61 6e 2d 6b 65 72 6e 65 6c 40 6c 69 73 74 73 ian-kernel@lists
|
||||
0x8800014000a0 2e 64 65 62 69 61 6e 2e 6f 72 67 29 20 28 67 63 .debian.org).(gc
|
||||
0x8800014000b0 63 20 76 65 72 73 69 6f 6e 20 34 2e 36 2e 33 20 c.version.4.6.3.
|
||||
0x8800014000c0 28 44 65 62 69 61 6e 20 34 2e 36 2e 33 2d 31 34 (Debian.4.6.3-14
|
||||
0x8800014000d0 29 20 29 20 23 31 20 53 4d 50 20 44 65 62 69 61 ).).#1.SMP.Debia
|
||||
0x8800014000e0 6e 20 33 2e 32 2e 35 37 2d 33 2b 64 65 62 37 75 n.3.2.57-3+deb7u
|
||||
|
||||
0x880001769027 4c 69 6e 75 78 20 76 65 72 73 69 6f 6e 20 33 2e Linux.version.3.
|
||||
0x880001769037 32 2e 30 2d 34 2d 61 6d 64 36 34 20 28 64 65 62 2.0-4-amd64.(deb
|
||||
0x880001769047 69 61 6e 2d 6b 65 72 6e 65 6c 40 6c 69 73 74 73 ian-kernel@lists
|
||||
0x880001769057 2e 64 65 62 69 61 6e 2e 6f 72 67 29 20 28 67 63 .debian.org).(gc
|
||||
0x880001769067 63 20 76 65 72 73 69 6f 6e 20 34 2e 36 2e 33 20 c.version.4.6.3.
|
||||
0x880001769077 28 44 65 62 69 61 6e 20 34 2e 36 2e 33 2d 31 34 (Debian.4.6.3-14
|
||||
0x880001769087 29 20 29 20 23 31 20 53 4d 50 20 44 65 62 69 61 ).).#1.SMP.Debia
|
||||
0x880001769097 6e 20 33 2e 32 2e 35 37 2d 33 2b 64 65 62 37 75 n.3.2.57-3+deb7u
|
||||
|
||||
0xffff81400070 4c 69 6e 75 78 20 76 65 72 73 69 6f 6e 20 33 2e Linux.version.3.
|
||||
0xffff81400080 32 2e 30 2d 34 2d 61 6d 64 36 34 20 28 64 65 62 2.0-4-amd64.(deb
|
||||
0xffff81400090 69 61 6e 2d 6b 65 72 6e 65 6c 40 6c 69 73 74 73 ian-kernel@lists
|
||||
0xffff814000a0 2e 64 65 62 69 61 6e 2e 6f 72 67 29 20 28 67 63 .debian.org).(gc
|
||||
0xffff814000b0 63 20 76 65 72 73 69 6f 6e 20 34 2e 36 2e 33 20 c.version.4.6.3.
|
||||
0xffff814000c0 28 44 65 62 69 61 6e 20 34 2e 36 2e 33 2d 31 34 (Debian.4.6.3-14
|
||||
0xffff814000d0 29 20 29 20 23 31 20 53 4d 50 20 44 65 62 69 61 ).).#1.SMP.Debia
|
||||
0xffff814000e0 6e 20 33 2e 32 2e 35 37 2d 33 2b 64 65 62 37 75 n.3.2.57-3+deb7u
|
||||
|
||||
0xffff81769027 4c 69 6e 75 78 20 76 65 72 73 69 6f 6e 20 33 2e Linux.version.3.
|
||||
0xffff81769037 32 2e 30 2d 34 2d 61 6d 64 36 34 20 28 64 65 62 2.0-4-amd64.(deb
|
||||
0xffff81769047 69 61 6e 2d 6b 65 72 6e 65 6c 40 6c 69 73 74 73 ian-kernel@lists
|
||||
0xffff81769057 2e 64 65 62 69 61 6e 2e 6f 72 67 29 20 28 67 63 .debian.org).(gc
|
||||
0xffff81769067 63 20 76 65 72 73 69 6f 6e 20 34 2e 36 2e 33 20 c.version.4.6.3.
|
||||
0xffff81769077 28 44 65 62 69 61 6e 20 34 2e 36 2e 33 2d 31 34 (Debian.4.6.3-14
|
||||
0xffff81769087 29 20 29 20 23 31 20 53 4d 50 20 44 65 62 69 61 ).).#1.SMP.Debia
|
||||
0xffff81769097 6e 20 33 2e 32 2e 35 37 2d 33 2b 64 65 62 37 75 n.3.2.57-3+deb7u
|
||||
|
||||
An optional size can be given for the displayed results as with the other fuctions (db, dw, dd, dq, etc).
|
||||
|
||||
You can, of course, specify a different layer name as well.
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
[mypy]
|
||||
mypy_path = ./stubs
|
||||
show_traceback = True
|
||||
ignore_missing_imports = True
|
||||
+76
-7
@@ -1,20 +1,68 @@
|
||||
[project]
|
||||
name = "volatility3"
|
||||
description = "Memory forensics framework"
|
||||
keywords = ["volatility", "memory", "forensics", "framework", "windows", "linux", "volshell"]
|
||||
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"]
|
||||
dynamic = ["version"]
|
||||
|
||||
dependencies = ["pefile>=2024.8.26"]
|
||||
|
||||
[project.optional-dependencies]
|
||||
full = [
|
||||
"yara-python>=4.5.1,<5",
|
||||
"capstone>=5.0.3,<6",
|
||||
"pycryptodome>=3.21.0,<4",
|
||||
"leechcorepyc>=2.19.2,<3; sys_platform != 'darwin'",
|
||||
# https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst
|
||||
# 10.0.0 dropped support for Python3.7
|
||||
# 11.0.0 dropped support for Python3.8, which is still supported by Volatility3
|
||||
"pillow>=10.0.0,<11.0.0",
|
||||
]
|
||||
|
||||
cloud = ["gcsfs>=2024.10.0", "s3fs>=2024.10.0"]
|
||||
|
||||
dev = [
|
||||
"volatility3[full,cloud]",
|
||||
"jsonschema>=4.23.0,<5",
|
||||
"pyinstaller>=6.5.0,<7",
|
||||
"pyinstaller-hooks-contrib>=2024.9",
|
||||
"types-jsonschema>=4.23.0,<5",
|
||||
]
|
||||
|
||||
arrow = ["pyarrow>=17.0.0"]
|
||||
|
||||
test = [
|
||||
"volatility3[dev]",
|
||||
"pytest>=8.3.3,<9",
|
||||
"pytest-cov>=5,<7",
|
||||
"yara-x>=0.10.0,<1",
|
||||
]
|
||||
|
||||
docs = [
|
||||
"volatility3[dev]",
|
||||
"sphinx>=4.0.0,<9",
|
||||
"sphinx-autodoc-typehints>=3.0.0,<4; python_version >= '3.11'",
|
||||
"sphinx-rtd-theme>=3.0.1,<4",
|
||||
]
|
||||
|
||||
[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"
|
||||
homepage = "https://github.com/volatilityfoundation/volatility3/"
|
||||
documentation = "https://volatility3.readthedocs.io/"
|
||||
repository = "https://github.com/volatilityfoundation/volatility3"
|
||||
issues = "https://github.com/volatilityfoundation/volatility3/issues"
|
||||
|
||||
[project.scripts]
|
||||
vol = "volatility3.cli:main"
|
||||
@@ -22,11 +70,32 @@ 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*"]
|
||||
|
||||
[tool.mypy]
|
||||
mypy_path = "./stubs"
|
||||
show_traceback = true
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 88
|
||||
target-version = "py38"
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
"F", # pyflakes
|
||||
"E", # pycodestyle errors
|
||||
"W", # pycodestyle warnings
|
||||
"G", # flake8-logging-format
|
||||
"PIE", # flake8-pie
|
||||
"UP", # pyupgrade
|
||||
]
|
||||
|
||||
ignore = [
|
||||
"E501", # ignore due to conflict with formatter
|
||||
]
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=68"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
-r requirements.txt
|
||||
|
||||
# This can improve error messages regarding improperly configured ISF files,
|
||||
# but is only recommended for development
|
||||
jsonschema>=2.3.0
|
||||
|
||||
# Used to build executable file
|
||||
pyinstaller>=6.5.0
|
||||
pyinstaller-hooks-contrib>=2024.3
|
||||
@@ -1,2 +0,0 @@
|
||||
# These packages are required for core functionality.
|
||||
pefile>=2023.2.7 #foo
|
||||
@@ -1,23 +0,0 @@
|
||||
# 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.
|
||||
|
||||
# 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.
|
||||
# FIXME: Version 6.0.0 is incompatible (#1336) so we'll need an adaptor at some point
|
||||
capstone>=3.0.5,<6.0.0
|
||||
|
||||
# This is required by plugins that decrypt passwords, password hashes, etc.
|
||||
pycryptodome
|
||||
|
||||
# This is required for memory acquisition via leechcore/pcileech.
|
||||
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
|
||||
@@ -1,24 +0,0 @@
|
||||
# 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 setuptools
|
||||
|
||||
|
||||
def get_requires(filename):
|
||||
requirements = []
|
||||
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(("#", "-r")):
|
||||
continue
|
||||
requirements.append(stripped_line)
|
||||
return requirements
|
||||
|
||||
|
||||
setuptools.setup(
|
||||
extras_require={
|
||||
"dev": get_requires("requirements-dev.txt"),
|
||||
"full": get_requires("requirements.txt"),
|
||||
},
|
||||
)
|
||||
+2
-4
@@ -2,14 +2,12 @@
|
||||
|
||||
## Requirements
|
||||
|
||||
The Volatility 3 Testing Framework requires the same version of Python as Volatility3 itself. To install the current set of dependencies that the framework requires, use a command like this:
|
||||
The Volatility 3 Testing Framework requires the same version of Python as Volatility 3 itself. To install the current set of dependencies that the framework requires, use a command like this:
|
||||
|
||||
```shell
|
||||
pip3 install -r requirements-testing.txt
|
||||
pip3 install -e .[test]
|
||||
```
|
||||
|
||||
NOTE: `requirements-testing.txt` can be found in this current `test/` directory.
|
||||
|
||||
## Quick Start: Manual Testing
|
||||
|
||||
1. To test Volatility 3 on an image, first download one with a command such as:
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
from enum import Enum
|
||||
from pathlib import Path
|
||||
|
||||
TESTS_ROOT_DIR = Path(__file__).parent
|
||||
WINDOWS_TESTS_DATA_DIR = TESTS_ROOT_DIR / "plugins" / "windows" / "test_data"
|
||||
|
||||
|
||||
class Sample:
|
||||
def __init__(self, path: str):
|
||||
self.path = path
|
||||
|
||||
|
||||
class WindowsSamples(Enum):
|
||||
WINDOWSXP_GENERIC = Sample("./test_images/win-xp-laptop-2005-06-25.img")
|
||||
"""WindowsXP sample from early Volatility training."""
|
||||
WINDOWS10_GENERIC = Sample("./test_images/win-10_19041-2025_03.dmp")
|
||||
"""Windows10 CrashDump sample."""
|
||||
|
||||
|
||||
class LinuxSamples(Enum):
|
||||
LINUX_GENERIC = Sample("./test_images/linux-sample-1.bin")
|
||||
"""Linux Debian 3.2.0-4 sample from early Volatility training."""
|
||||
|
||||
+28
-5
@@ -35,16 +35,39 @@ def pytest_addoption(parser):
|
||||
def pytest_generate_tests(metafunc):
|
||||
"""Parameterize tests based on image names"""
|
||||
|
||||
images = metafunc.config.getoption("image")
|
||||
images = metafunc.config.getoption("image").copy()
|
||||
for image_dir in metafunc.config.getoption("image_dir"):
|
||||
images = images + [
|
||||
os.path.join(image_dir, dir) for dir in os.listdir(image_dir)
|
||||
images += [
|
||||
os.path.join(image_dir, dir_name) for dir_name in os.listdir(image_dir)
|
||||
]
|
||||
|
||||
# tests with "image" parameter are run against images
|
||||
# tests with "image" parameter are run against image
|
||||
if "image" in metafunc.fixturenames:
|
||||
filtered_images = []
|
||||
ids = []
|
||||
for image in images:
|
||||
image_base = os.path.basename(image)
|
||||
test_name = metafunc.definition.originalname
|
||||
if test_name.startswith("test_windows_") and not image_base.startswith(
|
||||
"win-"
|
||||
):
|
||||
continue
|
||||
elif test_name.startswith("test_linux_") and not image_base.startswith(
|
||||
"linux-"
|
||||
):
|
||||
continue
|
||||
elif test_name.startswith("test_mac_") and not image_base.startswith(
|
||||
"mac-"
|
||||
):
|
||||
continue
|
||||
|
||||
filtered_images.append(image)
|
||||
ids.append(image_base)
|
||||
|
||||
metafunc.parametrize(
|
||||
"image", images, ids=[os.path.basename(image) for image in images]
|
||||
"image",
|
||||
filtered_images,
|
||||
ids=ids,
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,654 @@
|
||||
import contextlib
|
||||
import tempfile
|
||||
import os
|
||||
import re
|
||||
from test import test_volatility, LinuxSamples
|
||||
|
||||
|
||||
class TestLinuxVolshell:
|
||||
def test_linux_volshell(self, image, volatility, python):
|
||||
out = test_volatility.basic_volshell_test(
|
||||
image, volatility, python, volshellargs=("-l",)
|
||||
)
|
||||
assert out.count(b"<task_struct") > 100
|
||||
|
||||
|
||||
class TestLinuxPslist:
|
||||
def test_linux_generic_pslist(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.pslist.PsList", image, volatility, python
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
out = out.lower()
|
||||
assert (out.find(b"init") != -1) or (out.find(b"systemd") != -1)
|
||||
assert out.find(b"watchdog") != -1
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
class TestLinuxCheckIdt:
|
||||
def test_linux_generic_check_idt(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.malware.check_idt.Check_idt", image, volatility, python
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
out = out.lower()
|
||||
assert out.count(b"__kernel__") >= 10
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
class TestLinuxCheckSyscall:
|
||||
def test_linux_generic_check_syscall(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.malware.check_syscall.Check_syscall", image, volatility, python
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
out = out.lower()
|
||||
assert out.find(b"sys_close") != -1
|
||||
assert out.find(b"sys_open") != -1
|
||||
assert out.count(b"\n") > 100
|
||||
|
||||
|
||||
class TestLinuxLsmod:
|
||||
def test_linux_generic_lsmod(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.lsmod.Lsmod", image, volatility, python
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
out = out.lower()
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
class TestLinuxLsof:
|
||||
def test_linux_generic_lsof(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.lsof.Lsof", image, volatility, python
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
out = out.lower()
|
||||
assert out.count(b"socket:") >= 10
|
||||
assert out.count(b"\n") > 35
|
||||
|
||||
|
||||
class TestLinuxProcMaps:
|
||||
def test_linux_generic_proc_maps(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.proc.Maps", image, volatility, python
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
out = out.lower()
|
||||
assert out.count(b"anonymous mapping") >= 10
|
||||
assert out.count(b"\n") > 100
|
||||
|
||||
|
||||
class TestLinuxTtyCheck:
|
||||
def test_linux_generic_tty_check(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.malware.tty_check.Tty_Check", image, volatility, python
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
out = out.lower()
|
||||
assert out.find(b"__kernel__") != -1
|
||||
assert out.count(b"\n") >= 5
|
||||
|
||||
|
||||
class TestLinuxSockstat:
|
||||
def test_linux_generic_sockstat(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.sockstat.Sockstat", image, volatility, python
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
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
|
||||
|
||||
|
||||
class TestLinuxLibraryList:
|
||||
def test_linux_specific_library_list(self, volatility, python):
|
||||
image = LinuxSamples.LINUX_GENERIC.value.path
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.library_list.LibraryList",
|
||||
image,
|
||||
volatility,
|
||||
python,
|
||||
pluginargs=("--pids", "2363"),
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
assert re.search(
|
||||
rb"NetworkManager\s2363\s0x7f52cdda0000\s/lib/x86_64-linux-gnu/libnss_files.so.2",
|
||||
out,
|
||||
)
|
||||
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
class TestLinuxPstree:
|
||||
def test_linux_generic_pstree(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.pstree.PsTree", image, volatility, python
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
out = out.lower()
|
||||
assert (out.find(b"init") != -1) or (out.find(b"systemd") != -1)
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
class TestLinuxPidhashtable:
|
||||
def test_linux_generic_pidhashtable(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.pidhashtable.PIDHashTable", image, volatility, python
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
out = out.lower()
|
||||
assert (out.find(b"init") != -1) or (out.find(b"systemd") != -1)
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
class TestLinuxBash:
|
||||
def test_linux_bash(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.bash.Bash", image, volatility, python
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
class TestLinuxBoottime:
|
||||
def test_linux_generic_boottime(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.boottime.Boottime", image, volatility, python
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
out = out.lower()
|
||||
assert out.count(b"utc") >= 1
|
||||
|
||||
|
||||
class TestLinuxCapabilities:
|
||||
def test_linux_generic_capabilities(self, image, volatility, python):
|
||||
rc, out, err = test_volatility.runvol_plugin(
|
||||
"linux.capabilities.Capabilities",
|
||||
image,
|
||||
volatility,
|
||||
python,
|
||||
globalargs=("-vvv",),
|
||||
)
|
||||
|
||||
if rc != 0 and err.count(b"Unsupported kernel capabilities implementation") > 0:
|
||||
# The linux-sample-1.bin kernel implementation isn't supported.
|
||||
# However, we can still check that the plugin requirements are met.
|
||||
return None
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
class TestLinuxCheckCreds:
|
||||
def test_linux_generic_check_creds(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.malware.check_creds.Check_creds", image, volatility, python
|
||||
)
|
||||
|
||||
# linux-sample-1.bin has no processes sharing credentials.
|
||||
# This validates that plugin requirements are met and exceptions are not raised.
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
|
||||
class TestLinuxElfs:
|
||||
def test_linux_generic_elfs(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.elfs.Elfs", image, volatility, python
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
class TestLinuxEnvars:
|
||||
def test_linux_generic_envars(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.envars.Envars", image, volatility, python
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
class TestLinuxKthreads:
|
||||
def test_linux_generic_kthreads(self, image, volatility, python):
|
||||
rc, out, err = test_volatility.runvol_plugin(
|
||||
"linux.kthreads.Kthreads",
|
||||
image,
|
||||
volatility,
|
||||
python,
|
||||
globalargs=("-vvv",),
|
||||
)
|
||||
|
||||
if rc != 0 and err.count(b"Unsupported kthread implementation") > 0:
|
||||
# The linux-sample-1.bin kernel implementation isn't supported.
|
||||
# However, we can still check that the plugin requirements are met.
|
||||
return None
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
|
||||
class TestLinuxMalfind:
|
||||
def test_linux_generic_malfind(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.malware.malfind.Malfind", image, volatility, python
|
||||
)
|
||||
|
||||
# linux-sample-1.bin has no process memory ranges with potential injected code.
|
||||
# This validates that plugin requirements are met and exceptions are not raised.
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
|
||||
class TestLinuxMountinfo:
|
||||
def test_linux_generic_mountinfo(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.mountinfo.MountInfo", image, volatility, python
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
class TestLinuxPsaux:
|
||||
def test_linux_generic_psaux(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.psaux.PsAux", image, volatility, python
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 50
|
||||
|
||||
|
||||
class TestLinuxPtrace:
|
||||
def test_linux_generic_ptrace(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.ptrace.Ptrace", image, volatility, python
|
||||
)
|
||||
|
||||
# linux-sample-1.bin has no processes being ptraced.
|
||||
# This validates that plugin requirements are met and exceptions are not raised.
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
|
||||
class TestLinuxVmaregexscan:
|
||||
def test_linux_generic_vmaregexscan(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.vmaregexscan.VmaRegExScan",
|
||||
image,
|
||||
volatility,
|
||||
python,
|
||||
pluginargs=("--pid", "1", "--pattern", "\\x7fELF"),
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
class TestLinuxVmayarascanYaraRule:
|
||||
def test_linux_specific_vmayarascan_yara_rule(self, volatility, python):
|
||||
image = LinuxSamples.LINUX_GENERIC.value.path
|
||||
yara_rule_01 = r"""
|
||||
rule fullvmayarascan
|
||||
{
|
||||
strings:
|
||||
$s1 = "_nss_files_parse_grent"
|
||||
$s2 = "/lib64/ld-linux-x86-64.so.2"
|
||||
$s3 = "(bufferend - (char *) 0) % sizeof (char *) == 0"
|
||||
condition:
|
||||
all of them
|
||||
}
|
||||
"""
|
||||
|
||||
# FIXME: When the minimum Python version includes 3.12, replace the following with:
|
||||
# with tempfile.NamedTemporaryFile(delete_on_close=False) as fd: ...
|
||||
fd, filename = tempfile.mkstemp(suffix=".yar")
|
||||
try:
|
||||
with os.fdopen(fd, "w") as f:
|
||||
f.write(yara_rule_01)
|
||||
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.vmayarascan.VmaYaraScan",
|
||||
image,
|
||||
volatility,
|
||||
python,
|
||||
pluginargs=("--pid", "8600", "--yara-file", filename),
|
||||
)
|
||||
finally:
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
os.remove(filename)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 4
|
||||
|
||||
|
||||
class TestLinuxVmayarascanYaraString:
|
||||
def test_linux_generic_vmayarascan_yara_string(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.vmayarascan.VmaYaraScan",
|
||||
image,
|
||||
volatility,
|
||||
python,
|
||||
pluginargs=("--pid", "1", "--yara-string", "ELF"),
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
|
||||
class TestLinuxPageCacheFiles:
|
||||
def test_linux_specific_page_cache_files(self, volatility, python):
|
||||
image = LinuxSamples.LINUX_GENERIC.value.path
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.pagecache.Files",
|
||||
image,
|
||||
volatility,
|
||||
python,
|
||||
pluginargs=("--find", "/etc/passwd"),
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 4
|
||||
|
||||
# inode_num inode_addr ... file_path
|
||||
assert re.search(
|
||||
rb"146829\s0x88001ab5c270.*?/etc/passwd",
|
||||
out,
|
||||
)
|
||||
|
||||
|
||||
class TestLinuxPageCacheInodepages:
|
||||
def test_linux_specific_page_cache_inodepages(self, volatility, python):
|
||||
image = LinuxSamples.LINUX_GENERIC.value.path
|
||||
inode_address = hex(0x88001AB5C270)
|
||||
inode_dump_filename = f"inode_{inode_address}.dmp"
|
||||
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.pagecache.InodePages",
|
||||
image,
|
||||
volatility,
|
||||
python,
|
||||
pluginargs=("--inode", inode_address),
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 4
|
||||
|
||||
# PageVAddr PagePAddr MappingAddr .. DumpSafe
|
||||
assert re.search(
|
||||
rb"0xea000054c5f8\s0x18389000\s0x88001ab5c3b0.*?True",
|
||||
out,
|
||||
)
|
||||
|
||||
try:
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.pagecache.InodePages",
|
||||
image,
|
||||
volatility,
|
||||
python,
|
||||
pluginargs=("--inode", inode_address, "--dump"),
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
assert os.path.exists(inode_dump_filename)
|
||||
with open(inode_dump_filename, "rb") as fp:
|
||||
inode_contents = fp.read()
|
||||
assert inode_contents.count(b"\n") > 30
|
||||
assert inode_contents.count(b"root:x:0:0:root:/root:/bin/bash") > 0
|
||||
finally:
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
os.remove(inode_dump_filename)
|
||||
|
||||
|
||||
class TestLinuxCheckAfinfo:
|
||||
def test_linux_generic_check_afinfo(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.malware.check_afinfo.Check_afinfo", image, volatility, python
|
||||
)
|
||||
|
||||
# linux-sample-1.bin has no suspicious results.
|
||||
# This validates that plugin requirements are met and exceptions are not raised.
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
|
||||
class TestLinuxCheckModules:
|
||||
def test_linux_generic_check_modules(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.malware.check_modules.Check_modules", image, volatility, python
|
||||
)
|
||||
|
||||
# linux-sample-1.bin has no suspicious results.
|
||||
# This validates that plugin requirements are met and exceptions are not raised.
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
|
||||
class TestLinuxEbpf:
|
||||
def test_linux_generic_ebpf_progs(self, image, volatility, python):
|
||||
rc, out, err = test_volatility.runvol_plugin(
|
||||
"linux.ebpf.EBPF",
|
||||
image,
|
||||
volatility,
|
||||
python,
|
||||
globalargs=("-vvv",),
|
||||
)
|
||||
|
||||
if rc != 0 and err.count(b"Unsupported kernel") > 0:
|
||||
# The linux-sample-1.bin kernel implementation isn't supported.
|
||||
# However, we can still check that the plugin requirements are met.
|
||||
return None
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 4
|
||||
|
||||
|
||||
class TestLinuxIomem:
|
||||
def test_linux_generic_iomem(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.iomem.IOMem", image, volatility, python
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 100
|
||||
|
||||
|
||||
class TestLinuxKeyboardNotifiers:
|
||||
def test_linux_generic_keyboard_notifiers(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.malware.keyboard_notifiers.Keyboard_notifiers",
|
||||
image,
|
||||
volatility,
|
||||
python,
|
||||
)
|
||||
|
||||
# linux-sample-1.bin has no suspicious results for this plugin.
|
||||
# This validates that plugin requirements are met and exceptions are not raised.
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
|
||||
class TestLinuxKmesg:
|
||||
def test_linux_generic_kmesg(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.kmsg.Kmsg", image, volatility, python
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 100
|
||||
|
||||
|
||||
class TestLinuxNetfilter:
|
||||
def test_linux_generic_netfilter(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.malware.netfilter.Netfilter", image, volatility, python
|
||||
)
|
||||
|
||||
# linux-sample-1.bin has no suspicious results for this plugin.
|
||||
# This validates that plugin requirements are met and exceptions are not raised.
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
|
||||
class TestLinuxPsscan:
|
||||
def test_linux_generic__psscan(self, image, volatility, python):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.psscan.PsScan", image, volatility, python
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 100
|
||||
|
||||
|
||||
class TestLinuxHiddenModules:
|
||||
def test_linux_specific_hidden_modules(self, volatility, python):
|
||||
# TODO: this check should be specific, against a distinct infected sample
|
||||
image = LinuxSamples.LINUX_GENERIC.value.path
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.malware.hidden_modules.Hidden_modules", image, volatility, python
|
||||
)
|
||||
|
||||
# linux-sample-1.bin has no hidden modules.
|
||||
# This validates that plugin requirements are met and exceptions are not raised.
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
|
||||
class TestLinuxIpAddr:
|
||||
def test_linux_specific_ip_addr(self, volatility, python):
|
||||
image = LinuxSamples.LINUX_GENERIC.value.path
|
||||
rc, out, err = test_volatility.runvol_plugin(
|
||||
"linux.ip.Addr", image, volatility, python
|
||||
)
|
||||
|
||||
assert re.search(
|
||||
rb"2\s+eth0\s+00:0c:29:8f:ed:ca\s+False\s+192.168.201.161\s+24\s+global\s+UP",
|
||||
out,
|
||||
)
|
||||
assert re.search(
|
||||
rb"2\s+eth0\s+00:0c:29:8f:ed:ca\s+False\s+fe80::20c:29ff:fe8f:edca\s+64\s+link\s+UP",
|
||||
out,
|
||||
)
|
||||
assert out.count(b"\n") >= 8
|
||||
assert rc == 0
|
||||
|
||||
|
||||
class TestLinuxIpLink:
|
||||
def test_linux_specific_ip_link(self, volatility, python):
|
||||
image = LinuxSamples.LINUX_GENERIC.value.path
|
||||
rc, out, err = test_volatility.runvol_plugin(
|
||||
"linux.ip.Link", image, volatility, python
|
||||
)
|
||||
|
||||
assert re.search(
|
||||
rb"-\s+lo\s+00:00:00:00:00:00\s+UNKNOWN\s+16436\s+noqueue\s+0\s+LOOPBACK,LOWER_UP,UP",
|
||||
out,
|
||||
)
|
||||
assert re.search(
|
||||
rb"-\s+eth0\s+00:0c:29:8f:ed:ca\s+UP\s+1500\s+pfifo_fast\s+1000\s+BROADCAST,LOWER_UP,MULTICAST,UP",
|
||||
out,
|
||||
)
|
||||
assert out.count(b"\n") >= 6
|
||||
assert rc == 0
|
||||
|
||||
|
||||
class TestLinuxKallsyms:
|
||||
def test_linux_specific_kallsyms(self, volatility, python):
|
||||
image = LinuxSamples.LINUX_GENERIC.value.path
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.kallsyms.Kallsyms",
|
||||
image,
|
||||
volatility,
|
||||
python,
|
||||
pluginargs=("--modules",),
|
||||
)
|
||||
# linux-sample-1.bin has no hidden modules.
|
||||
# This validates that plugin requirements are met and exceptions are not raised.
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 1000
|
||||
|
||||
# Addr Type Size Exported SubSystem ModuleName SymbolName Description
|
||||
# 0xffffa009eba9 t 28 False module usbcore usb_mon_register Symbol is in the text (code) section
|
||||
assert re.search(
|
||||
rb"0xffffa009eba9\s+t\s+28\s+False\s+module\s+usbcore\s+usb_mon_register\s+Symbol is in the text \(code\) section",
|
||||
out,
|
||||
)
|
||||
|
||||
|
||||
class TestLinuxPscallstack:
|
||||
def test_linux_specific_pscallstack(self, volatility, python):
|
||||
image = LinuxSamples.LINUX_GENERIC.value.path
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.pscallstack.PsCallStack",
|
||||
image,
|
||||
volatility,
|
||||
python,
|
||||
pluginargs=("--pid", "1"),
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 30
|
||||
|
||||
# TID Comm Position Address Value Name Type Module
|
||||
# 1 init 39 0x88001f999a40 0xffff81109039 do_select T kernel
|
||||
assert re.search(
|
||||
rb"1\s+init\s+39\s+0x88001f999a40.*?0xffff81109039\s+do_select\s+T\s+kernel",
|
||||
out,
|
||||
)
|
||||
|
||||
|
||||
class TestLinuxSockscan:
|
||||
def test_linux_sockscan(self, volatility, python):
|
||||
# designed for linux-sample-1.dmp SHA1:1C3A4627EDCA94A7ADE3414592BEF0E62D7D3BB6
|
||||
image = LinuxSamples.LINUX_GENERIC.value.path
|
||||
rc, out, err = test_volatility.runvol_plugin(
|
||||
"linux.sockscan.Sockscan", image, volatility, python
|
||||
)
|
||||
|
||||
# ensure that multiple unix paths for sockets have been found
|
||||
assert (
|
||||
len(
|
||||
re.findall(
|
||||
rb"(/[ -~]+?){1,8}",
|
||||
out,
|
||||
)
|
||||
)
|
||||
>= 10
|
||||
)
|
||||
|
||||
# ensure that multiple IPv4 addresses have been found
|
||||
assert (
|
||||
len(
|
||||
re.findall(
|
||||
rb"((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}",
|
||||
out,
|
||||
)
|
||||
)
|
||||
>= 10
|
||||
)
|
||||
|
||||
assert out.count(b"\n") >= 50
|
||||
assert rc == 0
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"GENERIC": [
|
||||
"IRP_MJ_CREATE",
|
||||
"IRP_MJ_CREATE_NAMED_PIPE",
|
||||
"IRP_MJ_CLOSE",
|
||||
"IRP_MJ_READ",
|
||||
"IRP_MJ_WRITE",
|
||||
"IRP_MJ_QUERY_INFORMATION",
|
||||
"IRP_MJ_SET_INFORMATION",
|
||||
"IRP_MJ_QUERY_EA",
|
||||
"IRP_MJ_SET_EA",
|
||||
"IRP_MJ_FLUSH_BUFFERS",
|
||||
"IRP_MJ_QUERY_VOLUME_INFORMATION",
|
||||
"IRP_MJ_SET_VOLUME_INFORMATION",
|
||||
"IRP_MJ_DIRECTORY_CONTROL",
|
||||
"IRP_MJ_FILE_SYSTEM_CONTROL",
|
||||
"IRP_MJ_DEVICE_CONTROL",
|
||||
"IRP_MJ_INTERNAL_DEVICE_CONTROL",
|
||||
"IRP_MJ_SHUTDOWN",
|
||||
"IRP_MJ_LOCK_CONTROL",
|
||||
"IRP_MJ_CLEANUP",
|
||||
"IRP_MJ_CREATE_MAILSLOT",
|
||||
"IRP_MJ_QUERY_SECURITY",
|
||||
"IRP_MJ_SET_SECURITY",
|
||||
"IRP_MJ_POWER",
|
||||
"IRP_MJ_SYSTEM_CONTROL",
|
||||
"IRP_MJ_DEVICE_CHANGE",
|
||||
"IRP_MJ_QUERY_QUOTA",
|
||||
"IRP_MJ_SET_QUOTA",
|
||||
"IRP_MJ_PNP"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
{
|
||||
"WINDOWS10_GENERIC": [
|
||||
{
|
||||
"Value": "0xf8043601f000",
|
||||
"Variable": "Kernel Base"
|
||||
},
|
||||
{
|
||||
"Value": "0x6d4000",
|
||||
"Variable": "DTB"
|
||||
},
|
||||
{
|
||||
"Value": "True",
|
||||
"Variable": "Is64Bit"
|
||||
},
|
||||
{
|
||||
"Value": "False",
|
||||
"Variable": "IsPAE"
|
||||
},
|
||||
{
|
||||
"Value": "0 WindowsIntel32e",
|
||||
"Variable": "layer_name"
|
||||
},
|
||||
{
|
||||
"Value": "1 WindowsCrashDump64Layer",
|
||||
"Variable": "memory_layer"
|
||||
},
|
||||
{
|
||||
"Value": "2 FileLayer",
|
||||
"Variable": "base_layer"
|
||||
},
|
||||
{
|
||||
"Value": "0xf80436c1fb20",
|
||||
"Variable": "KdDebuggerDataBlock"
|
||||
},
|
||||
{
|
||||
"Value": "19041.1.amd64fre.vb_release.1912",
|
||||
"Variable": "NTBuildLab"
|
||||
},
|
||||
{
|
||||
"Value": "0",
|
||||
"Variable": "CSDVersion"
|
||||
},
|
||||
{
|
||||
"Value": "0xf80436c2e420",
|
||||
"Variable": "KdVersionBlock"
|
||||
},
|
||||
{
|
||||
"Value": "15.19041",
|
||||
"Variable": "Major/Minor"
|
||||
},
|
||||
{
|
||||
"Value": "34404",
|
||||
"Variable": "MachineType"
|
||||
},
|
||||
{
|
||||
"Value": "1",
|
||||
"Variable": "KeNumberProcessors"
|
||||
},
|
||||
{
|
||||
"Value": "2025-03-06 17:59:20+00:00",
|
||||
"Variable": "SystemTime"
|
||||
},
|
||||
{
|
||||
"Value": "C:\\Windows",
|
||||
"Variable": "NtSystemRoot"
|
||||
},
|
||||
{
|
||||
"Value": "NtProductWinNt",
|
||||
"Variable": "NtProductType"
|
||||
},
|
||||
{
|
||||
"Value": "10",
|
||||
"Variable": "NtMajorVersion"
|
||||
},
|
||||
{
|
||||
"Value": "0",
|
||||
"Variable": "NtMinorVersion"
|
||||
},
|
||||
{
|
||||
"Value": "10",
|
||||
"Variable": "PE MajorOperatingSystemVersion"
|
||||
},
|
||||
{
|
||||
"Value": "0",
|
||||
"Variable": "PE MinorOperatingSystemVersion"
|
||||
},
|
||||
{
|
||||
"Value": "34404",
|
||||
"Variable": "PE Machine"
|
||||
},
|
||||
{
|
||||
"Value": "Tue Sep 26 06:53:33 2023",
|
||||
"Variable": "PE TimeDateStamp"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,375 @@
|
||||
{
|
||||
"WINDOWS10_GENERIC": {
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Windows\\System32\\winlogon.exe",
|
||||
"Cmd": null,
|
||||
"CreateTime": "2025-03-06T17:49:34+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "winlogon.exe",
|
||||
"Offset(V)": 145201769754752,
|
||||
"PID": 3616,
|
||||
"PPID": 3568,
|
||||
"Path": null,
|
||||
"SessionId": 2,
|
||||
"Threads": 3,
|
||||
"Wow64": false,
|
||||
"__children": [
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Windows\\System32\\userinit.exe",
|
||||
"Cmd": null,
|
||||
"CreateTime": "2025-03-06T17:50:15+00:00",
|
||||
"ExitTime": "2025-03-06T17:50:32+00:00",
|
||||
"Handles": null,
|
||||
"ImageFileName": "userinit.exe",
|
||||
"Offset(V)": 145201786712256,
|
||||
"PID": 4832,
|
||||
"PPID": 3616,
|
||||
"Path": null,
|
||||
"SessionId": 2,
|
||||
"Threads": 0,
|
||||
"Wow64": false,
|
||||
"__children": [
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Windows\\explorer.exe",
|
||||
"Cmd": "C:\\Windows\\Explorer.EXE",
|
||||
"CreateTime": "2025-03-06T17:50:17+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "explorer.exe",
|
||||
"Offset(V)": 145201787191488,
|
||||
"PID": 4912,
|
||||
"PPID": 4832,
|
||||
"Path": "C:\\Windows\\Explorer.EXE",
|
||||
"SessionId": 2,
|
||||
"Threads": 57,
|
||||
"Wow64": false,
|
||||
"__children": [
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Windows\\System32\\SecurityHealthSystray.exe",
|
||||
"Cmd": "\"C:\\Windows\\System32\\SecurityHealthSystray.exe\" ",
|
||||
"CreateTime": "2025-03-06T17:51:05+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "SecurityHealth",
|
||||
"Offset(V)": 145201826054336,
|
||||
"PID": 1860,
|
||||
"PPID": 4912,
|
||||
"Path": "C:\\Windows\\System32\\SecurityHealthSystray.exe",
|
||||
"SessionId": 2,
|
||||
"Threads": 2,
|
||||
"Wow64": false,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --no-startup-window --win-session-start",
|
||||
"CreateTime": "2025-03-06T17:51:05+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "msedge.exe",
|
||||
"Offset(V)": 145201826906304,
|
||||
"PID": 3952,
|
||||
"PPID": 4912,
|
||||
"Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"SessionId": 2,
|
||||
"Threads": 61,
|
||||
"Wow64": false,
|
||||
"__children": [
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --type=renderer --string-annotations --instant-process --pdf-upsell-enabled --video-capture-use-gpu-memory-buffer --lang=en-US --js-flags=--ms-user-locale= --device-scale-factor=1 --num-raster-threads=1 --renderer-client-id=21 --time-ticks-at-unix-epoch=-1741283277737123 --launch-time-ticks=556708795 --always-read-main-dll --field-trial-handle=3928,i,15721868202469256575,6833569417170289141,262144 --variations-seed-version --mojo-platform-channel-handle=3996 /prefetch:1",
|
||||
"CreateTime": "2025-03-06T17:57:14+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "msedge.exe",
|
||||
"Offset(V)": 145201787768960,
|
||||
"PID": 5348,
|
||||
"PPID": 3952,
|
||||
"Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"SessionId": 2,
|
||||
"Threads": 19,
|
||||
"Wow64": false,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --type=utility --utility-sub-type=edge_xpay_wallet.mojom.EdgeXPayWalletService --lang=en-US --service-sandbox-type=utility --string-annotations --always-read-main-dll --field-trial-handle=6740,i,15721868202469256575,6833569417170289141,262144 --variations-seed-version --mojo-platform-channel-handle=6732 /prefetch:8",
|
||||
"CreateTime": "2025-03-06T17:57:52+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "msedge.exe",
|
||||
"Offset(V)": 145201832050880,
|
||||
"PID": 3876,
|
||||
"PPID": 3952,
|
||||
"Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"SessionId": 2,
|
||||
"Threads": 8,
|
||||
"Wow64": false,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --type=utility --utility-sub-type=edge_search_indexer.mojom.SearchIndexerInterfaceBroker --lang=en-US --service-sandbox-type=search_indexer --message-loop-type-ui --string-annotations --always-read-main-dll --field-trial-handle=7016,i,15721868202469256575,6833569417170289141,262144 --variations-seed-version --mojo-platform-channel-handle=7128 /prefetch:8",
|
||||
"CreateTime": "2025-03-06T17:57:59+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "msedge.exe",
|
||||
"Offset(V)": 145201827619008,
|
||||
"PID": 5604,
|
||||
"PPID": 3952,
|
||||
"Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"SessionId": 2,
|
||||
"Threads": 14,
|
||||
"Wow64": false,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --type=utility --utility-sub-type=entity_extraction_service.mojom.Extractor --lang=en-US --service-sandbox-type=entity_extraction --onnx-enabled-for-ee --string-annotations --always-read-main-dll --field-trial-handle=5520,i,15721868202469256575,6833569417170289141,262144 --variations-seed-version --mojo-platform-channel-handle=5608 /prefetch:8",
|
||||
"CreateTime": "2025-03-06T17:57:16+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "msedge.exe",
|
||||
"Offset(V)": 145201827774656,
|
||||
"PID": 1000,
|
||||
"PPID": 3952,
|
||||
"Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"SessionId": 2,
|
||||
"Threads": 9,
|
||||
"Wow64": false,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --type=renderer --string-annotations --pdf-upsell-enabled --disable-gpu-compositing --video-capture-use-gpu-memory-buffer --lang=en-US --js-flags=--ms-user-locale= --device-scale-factor=1 --num-raster-threads=1 --renderer-client-id=31 --time-ticks-at-unix-epoch=-1741283277737123 --launch-time-ticks=596993509 --always-read-main-dll --field-trial-handle=6904,i,15721868202469256575,6833569417170289141,262144 --variations-seed-version --mojo-platform-channel-handle=7092 /prefetch:1",
|
||||
"CreateTime": "2025-03-06T17:57:54+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "msedge.exe",
|
||||
"Offset(V)": 145201839592000,
|
||||
"PID": 7080,
|
||||
"PPID": 3952,
|
||||
"Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"SessionId": 2,
|
||||
"Threads": 15,
|
||||
"Wow64": false,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --type=renderer --string-annotations --pdf-upsell-enabled --disable-gpu-compositing --video-capture-use-gpu-memory-buffer --lang=en-US --js-flags=--ms-user-locale= --device-scale-factor=1 --num-raster-threads=1 --renderer-client-id=35 --time-ticks-at-unix-epoch=-1741283277737123 --launch-time-ticks=625057436 --always-read-main-dll --field-trial-handle=5592,i,15721868202469256575,6833569417170289141,262144 --variations-seed-version --mojo-platform-channel-handle=5688 /prefetch:1",
|
||||
"CreateTime": "2025-03-06T17:58:23+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "msedge.exe",
|
||||
"Offset(V)": 145201828201216,
|
||||
"PID": 5132,
|
||||
"PPID": 3952,
|
||||
"Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"SessionId": 2,
|
||||
"Threads": 17,
|
||||
"Wow64": false,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --type=renderer --string-annotations --pdf-upsell-enabled --disable-gpu-compositing --video-capture-use-gpu-memory-buffer --lang=en-US --js-flags=--ms-user-locale= --device-scale-factor=1 --num-raster-threads=1 --renderer-client-id=36 --time-ticks-at-unix-epoch=-1741283277737123 --launch-time-ticks=625148972 --always-read-main-dll --field-trial-handle=7112,i,15721868202469256575,6833569417170289141,262144 --variations-seed-version --mojo-platform-channel-handle=6396 /prefetch:1",
|
||||
"CreateTime": "2025-03-06T17:58:23+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "msedge.exe",
|
||||
"Offset(V)": 145201839919232,
|
||||
"PID": 5388,
|
||||
"PPID": 3952,
|
||||
"Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"SessionId": 2,
|
||||
"Threads": 15,
|
||||
"Wow64": false,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --type=utility --utility-sub-type=network.mojom.NetworkService --lang=en-US --service-sandbox-type=none --string-annotations --always-read-main-dll --field-trial-handle=2180,i,15721868202469256575,6833569417170289141,262144 --variations-seed-version --mojo-platform-channel-handle=2512 /prefetch:3",
|
||||
"CreateTime": "2025-03-06T17:51:14+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "msedge.exe",
|
||||
"Offset(V)": 145201835704512,
|
||||
"PID": 6448,
|
||||
"PPID": 3952,
|
||||
"Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"SessionId": 2,
|
||||
"Threads": 16,
|
||||
"Wow64": false,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"Cmd": null,
|
||||
"CreateTime": "2025-03-06T17:51:16+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "msedge.exe",
|
||||
"Offset(V)": 145201668321408,
|
||||
"PID": 6672,
|
||||
"PPID": 3952,
|
||||
"Path": null,
|
||||
"SessionId": 2,
|
||||
"Threads": 9,
|
||||
"Wow64": false,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --type=utility --utility-sub-type=price_comparison_service.mojom.DataProcessor --lang=en-US --service-sandbox-type=entity_extraction --string-annotations --always-read-main-dll --field-trial-handle=6188,i,15721868202469256575,6833569417170289141,262144 --variations-seed-version --mojo-platform-channel-handle=6580 /prefetch:8",
|
||||
"CreateTime": "2025-03-06T17:58:44+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "msedge.exe",
|
||||
"Offset(V)": 145201830273728,
|
||||
"PID": 6064,
|
||||
"PPID": 3952,
|
||||
"Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"SessionId": 2,
|
||||
"Threads": 9,
|
||||
"Wow64": false,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"Cmd": "\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\" --type=gpu-process --string-annotations --gpu-preferences=UAAAAAAAAADgAAAEAAAAAAAAAAAAAAAAAABgAAEAAAAAAAAAAAAAAAAAAAACAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAEAAAAAAAAAAIAAAAAAAAAAgAAAAAAAAA --always-read-main-dll --field-trial-handle=2472,i,15721868202469256575,6833569417170289141,262144 --variations-seed-version --mojo-platform-channel-handle=2468 /prefetch:2",
|
||||
"CreateTime": "2025-03-06T17:51:14+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "msedge.exe",
|
||||
"Offset(V)": 145201834049728,
|
||||
"PID": 6456,
|
||||
"PPID": 3952,
|
||||
"Path": "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"SessionId": 2,
|
||||
"Threads": 15,
|
||||
"Wow64": false,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
|
||||
"Cmd": null,
|
||||
"CreateTime": "2025-03-06T17:51:11+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "msedge.exe",
|
||||
"Offset(V)": 145201786340096,
|
||||
"PID": 6204,
|
||||
"PPID": 3952,
|
||||
"Path": null,
|
||||
"SessionId": 2,
|
||||
"Threads": 8,
|
||||
"Wow64": false,
|
||||
"__children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Users\\generic-user\\AppData\\Local\\Microsoft\\OneDrive\\OneDrive.exe",
|
||||
"Cmd": "\"C:\\Users\\generic-user\\AppData\\Local\\Microsoft\\OneDrive\\OneDrive.exe\" /background",
|
||||
"CreateTime": "2025-03-06T17:51:11+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "OneDrive.exe",
|
||||
"Offset(V)": 145201834340544,
|
||||
"PID": 6160,
|
||||
"PPID": 4912,
|
||||
"Path": "C:\\Users\\generic-user\\AppData\\Local\\Microsoft\\OneDrive\\OneDrive.exe",
|
||||
"SessionId": 2,
|
||||
"Threads": 22,
|
||||
"Wow64": true,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Windows\\System32\\cmd.exe",
|
||||
"Cmd": "\"C:\\Windows\\system32\\cmd.exe\" ",
|
||||
"CreateTime": "2025-03-06T17:51:44+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "cmd.exe",
|
||||
"Offset(V)": 145201834332288,
|
||||
"PID": 784,
|
||||
"PPID": 4912,
|
||||
"Path": "C:\\Windows\\system32\\cmd.exe",
|
||||
"SessionId": 2,
|
||||
"Threads": 1,
|
||||
"Wow64": false,
|
||||
"__children": [
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Windows\\System32\\conhost.exe",
|
||||
"Cmd": null,
|
||||
"CreateTime": "2025-03-06T17:51:49+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "conhost.exe",
|
||||
"Offset(V)": 145201834446976,
|
||||
"PID": 3896,
|
||||
"PPID": 784,
|
||||
"Path": null,
|
||||
"SessionId": 2,
|
||||
"Threads": 3,
|
||||
"Wow64": false,
|
||||
"__children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Windows\\System32\\notepad.exe",
|
||||
"Cmd": "\"C:\\Windows\\system32\\notepad.exe\" ",
|
||||
"CreateTime": "2025-03-06T17:52:33+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "notepad.exe",
|
||||
"Offset(V)": 145201839497344,
|
||||
"PID": 2968,
|
||||
"PPID": 4912,
|
||||
"Path": "C:\\Windows\\system32\\notepad.exe",
|
||||
"SessionId": 2,
|
||||
"Threads": 4,
|
||||
"Wow64": false,
|
||||
"__children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Windows\\System32\\fontdrvhost.exe",
|
||||
"Cmd": null,
|
||||
"CreateTime": "2025-03-06T17:49:42+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "fontdrvhost.ex",
|
||||
"Offset(V)": 145201770697088,
|
||||
"PID": 3812,
|
||||
"PPID": 3616,
|
||||
"Path": null,
|
||||
"SessionId": 2,
|
||||
"Threads": 5,
|
||||
"Wow64": false,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Audit": "\\Device\\HarddiskVolume4\\Windows\\System32\\dwm.exe",
|
||||
"Cmd": "\"dwm.exe\"",
|
||||
"CreateTime": "2025-03-06T17:49:42+00:00",
|
||||
"ExitTime": null,
|
||||
"Handles": null,
|
||||
"ImageFileName": "dwm.exe",
|
||||
"Offset(V)": 145201770352768,
|
||||
"PID": 3860,
|
||||
"PPID": 3616,
|
||||
"Path": "C:\\Windows\\system32\\dwm.exe",
|
||||
"SessionId": 2,
|
||||
"Threads": 16,
|
||||
"Wow64": false,
|
||||
"__children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"WINDOWS10_GENERIC": [
|
||||
{
|
||||
"Offset": 213323072327680,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213323069669376,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213323011252224,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213322964488192,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213323011387392,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213322962362368,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213323041546240,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213323013046272,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213323061571584,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213323079548928,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213323067502592,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213323081900032,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213322954362880,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213322954346496,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213323014123520,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213323067707392,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213323070193664,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213323078791168,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213323069112320,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213323047776256,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213323013799936,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213322954985472,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213322954969088,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Offset": 213323048636416,
|
||||
"__children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"WINDOWS10_GENERIC": [
|
||||
{
|
||||
"Data": "",
|
||||
"Hive Offset": 213322954346496,
|
||||
"Key": "[NONAME]",
|
||||
"Last Write Time": "2025-03-06T17:59:19+00:00",
|
||||
"Name": "A",
|
||||
"Type": "Key",
|
||||
"Volatile": false
|
||||
},
|
||||
{
|
||||
"Data": "",
|
||||
"Hive Offset": 213322954362880,
|
||||
"Key": "\\REGISTRY\\MACHINE\\SYSTEM",
|
||||
"Last Write Time": "2019-12-07T09:15:07+00:00",
|
||||
"Name": "ControlSet001",
|
||||
"Type": "Key",
|
||||
"Volatile": false
|
||||
},
|
||||
{
|
||||
"Data": "",
|
||||
"Hive Offset": 213322964488192,
|
||||
"Key": "\\SystemRoot\\System32\\Config\\SOFTWARE",
|
||||
"Last Write Time": "2025-03-06T17:38:01+00:00",
|
||||
"Name": "Classes",
|
||||
"Type": "Key",
|
||||
"Volatile": false
|
||||
},
|
||||
{
|
||||
"Data": "",
|
||||
"Hive Offset": 213323011252224,
|
||||
"Key": "\\SystemRoot\\System32\\Config\\SAM",
|
||||
"Last Write Time": "2025-01-31T13:01:49+00:00",
|
||||
"Name": "SAM",
|
||||
"Type": "Key",
|
||||
"Volatile": false,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Data": "",
|
||||
"Hive Offset": 213323048636416,
|
||||
"Key": "\\??\\C:\\Users\\generic-user\\ntuser.dat",
|
||||
"Last Write Time": "2025-03-06T17:50:04+00:00",
|
||||
"Name": "SOFTWARE",
|
||||
"Type": "Key",
|
||||
"Volatile": false,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Data": "",
|
||||
"Hive Offset": 213323047776256,
|
||||
"Key": "\\??\\C:\\Users\\generic-user\\AppData\\Local\\Microsoft\\Windows\\UsrClass.dat",
|
||||
"Last Write Time": "2025-03-05T18:36:09+00:00",
|
||||
"Name": ".eip",
|
||||
"Type": "Key",
|
||||
"Volatile": false,
|
||||
"__children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
{
|
||||
"WINDOWS10_GENERIC": {
|
||||
"Count": null,
|
||||
"Focus Count": null,
|
||||
"Hive Name": "\\??\\C:\\Users\\generic-user\\ntuser.dat",
|
||||
"Hive Offset": 213323048636416,
|
||||
"ID": null,
|
||||
"Last Updated": null,
|
||||
"Last Write Time": "2025-03-06T17:57:09+00:00",
|
||||
"Name": null,
|
||||
"Path": "ntuser.dat\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\\Count",
|
||||
"Raw Data": "N/A",
|
||||
"Time Focused": null,
|
||||
"Type": "Key",
|
||||
"__children": [
|
||||
{
|
||||
"Count": 7,
|
||||
"Focus Count": 0,
|
||||
"Hive Name": "\\??\\C:\\Users\\generic-user\\ntuser.dat",
|
||||
"Hive Offset": 213323048636416,
|
||||
"ID": null,
|
||||
"Last Updated": "2025-03-05T18:34:13+00:00",
|
||||
"Last Write Time": "2025-03-06T17:57:09+00:00",
|
||||
"Name": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs\\Accessories\\Paint.lnk",
|
||||
"Path": "ntuser.dat\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\\Count",
|
||||
"Raw Data": "00 00 00 00 07 00 00 00 00 00 00 00 07 00 00 00 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ff ff ff ff 90 86 6b 31 fd 8d db 01 00 00 00 00",
|
||||
"Time Focused": "0:00:00.507000",
|
||||
"Type": "Value",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Count": 1,
|
||||
"Focus Count": 0,
|
||||
"Hive Name": "\\??\\C:\\Users\\generic-user\\ntuser.dat",
|
||||
"Hive Offset": 213323048636416,
|
||||
"ID": null,
|
||||
"Last Updated": "2025-03-06T12:46:34+00:00",
|
||||
"Last Write Time": "2025-03-06T17:57:09+00:00",
|
||||
"Name": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs\\Administrative Tools\\Registry Editor.lnk",
|
||||
"Path": "ntuser.dat\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\\Count",
|
||||
"Raw Data": "00 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ff ff ff ff f0 82 cf ca 95 8e db 01 00 00 00 00",
|
||||
"Time Focused": "0:00:00.501000",
|
||||
"Type": "Value",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Count": 4,
|
||||
"Focus Count": 0,
|
||||
"Hive Name": "\\??\\C:\\Users\\generic-user\\ntuser.dat",
|
||||
"Hive Offset": 213323048636416,
|
||||
"ID": null,
|
||||
"Last Updated": "2025-03-06T17:36:33+00:00",
|
||||
"Last Write Time": "2025-03-06T17:57:09+00:00",
|
||||
"Name": "%APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Windows PowerShell\\Windows PowerShell.lnk",
|
||||
"Path": "ntuser.dat\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\\Count",
|
||||
"Raw Data": "00 00 00 00 04 00 00 00 00 00 00 00 04 00 00 00 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ff ff ff ff 10 67 cf 4d be 8e db 01 00 00 00 00",
|
||||
"Time Focused": "0:00:00.504000",
|
||||
"Type": "Value",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Count": 1,
|
||||
"Focus Count": 0,
|
||||
"Hive Name": "\\??\\C:\\Users\\generic-user\\ntuser.dat",
|
||||
"Hive Offset": 213323048636416,
|
||||
"ID": null,
|
||||
"Last Updated": "2025-03-06T17:51:44+00:00",
|
||||
"Last Write Time": "2025-03-06T17:57:09+00:00",
|
||||
"Name": "%APPDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\System Tools\\Command Prompt.lnk",
|
||||
"Path": "ntuser.dat\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\\Count",
|
||||
"Raw Data": "00 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ff ff ff ff d0 99 66 6c c0 8e db 01 00 00 00 00",
|
||||
"Time Focused": "0:00:00.501000",
|
||||
"Type": "Value",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Count": 1,
|
||||
"Focus Count": 0,
|
||||
"Hive Name": "\\??\\C:\\Users\\generic-user\\ntuser.dat",
|
||||
"Hive Offset": 213323048636416,
|
||||
"ID": null,
|
||||
"Last Updated": "2025-03-06T17:52:33+00:00",
|
||||
"Last Write Time": "2025-03-06T17:57:09+00:00",
|
||||
"Name": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs\\Accessories\\Notepad.lnk",
|
||||
"Path": "ntuser.dat\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\\Count",
|
||||
"Raw Data": "00 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ff ff ff ff 00 62 ba 89 c0 8e db 01 00 00 00 00",
|
||||
"Time Focused": "0:00:00.501000",
|
||||
"Type": "Value",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Count": 2,
|
||||
"Focus Count": 0,
|
||||
"Hive Name": "\\??\\C:\\Users\\generic-user\\ntuser.dat",
|
||||
"Hive Offset": 213323048636416,
|
||||
"ID": null,
|
||||
"Last Updated": "2025-03-06T17:56:50+00:00",
|
||||
"Last Write Time": "2025-03-06T17:57:09+00:00",
|
||||
"Name": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs\\Administrative Tools\\Task Scheduler.lnk",
|
||||
"Path": "ntuser.dat\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\\Count",
|
||||
"Raw Data": "00 00 00 00 02 00 00 00 00 00 00 00 02 00 00 00 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ff ff ff ff b0 24 49 23 c1 8e db 01 00 00 00 00",
|
||||
"Time Focused": "0:00:00.502000",
|
||||
"Type": "Value",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Count": 1,
|
||||
"Focus Count": 0,
|
||||
"Hive Name": "\\??\\C:\\Users\\generic-user\\ntuser.dat",
|
||||
"Hive Offset": 213323048636416,
|
||||
"ID": null,
|
||||
"Last Updated": "2025-03-06T17:57:09+00:00",
|
||||
"Last Write Time": "2025-03-06T17:57:09+00:00",
|
||||
"Name": "%ALLUSERSPROFILE%\\Microsoft\\Windows\\Start Menu\\Programs\\Microsoft Edge.lnk",
|
||||
"Path": "ntuser.dat\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\UserAssist\\{F4E57C4B-2036-45F0-A9AB-443BCFE33D9F}\\Count",
|
||||
"Raw Data": "00 00 00 00 01 00 00 00 00 00 00 00 01 00 00 00 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf 00 00 80 bf ff ff ff ff 60 3d 89 2e c1 8e db 01 00 00 00 00",
|
||||
"Time Focused": "0:00:00.501000",
|
||||
"Type": "Value",
|
||||
"__children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"WINDOWS10_GENERIC": [
|
||||
{
|
||||
"Create Time": "2025-03-06T17:48:02+00:00",
|
||||
"Process": "System",
|
||||
"Process ID": 4
|
||||
},
|
||||
{
|
||||
"Create Time": "2025-03-06T17:48:37+00:00",
|
||||
"Process": "lsass.exe",
|
||||
"Process ID": 696,
|
||||
"Session ID": 0,
|
||||
"Session Type": null,
|
||||
"User Name": "/SYSTEM",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Create Time": "2025-03-06T17:49:03+00:00",
|
||||
"Process": "MsMpEng.exe",
|
||||
"Process ID": 1956,
|
||||
"Session ID": 0,
|
||||
"Session Type": null,
|
||||
"User Name": "WORKGROUP/Windows-generic$",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Create Time": "2025-03-06T17:50:08+00:00",
|
||||
"Process": "rdpclip.exe",
|
||||
"Process ID": 4180,
|
||||
"Session ID": 2,
|
||||
"Session Type": null,
|
||||
"User Name": "Windows-generic/generic-user",
|
||||
"__children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"WINDOWS10_GENERIC":
|
||||
[
|
||||
{
|
||||
"Exec Flag": null,
|
||||
"File Path": "C:\\Windows\\System32\\cmdl32.exe",
|
||||
"File Size": null,
|
||||
"Last Modified": "2019-12-07T09:09:07+00:00",
|
||||
"Last Update": null,
|
||||
"Order": 0,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Exec Flag": null,
|
||||
"File Path": "C:\\Windows\\System32\\cmdkey.exe",
|
||||
"File Size": null,
|
||||
"Last Modified": "2019-12-07T09:09:09+00:00",
|
||||
"Last Update": null,
|
||||
"Order": 1,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Exec Flag": null,
|
||||
"File Path": "C:\\Windows\\system32\\whoami.exe",
|
||||
"File Size": null,
|
||||
"Last Modified": "2019-12-07T09:09:51+00:00",
|
||||
"Last Update": null,
|
||||
"Order": 2,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"Exec Flag": null,
|
||||
"File Path": null,
|
||||
"File Size": null,
|
||||
"Last Modified": "2023-12-13T16:43:28+00:00",
|
||||
"Last Update": null,
|
||||
"Order": 3,
|
||||
"__children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,684 @@
|
||||
{
|
||||
"WINDOWSXP_GENERIC": [
|
||||
{
|
||||
"DueTime": "0x00000001:0xb6912640",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2180960760,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 2152618407,
|
||||
"Signaled": "-",
|
||||
"Symbol": "ExpTimerDpcRoutine",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xe2e5d9fc",
|
||||
"Module": "ks",
|
||||
"Offset": 2181100944,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4162614588,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x80000001:0x838d66d0",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2180726816,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 2152618407,
|
||||
"Signaled": "-",
|
||||
"Symbol": "ExpTimerDpcRoutine",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xbe54efd0",
|
||||
"Module": "NDIS",
|
||||
"Offset": 2182586488,
|
||||
"Period(ms)": 60000,
|
||||
"Routine": 4164630316,
|
||||
"Signaled": "Yes",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x80000000:0x6d915dc0",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2182771520,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 2152618407,
|
||||
"Signaled": "-",
|
||||
"Symbol": "ExpTimerDpcRoutine",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xd5616870",
|
||||
"Module": "HTTP",
|
||||
"Offset": 4122586976,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4122527634,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xaa92535f",
|
||||
"Module": "USBPORT",
|
||||
"Offset": 2179614512,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4163343596,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x80000000:0x28ec7d80",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2167895896,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 2152618407,
|
||||
"Signaled": "-",
|
||||
"Symbol": "ExpTimerDpcRoutine",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xd5f1ddd0",
|
||||
"Module": "afd",
|
||||
"Offset": 4289149728,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4146614848,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xe64e9c50",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 4289091536,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 2152618407,
|
||||
"Signaled": "-",
|
||||
"Symbol": "ExpTimerDpcRoutine",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000002:0x10691f50",
|
||||
"Module": "BATTC",
|
||||
"Offset": 2182647568,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4170619626,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000002:0x10691f50",
|
||||
"Module": "BATTC",
|
||||
"Offset": 2184852912,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4170619626,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xe650bd40",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 4289091352,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 2152618407,
|
||||
"Signaled": "-",
|
||||
"Symbol": "ExpTimerDpcRoutine",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xaf39aa70",
|
||||
"Module": "watchdog",
|
||||
"Offset": 2182133296,
|
||||
"Period(ms)": 10000,
|
||||
"Routine": 4170290884,
|
||||
"Signaled": "Yes",
|
||||
"Symbol": "_imp__KdSave",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xaf39aa70",
|
||||
"Module": "watchdog",
|
||||
"Offset": 2181849992,
|
||||
"Period(ms)": 10000,
|
||||
"Routine": 4170290884,
|
||||
"Signaled": "Yes",
|
||||
"Symbol": "_imp__KdSave",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000008:0x61e17090",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2153125248,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 2152824977,
|
||||
"Signaled": "-",
|
||||
"Symbol": "ExpTimeRefreshDpcRoutine",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xa9537f40",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2153083360,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 2152814203,
|
||||
"Signaled": "-",
|
||||
"Symbol": "CmpLazyFlushDpcRoutine",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xc5addbe0",
|
||||
"Module": "TDI",
|
||||
"Offset": 4147138000,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4169831408,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xaadb3be0",
|
||||
"Module": "netbt",
|
||||
"Offset": 2182155920,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4146697354,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xaadb9040",
|
||||
"Module": "TDI",
|
||||
"Offset": 2182059040,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4169831408,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xa9537f40",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2153083360,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 2152814203,
|
||||
"Signaled": "-",
|
||||
"Symbol": "CmpLazyFlushDpcRoutine",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x80000000:0x19eae820",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2182169704,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 2152618407,
|
||||
"Signaled": "-",
|
||||
"Symbol": "ExpTimerDpcRoutine",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xaa9b2a20",
|
||||
"Module": "NDIS",
|
||||
"Offset": 2179568032,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4164628447,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000002:0x1a97a160",
|
||||
"Module": "Ntfs",
|
||||
"Offset": 4164841808,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4164732734,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x80000000:0x2c5fb7e0",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2180369200,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 2152618407,
|
||||
"Signaled": "-",
|
||||
"Symbol": "ExpTimerDpcRoutine",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000002:0x3dcf47a0",
|
||||
"Module": "afd",
|
||||
"Offset": 2183169664,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4146614848,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xc4906e60",
|
||||
"Module": "rdbss",
|
||||
"Offset": 4146422176,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4146381701,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xaa92535f",
|
||||
"Module": "USBPORT",
|
||||
"Offset": 2179614512,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4163343596,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xaa8619e0",
|
||||
"Module": "TDI",
|
||||
"Offset": 2182064136,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4169831408,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xc45ec240",
|
||||
"Module": "tcpip",
|
||||
"Offset": 4147157776,
|
||||
"Period(ms)": 100,
|
||||
"Routine": 4146861021,
|
||||
"Signaled": "Yes",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x80000000:0x35b72850",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2180437784,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 2152618407,
|
||||
"Signaled": "-",
|
||||
"Symbol": "ExpTimerDpcRoutine",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xaad6fab0",
|
||||
"Module": "NDIS",
|
||||
"Offset": 2181321392,
|
||||
"Period(ms)": 1000,
|
||||
"Routine": 4164630316,
|
||||
"Signaled": "Yes",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xaadb9040",
|
||||
"Module": "TDI",
|
||||
"Offset": 2182059040,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4169831408,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xa9537f40",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2153083360,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 2152814203,
|
||||
"Signaled": "-",
|
||||
"Symbol": "CmpLazyFlushDpcRoutine",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xe2e5ee70",
|
||||
"Module": "ipsec",
|
||||
"Offset": 4147285920,
|
||||
"Period(ms)": 60000,
|
||||
"Routine": 4147221715,
|
||||
"Signaled": "Yes",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xe2e5ee70",
|
||||
"Module": "ipsec",
|
||||
"Offset": 4147284744,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4147221577,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xe2e5d9fc",
|
||||
"Module": "ks",
|
||||
"Offset": 2181100944,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4162614588,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000098:0xa67683e0",
|
||||
"Module": "NDIS",
|
||||
"Offset": 2183038120,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4164628447,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xa6beb810",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2153086208,
|
||||
"Period(ms)": 1000,
|
||||
"Routine": 2152611575,
|
||||
"Signaled": "Yes",
|
||||
"Symbol": "IopTimerDispatch",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xe2f53650",
|
||||
"Module": "ipnat",
|
||||
"Offset": 4146164320,
|
||||
"Period(ms)": 60000,
|
||||
"Routine": 4146134936,
|
||||
"Signaled": "Yes",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x80000001:0x838d66d0",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2180726816,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 2152618407,
|
||||
"Signaled": "-",
|
||||
"Symbol": "ExpTimerDpcRoutine",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xa6c34da0",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2153118912,
|
||||
"Period(ms)": 1000,
|
||||
"Routine": 2152615232,
|
||||
"Signaled": "Yes",
|
||||
"Symbol": "PopScanIdleList",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000098:0xa67683e0",
|
||||
"Module": "NDIS",
|
||||
"Offset": 2183038120,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4164628447,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xa6d41cb0",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2153080200,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 2152616496,
|
||||
"Signaled": "-",
|
||||
"Symbol": "CcScanDpc",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x80000000:0x14500b10",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2182113448,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 2152618407,
|
||||
"Signaled": "-",
|
||||
"Symbol": "ExpTimerDpcRoutine",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xaba81b20",
|
||||
"Module": "NDIS",
|
||||
"Offset": 2182656416,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4164628447,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xab110bd0",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2181090272,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 2152754200,
|
||||
"Signaled": "-",
|
||||
"Symbol": "CcPfTraceTimerRoutine",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000098:0xa6ad86a0",
|
||||
"Module": "NDIS",
|
||||
"Offset": 2182615456,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4164628447,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xd56a01e0",
|
||||
"Module": "HTTP",
|
||||
"Offset": 4122576520,
|
||||
"Period(ms)": 60000,
|
||||
"Routine": 4122481076,
|
||||
"Signaled": "Yes",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xd56a7ca0",
|
||||
"Module": "HTTP",
|
||||
"Offset": 4122577120,
|
||||
"Period(ms)": 30000,
|
||||
"Routine": 4122510208,
|
||||
"Signaled": "Yes",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xd56a7ca0",
|
||||
"Module": "HTTP",
|
||||
"Offset": 4122586816,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4122536296,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x80000000:0x14500b10",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2182113448,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 2152618407,
|
||||
"Signaled": "-",
|
||||
"Symbol": "ExpTimerDpcRoutine",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000002:0x10691f50",
|
||||
"Module": "BATTC",
|
||||
"Offset": 2184852912,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4170619626,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000002:0x10691f50",
|
||||
"Module": "BATTC",
|
||||
"Offset": 2182647568,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4170619626,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xaa90b010",
|
||||
"Module": "sr",
|
||||
"Offset": 2184659528,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4165384494,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000131:0x3cf80b10",
|
||||
"Module": "NDIS",
|
||||
"Offset": 2181526704,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4164628447,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xaa9b2a20",
|
||||
"Module": "NDIS",
|
||||
"Offset": 2179568032,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4164628447,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xaa9cb150",
|
||||
"Module": "NDIS",
|
||||
"Offset": 2179569720,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4164628447,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xaa9e70c0",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2182248232,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 2152618407,
|
||||
"Signaled": "-",
|
||||
"Symbol": "ExpTimerDpcRoutine",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xd56bd340",
|
||||
"Module": "srv",
|
||||
"Offset": 4128143248,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4128080773,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xbae86a40",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2182222184,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 2152618407,
|
||||
"Signaled": "-",
|
||||
"Symbol": "ExpTimerDpcRoutine",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xacafcdd0",
|
||||
"Module": "Ntfs",
|
||||
"Offset": 4164841712,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4164719155,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xaa92535f",
|
||||
"Module": "USBPORT",
|
||||
"Offset": 2179614512,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4163343596,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xad54f9d0",
|
||||
"Module": "ntoskrnl",
|
||||
"Offset": 2153085904,
|
||||
"Period(ms)": 60000,
|
||||
"Routine": 2152638914,
|
||||
"Signaled": "Yes",
|
||||
"Symbol": "IopIrpStackProfilerTimer",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000008:0x73b44670",
|
||||
"Module": "ipsec",
|
||||
"Offset": 4147284848,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4147221577,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xaad6c270",
|
||||
"Module": "NDIS",
|
||||
"Offset": 2179663392,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4164642677,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000001:0xaad6fab0",
|
||||
"Module": "NDIS",
|
||||
"Offset": 2181321392,
|
||||
"Period(ms)": 1000,
|
||||
"Routine": 4164630316,
|
||||
"Signaled": "Yes",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"DueTime": "0x00000008:0x73bef8c0",
|
||||
"Module": "netbt",
|
||||
"Offset": 2182155520,
|
||||
"Period(ms)": 0,
|
||||
"Routine": 4146697354,
|
||||
"Signaled": "-",
|
||||
"Symbol": null,
|
||||
"__children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"WINDOWS10_GENERIC": [
|
||||
{
|
||||
"EndAddress": 18446735295750344704,
|
||||
"Name": "hwpolicy.sys",
|
||||
"StartAddress": 18446735295750275072,
|
||||
"Time": "2025-03-06T17:47:55+00:00",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"EndAddress": 18446735295728652288,
|
||||
"Name": "WdBoot.sys",
|
||||
"StartAddress": 18446735295728582656,
|
||||
"Time": "2025-03-06T17:47:56+00:00",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"EndAddress": 18446735295786053632,
|
||||
"Name": "dam.sys",
|
||||
"StartAddress": 18446735295785926656,
|
||||
"Time": "2025-03-06T17:48:02+00:00",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"EndAddress": 18446735295788797952,
|
||||
"Name": "serial.sys",
|
||||
"StartAddress": 18446735295788679168,
|
||||
"Time": "2025-03-06T17:48:21+00:00",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"EndAddress": 18446735295788875776,
|
||||
"Name": "serenum.sys",
|
||||
"StartAddress": 18446735295788810240,
|
||||
"Time": "2025-03-06T17:48:21+00:00",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"EndAddress": 18446735295761932288,
|
||||
"Name": "dump_dumpfve.sys",
|
||||
"StartAddress": 18446735295761809408,
|
||||
"Time": "2025-03-06T17:48:27+00:00",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"EndAddress": 18446735295761629184,
|
||||
"Name": "dump_vmbkmcl.sys",
|
||||
"StartAddress": 18446735295761481728,
|
||||
"Time": "2025-03-06T17:48:27+00:00",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"EndAddress": 18446735295761481728,
|
||||
"Name": "dump_storvsc.sys",
|
||||
"StartAddress": 18446735295761416192,
|
||||
"Time": "2025-03-06T17:48:27+00:00",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"EndAddress": 18446735295761350656,
|
||||
"Name": "dump_storport.sys",
|
||||
"StartAddress": 18446735295761285120,
|
||||
"Time": "2025-03-06T17:48:27+00:00",
|
||||
"__children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"WINDOWS10_GENERIC": [
|
||||
{
|
||||
"CommitCharge": 1,
|
||||
"End VPN": 2147381247,
|
||||
"File": null,
|
||||
"File output": "Disabled",
|
||||
"Offset": 18446607800399740208,
|
||||
"PID": 4,
|
||||
"Parent": 0,
|
||||
"PrivateMemory": 1,
|
||||
"Process": "System",
|
||||
"Protection": "PAGE_READONLY",
|
||||
"Start VPN": 2147377152,
|
||||
"Tag": "VadS",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"CommitCharge": 1,
|
||||
"End VPN": 2147356671,
|
||||
"File": null,
|
||||
"File output": "Disabled",
|
||||
"Offset": 18446607800399739968,
|
||||
"PID": 4,
|
||||
"Parent": 18446607800399740208,
|
||||
"PrivateMemory": 1,
|
||||
"Process": "System",
|
||||
"Protection": "PAGE_READONLY",
|
||||
"Start VPN": 2147352576,
|
||||
"Tag": "VadS",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"CommitCharge": 9,
|
||||
"End VPN": 2004303871,
|
||||
"File": "\\Windows\\SysWOW64\\ntdll.dll",
|
||||
"File output": "Disabled",
|
||||
"Offset": 18446607800410763840,
|
||||
"PID": 4,
|
||||
"Parent": 18446607800399739968,
|
||||
"PrivateMemory": 0,
|
||||
"Process": "System",
|
||||
"Protection": "PAGE_EXECUTE_WRITECOPY",
|
||||
"Start VPN": 2002583552,
|
||||
"Tag": "Vad ",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"CommitCharge": 9,
|
||||
"End VPN": 140703785033727,
|
||||
"File": "\\Windows\\System32\\vertdll.dll",
|
||||
"File output": "Disabled",
|
||||
"Offset": 18446607800410768000,
|
||||
"PID": 4,
|
||||
"Parent": 18446607800399740208,
|
||||
"PrivateMemory": 0,
|
||||
"Process": "System",
|
||||
"Protection": "PAGE_EXECUTE_WRITECOPY",
|
||||
"Start VPN": 140703784828928,
|
||||
"Tag": "Vad ",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"CommitCharge": 0,
|
||||
"End VPN": 2873390796799,
|
||||
"File": null,
|
||||
"File output": "Disabled",
|
||||
"Offset": 18446607800474375600,
|
||||
"PID": 4,
|
||||
"Parent": 18446607800410768000,
|
||||
"PrivateMemory": 0,
|
||||
"Process": "System",
|
||||
"Protection": "PAGE_READWRITE",
|
||||
"Start VPN": 2873390792704,
|
||||
"Tag": "Vad ",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"CommitCharge": 16,
|
||||
"End VPN": 140703787155455,
|
||||
"File": "\\Windows\\System32\\ntdll.dll",
|
||||
"File output": "Disabled",
|
||||
"Offset": 18446607800410767520,
|
||||
"PID": 4,
|
||||
"Parent": 18446607800410768000,
|
||||
"PrivateMemory": 0,
|
||||
"Process": "System",
|
||||
"Protection": "PAGE_EXECUTE_WRITECOPY",
|
||||
"Start VPN": 140703785091072,
|
||||
"Tag": "Vad ",
|
||||
"__children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"WINDOWS10_GENERIC": [
|
||||
{
|
||||
"End": 2147381247,
|
||||
"Left": 145201666899008,
|
||||
"Offset": 145201666899248,
|
||||
"PID": 4,
|
||||
"Parent": 0,
|
||||
"Process": "System",
|
||||
"Right": 145201677927040,
|
||||
"Start": 2147377152,
|
||||
"Tag": "VadS",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"End": 2147356671,
|
||||
"Left": 145201677922880,
|
||||
"Offset": 145201666899008,
|
||||
"PID": 4,
|
||||
"Parent": 145201666899248,
|
||||
"Process": "System",
|
||||
"Right": 0,
|
||||
"Start": 2147352576,
|
||||
"Tag": "VadS",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"End": 2004303871,
|
||||
"Left": 0,
|
||||
"Offset": 145201677922880,
|
||||
"PID": 4,
|
||||
"Parent": 145201666899008,
|
||||
"Process": "System",
|
||||
"Right": 0,
|
||||
"Start": 2002583552,
|
||||
"Tag": "Vad ",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"End": 140703785033727,
|
||||
"Left": 145201741534640,
|
||||
"Offset": 145201677927040,
|
||||
"PID": 4,
|
||||
"Parent": 145201666899248,
|
||||
"Process": "System",
|
||||
"Right": 145201677926560,
|
||||
"Start": 140703784828928,
|
||||
"Tag": "Vad ",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"End": 2873390796799,
|
||||
"Left": 0,
|
||||
"Offset": 145201741534640,
|
||||
"PID": 4,
|
||||
"Parent": 145201677927040,
|
||||
"Process": "System",
|
||||
"Right": 0,
|
||||
"Start": 2873390792704,
|
||||
"Tag": "Vad ",
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"End": 140703787155455,
|
||||
"Left": 0,
|
||||
"Offset": 145201677926560,
|
||||
"PID": 4,
|
||||
"Parent": 145201677927040,
|
||||
"Process": "System",
|
||||
"Right": 0,
|
||||
"Start": 140703785091072,
|
||||
"Tag": "Vad ",
|
||||
"__children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"WINDOWS10_GENERIC": [
|
||||
{
|
||||
"End offset": 17592186044416,
|
||||
"Region": "MiVaBootLoaded",
|
||||
"Start offset": 238594023227392,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"End offset": 549755813888,
|
||||
"Region": "MiVaDriverImages",
|
||||
"Start offset": 272678883688448,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"End offset": 549755813888,
|
||||
"Region": "MiVaHal",
|
||||
"Start offset": 258385232527360,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"End offset": 3848290697216,
|
||||
"Region": "MiVaNonPagedPool",
|
||||
"Start offset": 266081813921792,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"End offset": 2748779069440,
|
||||
"Region": "MiVaPagedPool",
|
||||
"Start offset": 278135644927560,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"End offset": 17592186044416,
|
||||
"Region": "MiVaPfnDatabase",
|
||||
"Start offset": 166026255794176,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"End offset": 17592186044416,
|
||||
"Region": "MiVaProcessSpace",
|
||||
"Start offset": 191315023233024,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"End offset": 549755813888,
|
||||
"Region": "MiVaSessionGlobalSpace",
|
||||
"Start offset": 186367220908032,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"End offset": 17592186044416,
|
||||
"Region": "MiVaSessionSpace",
|
||||
"Start offset": 213305255788544,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"End offset": 1099511627776,
|
||||
"Region": "MiVaSpecialPoolPaged",
|
||||
"Start offset": 261683767410688,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"End offset": 1099511627776,
|
||||
"Region": "MiVaSystemCache",
|
||||
"Start offset": 208907209277440,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"End offset": 549755813888,
|
||||
"Region": "MiVaSystemPtes",
|
||||
"Start offset": 274328151130112,
|
||||
"__children": []
|
||||
},
|
||||
{
|
||||
"End offset": 17592186044416,
|
||||
"Region": "MiVaUnused",
|
||||
"Start offset": 145135534866432,
|
||||
"__children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,151 @@
|
||||
import io
|
||||
import pytest
|
||||
from abc import ABC, abstractmethod
|
||||
from test import test_volatility
|
||||
|
||||
HAS_PYARROW = False
|
||||
try:
|
||||
import pyarrow as pa
|
||||
import pyarrow.parquet as pq
|
||||
import pyarrow.compute as pc
|
||||
|
||||
HAS_PYARROW = True
|
||||
except ImportError:
|
||||
# The user doesn't have pyarrow installed, but HAS_PYARROW will be false so just continue
|
||||
pass
|
||||
|
||||
|
||||
@pytest.mark.skipif(not HAS_PYARROW, reason="pyarrow not installed")
|
||||
class TestArrowRendererBase(ABC):
|
||||
"""Base class for testing Arrow-based renderers.
|
||||
|
||||
Re-implements Windows and Linux plugin tests using PyArrow operations
|
||||
instead of text-based assertions.
|
||||
"""
|
||||
|
||||
renderer_format = None # Override in subclasses
|
||||
|
||||
@abstractmethod
|
||||
def _get_table_from_output(self, output_bytes) -> "pa.Table":
|
||||
"""Parse output bytes into Arrow table. Override in subclasses."""
|
||||
|
||||
def test_windows_generic_pslist(self, volatility, python, image):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"windows.pslist.PsList",
|
||||
image,
|
||||
volatility,
|
||||
python,
|
||||
globalargs=("-r", self.renderer_format),
|
||||
)
|
||||
assert rc == 0
|
||||
|
||||
table = self._get_table_from_output(out)
|
||||
assert table.num_rows > 10
|
||||
|
||||
assert (
|
||||
table.filter(
|
||||
pc.match_substring(
|
||||
pc.utf8_lower(table.column("ImageFileName")), "system"
|
||||
)
|
||||
).num_rows
|
||||
> 0
|
||||
)
|
||||
assert (
|
||||
table.filter(
|
||||
pc.match_substring(
|
||||
pc.utf8_lower(table.column("ImageFileName")), "csrss.exe"
|
||||
)
|
||||
).num_rows
|
||||
> 0
|
||||
)
|
||||
assert (
|
||||
table.filter(
|
||||
pc.match_substring(
|
||||
pc.utf8_lower(table.column("ImageFileName")), "svchost.exe"
|
||||
)
|
||||
).num_rows
|
||||
> 0
|
||||
)
|
||||
assert (
|
||||
table.filter(pc.greater(table.column("PID"), 0)).num_rows == table.num_rows
|
||||
)
|
||||
|
||||
def test_linux_generic_pslist(self, volatility, python, image):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.pslist.PsList",
|
||||
image,
|
||||
volatility,
|
||||
python,
|
||||
globalargs=("-r", self.renderer_format),
|
||||
)
|
||||
assert rc == 0
|
||||
|
||||
table = self._get_table_from_output(out)
|
||||
assert table.num_rows > 10
|
||||
|
||||
init_rows = table.filter(
|
||||
pc.match_substring(pc.utf8_lower(table.column("COMM")), "init")
|
||||
)
|
||||
systemd_rows = table.filter(
|
||||
pc.match_substring(pc.utf8_lower(table.column("COMM")), "systemd")
|
||||
)
|
||||
assert (init_rows.num_rows > 0) or (systemd_rows.num_rows > 0)
|
||||
|
||||
assert (
|
||||
table.filter(
|
||||
pc.match_substring(pc.utf8_lower(table.column("COMM")), "watchdog")
|
||||
).num_rows
|
||||
> 0
|
||||
)
|
||||
assert (
|
||||
table.filter(pc.greater(table.column("PID"), 0)).num_rows == table.num_rows
|
||||
)
|
||||
|
||||
def test_windows_generic_handles(self, volatility, python, image):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"windows.handles.Handles",
|
||||
image,
|
||||
volatility,
|
||||
python,
|
||||
globalargs=("-r", self.renderer_format),
|
||||
pluginargs=("--pid", "4"),
|
||||
)
|
||||
assert rc == 0
|
||||
|
||||
table = self._get_table_from_output(out)
|
||||
assert table.num_rows > 500
|
||||
assert (
|
||||
table.filter(
|
||||
pc.match_substring(
|
||||
pc.utf8_lower(table.column("Name")), "machine\\system"
|
||||
)
|
||||
).num_rows
|
||||
> 0
|
||||
)
|
||||
|
||||
def test_linux_generic_lsof(self, volatility, python, image):
|
||||
rc, out, _err = test_volatility.runvol_plugin(
|
||||
"linux.lsof.Lsof",
|
||||
image,
|
||||
volatility,
|
||||
python,
|
||||
globalargs=("-r", self.renderer_format),
|
||||
)
|
||||
assert rc == 0
|
||||
|
||||
table = self._get_table_from_output(out)
|
||||
assert table.num_rows > 35
|
||||
|
||||
|
||||
class TestParquetRenderer(TestArrowRendererBase):
|
||||
renderer_format = "parquet"
|
||||
|
||||
def _get_table_from_output(self, output_bytes):
|
||||
return pq.read_table(io.BytesIO(output_bytes))
|
||||
|
||||
|
||||
class TestArrowRenderer(TestArrowRendererBase):
|
||||
renderer_format = "arrow"
|
||||
|
||||
def _get_table_from_output(self, output_bytes):
|
||||
return pa.ipc.open_stream(io.BytesIO(output_bytes)).read_all()
|
||||
@@ -1,11 +0,0 @@
|
||||
# These packages are required for core functionality.
|
||||
pefile>=2017.8.1 #foo
|
||||
|
||||
# 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
|
||||
yara-x>=0.5.0
|
||||
|
||||
pytest>=7.0.0
|
||||
+194
-335
@@ -6,25 +6,31 @@
|
||||
#
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import shutil
|
||||
import tempfile
|
||||
import hashlib
|
||||
import ntpath
|
||||
import contextlib
|
||||
import functools
|
||||
import json
|
||||
import logging
|
||||
from typing import List, Tuple
|
||||
|
||||
from test import WINDOWS_TESTS_DATA_DIR
|
||||
|
||||
test_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
#
|
||||
# HELPER FUNCTIONS
|
||||
#
|
||||
|
||||
|
||||
@functools.lru_cache
|
||||
def runvol(args, volatility, python):
|
||||
volpy = volatility
|
||||
python_cmd = python
|
||||
|
||||
cmd = [python_cmd, volpy] + args
|
||||
cmd = (python_cmd, volpy) + args
|
||||
print(" ".join(cmd))
|
||||
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
stdout, stderr = p.communicate()
|
||||
@@ -38,352 +44,203 @@ def runvol(args, volatility, python):
|
||||
return p.returncode, stdout, stderr
|
||||
|
||||
|
||||
def runvol_plugin(plugin, img, volatility, python, pluginargs=[], globalargs=[]):
|
||||
@functools.lru_cache
|
||||
def runvol_plugin(
|
||||
plugin, img, volatility, python, pluginargs: Tuple = (), globalargs: Tuple = ()
|
||||
):
|
||||
args = (
|
||||
globalargs
|
||||
+ [
|
||||
+ (
|
||||
"--single-location",
|
||||
img,
|
||||
"-q",
|
||||
plugin,
|
||||
]
|
||||
)
|
||||
+ pluginargs
|
||||
)
|
||||
|
||||
return runvol(args, volatility, python)
|
||||
|
||||
|
||||
def runvolshell(
|
||||
img, volshell, python, volshellargs: Tuple = (), globalargs: Tuple = ()
|
||||
):
|
||||
args = (
|
||||
globalargs
|
||||
+ (
|
||||
"--single-location",
|
||||
img,
|
||||
"-q",
|
||||
)
|
||||
+ volshellargs
|
||||
)
|
||||
|
||||
return runvol(args, volshell, python)
|
||||
|
||||
|
||||
def load_test_data(plugin: str, test_key: str):
|
||||
if plugin.startswith("windows."):
|
||||
data_path = WINDOWS_TESTS_DATA_DIR / f"{plugin}.json"
|
||||
# TODO: add Linux and macOS when any of these requires this API
|
||||
else:
|
||||
raise Exception(f"Cannot determine OS of plugin: {plugin}")
|
||||
|
||||
if not data_path.exists():
|
||||
raise FileNotFoundError(
|
||||
f"Test data not found for plugin {plugin} at {data_path}"
|
||||
)
|
||||
|
||||
with open(data_path) as f:
|
||||
# This will raise an explicit exception by itself on failures
|
||||
return json.load(f)[test_key]
|
||||
|
||||
|
||||
def dict_lower_strvalues(dict_to_convert: dict):
|
||||
"""Lower each value of type string of a dictionary
|
||||
|
||||
Args:
|
||||
dict_to_convert: The dictionary in which to lower the string values
|
||||
Returns:
|
||||
A copy of the dictionary with lowered string values
|
||||
"""
|
||||
|
||||
converted = {}
|
||||
for key, value in dict_to_convert.items():
|
||||
if isinstance(value, str):
|
||||
converted[key] = value.lower()
|
||||
else:
|
||||
converted[key] = value
|
||||
return converted
|
||||
|
||||
|
||||
def match_output_row(
|
||||
expected_row: dict,
|
||||
plugin_json_out: List[dict],
|
||||
exact_match: bool = False,
|
||||
case_sensitive: bool = True,
|
||||
children_recursive: bool = False,
|
||||
):
|
||||
"""Search each row in a plugin's JSON output for a matching row.
|
||||
This method supports recursive comparisons using the "__children" key, making it useful for testing hierarchical plugins like windows.pstree.
|
||||
It also maintains case sensitivity and exact matching behavior when traversing nested structures.
|
||||
|
||||
Args:
|
||||
expected_row: The expected row to be found in the output
|
||||
plugin_json_out: The plugin's output in JSON format (typically obtained through -r json and json.loads)
|
||||
exact_match: Require exactly the expected row, no more no less, or anticipate columns' addition by checking only
|
||||
the expected row keys and values
|
||||
case_sensitive: Operate case sensitive match for str values of both dictionaries or not
|
||||
children_recursive: Perform a recursive match by inspecting "__children" keys of each expected_row
|
||||
|
||||
Returns:
|
||||
A boolean indicating whether a match was found or not
|
||||
"""
|
||||
|
||||
# Lower each string value of both dicts
|
||||
if not case_sensitive:
|
||||
expected_row = dict_lower_strvalues(expected_row)
|
||||
plugin_json_out_tmp = []
|
||||
for row in plugin_json_out:
|
||||
plugin_json_out_tmp.append(dict_lower_strvalues(row))
|
||||
plugin_json_out = plugin_json_out_tmp
|
||||
|
||||
if not exact_match:
|
||||
for row in plugin_json_out:
|
||||
if all(
|
||||
expected_item in row.items()
|
||||
for expected_item in expected_row.items()
|
||||
if not expected_item[0] == "__children"
|
||||
):
|
||||
if (
|
||||
children_recursive
|
||||
and "__children" in expected_row
|
||||
and "__children" in row
|
||||
):
|
||||
for children_expected_row in expected_row["__children"]:
|
||||
if not match_output_row(
|
||||
children_expected_row,
|
||||
row["__children"],
|
||||
case_sensitive=case_sensitive,
|
||||
children_recursive=True,
|
||||
):
|
||||
break
|
||||
else:
|
||||
# We matched all the children keys
|
||||
return True
|
||||
else:
|
||||
# No recursion required and we already matched the row
|
||||
return True
|
||||
else:
|
||||
# No "__children" recursion here as we want to match the whole tree at once
|
||||
for row in plugin_json_out:
|
||||
if expected_row == row:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def count_entries_flat(plugin_json_out: List[dict]):
|
||||
"""Count the number of entries as if -r json wasn't specified. Allows to get a non-hierarchical count, without running a plugin twice
|
||||
(once with "-r json" and once without) while still preserving JSON features.
|
||||
|
||||
Args:
|
||||
plugin_json_out: The plugin's output in JSON format (typically obtained through -r json and json.loads)
|
||||
"""
|
||||
# Remove whitespaces between entries
|
||||
# If a value contains {", it will be represented by {\" so no confusion
|
||||
return json.dumps(plugin_json_out, separators=(",", ":")).count('{"')
|
||||
|
||||
|
||||
#
|
||||
# TESTS
|
||||
#
|
||||
|
||||
# WINDOWS
|
||||
|
||||
def basic_volshell_test(
|
||||
image, volatility, python, volshellargs: Tuple = (), globalargs: Tuple = ()
|
||||
):
|
||||
# Basic VolShell test to verify requirements and ensure VolShell runs without crashing
|
||||
|
||||
def test_windows_pslist(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("windows.pslist.PsList", image, volatility, python)
|
||||
out = out.lower()
|
||||
assert out.find(b"system") != -1
|
||||
assert out.find(b"csrss.exe") != -1
|
||||
assert out.find(b"svchost.exe") != -1
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
|
||||
rc, out, err = runvol_plugin(
|
||||
"windows.pslist.PsList", image, volatility, python, pluginargs=["--pid", "4"]
|
||||
)
|
||||
out = out.lower()
|
||||
assert out.find(b"system") != -1
|
||||
assert out.count(b"\n") < 10
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_windows_psscan(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("windows.psscan.PsScan", image, volatility, python)
|
||||
out = out.lower()
|
||||
assert out.find(b"system") != -1
|
||||
assert out.find(b"csrss.exe") != -1
|
||||
assert out.find(b"svchost.exe") != -1
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_windows_dlllist(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("windows.dlllist.DllList", image, volatility, python)
|
||||
out = out.lower()
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_windows_modules(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("windows.modules.Modules", image, volatility, python)
|
||||
out = out.lower()
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_windows_hivelist(image, volatility, python):
|
||||
rc, out, err = runvol_plugin(
|
||||
"windows.registry.hivelist.HiveList", image, volatility, python
|
||||
)
|
||||
out = out.lower()
|
||||
|
||||
not_xp = out.find(b"\\systemroot\\system32\\config\\software")
|
||||
if not_xp == -1:
|
||||
assert (
|
||||
out.find(b"\\device\\harddiskvolume1\\windows\\system32\\config\\software")
|
||||
!= -1
|
||||
)
|
||||
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_windows_dumpfiles(image, volatility, python):
|
||||
|
||||
with open("./test/known_files.json") as json_file:
|
||||
known_files = json.load(json_file)
|
||||
|
||||
failed_chksms = 0
|
||||
|
||||
if sys.platform == "win32":
|
||||
file_name = ntpath.basename(image)
|
||||
else:
|
||||
file_name = os.path.basename(image)
|
||||
volshell_commands = [
|
||||
"print(ps())",
|
||||
"exit()",
|
||||
]
|
||||
|
||||
# FIXME: When the minimum Python version includes 3.12, replace the following with:
|
||||
# with tempfile.NamedTemporaryFile(delete_on_close=False) as fd: ...
|
||||
fd, filename = tempfile.mkstemp(suffix=".txt")
|
||||
try:
|
||||
for addr in known_files["windows_dumpfiles"][file_name]:
|
||||
volshell_script = "\n".join(volshell_commands)
|
||||
with os.fdopen(fd, "w") as f:
|
||||
f.write(volshell_script)
|
||||
|
||||
path = tempfile.mkdtemp()
|
||||
|
||||
rc, out, err = runvol_plugin(
|
||||
"windows.dumpfiles.DumpFiles",
|
||||
image,
|
||||
volatility,
|
||||
python,
|
||||
globalargs=["-o", path],
|
||||
pluginargs=["--virtaddr", addr],
|
||||
)
|
||||
|
||||
for file in os.listdir(path):
|
||||
with open(os.path.join(path, file), "rb") as fp:
|
||||
if (
|
||||
hashlib.md5(fp.read()).hexdigest()
|
||||
not in known_files["windows_dumpfiles"][file_name][addr]
|
||||
):
|
||||
failed_chksms += 1
|
||||
|
||||
shutil.rmtree(path)
|
||||
|
||||
json_file.close()
|
||||
|
||||
assert failed_chksms == 0
|
||||
assert rc == 0
|
||||
except Exception as e:
|
||||
json_file.close()
|
||||
print("Key Error raised on " + str(e))
|
||||
assert False
|
||||
|
||||
|
||||
def test_windows_handles(image, volatility, python):
|
||||
rc, out, err = runvol_plugin(
|
||||
"windows.handles.Handles", image, volatility, python, pluginargs=["--pid", "4"]
|
||||
)
|
||||
|
||||
assert out.find(b"System Pid 4") != -1
|
||||
assert (
|
||||
out.find(
|
||||
b"MACHINE\\SYSTEM\\CONTROLSET001\\CONTROL\\SESSION MANAGER\\MEMORY MANAGEMENT\\PREFETCHPARAMETERS"
|
||||
rc, out, _err = runvolshell(
|
||||
img=image,
|
||||
volshell=volatility,
|
||||
python=python,
|
||||
volshellargs=("--script", filename) + volshellargs,
|
||||
globalargs=globalargs,
|
||||
)
|
||||
!= -1
|
||||
)
|
||||
assert out.find(b"MACHINE\\SYSTEM\\SETUP") != -1
|
||||
assert out.count(b"\n") > 500
|
||||
finally:
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
os.remove(filename)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
|
||||
def test_windows_svcscan(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("windows.svcscan.SvcScan", image, volatility, python)
|
||||
|
||||
assert out.find(b"Microsoft ACPI Driver") != -1
|
||||
assert out.count(b"\n") > 250
|
||||
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"]
|
||||
)
|
||||
|
||||
assert out.find(b"SeCreateTokenPrivilege") != -1
|
||||
assert out.find(b"SeCreateGlobalPrivilege") != -1
|
||||
assert out.find(b"SeAssignPrimaryTokenPrivilege") != -1
|
||||
assert out.count(b"\n") > 20
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_windows_getsids(image, volatility, python):
|
||||
rc, out, err = runvol_plugin(
|
||||
"windows.getsids.GetSIDs", image, volatility, python, pluginargs=["--pid", "4"]
|
||||
)
|
||||
|
||||
assert out.find(b"Local System") != -1
|
||||
assert out.find(b"Administrators") != -1
|
||||
assert out.find(b"Everyone") != -1
|
||||
assert out.find(b"Authenticated Users") != -1
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_windows_envars(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("windows.envars.Envars", image, volatility, python)
|
||||
|
||||
assert out.find(b"PATH") != -1
|
||||
assert out.find(b"PROCESSOR_ARCHITECTURE") != -1
|
||||
assert out.find(b"USERNAME") != -1
|
||||
assert out.find(b"SystemRoot") != -1
|
||||
assert out.find(b"CommonProgramFiles") != -1
|
||||
assert out.count(b"\n") > 500
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_windows_callbacks(image, volatility, python):
|
||||
rc, out, err = runvol_plugin(
|
||||
"windows.callbacks.Callbacks", image, volatility, python
|
||||
)
|
||||
|
||||
assert out.find(b"PspCreateProcessNotifyRoutine") != -1
|
||||
assert out.find(b"KeBugCheckCallbackListHead") != -1
|
||||
assert out.find(b"KeBugCheckReasonCallbackListHead") != -1
|
||||
assert out.count(b"KeBugCheckReasonCallbackListHead ") > 5
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_windows_vadwalk(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("windows.vadwalk.VadWalk", image, volatility, python)
|
||||
|
||||
assert out.find(b"Vad") != -1
|
||||
assert out.find(b"VadS") != -1
|
||||
assert out.find(b"Vadl") != -1
|
||||
assert out.find(b"VadF") != -1
|
||||
assert out.find(b"0x0") != -1
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_windows_devicetree(image, volatility, python):
|
||||
rc, out, err = runvol_plugin(
|
||||
"windows.devicetree.DeviceTree", image, volatility, python
|
||||
)
|
||||
|
||||
assert out.find(b"DEV") != -1
|
||||
assert out.find(b"DRV") != -1
|
||||
assert out.find(b"ATT") != -1
|
||||
assert out.find(b"FILE_DEVICE_CONTROLLER") != -1
|
||||
assert out.find(b"FILE_DEVICE_DISK") != -1
|
||||
assert out.find(b"FILE_DEVICE_DISK_FILE_SYSTEM") != -1
|
||||
assert rc == 0
|
||||
|
||||
|
||||
# LINUX
|
||||
|
||||
|
||||
def test_linux_pslist(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("linux.pslist.PsList", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert (out.find(b"init") != -1) or (out.find(b"systemd") != -1)
|
||||
assert out.find(b"watchdog") != -1
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_linux_check_idt(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("linux.check_idt.Check_idt", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert out.count(b"__kernel__") >= 10
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_linux_check_syscall(image, volatility, python):
|
||||
rc, out, err = runvol_plugin(
|
||||
"linux.check_syscall.Check_syscall", image, volatility, python
|
||||
)
|
||||
out = out.lower()
|
||||
|
||||
assert out.find(b"sys_close") != -1
|
||||
assert out.find(b"sys_open") != -1
|
||||
assert out.count(b"\n") > 100
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_linux_lsmod(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("linux.lsmod.Lsmod", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert out.count(b"\n") > 10
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_linux_lsof(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("linux.lsof.Lsof", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert out.count(b"socket:") >= 10
|
||||
assert out.count(b"\n") > 35
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_linux_proc_maps(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("linux.proc.Maps", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert out.count(b"anonymous mapping") >= 10
|
||||
assert out.count(b"\n") > 100
|
||||
assert rc == 0
|
||||
|
||||
|
||||
def test_linux_tty_check(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("linux.tty_check.tty_check", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert out.find(b"__kernel__") != -1
|
||||
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
|
||||
return out
|
||||
|
||||
|
||||
# MAC
|
||||
# TODO: Migrate and integrate in testing (once analysis is fixed ?)
|
||||
|
||||
|
||||
def test_mac_volshell(image, volatility, python):
|
||||
basic_volshell_test(image, volatility, python, globalargs=["-m"])
|
||||
|
||||
|
||||
def test_mac_pslist(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("mac.pslist.PsList", image, volatility, python)
|
||||
rc, out, _err = runvol_plugin("mac.pslist.PsList", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert (out.find(b"kernel_task") != -1) or (out.find(b"launchd") != -1)
|
||||
@@ -392,7 +249,7 @@ def test_mac_pslist(image, volatility, python):
|
||||
|
||||
|
||||
def test_mac_check_syscall(image, volatility, python):
|
||||
rc, out, err = runvol_plugin(
|
||||
rc, out, _err = runvol_plugin(
|
||||
"mac.check_syscall.Check_syscall", image, volatility, python
|
||||
)
|
||||
out = out.lower()
|
||||
@@ -405,7 +262,7 @@ def test_mac_check_syscall(image, volatility, python):
|
||||
|
||||
|
||||
def test_mac_check_sysctl(image, volatility, python):
|
||||
rc, out, err = runvol_plugin(
|
||||
rc, out, _err = runvol_plugin(
|
||||
"mac.check_sysctl.Check_sysctl", image, volatility, python
|
||||
)
|
||||
out = out.lower()
|
||||
@@ -416,7 +273,7 @@ def test_mac_check_sysctl(image, volatility, python):
|
||||
|
||||
|
||||
def test_mac_check_trap_table(image, volatility, python):
|
||||
rc, out, err = runvol_plugin(
|
||||
rc, out, _err = runvol_plugin(
|
||||
"mac.check_trap_table.Check_trap_table", image, volatility, python
|
||||
)
|
||||
out = out.lower()
|
||||
@@ -427,7 +284,7 @@ def test_mac_check_trap_table(image, volatility, python):
|
||||
|
||||
|
||||
def test_mac_ifconfig(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("mac.ifconfig.Ifconfig", image, volatility, python)
|
||||
rc, out, _err = runvol_plugin("mac.ifconfig.Ifconfig", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert out.find(b"127.0.0.1") != -1
|
||||
@@ -437,7 +294,7 @@ def test_mac_ifconfig(image, volatility, python):
|
||||
|
||||
|
||||
def test_mac_lsmod(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("mac.lsmod.Lsmod", image, volatility, python)
|
||||
rc, out, _err = runvol_plugin("mac.lsmod.Lsmod", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert out.find(b"com.apple") != -1
|
||||
@@ -446,7 +303,7 @@ def test_mac_lsmod(image, volatility, python):
|
||||
|
||||
|
||||
def test_mac_lsof(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("mac.lsof.Lsof", image, volatility, python)
|
||||
rc, out, _err = runvol_plugin("mac.lsof.Lsof", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert out.count(b"\n") > 50
|
||||
@@ -454,7 +311,7 @@ def test_mac_lsof(image, volatility, python):
|
||||
|
||||
|
||||
def test_mac_malfind(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("mac.malfind.Malfind", image, volatility, python)
|
||||
rc, out, _err = runvol_plugin("mac.malfind.Malfind", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert out.count(b"\n") > 20
|
||||
@@ -462,7 +319,7 @@ def test_mac_malfind(image, volatility, python):
|
||||
|
||||
|
||||
def test_mac_mount(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("mac.mount.Mount", image, volatility, python)
|
||||
rc, out, _err = runvol_plugin("mac.mount.Mount", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert out.find(b"/dev") != -1
|
||||
@@ -471,7 +328,7 @@ def test_mac_mount(image, volatility, python):
|
||||
|
||||
|
||||
def test_mac_netstat(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("mac.netstat.Netstat", image, volatility, python)
|
||||
rc, out, _err = runvol_plugin("mac.netstat.Netstat", image, volatility, python)
|
||||
|
||||
assert out.find(b"TCP") != -1
|
||||
assert out.find(b"UDP") != -1
|
||||
@@ -481,7 +338,7 @@ def test_mac_netstat(image, volatility, python):
|
||||
|
||||
|
||||
def test_mac_proc_maps(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("mac.proc_maps.Maps", image, volatility, python)
|
||||
rc, out, _err = runvol_plugin("mac.proc_maps.Maps", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert out.find(b"[heap]") != -1
|
||||
@@ -490,7 +347,7 @@ def test_mac_proc_maps(image, volatility, python):
|
||||
|
||||
|
||||
def test_mac_psaux(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("mac.psaux.Psaux", image, volatility, python)
|
||||
rc, out, _err = runvol_plugin("mac.psaux.Psaux", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert out.find(b"executable_path") != -1
|
||||
@@ -499,7 +356,7 @@ def test_mac_psaux(image, volatility, python):
|
||||
|
||||
|
||||
def test_mac_socket_filters(image, volatility, python):
|
||||
rc, out, err = runvol_plugin(
|
||||
rc, out, _err = runvol_plugin(
|
||||
"mac.socket_filters.Socket_filters", image, volatility, python
|
||||
)
|
||||
out = out.lower()
|
||||
@@ -509,7 +366,7 @@ def test_mac_socket_filters(image, volatility, python):
|
||||
|
||||
|
||||
def test_mac_timers(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("mac.timers.Timers", image, volatility, python)
|
||||
rc, out, _err = runvol_plugin("mac.timers.Timers", image, volatility, python)
|
||||
out = out.lower()
|
||||
|
||||
assert out.count(b"\n") > 6
|
||||
@@ -517,7 +374,9 @@ def test_mac_timers(image, volatility, python):
|
||||
|
||||
|
||||
def test_mac_trustedbsd(image, volatility, python):
|
||||
rc, out, err = runvol_plugin("mac.trustedbsd.Trustedbsd", image, volatility, python)
|
||||
rc, out, _err = runvol_plugin(
|
||||
"mac.trustedbsd.Trustedbsd", image, volatility, python
|
||||
)
|
||||
out = out.lower()
|
||||
|
||||
assert out.count(b"\n") > 10
|
||||
|
||||
@@ -0,0 +1,440 @@
|
||||
"""
|
||||
This script performs syntax analysis on the volatility3 source tree through a combination of AST analysis and import-time introspection of classes.
|
||||
|
||||
The current checks it implements are:
|
||||
1. Ensure that classes derived from `ConfigurableInterface` properly
|
||||
declare all `VersionableInterface` classes that they make use of in their
|
||||
`get_requirements()` classmethod.
|
||||
|
||||
:WARNING: a notable exception to this are classes defined within factory
|
||||
functions. Because these classes are not created until the factory function
|
||||
is called, they therefore do no exist at import time and cannot be checked
|
||||
by this script. It is important to keep in mind during code review that
|
||||
this is a best-effort check and does not make guarantees about the
|
||||
completeness of declared requirements.
|
||||
"""
|
||||
|
||||
import abc
|
||||
import argparse
|
||||
import ast
|
||||
import importlib
|
||||
import inspect
|
||||
import logging
|
||||
import pkgutil
|
||||
import sys
|
||||
import traceback
|
||||
import types
|
||||
from typing import Any, Iterator, List, Optional, Tuple, Type, Union
|
||||
|
||||
from volatility3.framework import configuration, interfaces
|
||||
from volatility3.framework.deprecation import PluginRenameClass
|
||||
|
||||
logging.basicConfig(format="%(levelname)s: %(message)s")
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NodeVisitor:
|
||||
def visit(self, node):
|
||||
"""Visit a node."""
|
||||
method = "visit_" + node.__class__.__name__
|
||||
visitor = getattr(self, method, self.generic_visit)
|
||||
self.enter(node)
|
||||
result = visitor(node)
|
||||
self.leave(node)
|
||||
return result
|
||||
|
||||
def enter(self, node):
|
||||
"""Called when entering a node."""
|
||||
method = "enter_" + node.__class__.__name__
|
||||
visitor = getattr(self, method, self.generic_enter)
|
||||
return visitor(node)
|
||||
|
||||
def leave(self, node):
|
||||
"""Called when leaving a node."""
|
||||
method = "leave_" + node.__class__.__name__
|
||||
visitor = getattr(self, method, self.generic_leave)
|
||||
return visitor(node)
|
||||
|
||||
def generic_visit(self, node):
|
||||
"""Called if no explicit visitor function exists for a node."""
|
||||
for _, value in ast.iter_fields(node):
|
||||
if isinstance(value, list):
|
||||
for item in value:
|
||||
if isinstance(item, ast.AST):
|
||||
self.visit(item)
|
||||
elif isinstance(value, ast.AST):
|
||||
self.visit(value)
|
||||
|
||||
def generic_enter(self, node):
|
||||
"""Default enter behavior."""
|
||||
|
||||
def generic_leave(self, node):
|
||||
"""Default leave behavior."""
|
||||
|
||||
|
||||
class CodeViolation(metaclass=abc.ABCMeta):
|
||||
def __init__(self, module: types.ModuleType, node: ast.AST) -> None:
|
||||
self.module = module
|
||||
self.node = node
|
||||
|
||||
def __str__(self):
|
||||
return f"Issue in module {self.module.__name__}: line {self.node.lineno}, col {self.node.col_offset}"
|
||||
|
||||
|
||||
class UnrequiredVersionableUsage(CodeViolation):
|
||||
def __init__(
|
||||
self,
|
||||
module: types.ModuleType,
|
||||
node: ast.AST,
|
||||
consuming_class: str,
|
||||
versionable_item_class: str,
|
||||
) -> None:
|
||||
super().__init__(module, node)
|
||||
self.consuming_class = consuming_class
|
||||
self.versionable_item_class = versionable_item_class
|
||||
|
||||
def __str__(self) -> str:
|
||||
return (
|
||||
super().__str__()
|
||||
+ ": "
|
||||
+ (
|
||||
f"Found usage of {self.versionable_item_class} "
|
||||
f"in class {self.consuming_class} that is not declared "
|
||||
f"in {self.consuming_class}'s `get_requirements()` classmethod"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class DirectVolatilityImportUsage(CodeViolation):
|
||||
def __init__(
|
||||
self,
|
||||
module: types.ModuleType,
|
||||
node: ast.AST,
|
||||
importing_module: str,
|
||||
imported_item: object,
|
||||
imported_name: str,
|
||||
) -> None:
|
||||
self.imported_item = imported_item
|
||||
self.imported_name = imported_name
|
||||
self.importing_module = importing_module
|
||||
super().__init__(module, node)
|
||||
|
||||
def __str__(self) -> str:
|
||||
components = self.importing_module.split(".")
|
||||
return (
|
||||
super().__str__()
|
||||
+ ": "
|
||||
+ (
|
||||
f"Direct import of {self.imported_name} "
|
||||
f"({type(self.imported_item)}) "
|
||||
f"from module {self.importing_module} - "
|
||||
"change to "
|
||||
f"'from {'.'.join(components[:-1])} import {components[-1]} and using {components[-1]}.{self.imported_name}"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def is_versionable(var):
|
||||
try:
|
||||
return (
|
||||
issubclass(var, interfaces.configuration.VersionableInterface)
|
||||
and var is not interfaces.configuration.VersionableInterface
|
||||
and not inspect.isabstract(var)
|
||||
and not (hasattr(var, "hidden") and getattr(var, "hidden") is True)
|
||||
)
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
|
||||
def is_configurable(var):
|
||||
try:
|
||||
return issubclass(var, interfaces.configuration.ConfigurableInterface)
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
|
||||
class ModuleVisitor(NodeVisitor):
|
||||
def __init__(self, module: types.ModuleType) -> None:
|
||||
self._module = module
|
||||
self._scopes = []
|
||||
self._violations = []
|
||||
|
||||
@property
|
||||
def violations(self):
|
||||
return self._violations
|
||||
|
||||
def _check_vol3_import_from(self, node: ast.ImportFrom):
|
||||
"""
|
||||
Ensure that the only thing imported from a volatility3 module (apart
|
||||
from the root volatility3 module) are functions and modules. This
|
||||
prevents re-exporting of classes and variables from modules that use
|
||||
them.
|
||||
"""
|
||||
if (
|
||||
node.module
|
||||
and node.module.startswith(
|
||||
"volatility3."
|
||||
) # Give a pass to volatility3 module
|
||||
and node.module
|
||||
!= "volatility3.framework.constants._version" # make an exception for this
|
||||
):
|
||||
for name in node.names:
|
||||
try:
|
||||
item = vars(self._module)[
|
||||
name.asname if name.asname is not None else name.name
|
||||
]
|
||||
except KeyError:
|
||||
logger.debug(
|
||||
"Couldn't find imported name %s in module %s",
|
||||
name.asname or name.name,
|
||||
self._module.__name__,
|
||||
)
|
||||
continue
|
||||
|
||||
if not (isinstance(item, types.ModuleType) or inspect.isfunction(item)):
|
||||
self._violations.append(
|
||||
DirectVolatilityImportUsage(
|
||||
self._module,
|
||||
node,
|
||||
node.module,
|
||||
item,
|
||||
name.asname or name.name,
|
||||
)
|
||||
)
|
||||
|
||||
def enter_ImportFrom(self, node: ast.ImportFrom):
|
||||
self._check_vol3_import_from(node)
|
||||
|
||||
def enter_ClassDef(self, node: ast.ClassDef) -> Any:
|
||||
logger.debug("Entering class %s", node.name)
|
||||
clazz = None
|
||||
try:
|
||||
clazz = vars(self._module)[str(node.name)]
|
||||
except KeyError:
|
||||
logger.debug(
|
||||
"Failed to get %s from module scope: (%s)",
|
||||
node.name,
|
||||
self._module.__name__,
|
||||
)
|
||||
if self._scopes:
|
||||
try:
|
||||
logger.debug(
|
||||
"Attempting to get class %s from scope of %s",
|
||||
node.name,
|
||||
self._scopes[-1].__name__,
|
||||
)
|
||||
clazz = getattr(self._scopes[-1], node.name)
|
||||
except AttributeError:
|
||||
logger.debug(
|
||||
"Class not found in scope of %s", self._scopes[-1].__name__
|
||||
)
|
||||
if clazz:
|
||||
self._scopes.append(clazz)
|
||||
|
||||
if clazz and is_configurable(clazz):
|
||||
logger.info("Checking configurable class %s", clazz.__name__)
|
||||
visitor = ConfigurableClassVisitor(self._module, clazz)
|
||||
visitor.visit(node)
|
||||
self._violations += visitor.violations
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
def leave_ClassDef(self, node: ast.ClassDef):
|
||||
logger.debug("Leaving class %s", node.name)
|
||||
try:
|
||||
scoped_class = next(
|
||||
scope for scope in self._scopes if scope.__name__ == node.name
|
||||
)
|
||||
self._scopes.remove(scoped_class)
|
||||
except StopIteration:
|
||||
logger.debug("%s not found in scope list", node.name)
|
||||
|
||||
|
||||
class ConfigurableClassVisitor(NodeVisitor):
|
||||
def __init__(
|
||||
self,
|
||||
module: types.ModuleType,
|
||||
clazz: Optional[Type[interfaces.configuration.ConfigurableInterface]],
|
||||
) -> None:
|
||||
self._module = module
|
||||
self._current_object = None
|
||||
self._clazz = clazz
|
||||
self._seen = set()
|
||||
self._violations: List[CodeViolation] = []
|
||||
|
||||
@property
|
||||
def versioned_classes(self):
|
||||
return (
|
||||
[
|
||||
req._component
|
||||
for req in self._clazz.get_requirements()
|
||||
if isinstance(req, configuration.requirements.VersionRequirement)
|
||||
]
|
||||
if self._clazz is not None
|
||||
else []
|
||||
)
|
||||
|
||||
def check_item(self, item: Type, node: Union[ast.Name, ast.Attribute]):
|
||||
if (
|
||||
is_versionable(item)
|
||||
and self._clazz is not None
|
||||
and item not in self.versioned_classes
|
||||
and item is not self._clazz
|
||||
and not issubclass(self._clazz, PluginRenameClass)
|
||||
):
|
||||
logger.info(
|
||||
"Found versionable item %s, checking against %s",
|
||||
str(item),
|
||||
str(self.versioned_classes),
|
||||
)
|
||||
result = UnrequiredVersionableUsage(
|
||||
self._module, node, self._clazz.__name__, item.__name__
|
||||
)
|
||||
self._violations.append(result)
|
||||
|
||||
@property
|
||||
def violations(self):
|
||||
return self._violations
|
||||
|
||||
def visit_Name(self, node: ast.Name):
|
||||
try:
|
||||
logger.debug(
|
||||
"Checking module %s for name %s", self._module.__name__, node.id
|
||||
)
|
||||
item = vars(self._module)[str(node.id)]
|
||||
logger.debug("Found %s in %s namespace", node.id, self._module.__name__)
|
||||
except KeyError:
|
||||
return
|
||||
|
||||
self.check_item(item, node)
|
||||
|
||||
def visit_Attribute(
|
||||
self, node: ast.Attribute
|
||||
) -> Optional[UnrequiredVersionableUsage]:
|
||||
if self._clazz is None:
|
||||
self.generic_visit(node)
|
||||
return
|
||||
|
||||
if (node.lineno, node.col_offset) in self._seen:
|
||||
return
|
||||
|
||||
self._seen.add((node.lineno, node.col_offset))
|
||||
|
||||
stack = []
|
||||
root = node
|
||||
while True:
|
||||
stack.append(node.attr)
|
||||
if isinstance(node.value, ast.Attribute):
|
||||
node = node.value
|
||||
elif isinstance(node.value, ast.Name):
|
||||
stack.append(node.value.id)
|
||||
break
|
||||
else:
|
||||
break
|
||||
|
||||
current = None
|
||||
logger.debug("Checking %s", ".".join(stack[::-1]))
|
||||
for item in stack[::-1]:
|
||||
try:
|
||||
current = (
|
||||
vars(self._module)[item]
|
||||
if current is None
|
||||
else getattr(current, item)
|
||||
)
|
||||
except (KeyError, AttributeError) as exc:
|
||||
logger.debug(
|
||||
"Failed to get attribute %s (%s)%s",
|
||||
item,
|
||||
exc.__class__.__name__,
|
||||
(" on" + str(current)) if current is not None else "",
|
||||
)
|
||||
break
|
||||
|
||||
self.check_item(current, root)
|
||||
|
||||
|
||||
def report_missing_requirements() -> Iterator[Tuple[str, UnrequiredVersionableUsage]]:
|
||||
vol3 = importlib.import_module("volatility3")
|
||||
|
||||
for _, module_name, _ in pkgutil.walk_packages(
|
||||
vol3.__path__, vol3.__name__ + ".", onerror=lambda _: None
|
||||
):
|
||||
modname = module_name.replace(
|
||||
"volatility3.framework.plugins", "volatility3.plugins"
|
||||
)
|
||||
try:
|
||||
# import the module that we want to check
|
||||
plugin_module = importlib.import_module(modname)
|
||||
|
||||
except ImportError as exc:
|
||||
logger.warning("Failed to import %s: %s", modname, str(exc))
|
||||
continue
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"An unexpected exception occurred while importing %s: %s",
|
||||
modname,
|
||||
str(exc),
|
||||
)
|
||||
traceback.print_exc()
|
||||
continue
|
||||
|
||||
logger.info("Checking module %s", plugin_module.__name__)
|
||||
if plugin_module.__file__ is None:
|
||||
logger.warning("Plugin module %s has no source file", modname)
|
||||
continue
|
||||
|
||||
try:
|
||||
with open(plugin_module.__file__, "rb") as f:
|
||||
source = f.read()
|
||||
except OSError:
|
||||
logger.warning(
|
||||
"Failed to read file contents for %s", plugin_module.__file__
|
||||
)
|
||||
continue
|
||||
|
||||
try:
|
||||
module_ast_root = ast.parse(source)
|
||||
except (SyntaxError, ValueError) as exc:
|
||||
logger.warning(
|
||||
"Failed to parse source for %s: %s", plugin_module.__file__, str(exc)
|
||||
)
|
||||
raise
|
||||
|
||||
mod_visitor = ModuleVisitor(plugin_module)
|
||||
mod_visitor.visit(module_ast_root)
|
||||
|
||||
if mod_visitor.violations:
|
||||
yield from (
|
||||
(plugin_module.__name__, res) for res in iter(mod_visitor.violations)
|
||||
)
|
||||
|
||||
|
||||
def perform_review():
|
||||
found = 0
|
||||
for mod, usage in report_missing_requirements():
|
||||
found += 1
|
||||
print(str(usage))
|
||||
|
||||
if found:
|
||||
print(f"Found {found} issues")
|
||||
sys.exit(1)
|
||||
|
||||
print("All configurable classes passed validation!")
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-v", "--verbose", action="count", dest="verbosity", default=0)
|
||||
return parser.parse_args()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args = parse_args()
|
||||
if args.verbosity == 0:
|
||||
logger.setLevel(logging.WARNING)
|
||||
elif args.verbosity == 1:
|
||||
logger.setLevel(logging.INFO)
|
||||
elif args.verbosity > 1:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
perform_review()
|
||||
@@ -2,6 +2,7 @@
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
"""Volatility 3 - An open-source memory forensics framework"""
|
||||
|
||||
import inspect
|
||||
import sys
|
||||
from importlib import abc
|
||||
|
||||
+74
-54
@@ -10,6 +10,7 @@ User interfaces make use of the framework to:
|
||||
* run the plugin
|
||||
* display the results
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import inspect
|
||||
import io
|
||||
@@ -19,7 +20,7 @@ import os
|
||||
import sys
|
||||
import tempfile
|
||||
import traceback
|
||||
from typing import Any, Dict, List, Tuple, Type, Union
|
||||
from typing import Any, Dict, List, Optional, Tuple, Type, Union
|
||||
from urllib import parse, request
|
||||
|
||||
try:
|
||||
@@ -57,14 +58,14 @@ formatter = logging.Formatter("%(levelname)-8s %(name)-12s: %(message)s")
|
||||
console.setFormatter(formatter)
|
||||
|
||||
|
||||
class PrintedProgress(object):
|
||||
class PrintedProgress:
|
||||
"""A progress handler that prints the progress value and the description
|
||||
onto the command line."""
|
||||
|
||||
def __init__(self):
|
||||
self._max_message_len = 0
|
||||
|
||||
def __call__(self, progress: Union[int, float], description: str = None):
|
||||
def __call__(self, progress: Union[int, float], description: Optional[str] = None):
|
||||
"""A simple function for providing text-based feedback.
|
||||
|
||||
.. warning:: Only for development use.
|
||||
@@ -81,14 +82,14 @@ class PrintedProgress(object):
|
||||
class MuteProgress(PrintedProgress):
|
||||
"""A dummy progress handler that produces no output when called."""
|
||||
|
||||
def __call__(self, progress: Union[int, float], description: str = None):
|
||||
def __call__(self, progress: Union[int, float], description: Optional[str] = None):
|
||||
pass
|
||||
|
||||
|
||||
class CommandLine:
|
||||
"""Constructs a command-line interface object for users to run plugins."""
|
||||
|
||||
CLI_NAME = "volatility"
|
||||
CLI_NAME = os.path.basename(sys.argv[0]) # vol or volatility
|
||||
|
||||
def __init__(self):
|
||||
self.setup_logging()
|
||||
@@ -106,13 +107,6 @@ class CommandLine:
|
||||
|
||||
volatility3.framework.require_interface_version(2, 0, 0)
|
||||
|
||||
renderers = dict(
|
||||
[
|
||||
(x.name.lower(), x)
|
||||
for x in framework.class_subclasses(text_renderer.CLIRenderer)
|
||||
]
|
||||
)
|
||||
|
||||
# Load up system defaults
|
||||
delayed_logs, default_config = self.load_system_defaults("vol.json")
|
||||
|
||||
@@ -126,9 +120,7 @@ class CommandLine:
|
||||
"--help",
|
||||
action="help",
|
||||
default=argparse.SUPPRESS,
|
||||
help="Show this help message and exit, for specific plugin options use '{} <pluginname> --help'".format(
|
||||
parser.prog
|
||||
),
|
||||
help=f"Show this help message and exit, for specific plugin options use '{parser.prog} <pluginname> --help'",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
@@ -195,14 +187,6 @@ class CommandLine:
|
||||
default=False,
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-r",
|
||||
"--renderer",
|
||||
metavar="RENDERER",
|
||||
help=f"Determines how to render the output ({', '.join(list(renderers))})",
|
||||
default="quick",
|
||||
choices=list(renderers),
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--file",
|
||||
@@ -272,11 +256,6 @@ class CommandLine:
|
||||
known_args = [arg for arg in sys.argv if arg != "--help" and arg != "-h"]
|
||||
partial_args, _ = parser.parse_known_args(known_args)
|
||||
|
||||
banner_output = sys.stdout
|
||||
if renderers[partial_args.renderer].structured_output:
|
||||
banner_output = sys.stderr
|
||||
banner_output.write(f"Volatility 3 Framework {constants.PACKAGE_VERSION}\n")
|
||||
|
||||
### Start up logging
|
||||
if partial_args.log:
|
||||
file_logger = logging.FileHandler(partial_args.log)
|
||||
@@ -348,6 +327,24 @@ class CommandLine:
|
||||
|
||||
plugin_list = framework.list_plugins()
|
||||
|
||||
# Discover renderers after plugin directories are loaded
|
||||
# This allows custom renderers to be found in plugin directories
|
||||
renderers = dict(
|
||||
[
|
||||
(x.name.lower(), x)
|
||||
for x in framework.class_subclasses(text_renderer.CLIRenderer)
|
||||
]
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"-r",
|
||||
"--renderer",
|
||||
metavar="RENDERER",
|
||||
help=f"Determines how to render the output ({', '.join(list(renderers))})",
|
||||
default="quick",
|
||||
choices=list(renderers),
|
||||
)
|
||||
|
||||
seen_automagics = set()
|
||||
chosen_configurables_list = {}
|
||||
for amagic in automagics:
|
||||
@@ -360,16 +357,26 @@ class CommandLine:
|
||||
subparser = parser.add_subparsers(
|
||||
title="Plugins",
|
||||
dest="plugin",
|
||||
description="For plugin specific options, run '{} <plugin> --help'".format(
|
||||
self.CLI_NAME
|
||||
),
|
||||
description=f"For plugin specific options, run '{self.CLI_NAME} <plugin> --help'",
|
||||
action=volargparse.HelpfulSubparserAction,
|
||||
metavar="PLUGIN",
|
||||
)
|
||||
for plugin in sorted(plugin_list):
|
||||
# First line of a plugin docstring will be the short description for -h.
|
||||
# Text after the first two consecutive new lines will be
|
||||
# the additional description (argparse epilog).
|
||||
short_help = additional_help = None
|
||||
if plugin_list[plugin].__doc__ is not None:
|
||||
doc_split = plugin_list[plugin].__doc__.split("\n\n", 1)
|
||||
short_help = doc_split[0].strip()
|
||||
if len(doc_split) > 1:
|
||||
additional_help = doc_split[1].strip()
|
||||
|
||||
plugin_parser = subparser.add_parser(
|
||||
plugin,
|
||||
help=plugin_list[plugin].__doc__,
|
||||
description=plugin_list[plugin].__doc__,
|
||||
help=short_help,
|
||||
description=short_help,
|
||||
epilog=additional_help,
|
||||
)
|
||||
self.populate_requirements_argparse(plugin_parser, plugin_list[plugin])
|
||||
|
||||
@@ -384,8 +391,17 @@ class CommandLine:
|
||||
# before all the plugins have been added
|
||||
argcomplete.autocomplete(parser)
|
||||
args = parser.parse_args()
|
||||
|
||||
# Display banner - redirect to stderr if using structured output
|
||||
banner_output = sys.stdout
|
||||
if renderers[args.renderer].structured_output:
|
||||
banner_output = sys.stderr
|
||||
banner_output.write(f"Volatility 3 Framework {constants.PACKAGE_VERSION}\n")
|
||||
|
||||
if args.plugin is None:
|
||||
parser.error("Please select a plugin to run")
|
||||
parser.error(
|
||||
f"Please select a plugin to run (see '{self.CLI_NAME} --help' for options"
|
||||
)
|
||||
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVV, f"Cache directory used: {constants.CACHE_PATH}"
|
||||
@@ -413,7 +429,7 @@ class CommandLine:
|
||||
|
||||
# UI fills in the config, here we load it from the config file and do it before we process the CL parameters
|
||||
if args.config:
|
||||
with open(args.config, "r") as f:
|
||||
with open(args.config) as f:
|
||||
json_val = json.load(f)
|
||||
ctx.config.splice(
|
||||
plugin_config_path,
|
||||
@@ -443,8 +459,9 @@ class CommandLine:
|
||||
raise ValueError(
|
||||
"Invalid extension (extensions must be of the format \"conf.path.value='value'\")"
|
||||
)
|
||||
address, value = extension[: extension.find("=")], json.loads(
|
||||
extension[extension.find("=") + 1 :]
|
||||
address, value = (
|
||||
extension[: extension.find("=")],
|
||||
json.loads(extension[extension.find("=") + 1 :]),
|
||||
)
|
||||
ctx.config[address] = value
|
||||
|
||||
@@ -495,6 +512,9 @@ class CommandLine:
|
||||
try:
|
||||
# Construct and run the plugin
|
||||
if constructed:
|
||||
vollog.debug(
|
||||
f"Successfully constructed {args.plugin} {constructed.version}"
|
||||
)
|
||||
grid = constructed.run()
|
||||
renderer = renderers[args.renderer]()
|
||||
renderer.filter = text_filter.CLIFilter(grid, args.filters)
|
||||
@@ -556,7 +576,7 @@ class CommandLine:
|
||||
delayed_logs.append(
|
||||
(
|
||||
logging.DEBUG,
|
||||
f"Loaded configuration: {json.dumps(result, indent = 2, sort_keys = True)}",
|
||||
f"Loaded configuration: {json.dumps(result, indent=2, sort_keys=True)}",
|
||||
)
|
||||
)
|
||||
return delayed_logs, result
|
||||
@@ -573,6 +593,8 @@ class CommandLine:
|
||||
fulltrace = traceback.TracebackException.from_exception(excp).format(chain=True)
|
||||
vollog.debug("".join(fulltrace))
|
||||
|
||||
file_a_bug_msg = f"Please re-run with -vvv and file a bug with the output at {constants.BUG_URL}"
|
||||
|
||||
if isinstance(excp, exceptions.InvalidAddressException):
|
||||
general = "Volatility was unable to read a requested page:"
|
||||
if isinstance(excp, exceptions.SwappedInvalidAddressException):
|
||||
@@ -617,9 +639,7 @@ class CommandLine:
|
||||
elif isinstance(excp, exceptions.LayerException):
|
||||
general = f"Volatility experienced a layer-related issue: {excp.layer_name}"
|
||||
detail = f"{excp}"
|
||||
caused_by = [
|
||||
"A faulty layer implementation (re-run with -vvv and file a bug)"
|
||||
]
|
||||
caused_by = [f"A faulty layer implementation. {file_a_bug_msg}"]
|
||||
elif isinstance(excp, exceptions.MissingModuleException):
|
||||
general = f"Volatility could not import a necessary module: {excp.module}"
|
||||
detail = f"{excp}"
|
||||
@@ -630,13 +650,17 @@ class CommandLine:
|
||||
general = "Volatility experienced an issue when rendering the output:"
|
||||
detail = f"{excp}"
|
||||
caused_by = ["An invalid renderer option, such as no visible columns"]
|
||||
elif isinstance(excp, exceptions.VersionMismatchException):
|
||||
general = "A version mismatch was detected between two components:"
|
||||
detail = f"{excp}"
|
||||
caused_by = [
|
||||
excp.failure_reason or "An outdated API caller, such as a method.",
|
||||
file_a_bug_msg,
|
||||
]
|
||||
else:
|
||||
general = "Volatility encountered an unexpected situation."
|
||||
detail = ""
|
||||
caused_by = [
|
||||
"Please re-run using with -vvv and file a bug with the output",
|
||||
f"at {constants.BUG_URL}",
|
||||
]
|
||||
caused_by = [file_a_bug_msg]
|
||||
|
||||
# Code that actually renders the exception
|
||||
output = sys.stderr
|
||||
@@ -719,9 +743,7 @@ class CommandLine:
|
||||
if isinstance(requirement, requirements.ListRequirement):
|
||||
if not isinstance(value, list):
|
||||
raise TypeError(
|
||||
"Configuration for ListRequirement was not a list: {}".format(
|
||||
requirement.name
|
||||
)
|
||||
f"Configuration for ListRequirement was not a list: {requirement.name}"
|
||||
)
|
||||
value = [requirement.element_type(x) for x in value]
|
||||
if not inspect.isclass(configurables_list[configurable]):
|
||||
@@ -743,7 +765,7 @@ class CommandLine:
|
||||
constants.LOGLEVEL_VVVV,
|
||||
]
|
||||
):
|
||||
logging.addLevelName(level_value, f"DETAIL {level+1}")
|
||||
logging.addLevelName(level_value, f"DETAIL {level + 1}")
|
||||
|
||||
def file_handler_class_factory(self, direct=True):
|
||||
output_dir = self.output_dir
|
||||
@@ -794,7 +816,7 @@ class CommandLine:
|
||||
fd, self._name = tempfile.mkstemp(
|
||||
suffix=".vol3", prefix="tmp_", dir=output_dir
|
||||
)
|
||||
self._file = io.open(fd, mode="w+b")
|
||||
self._file = open(fd, mode="w+b")
|
||||
CLIFileHandler.__init__(self, filename)
|
||||
for item in dir(self._file):
|
||||
if not item.startswith("_") and item not in (
|
||||
@@ -867,9 +889,7 @@ class CommandLine:
|
||||
requirement, interfaces.configuration.RequirementInterface
|
||||
):
|
||||
raise TypeError(
|
||||
"Plugin contains requirements that are not RequirementInterfaces: {}".format(
|
||||
configurable.__name__
|
||||
)
|
||||
f"Plugin contains requirements that are not RequirementInterfaces: {configurable.__name__}"
|
||||
)
|
||||
if isinstance(requirement, interfaces.configuration.SimpleTypeRequirement):
|
||||
additional["type"] = requirement.instance_type
|
||||
@@ -884,7 +904,7 @@ class CommandLine:
|
||||
volatility3.framework.configuration.requirements.ListRequirement,
|
||||
):
|
||||
# Allow a list of integers, specified with the convenient 0x hexadecimal format
|
||||
if requirement.element_type == int:
|
||||
if requirement.element_type is int:
|
||||
additional["type"] = lambda x: int(x, 0)
|
||||
else:
|
||||
additional["type"] = requirement.element_type
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import logging
|
||||
from typing import Any, List, Optional
|
||||
from volatility3.framework import constants, interfaces
|
||||
import re
|
||||
from typing import Any, List, Optional
|
||||
|
||||
from volatility3.framework import constants, interfaces
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
@@ -67,16 +68,16 @@ class ColumnFilter:
|
||||
) -> None:
|
||||
self.column_num = column_num
|
||||
self.pattern = pattern
|
||||
self.exclude = exclude
|
||||
self.regex = regex
|
||||
self.exclude = exclude
|
||||
|
||||
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 bool(re.search(self.pattern, f"{item}"))
|
||||
return self.pattern in f"{item}"
|
||||
except IOError:
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
def found(self, row: List[Any]) -> bool:
|
||||
|
||||
@@ -9,9 +9,9 @@ import random
|
||||
import string
|
||||
import sys
|
||||
from functools import wraps
|
||||
from typing import Any, Callable, Dict, List, Tuple
|
||||
from volatility3.cli import text_filter
|
||||
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, TypeVar, Union
|
||||
|
||||
from volatility3.cli import text_filter
|
||||
from volatility3.framework import exceptions, interfaces, renderers
|
||||
from volatility3.framework.renderers import format_hints
|
||||
|
||||
@@ -49,10 +49,12 @@ def hex_bytes_as_text(value: bytes, width: int = 16) -> str:
|
||||
output += "\n"
|
||||
printables = ""
|
||||
|
||||
# Handle leftovers when the lenght is not mutiple of width
|
||||
# Handle leftovers when the length is not a multiple of width
|
||||
if printables:
|
||||
output += " " * (width - len(printables))
|
||||
padding = width - len(printables)
|
||||
output += " " * padding
|
||||
output += printables
|
||||
output += " " * padding
|
||||
|
||||
return output
|
||||
|
||||
@@ -78,7 +80,12 @@ def multitypedata_as_text(value: format_hints.MultiTypeData) -> str:
|
||||
return hex_bytes_as_text(value)
|
||||
|
||||
|
||||
def optional(func: Callable) -> Callable:
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def optional(
|
||||
func: Callable[[Union[interfaces.renderers.BaseAbsentValue, T]], str],
|
||||
) -> Callable[[T], str]:
|
||||
@wraps(func)
|
||||
def wrapped(x: Any) -> str:
|
||||
if isinstance(x, interfaces.renderers.BaseAbsentValue):
|
||||
@@ -108,7 +115,7 @@ def quoted_optional(func: Callable) -> Callable:
|
||||
return wrapped
|
||||
|
||||
|
||||
def display_disassembly(disasm: interfaces.renderers.Disassembly) -> str:
|
||||
def display_disassembly(disasm: renderers.Disassembly) -> str:
|
||||
"""Renders a disassembly renderer type into string format.
|
||||
|
||||
Args:
|
||||
@@ -130,18 +137,121 @@ def display_disassembly(disasm: interfaces.renderers.Disassembly) -> str:
|
||||
for i in disasm_types[disasm.architecture].disasm(
|
||||
disasm.data, disasm.offset
|
||||
):
|
||||
output += f"\n0x{i.address:x}:\t{i.mnemonic}\t{i.op_str}"
|
||||
output += f"\n{i.address:#x}:\t{i.mnemonic}\t{i.op_str}"
|
||||
return output
|
||||
return QuickTextRenderer._type_renderers[bytes](disasm.data)
|
||||
|
||||
|
||||
class CLITypeRenderer(interfaces.renderers.TypeRendererInterface):
|
||||
def __init__(self, func):
|
||||
super().__init__(func=optional(func))
|
||||
|
||||
|
||||
class LayerDataRenderer(CLITypeRenderer):
|
||||
"""Renders a LayerData object into data/bytes"""
|
||||
|
||||
def __init__(self):
|
||||
self.context_byte_len = 0
|
||||
self.width = 16
|
||||
self.display_offset = False
|
||||
self.display_hex = True
|
||||
self.display_ascii = True
|
||||
|
||||
def render(
|
||||
data: Union[renderers.LayerData, interfaces.renderers.BaseAbsentValue],
|
||||
) -> str:
|
||||
if isinstance(data, interfaces.renderers.BaseAbsentValue):
|
||||
# FIXME: Do something cleverer here
|
||||
return ""
|
||||
|
||||
specific_data, error_bytes = self.render_bytes(data)
|
||||
|
||||
printables = ""
|
||||
output = "\n"
|
||||
for count, byte in enumerate(specific_data):
|
||||
if count not in error_bytes:
|
||||
output += f"{byte:02x} "
|
||||
char = chr(byte)
|
||||
printables += char if 0x20 <= byte <= 0x7E else "."
|
||||
else:
|
||||
output += "__ "
|
||||
printables += "."
|
||||
if count % self.width == self.width - 1:
|
||||
output += printables
|
||||
if count < len(specific_data) - 1:
|
||||
output += "\n"
|
||||
printables = ""
|
||||
|
||||
# Handle leftovers when the length is not a multiple of width
|
||||
if printables:
|
||||
padding = self.width - len(printables)
|
||||
output += " " * padding
|
||||
output += printables
|
||||
output += " " * padding
|
||||
|
||||
return output
|
||||
|
||||
render_func = render
|
||||
return super().__init__(render_func)
|
||||
|
||||
def render_bytes(self, data: renderers.LayerData) -> Tuple[bytes, Set[int]]:
|
||||
"""Renders a valid LayerData into bytes (with context bytes)"""
|
||||
context_byte_len = self.context_byte_len if not data.no_surrounding else 0
|
||||
|
||||
layer = data.context.layers[data.layer_name]
|
||||
# Map of the holes
|
||||
error_bytes = set()
|
||||
start_offset = data.offset - context_byte_len
|
||||
end_offset = data.offset + data.length + context_byte_len
|
||||
if isinstance(layer, interfaces.layers.TranslationLayerInterface):
|
||||
error_bytes = set()
|
||||
mapping = iter(layer.mapping(start_offset, end_offset, True))
|
||||
current_map = next(mapping)
|
||||
for i in range(start_offset, end_offset):
|
||||
# Run through the bytes, check if they're present
|
||||
offset, sublength, _, _, _ = current_map
|
||||
if i < offset:
|
||||
error_bytes.add(i - start_offset)
|
||||
if i > offset + sublength:
|
||||
try:
|
||||
current_map = next(mapping)
|
||||
except StopIteration:
|
||||
pass
|
||||
offset, sublength, _, _, _ = current_map
|
||||
if i > offset + sublength:
|
||||
error_bytes.add(i - start_offset)
|
||||
|
||||
# Padded data
|
||||
specific_data = data.context.layers[data.layer_name].read(
|
||||
start_offset,
|
||||
end_offset - start_offset,
|
||||
True,
|
||||
)
|
||||
|
||||
return specific_data, error_bytes
|
||||
|
||||
|
||||
class CLIRenderer(interfaces.renderers.Renderer):
|
||||
"""Class to add specific requirements for CLI renderers."""
|
||||
|
||||
_type_renderers = {
|
||||
format_hints.Bin: CLITypeRenderer(lambda x: f"0b{x:b}"),
|
||||
format_hints.Hex: CLITypeRenderer(lambda x: f"0x{x:x}"),
|
||||
format_hints.HexBytes: CLITypeRenderer(hex_bytes_as_text),
|
||||
format_hints.MultiTypeData: CLITypeRenderer(multitypedata_as_text),
|
||||
renderers.Disassembly: CLITypeRenderer(display_disassembly),
|
||||
bytes: CLITypeRenderer(lambda x: " ".join(f"{b:02x}" for b in x)),
|
||||
renderers.LayerData: LayerDataRenderer(),
|
||||
datetime.datetime: CLITypeRenderer(
|
||||
lambda x: x.strftime("%Y-%m-%d %H:%M:%S.%f %Z")
|
||||
),
|
||||
"default": CLITypeRenderer(lambda x: f"{x}"),
|
||||
}
|
||||
|
||||
name = "unnamed"
|
||||
structured_output = False
|
||||
filter: text_filter.CLIFilter = None
|
||||
column_hide_list: list = None
|
||||
filter: Optional[text_filter.CLIFilter] = None
|
||||
column_hide_list: Optional[list] = None
|
||||
|
||||
def ignored_columns(
|
||||
self,
|
||||
@@ -168,21 +278,10 @@ class CLIRenderer(interfaces.renderers.Renderer):
|
||||
|
||||
|
||||
class QuickTextRenderer(CLIRenderer):
|
||||
_type_renderers = {
|
||||
format_hints.Bin: optional(lambda x: f"0b{x:b}"),
|
||||
format_hints.Hex: optional(lambda x: f"0x{x:x}"),
|
||||
format_hints.HexBytes: optional(hex_bytes_as_text),
|
||||
format_hints.MultiTypeData: quoted_optional(multitypedata_as_text),
|
||||
interfaces.renderers.Disassembly: optional(display_disassembly),
|
||||
bytes: optional(lambda x: " ".join([f"{b:02x}" for b in x])),
|
||||
datetime.datetime: optional(lambda x: x.strftime("%Y-%m-%d %H:%M:%S.%f %Z")),
|
||||
"default": optional(lambda x: f"{x}"),
|
||||
}
|
||||
|
||||
name = "quick"
|
||||
|
||||
def get_render_options(self):
|
||||
pass
|
||||
return []
|
||||
|
||||
def render(self, grid: interfaces.renderers.TreeGrid) -> None:
|
||||
"""Renders each column immediately to stdout.
|
||||
@@ -240,7 +339,7 @@ class NoneRenderer(CLIRenderer):
|
||||
name = "none"
|
||||
|
||||
def get_render_options(self):
|
||||
pass
|
||||
return []
|
||||
|
||||
def render(self, grid: interfaces.renderers.TreeGrid) -> None:
|
||||
if not grid.populated:
|
||||
@@ -248,22 +347,11 @@ class NoneRenderer(CLIRenderer):
|
||||
|
||||
|
||||
class CSVRenderer(CLIRenderer):
|
||||
_type_renderers = {
|
||||
format_hints.Bin: optional(lambda x: f"0b{x:b}"),
|
||||
format_hints.Hex: optional(lambda x: f"0x{x:x}"),
|
||||
format_hints.HexBytes: optional(hex_bytes_as_text),
|
||||
format_hints.MultiTypeData: optional(multitypedata_as_text),
|
||||
interfaces.renderers.Disassembly: optional(display_disassembly),
|
||||
bytes: optional(lambda x: " ".join([f"{b:02x}" for b in x])),
|
||||
datetime.datetime: optional(lambda x: x.strftime("%Y-%m-%d %H:%M:%S.%f %Z")),
|
||||
"default": optional(lambda x: f"{x}"),
|
||||
}
|
||||
|
||||
name = "csv"
|
||||
structured_output = True
|
||||
|
||||
def get_render_options(self):
|
||||
pass
|
||||
return []
|
||||
|
||||
def render(self, grid: interfaces.renderers.TreeGrid) -> None:
|
||||
"""Renders each row immediately to stdout.
|
||||
@@ -314,12 +402,10 @@ class CSVRenderer(CLIRenderer):
|
||||
|
||||
|
||||
class PrettyTextRenderer(CLIRenderer):
|
||||
_type_renderers = QuickTextRenderer._type_renderers
|
||||
|
||||
name = "pretty"
|
||||
|
||||
def get_render_options(self):
|
||||
pass
|
||||
return []
|
||||
|
||||
def render(self, grid: interfaces.renderers.TreeGrid) -> None:
|
||||
"""Renders each column immediately to stdout.
|
||||
@@ -340,7 +426,7 @@ class PrettyTextRenderer(CLIRenderer):
|
||||
column_separator = " | "
|
||||
|
||||
tree_indent_column = "".join(
|
||||
random.choice(string.ascii_uppercase + string.digits) for _ in range(20)
|
||||
random.choices(string.ascii_uppercase + string.digits, k=20)
|
||||
)
|
||||
max_column_widths = dict(
|
||||
[(column.name, len(column.name)) for column in grid.columns]
|
||||
@@ -378,7 +464,9 @@ class PrettyTextRenderer(CLIRenderer):
|
||||
accumulator.append((node.path_depth, line))
|
||||
return accumulator
|
||||
|
||||
final_output: List[Tuple[int, Dict[interfaces.renderers.Column, bytes]]] = []
|
||||
final_output: List[Tuple[int, Dict[interfaces.renderers.Column, list[str]]]] = (
|
||||
[]
|
||||
)
|
||||
if not grid.populated:
|
||||
grid.populate(visitor, final_output)
|
||||
else:
|
||||
@@ -445,10 +533,19 @@ class PrettyTextRenderer(CLIRenderer):
|
||||
|
||||
class JsonRenderer(CLIRenderer):
|
||||
_type_renderers = {
|
||||
format_hints.HexBytes: quoted_optional(hex_bytes_as_text),
|
||||
interfaces.renderers.Disassembly: quoted_optional(display_disassembly),
|
||||
format_hints.HexBytes: lambda x: (
|
||||
x.hex(" ")
|
||||
if not isinstance(x, interfaces.renderers.BaseAbsentValue)
|
||||
else "N/A"
|
||||
),
|
||||
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])),
|
||||
renderers.LayerData: lambda x: (
|
||||
LayerDataRenderer().render_bytes(x)[0].hex(" ")
|
||||
if not isinstance(x, interfaces.renderers.BaseAbsentValue)
|
||||
else "N/A"
|
||||
),
|
||||
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)
|
||||
@@ -461,11 +558,11 @@ class JsonRenderer(CLIRenderer):
|
||||
structured_output = True
|
||||
|
||||
def get_render_options(self) -> List[interfaces.renderers.RenderOption]:
|
||||
pass
|
||||
return []
|
||||
|
||||
def output_result(self, outfd, result):
|
||||
"""Outputs the JSON data to a file in a particular format"""
|
||||
outfd.write("{}\n".format(json.dumps(result, indent=2, sort_keys=True)))
|
||||
outfd.write(f"{json.dumps(result, indent=2, sort_keys=True)}\n")
|
||||
|
||||
def render(self, grid: interfaces.renderers.TreeGrid):
|
||||
outfd = sys.stdout
|
||||
@@ -501,7 +598,8 @@ class JsonRenderer(CLIRenderer):
|
||||
if self.filter and self.filter.filter(line):
|
||||
return accumulator
|
||||
|
||||
if node.parent:
|
||||
# Only add if the parent hasn't been filtered out
|
||||
if node.parent and node.parent.path in acc_map:
|
||||
acc_map[node.parent.path]["__children"].append(node_dict)
|
||||
else:
|
||||
final_tree.append(node_dict)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import argparse
|
||||
import gettext
|
||||
import re
|
||||
from typing import List, Optional, Sequence, Any, Union
|
||||
from typing import Optional, Sequence, Any, Union
|
||||
|
||||
|
||||
# This effectively overrides/monkeypatches the core argparse module to provide more helpful output around choices
|
||||
|
||||
@@ -49,7 +49,7 @@ class VolShell(cli.CommandLine):
|
||||
python terminal with all the volatility support calls available.
|
||||
"""
|
||||
|
||||
CLI_NAME = "volshell"
|
||||
CLI_NAME = os.path.basename(sys.argv[0]) # volshell
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
@@ -282,9 +282,7 @@ class VolShell(cli.CommandLine):
|
||||
for plugin in volshell_plugin_list:
|
||||
subparser = parser.add_argument_group(
|
||||
title=plugin.capitalize(),
|
||||
description="Configuration options based on {} options".format(
|
||||
plugin.capitalize()
|
||||
),
|
||||
description=f"Configuration options based on {plugin.capitalize()} options",
|
||||
)
|
||||
self.populate_requirements_argparse(subparser, volshell_plugin_list[plugin])
|
||||
configurables_list[plugin] = volshell_plugin_list[plugin]
|
||||
@@ -331,7 +329,7 @@ class VolShell(cli.CommandLine):
|
||||
|
||||
# UI fills in the config, here we load it from the config file and do it before we process the CL parameters
|
||||
if args.config:
|
||||
with open(args.config, "r") as f:
|
||||
with open(args.config) as f:
|
||||
json_val = json.load(f)
|
||||
ctx.config.splice(
|
||||
plugin_config_path,
|
||||
@@ -346,8 +344,9 @@ class VolShell(cli.CommandLine):
|
||||
raise ValueError(
|
||||
"Invalid extension (extensions must be of the format \"conf.path.value='value'\")"
|
||||
)
|
||||
address, value = extension[: extension.find("=")], json.loads(
|
||||
extension[extension.find("=") + 1 :]
|
||||
address, value = (
|
||||
extension[: extension.find("=")],
|
||||
json.loads(extension[extension.find("=") + 1 :]),
|
||||
)
|
||||
ctx.config[address] = value
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import random
|
||||
import string
|
||||
import struct
|
||||
import sys
|
||||
import textwrap
|
||||
from typing import Any, Dict, Iterable, List, Optional, Tuple, Type, Union
|
||||
from urllib import parse, request
|
||||
|
||||
@@ -23,12 +24,24 @@ try:
|
||||
except ImportError:
|
||||
has_capstone = False
|
||||
|
||||
try:
|
||||
from IPython import terminal
|
||||
from traitlets import config as traitlets_config
|
||||
|
||||
has_ipython = True
|
||||
except ImportError:
|
||||
has_ipython = False
|
||||
|
||||
MAX_DEREFERENCE_COUNT = 4 # the max number of times display_type should follow pointers
|
||||
|
||||
|
||||
class Volshell(interfaces.plugins.PluginInterface):
|
||||
"""Shell environment to directly interact with a memory image."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
_version = (1, 0, 0)
|
||||
|
||||
DEFAULT_NUM_DISPLAY_BYTES = 128
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@@ -43,24 +56,36 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
reqs: List[interfaces.configuration.RequirementInterface] = []
|
||||
reqs: List[interfaces.configuration.RequirementInterface] = [
|
||||
requirements.VersionRequirement(
|
||||
name="regex_scanner",
|
||||
component=scanners.RegExScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
]
|
||||
if cls == Volshell:
|
||||
reqs = [
|
||||
reqs += [
|
||||
requirements.TranslationLayerRequirement(
|
||||
name="primary", description="Memory layer for the kernel"
|
||||
),
|
||||
requirements.URIRequirement(
|
||||
name="script",
|
||||
description="File to load and execute at start",
|
||||
default=None,
|
||||
optional=True,
|
||||
)
|
||||
),
|
||||
requirements.BooleanRequirement(
|
||||
name="script-only",
|
||||
description="Exit volshell after the script specified in --script completes",
|
||||
default=False,
|
||||
optional=True,
|
||||
),
|
||||
]
|
||||
return reqs + [
|
||||
requirements.TranslationLayerRequirement(
|
||||
name="primary", description="Memory layer for the kernel"
|
||||
),
|
||||
]
|
||||
|
||||
return reqs
|
||||
|
||||
def run(
|
||||
self, additional_locals: Dict[str, Any] = {}
|
||||
self, additional_locals: Dict[str, Any] = None
|
||||
) -> interfaces.renderers.TreeGrid:
|
||||
"""Runs the interactive volshell plugin.
|
||||
|
||||
@@ -68,44 +93,74 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
Return a TreeGrid but this is always empty since the point of this plugin is to run interactively
|
||||
"""
|
||||
|
||||
# Try to enable tab completion
|
||||
try:
|
||||
import readline
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
import rlcompleter
|
||||
if additional_locals is None:
|
||||
additional_locals = {}
|
||||
|
||||
completer = rlcompleter.Completer(namespace=self._construct_locals_dict())
|
||||
readline.set_completer(completer.complete)
|
||||
readline.parse_and_bind("tab: complete")
|
||||
print("Readline imported successfully")
|
||||
# Try to enable tab completion
|
||||
if not has_ipython:
|
||||
try:
|
||||
import readline
|
||||
import rlcompleter
|
||||
|
||||
completer = rlcompleter.Completer(
|
||||
namespace=self._construct_locals_dict()
|
||||
)
|
||||
readline.set_completer(completer.complete)
|
||||
readline.parse_and_bind("tab: complete")
|
||||
print("Readline imported successfully")
|
||||
except ImportError:
|
||||
print(
|
||||
"Readline or rlcompleter module could not be imported. Tab completion will not be available."
|
||||
)
|
||||
|
||||
# TODO: provide help, consider generic functions (pslist?) and/or providing windows/linux functions
|
||||
|
||||
mode = self.__module__.split(".")[-1]
|
||||
mode = mode[0].upper() + mode[1:]
|
||||
|
||||
banner = f"""
|
||||
Call help() to see available functions
|
||||
banner = textwrap.dedent(
|
||||
f"""
|
||||
Call help() to see available functions
|
||||
|
||||
Volshell mode : {mode}
|
||||
Current Layer : {self.current_layer}
|
||||
Current Symbol Table : {self.current_symbol_table}
|
||||
Current Kernel Name : {self.current_kernel_name}
|
||||
"""
|
||||
Volshell mode : {mode}
|
||||
Current Layer : {self.current_layer}
|
||||
Current Symbol Table : {self.current_symbol_table}
|
||||
Current Kernel Name : {self.current_kernel_name}
|
||||
"""
|
||||
)
|
||||
|
||||
sys.ps1 = f"({self.current_layer}) >>> "
|
||||
# Dict self._construct_locals_dict() will have priority on keys
|
||||
combined_locals = additional_locals.copy()
|
||||
combined_locals.update(self._construct_locals_dict())
|
||||
self.__console = code.InteractiveConsole(locals=combined_locals)
|
||||
if has_ipython:
|
||||
|
||||
class LayerNamePrompt(terminal.prompts.Prompts):
|
||||
def in_prompt_tokens(self, cli=None):
|
||||
slf = self.shell.user_ns.get("self")
|
||||
layer_name = slf.current_layer if slf else "no_layer"
|
||||
return [(terminal.prompts.Token.Prompt, f"[{layer_name}]> ")]
|
||||
|
||||
c = traitlets_config.Config()
|
||||
c.TerminalInteractiveShell.prompts_class = LayerNamePrompt
|
||||
c.InteractiveShellEmbed.banner2 = banner
|
||||
self.__console = terminal.embed.InteractiveShellEmbed(
|
||||
config=c, user_ns=combined_locals
|
||||
)
|
||||
else:
|
||||
self.__console = code.InteractiveConsole(locals=combined_locals)
|
||||
# Since we have to do work to add the option only once for all different modes of volshell, we can't
|
||||
# rely on the default having been set
|
||||
if self.config.get("script", None) is not None:
|
||||
self.run_script(location=self.config["script"])
|
||||
|
||||
self.__console.interact(banner=banner)
|
||||
if self.config.get("script-only"):
|
||||
exit()
|
||||
|
||||
if has_ipython:
|
||||
self.__console()
|
||||
else:
|
||||
self.__console.interact(banner=banner)
|
||||
|
||||
return renderers.TreeGrid([("Terminating", str)], None)
|
||||
|
||||
@@ -132,6 +187,9 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
def construct_locals(self) -> List[Tuple[List[str], Any]]:
|
||||
"""Returns a listing of the functions to be added to the environment."""
|
||||
return [
|
||||
(["bc", "breakpoint_clear"], self.breakpoint_clear),
|
||||
(["bl", "breakpoint_list"], self.breakpoint_list),
|
||||
(["bp", "breakpoint"], self.breakpoint),
|
||||
(["dt", "display_type"], self.display_type),
|
||||
(["db", "display_bytes"], self.display_bytes),
|
||||
(["dw", "display_words"], self.display_words),
|
||||
@@ -203,7 +261,7 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
connector = " "
|
||||
if chunk_size < 2:
|
||||
connector = ""
|
||||
ascii_data = connector.join([self._ascii_bytes(x) for x in valid_data])
|
||||
ascii_data = connector.join(self._ascii_bytes(x) for x in valid_data)
|
||||
|
||||
print(hex(offset), " ", hex_data, " ", ascii_data)
|
||||
offset += 16
|
||||
@@ -240,7 +298,7 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
return None
|
||||
return self.context.modules[self.current_kernel_name]
|
||||
|
||||
def change_layer(self, layer_name: str = None):
|
||||
def change_layer(self, layer_name: Optional[str] = None):
|
||||
"""Changes the current default layer"""
|
||||
if not layer_name:
|
||||
layer_name = self.current_layer
|
||||
@@ -250,7 +308,7 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
self.__current_layer = layer_name
|
||||
sys.ps1 = f"({self.current_layer}) >>> "
|
||||
|
||||
def change_symbol_table(self, symbol_table_name: str = None):
|
||||
def change_symbol_table(self, symbol_table_name: Optional[str] = None):
|
||||
"""Changes the current_symbol_table"""
|
||||
if not symbol_table_name:
|
||||
print("No symbol table provided, not changing current symbol table")
|
||||
@@ -262,7 +320,7 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
self.__current_symbol_table = symbol_table_name
|
||||
print(f"Current Symbol Table: {self.current_symbol_table}")
|
||||
|
||||
def change_kernel(self, kernel_name: str = None):
|
||||
def change_kernel(self, kernel_name: Optional[str] = None):
|
||||
if not kernel_name:
|
||||
print("No kernel module name provided, not changing current kernel")
|
||||
if kernel_name not in self.context.modules:
|
||||
@@ -277,23 +335,25 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
self._display_data(offset, remaining_data)
|
||||
|
||||
def display_quadwords(
|
||||
self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None
|
||||
self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None, byteorder="@"
|
||||
):
|
||||
"""Displays quad-word values (8 bytes) and corresponding ASCII characters"""
|
||||
remaining_data = self._read_data(offset, count=count, layer_name=layer_name)
|
||||
self._display_data(offset, remaining_data, format_string="Q")
|
||||
self._display_data(offset, remaining_data, format_string=f"{byteorder}Q")
|
||||
|
||||
def display_doublewords(
|
||||
self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None
|
||||
self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None, byteorder="@"
|
||||
):
|
||||
"""Displays double-word values (4 bytes) and corresponding ASCII characters"""
|
||||
remaining_data = self._read_data(offset, count=count, layer_name=layer_name)
|
||||
self._display_data(offset, remaining_data, format_string="I")
|
||||
self._display_data(offset, remaining_data, format_string=f"{byteorder}I")
|
||||
|
||||
def display_words(self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None):
|
||||
def display_words(
|
||||
self, offset, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None, byteorder="@"
|
||||
):
|
||||
"""Displays word values (2 bytes) and corresponding ASCII characters"""
|
||||
remaining_data = self._read_data(offset, count=count, layer_name=layer_name)
|
||||
self._display_data(offset, remaining_data, format_string="H")
|
||||
self._display_data(offset, remaining_data, format_string=f"{byteorder}H")
|
||||
|
||||
def regex_scan(self, pattern, count=DEFAULT_NUM_DISPLAY_BYTES, layer_name=None):
|
||||
"""Scans for regex pattern in layer using RegExScanner."""
|
||||
@@ -342,14 +402,39 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
for i in disasm_types[architecture].disasm(remaining_data, offset):
|
||||
print(f"0x{i.address:x}:\t{i.mnemonic}\t{i.op_str}")
|
||||
|
||||
def _get_type_name_with_pointer(
|
||||
self,
|
||||
member_type: Union[
|
||||
str, interfaces.objects.ObjectInterface, interfaces.objects.Template
|
||||
],
|
||||
depth: int = 0,
|
||||
) -> str:
|
||||
"""Takes a member_type from and returns the subtype name with a * if the member_type is
|
||||
a pointer otherwise it returns just the normal type name."""
|
||||
pointer_marker = "*" * depth
|
||||
try:
|
||||
if member_type.vol.object_class == objects.Pointer:
|
||||
sub_member_type = member_type.vol.subtype
|
||||
# follow at most MAX_DEREFERENCE_COUNT pointers. A guard against, hopefully unlikely, infinite loops
|
||||
if depth < MAX_DEREFERENCE_COUNT:
|
||||
return self._get_type_name_with_pointer(sub_member_type, depth + 1)
|
||||
except AttributeError:
|
||||
pass # not all objects get a `object_class`, and those that don't are not pointers.
|
||||
finally:
|
||||
member_type_name = pointer_marker + member_type.vol.type_name
|
||||
return member_type_name
|
||||
|
||||
def display_type(
|
||||
self,
|
||||
object: Union[
|
||||
str, interfaces.objects.ObjectInterface, interfaces.objects.Template
|
||||
],
|
||||
offset: int = None,
|
||||
offset: Optional[int] = None,
|
||||
):
|
||||
"""Display Type describes the members of a particular object in alphabetical order"""
|
||||
|
||||
MAX_TYPENAME_DISPLAY_LENGTH = 256
|
||||
|
||||
if not isinstance(
|
||||
object,
|
||||
(str, interfaces.objects.ObjectInterface, interfaces.objects.Template),
|
||||
@@ -374,26 +459,56 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
volobject.vol.type_name, layer_name=self.current_layer, offset=offset
|
||||
)
|
||||
|
||||
if hasattr(volobject.vol, "size"):
|
||||
print(f"{volobject.vol.type_name} ({volobject.vol.size} bytes)")
|
||||
elif hasattr(volobject.vol, "data_format"):
|
||||
data_format = volobject.vol.data_format
|
||||
print(
|
||||
"{} ({} bytes, {} endian, {})".format(
|
||||
volobject.vol.type_name,
|
||||
data_format.length,
|
||||
data_format.byteorder,
|
||||
"signed" if data_format.signed else "unsigned",
|
||||
)
|
||||
)
|
||||
# add special case for pointer so that information about the struct the
|
||||
# pointer is pointing to is shown rather than simply the fact this is a
|
||||
# pointer object. The "dereference_count < MAX_DEREFERENCE_COUNT" is to
|
||||
# guard against loops
|
||||
dereference_count = 0
|
||||
while (
|
||||
isinstance(volobject, objects.Pointer)
|
||||
and dereference_count < MAX_DEREFERENCE_COUNT
|
||||
):
|
||||
# before defreerencing the pointer, show it's information
|
||||
print(f"{' ' * dereference_count}{self._display_simple_type(volobject)}")
|
||||
|
||||
# check that we can follow the pointer before dereferencing and do not
|
||||
# attempt to follow null pointers.
|
||||
if volobject.is_readable() and volobject != 0:
|
||||
# now deference the pointer and store this as the new volobject
|
||||
volobject = volobject.dereference()
|
||||
dereference_count = dereference_count + 1
|
||||
else:
|
||||
# if we aren't able to follow the pointers anymore then there will
|
||||
# be no more information to display as we've already printed the
|
||||
# details of this pointer including the fact that we're not able to
|
||||
# follow it anywhere
|
||||
return
|
||||
|
||||
if hasattr(volobject.vol, "members"):
|
||||
# display the header for this object, if the original object was just a type string, display the type information
|
||||
struct_header = f"{' ' * dereference_count}{volobject.vol.type_name} ({volobject.vol.size} bytes)"
|
||||
if isinstance(object, str) and offset is None:
|
||||
suffix = ":"
|
||||
else:
|
||||
# this is an actual object or an offset was given so the offset should be displayed
|
||||
suffix = f" @ {hex(volobject.vol.offset)}:"
|
||||
print(struct_header + suffix)
|
||||
|
||||
# it is a more complex type, so all members also need information displayed
|
||||
longest_member = longest_offset = longest_typename = 0
|
||||
for member in volobject.vol.members:
|
||||
relative_offset, member_type = volobject.vol.members[member]
|
||||
longest_member = max(len(member), longest_member)
|
||||
longest_offset = max(len(hex(relative_offset)), longest_offset)
|
||||
longest_typename = max(len(member_type.vol.type_name), longest_typename)
|
||||
member_type_name = self._get_type_name_with_pointer(
|
||||
member_type
|
||||
) # special case for pointers to show what they point to
|
||||
|
||||
# find the longest typename
|
||||
longest_typename = max(len(member_type_name), longest_typename)
|
||||
|
||||
# if the typename is very long then limit it to MAX_TYPENAME_DISPLAY_LENGTH
|
||||
longest_typename = min(longest_typename, MAX_TYPENAME_DISPLAY_LENGTH)
|
||||
|
||||
for member in sorted(
|
||||
volobject.vol.members, key=lambda x: (volobject.vol.members[x][0], x)
|
||||
@@ -401,40 +516,148 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
relative_offset, member_type = volobject.vol.members[member]
|
||||
len_offset = len(hex(relative_offset))
|
||||
len_member = len(member)
|
||||
len_typename = len(member_type.vol.type_name)
|
||||
|
||||
member_type_name = self._get_type_name_with_pointer(
|
||||
member_type
|
||||
) # special case for pointers to show what they point to
|
||||
len_typename = len(member_type_name)
|
||||
if len(member_type_name) > MAX_TYPENAME_DISPLAY_LENGTH:
|
||||
len_typename = MAX_TYPENAME_DISPLAY_LENGTH
|
||||
member_type_name = f"{member_type_name[: len_typename - 3]}..."
|
||||
|
||||
if isinstance(volobject, interfaces.objects.ObjectInterface):
|
||||
# We're an instance, so also display the data
|
||||
try:
|
||||
value = self._display_value(volobject.member(member))
|
||||
except exceptions.InvalidAddressException:
|
||||
value = self._display_value(renderers.NotAvailableValue())
|
||||
print(
|
||||
" " * dereference_count,
|
||||
" " * (longest_offset - len_offset),
|
||||
hex(relative_offset),
|
||||
": ",
|
||||
member,
|
||||
" " * (longest_member - len_member),
|
||||
" ",
|
||||
member_type.vol.type_name,
|
||||
member_type_name,
|
||||
" " * (longest_typename - len_typename),
|
||||
" ",
|
||||
self._display_value(getattr(volobject, member)),
|
||||
value,
|
||||
)
|
||||
else:
|
||||
# not provided with an actual object, nor an offset so just display the types
|
||||
print(
|
||||
" " * dereference_count,
|
||||
" " * (longest_offset - len_offset),
|
||||
hex(relative_offset),
|
||||
": ",
|
||||
member,
|
||||
" " * (longest_member - len_member),
|
||||
" ",
|
||||
member_type.vol.type_name,
|
||||
member_type_name,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _display_value(cls, value: Any) -> str:
|
||||
if isinstance(value, objects.PrimitiveObject):
|
||||
return repr(value)
|
||||
elif isinstance(value, objects.Array):
|
||||
return repr([cls._display_value(val) for val in value])
|
||||
else: # simple type with no members, only one line to print
|
||||
# if the original object was just a type string, display the type information
|
||||
if isinstance(object, str) and offset is None:
|
||||
print(self._display_simple_type(volobject, include_value=False))
|
||||
|
||||
# if the original object was an actual volobject or was a type string
|
||||
# with an offset. Then append the actual data to the display.
|
||||
else:
|
||||
print(" " * dereference_count, self._display_simple_type(volobject))
|
||||
|
||||
def _display_simple_type(
|
||||
self,
|
||||
volobject: Union[
|
||||
interfaces.objects.ObjectInterface, interfaces.objects.Template
|
||||
],
|
||||
include_value: bool = True,
|
||||
) -> str:
|
||||
# build the display_type_string based on the available information
|
||||
|
||||
if hasattr(volobject.vol, "size"):
|
||||
# the most common type to display, this shows their full size, e.g.:
|
||||
# (layer_name) >>> dt('task_struct')
|
||||
# symbol_table_name1!task_struct (1784 bytes)
|
||||
display_type_string = (
|
||||
f"{volobject.vol.type_name} ({volobject.vol.size} bytes)"
|
||||
)
|
||||
elif hasattr(volobject.vol, "data_format"):
|
||||
# this is useful for very simple types like ints, e.g.:
|
||||
# (layer_name) >>> dt('int')
|
||||
# symbol_table_name1!int (4 bytes, little endian, signed)
|
||||
data_format = volobject.vol.data_format
|
||||
display_type_string = "{} ({} bytes, {} endian, {})".format(
|
||||
volobject.vol.type_name,
|
||||
data_format.length,
|
||||
data_format.byteorder,
|
||||
"signed" if data_format.signed else "unsigned",
|
||||
)
|
||||
elif hasattr(volobject.vol, "type_name"):
|
||||
# types like void have almost no values to display other than their name, e.g.:
|
||||
# (layer_name) >>> dt('void')
|
||||
# symbol_table_name1!void
|
||||
display_type_string = volobject.vol.type_name
|
||||
else:
|
||||
return hex(value.vol.offset)
|
||||
# it should not be possible to have a volobject without at least a type_name
|
||||
raise AttributeError("Unable to find any details for object")
|
||||
|
||||
if include_value: # if include_value is true also add the value to the display
|
||||
if isinstance(volobject, objects.Pointer):
|
||||
# for pointers include the location of the pointer and where it points to
|
||||
return f"{display_type_string} @ {hex(volobject.vol.offset)} -> {self._display_value(volobject)}"
|
||||
else:
|
||||
return f"{display_type_string}: {self._display_value(volobject)}"
|
||||
|
||||
else:
|
||||
return display_type_string
|
||||
|
||||
def _display_value(self, value: Any) -> str:
|
||||
try:
|
||||
# if value is a BaseAbsentValue they display N/A
|
||||
if isinstance(value, interfaces.renderers.BaseAbsentValue):
|
||||
return "N/A"
|
||||
else:
|
||||
# volobject branch
|
||||
if isinstance(
|
||||
value,
|
||||
(interfaces.objects.ObjectInterface, interfaces.objects.Template),
|
||||
):
|
||||
if isinstance(value, objects.Pointer):
|
||||
# show pointers in hex to match output for struct addrs
|
||||
# highlight null or unreadable pointers
|
||||
try:
|
||||
if value == 0:
|
||||
suffix = " (null pointer)"
|
||||
elif not value.is_readable():
|
||||
suffix = " (unreadable pointer)"
|
||||
else:
|
||||
suffix = ""
|
||||
except exceptions.SymbolError as exc:
|
||||
suffix = f" (unknown sized {exc.symbol_name})"
|
||||
return f"{hex(value)}{suffix}"
|
||||
elif isinstance(value, objects.PrimitiveObject):
|
||||
return repr(value)
|
||||
elif isinstance(value, objects.Array):
|
||||
return repr([self._display_value(val) for val in value])
|
||||
else:
|
||||
if self.context.layers[self.current_layer].is_valid(
|
||||
value.vol.offset
|
||||
):
|
||||
return f"offset: 0x{value.vol.offset:x}"
|
||||
else:
|
||||
return f"offset: 0x{value.vol.offset:x} (unreadable)"
|
||||
else:
|
||||
# non volobject
|
||||
if value is None:
|
||||
return "N/A"
|
||||
else:
|
||||
return repr(value)
|
||||
|
||||
except exceptions.InvalidAddressException:
|
||||
# if value causes an InvalidAddressException like BaseAbsentValue then display N/A
|
||||
return "N/A"
|
||||
|
||||
def generate_treegrid(
|
||||
self, plugin: Type[interfaces.plugins.PluginInterface], **kwargs
|
||||
@@ -479,7 +702,7 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
if treegrid is not None:
|
||||
self.render_treegrid(treegrid)
|
||||
|
||||
def display_symbols(self, symbol_table: str = None):
|
||||
def display_symbols(self, symbol_table: Optional[str] = None):
|
||||
"""Prints an alphabetical list of symbols for a symbol table"""
|
||||
if symbol_table is None:
|
||||
print("No symbol table provided")
|
||||
@@ -508,10 +731,13 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
location = "file:" + request.pathname2url(location)
|
||||
print(f"Running code from {location}\n")
|
||||
accessor = resources.ResourceAccessor()
|
||||
with accessor.open(url=location) as fp:
|
||||
self.__console.runsource(
|
||||
io.TextIOWrapper(fp, encoding="utf-8").read(), symbol="exec"
|
||||
)
|
||||
with accessor.open(url=location) as handle, io.TextIOWrapper(
|
||||
handle, encoding="utf-8"
|
||||
) as fp:
|
||||
if has_ipython:
|
||||
self.__console.ex(fp.read())
|
||||
else:
|
||||
self.__console.runsource(fp.read(), symbol="exec")
|
||||
print("\nCode complete")
|
||||
|
||||
def load_file(self, location: str):
|
||||
@@ -553,17 +779,16 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
if argname in kwargs:
|
||||
del kwargs[argname]
|
||||
|
||||
for keyword in kwargs:
|
||||
val = kwargs[keyword]
|
||||
if not isinstance(
|
||||
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
|
||||
):
|
||||
raise TypeError(
|
||||
"Configurable values must be simple types (int, bool, str, bytes)"
|
||||
)
|
||||
for keyword, val in kwargs.items():
|
||||
BasicType_or_list_of_BasicType = False # excludes list of lists
|
||||
if isinstance(val, interfaces.configuration.BasicTypes):
|
||||
BasicType_or_list_of_BasicType = True
|
||||
if all(isinstance(x, interfaces.configuration.BasicTypes) for x in val):
|
||||
BasicType_or_list_of_BasicType = True
|
||||
if not BasicType_or_list_of_BasicType:
|
||||
raise TypeError(
|
||||
"Configurable values must be simple types (int, bool, str, bytes)"
|
||||
)
|
||||
self.context.config[config_path + "." + keyword] = val
|
||||
|
||||
constructed = clazz(self.context, config_path, **constructor_args)
|
||||
@@ -575,6 +800,89 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
|
||||
return constructed
|
||||
|
||||
def breakpoint(
|
||||
self, address: int, layer_name: Optional[str] = None, lowest: bool = False
|
||||
) -> None:
|
||||
"""Sets a breakpoint on a particular address (within a specific layer)"""
|
||||
if layer_name is None:
|
||||
if self.current_layer is None:
|
||||
raise ValueError("Current layer must be set")
|
||||
layer_name = self.current_layer
|
||||
|
||||
layer: interfaces.layers.DataLayerInterface = self.context.layers[layer_name]
|
||||
|
||||
if lowest:
|
||||
while isinstance(layer, interfaces.layers.TranslationLayerInterface):
|
||||
mapping = layer.mapping(address, 1)
|
||||
if not mapping:
|
||||
raise ValueError(
|
||||
"Offset cannot be mapped lower, cannot break at lowest layer"
|
||||
)
|
||||
_, _, mapped_offset, _, mapped_layer_name = next(mapping)
|
||||
layer = self.context.layers[mapped_layer_name]
|
||||
address = mapped_offset
|
||||
|
||||
# Check if the read value is already overloaded
|
||||
if not hasattr(layer.read, "breakpoints"):
|
||||
# Layer read is not yet wrapped
|
||||
def wrapped_read(offset: int, length: int, pad: bool = False) -> bytes:
|
||||
original_read = getattr(wrapped_read, "original_read")
|
||||
for breakpoint in getattr(wrapped_read, "breakpoints"):
|
||||
if (offset <= breakpoint) and (breakpoint < offset + length):
|
||||
print(
|
||||
"Hit breakpoint, entering python debugger. To continue running without the debugger use the command continue"
|
||||
)
|
||||
import pdb
|
||||
|
||||
pdb.set_trace()
|
||||
_ = "First statement after the breakpoint, use u(p), d(own) and list to navigate through the execution frames"
|
||||
return original_read(offset, length, pad)
|
||||
|
||||
setattr(wrapped_read, "breakpoints", set())
|
||||
setattr(wrapped_read, "original_read", layer.read)
|
||||
setattr(layer, "read", wrapped_read)
|
||||
|
||||
# Add the new breakpoint
|
||||
print(f"Setting breakpoint {address:#x} on {layer.name}")
|
||||
breakpoints = getattr(layer.read, "breakpoints")
|
||||
breakpoints.add(address)
|
||||
setattr(layer.read, "breakpoints", breakpoints)
|
||||
|
||||
def breakpoint_list(self, layer_names: Optional[List[str]] = None):
|
||||
"""List available breakpoints for a set of layers"""
|
||||
if not layer_names:
|
||||
layer_names = [layer_name for layer_name in self.context.layers]
|
||||
|
||||
print("Listing breakpoints:")
|
||||
for layer_name in layer_names:
|
||||
print(f" {layer_name}")
|
||||
layer = self.context.layers.get(layer_name, None)
|
||||
if layer and hasattr(layer.read, "breakpoints"):
|
||||
for breakpoint in layer.read.breakpoints:
|
||||
print(f" {breakpoint:#x}")
|
||||
|
||||
def breakpoint_clear(
|
||||
self, offset: Optional[int] = None, layer_name: Optional[str] = None
|
||||
):
|
||||
"""Clears a offset breakpoint on a layer (or all breakpoints if offset or layer not specified)
|
||||
|
||||
Args:
|
||||
offset: Address of the breakpoint to clear (or all if None)
|
||||
layer_name: Layer to clear breakpoints from (or all if None)
|
||||
"""
|
||||
print("Clearing breakpoints:")
|
||||
for candidate_layer_name in self.context.layers:
|
||||
candidate_layer = self.context.layers[candidate_layer_name]
|
||||
if layer_name is None or layer_name == candidate_layer_name:
|
||||
print(f" {candidate_layer_name}")
|
||||
if hasattr(candidate_layer.read, "breakpoints"):
|
||||
breakpoints_to_remove = set()
|
||||
for breakpoint in candidate_layer.read.breakpoints:
|
||||
if offset is None or offset == breakpoint:
|
||||
print(f" clearing {breakpoint:#x}")
|
||||
breakpoints_to_remove.add(breakpoint)
|
||||
candidate_layer.read.breakpoints -= breakpoints_to_remove
|
||||
|
||||
|
||||
class NullFileHandler(io.BytesIO, interfaces.plugins.FileHandlerInterface):
|
||||
"""Null FileHandler that swallows files whole without consuming memory"""
|
||||
@@ -585,7 +893,6 @@ class NullFileHandler(io.BytesIO, interfaces.plugins.FileHandlerInterface):
|
||||
|
||||
def writelines(self, lines: Iterable[bytes]):
|
||||
"""Dummy method"""
|
||||
pass
|
||||
|
||||
def write(self, b: bytes):
|
||||
"""Dummy method"""
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
|
||||
from typing import Any, List, Tuple, Union
|
||||
from typing import Any, List, Optional, Tuple, Union
|
||||
from enum import Enum
|
||||
|
||||
from volatility3.cli.volshell import generic
|
||||
from volatility3.framework import constants, interfaces
|
||||
@@ -10,6 +11,16 @@ from volatility3.framework.configuration import requirements
|
||||
from volatility3.plugins.linux import pslist
|
||||
|
||||
|
||||
# Could import the enum from psscan.py to avoid code duplication
|
||||
class DescExitStateEnum(Enum):
|
||||
"""Enum for linux task exit_state as defined in include/linux/sched.h"""
|
||||
|
||||
TASK_RUNNING = 0x00000000
|
||||
EXIT_DEAD = 0x00000010
|
||||
EXIT_ZOMBIE = 0x00000020
|
||||
EXIT_TRACE = EXIT_ZOMBIE | EXIT_DEAD
|
||||
|
||||
|
||||
class Volshell(generic.Volshell):
|
||||
"""Shell environment to directly interact with a linux memory image."""
|
||||
|
||||
@@ -19,13 +30,18 @@ class Volshell(generic.Volshell):
|
||||
requirements.ModuleRequirement(
|
||||
name="kernel", description="Linux kernel module"
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
|
||||
requirements.VersionRequirement(
|
||||
name="pslist", component=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
requirements.IntRequirement(
|
||||
name="pid", description="Process ID", optional=True
|
||||
),
|
||||
]
|
||||
requirements.VersionRequirement(
|
||||
name="generic_volshell",
|
||||
component=generic.Volshell,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
] + super().get_requirements()
|
||||
|
||||
def change_task(self, pid=None):
|
||||
"""Change the current process and layer, based on a process ID"""
|
||||
@@ -40,6 +56,71 @@ class Volshell(generic.Volshell):
|
||||
return None
|
||||
print(f"No task with task ID {pid} found")
|
||||
|
||||
def get_process(self, pid=None, virtaddr=None, physaddr=None):
|
||||
"""Return the task_struct object that matches the pid. If a physical or a virtual address is provided, construct the task_struct object at said address. Only one parameter is allowed.
|
||||
|
||||
Args:
|
||||
pid (int, optional): PID to search for
|
||||
virtaddr (int, optional): Virtual address to construct object at
|
||||
physaddr (int, optional): Physical address to construct object at
|
||||
|
||||
Returns:
|
||||
ObjectInterface: task_struct Object
|
||||
"""
|
||||
|
||||
if sum(1 if x is not None else 0 for x in [pid, virtaddr, physaddr]) != 1:
|
||||
print("Only one parameter is accepted")
|
||||
return None
|
||||
|
||||
vmlinux_module_name = self.config["kernel"]
|
||||
vmlinux = self.context.modules[vmlinux_module_name]
|
||||
|
||||
kernel_layer_name = vmlinux.layer_name
|
||||
kernel_layer = self.context.layers[kernel_layer_name]
|
||||
|
||||
memory_layer_name = kernel_layer.dependencies[0]
|
||||
|
||||
task_struct_symbol = vmlinux.symbol_table_name + constants.BANG + "task_struct"
|
||||
|
||||
if virtaddr is not None:
|
||||
task = self.context.object(
|
||||
task_struct_symbol,
|
||||
layer_name=kernel_layer_name,
|
||||
offset=virtaddr,
|
||||
)
|
||||
|
||||
if physaddr is not None:
|
||||
task = self.context.object(
|
||||
task_struct_symbol,
|
||||
layer_name=memory_layer_name,
|
||||
offset=physaddr,
|
||||
native_layer_name=kernel_layer_name,
|
||||
)
|
||||
|
||||
if physaddr is not None or virtaddr is not None:
|
||||
try:
|
||||
DescExitStateEnum(task.exit_state)
|
||||
except ValueError:
|
||||
print(
|
||||
f"task_struct @ {hex(task.vol.offset)} as exit_state {task.exit_state} is likely not valid"
|
||||
)
|
||||
|
||||
if not (0 < task.pid < 65535):
|
||||
print(
|
||||
f"task_struct @ {hex(task.vol.offset)} as pid {task.pid} is likely not valid"
|
||||
)
|
||||
|
||||
return task
|
||||
|
||||
if pid is not None:
|
||||
tasks = self.list_tasks()
|
||||
for task in tasks:
|
||||
if task.pid == pid:
|
||||
return task
|
||||
print(f"No task with task ID {pid} found")
|
||||
|
||||
return None
|
||||
|
||||
def list_tasks(self):
|
||||
"""Returns a list of task objects from the primary layer"""
|
||||
# We always use the main kernel memory and associated symbols
|
||||
@@ -50,6 +131,7 @@ class Volshell(generic.Volshell):
|
||||
result += [
|
||||
(["ct", "change_task", "cp"], self.change_task),
|
||||
(["lt", "list_tasks", "ps"], self.list_tasks),
|
||||
(["gp", "get_process", "get_task"], self.get_process),
|
||||
(["symbols"], self.context.symbol_space[self.current_symbol_table]),
|
||||
]
|
||||
if self.config.get("pid", None) is not None:
|
||||
@@ -61,7 +143,7 @@ class Volshell(generic.Volshell):
|
||||
object: Union[
|
||||
str, interfaces.objects.ObjectInterface, interfaces.objects.Template
|
||||
],
|
||||
offset: int = None,
|
||||
offset: Optional[int] = None,
|
||||
):
|
||||
"""Display Type describes the members of a particular object in alphabetical order"""
|
||||
if isinstance(object, str):
|
||||
@@ -69,7 +151,7 @@ class Volshell(generic.Volshell):
|
||||
object = self.current_symbol_table + constants.BANG + object
|
||||
return super().display_type(object, offset)
|
||||
|
||||
def display_symbols(self, symbol_table: str = None):
|
||||
def display_symbols(self, symbol_table: Optional[str] = None):
|
||||
"""Prints an alphabetical list of symbols for a symbol table"""
|
||||
if symbol_table is None:
|
||||
symbol_table = self.current_symbol_table
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
|
||||
from typing import Any, List, Tuple, Union
|
||||
from typing import Any, List, Optional, Tuple, Union
|
||||
|
||||
from volatility3.cli.volshell import generic
|
||||
from volatility3.framework import constants, interfaces
|
||||
@@ -19,13 +19,18 @@ class Volshell(generic.Volshell):
|
||||
requirements.ModuleRequirement(
|
||||
name="kernel", description="Darwin kernel module"
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(3, 0, 0)
|
||||
requirements.VersionRequirement(
|
||||
name="pslist", component=pslist.PsList, version=(3, 0, 0)
|
||||
),
|
||||
requirements.IntRequirement(
|
||||
name="pid", description="Process ID", optional=True
|
||||
),
|
||||
]
|
||||
requirements.VersionRequirement(
|
||||
name="generic_volshell",
|
||||
component=generic.Volshell,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
] + super().get_requirements()
|
||||
|
||||
def change_task(self, pid=None):
|
||||
"""Change the current process and layer, based on a process ID"""
|
||||
@@ -63,7 +68,7 @@ class Volshell(generic.Volshell):
|
||||
object: Union[
|
||||
str, interfaces.objects.ObjectInterface, interfaces.objects.Template
|
||||
],
|
||||
offset: int = None,
|
||||
offset: Optional[int] = None,
|
||||
):
|
||||
"""Display Type describes the members of a particular object in alphabetical order"""
|
||||
if isinstance(object, str):
|
||||
@@ -71,7 +76,7 @@ class Volshell(generic.Volshell):
|
||||
object = self.current_symbol_table + constants.BANG + object
|
||||
return super().display_type(object, offset)
|
||||
|
||||
def display_symbols(self, symbol_table: str = None):
|
||||
def display_symbols(self, symbol_table: Optional[str] = None):
|
||||
"""Prints an alphabetical list of symbols for a symbol table"""
|
||||
if symbol_table is None:
|
||||
symbol_table = self.current_symbol_table
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
|
||||
from typing import Any, List, Tuple, Union
|
||||
from typing import Any, List, Optional, Tuple, Union
|
||||
|
||||
from volatility3.cli.volshell import generic
|
||||
from volatility3.framework import constants, interfaces
|
||||
@@ -17,13 +17,18 @@ class Volshell(generic.Volshell):
|
||||
def get_requirements(cls):
|
||||
return [
|
||||
requirements.ModuleRequirement(name="kernel", description="Windows kernel"),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
|
||||
requirements.VersionRequirement(
|
||||
name="pslist", component=pslist.PsList, version=(3, 0, 0)
|
||||
),
|
||||
requirements.IntRequirement(
|
||||
name="pid", description="Process ID", optional=True
|
||||
),
|
||||
]
|
||||
requirements.VersionRequirement(
|
||||
name="generic_volshell",
|
||||
component=generic.Volshell,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
] + super().get_requirements()
|
||||
|
||||
def change_process(self, pid=None):
|
||||
"""Change the current process and layer, based on a process ID"""
|
||||
@@ -39,16 +44,70 @@ class Volshell(generic.Volshell):
|
||||
"""Returns a list of EPROCESS objects from the primary layer"""
|
||||
# We always use the main kernel memory and associated symbols
|
||||
return list(
|
||||
pslist.PsList.list_processes(
|
||||
self.context, self.current_layer, self.current_symbol_table
|
||||
)
|
||||
pslist.PsList.list_processes(self.context, self.current_kernel_name)
|
||||
)
|
||||
|
||||
def get_process(self, pid=None, virtaddr=None, physaddr=None):
|
||||
"""Returns the _EPROCESS object that matches the pid. If a physical or a virtual address is provided, construct the _EPROCESS object at said address. Only one parameter is allowed.
|
||||
|
||||
Args:
|
||||
pid (int, optional): PID / UniqueProcessId to search for.
|
||||
virtaddr (int, optional): Virtual address to construct object at
|
||||
physaddr (int, optional): Physical address to construct object at
|
||||
|
||||
Returns:
|
||||
ObjectInterface: _EPROCESS Object
|
||||
"""
|
||||
|
||||
if sum(1 if x is not None else 0 for x in [pid, virtaddr, physaddr]) != 1:
|
||||
print("Only one parameter is accepted")
|
||||
return None
|
||||
|
||||
kernel_name = self.config["kernel"]
|
||||
kernel = self.context.modules[kernel_name]
|
||||
|
||||
kernel_layer_name = kernel.layer_name
|
||||
|
||||
kernel_layer = self.context.layers[kernel_layer_name]
|
||||
memory_layer_name = kernel_layer.dependencies[0]
|
||||
|
||||
eprocess_symbol = kernel.symbol_table_name + constants.BANG + "_EPROCESS"
|
||||
|
||||
if virtaddr is not None:
|
||||
eproc = self.context.object(
|
||||
eprocess_symbol,
|
||||
layer_name=kernel_layer_name,
|
||||
offset=virtaddr,
|
||||
)
|
||||
|
||||
return eproc
|
||||
|
||||
if physaddr is not None:
|
||||
eproc = self.context.object(
|
||||
eprocess_symbol,
|
||||
layer_name=memory_layer_name,
|
||||
offset=physaddr,
|
||||
native_layer_name=kernel_layer_name,
|
||||
)
|
||||
|
||||
return eproc
|
||||
|
||||
if pid is not None:
|
||||
processes = self.list_processes()
|
||||
for process in processes:
|
||||
if process.UniqueProcessId == pid:
|
||||
return process
|
||||
print(f"No process with process ID {pid} found")
|
||||
return None
|
||||
|
||||
return None
|
||||
|
||||
def construct_locals(self) -> List[Tuple[List[str], Any]]:
|
||||
result = super().construct_locals()
|
||||
result += [
|
||||
(["cp", "change_process"], self.change_process),
|
||||
(["lp", "list_processes", "ps"], self.list_processes),
|
||||
(["gp", "get_process"], self.get_process),
|
||||
(["symbols"], self.context.symbol_space[self.current_symbol_table]),
|
||||
]
|
||||
if self.config.get("pid", None) is not None:
|
||||
@@ -60,7 +119,7 @@ class Volshell(generic.Volshell):
|
||||
object: Union[
|
||||
str, interfaces.objects.ObjectInterface, interfaces.objects.Template
|
||||
],
|
||||
offset: int = None,
|
||||
offset: Optional[int] = None,
|
||||
):
|
||||
"""Display Type describes the members of a particular object in alphabetical order"""
|
||||
if isinstance(object, str):
|
||||
@@ -68,7 +127,7 @@ class Volshell(generic.Volshell):
|
||||
object = self.current_symbol_table + constants.BANG + object
|
||||
return super().display_type(object, offset)
|
||||
|
||||
def display_symbols(self, symbol_table: str = None):
|
||||
def display_symbols(self, symbol_table: Optional[str] = None):
|
||||
"""Prints an alphabetical list of symbols for a symbol table"""
|
||||
if symbol_table is None:
|
||||
symbol_table = self.current_symbol_table
|
||||
|
||||
@@ -2,35 +2,31 @@
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
"""Volatility 3 framework."""
|
||||
|
||||
# Check the python version to ensure it's suitable
|
||||
import glob
|
||||
import sys
|
||||
import zipfile
|
||||
|
||||
required_python_version = (3, 8, 0)
|
||||
if (
|
||||
sys.version_info.major != required_python_version[0]
|
||||
or sys.version_info.minor < required_python_version[1]
|
||||
or (
|
||||
sys.version_info.minor == required_python_version[1]
|
||||
and sys.version_info.micro < required_python_version[2]
|
||||
)
|
||||
):
|
||||
raise RuntimeError(
|
||||
"Volatility framework requires python version {}.{}.{} or greater".format(
|
||||
*required_python_version
|
||||
)
|
||||
)
|
||||
|
||||
import importlib
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
import traceback
|
||||
from typing import Any, Dict, Generator, List, Tuple, Type, TypeVar
|
||||
from typing import Any, Dict, Generator, List, Optional, Tuple, Type, TypeVar
|
||||
|
||||
from volatility3.framework import constants, interfaces
|
||||
from volatility3.framework import constants, interfaces, versionutils
|
||||
|
||||
if (
|
||||
sys.version_info.major != constants.REQUIRED_PYTHON_VERSION[0]
|
||||
or sys.version_info.minor < constants.REQUIRED_PYTHON_VERSION[1]
|
||||
or (
|
||||
sys.version_info.minor == constants.REQUIRED_PYTHON_VERSION[1]
|
||||
and sys.version_info.micro < constants.REQUIRED_PYTHON_VERSION[2]
|
||||
)
|
||||
):
|
||||
raise RuntimeError(
|
||||
f"Volatility framework requires python version {'.'.join(str(x) for x in constants.REQUIRED_PYTHON_VERSION)} or greater"
|
||||
)
|
||||
|
||||
# ##
|
||||
#
|
||||
@@ -53,30 +49,22 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
def require_interface_version(*args) -> None:
|
||||
"""Checks the required version of a plugin."""
|
||||
if len(args):
|
||||
if args[0] != interface_version()[0]:
|
||||
raise RuntimeError(
|
||||
"Framework interface version {} is incompatible with required version {}".format(
|
||||
interface_version()[0], args[0]
|
||||
)
|
||||
if not versionutils.matches_required(args, interface_version()):
|
||||
raise RuntimeError(
|
||||
"Framework interface version {} is incompatible with required version {}".format(
|
||||
".".join(str(x) for x in interface_version()[0:2]),
|
||||
".".join(str(x) for x in args[0:2]),
|
||||
)
|
||||
if len(args) > 1:
|
||||
if args[1] > interface_version()[1]:
|
||||
raise RuntimeError(
|
||||
"Framework interface version {} is an older revision than the required version {}".format(
|
||||
".".join([str(x) for x in interface_version()[0:2]]),
|
||||
".".join([str(x) for x in args[0:2]]),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class NonInheritable(object):
|
||||
class NonInheritable:
|
||||
def __init__(self, value: Any, cls: Type) -> None:
|
||||
self.default_value = value
|
||||
self.cls = cls
|
||||
|
||||
def __get__(self, obj: Any, get_type: Type = None) -> Any:
|
||||
if type == self.cls:
|
||||
def __get__(self, obj: Any, get_type: Type = Optional[None]) -> Any:
|
||||
if type is self.cls:
|
||||
if hasattr(self.default_value, "__get__"):
|
||||
return self.default_value.__get__(obj, get_type)
|
||||
return self.default_value
|
||||
@@ -99,8 +87,7 @@ def class_subclasses(cls: Type[T]) -> Generator[Type[T], None, None]:
|
||||
# The typing system is not clever enough to realize that clazz has a hidden attr after the hasattr check
|
||||
if not hasattr(clazz, "hidden") or not clazz.hidden: # type: ignore
|
||||
yield clazz
|
||||
for return_value in class_subclasses(clazz):
|
||||
yield return_value
|
||||
yield from class_subclasses(clazz)
|
||||
|
||||
|
||||
def import_files(base_module, ignore_errors: bool = False) -> List[str]:
|
||||
@@ -161,11 +148,7 @@ def import_files(base_module, ignore_errors: bool = False) -> List[str]:
|
||||
|
||||
def _filter_files(filename: str):
|
||||
"""Ensures that a filename traversed is an importable python file"""
|
||||
return (
|
||||
filename.endswith(".py")
|
||||
or filename.endswith(".pyc")
|
||||
or filename.endswith(".pyo")
|
||||
) and not filename.startswith("__")
|
||||
return (filename.endswith((".py", ".pyc"))) and not filename.startswith("__")
|
||||
|
||||
|
||||
def import_file(module: str, path: str, ignore_errors: bool = False) -> List[str]:
|
||||
@@ -189,9 +172,7 @@ def import_file(module: str, path: str, ignore_errors: bool = False) -> List[str
|
||||
traceback.TracebackException.from_exception(e).format(chain=True)
|
||||
)
|
||||
)
|
||||
vollog.debug(
|
||||
"Failed to import module {} based on file: {}".format(module, path)
|
||||
)
|
||||
vollog.debug(f"Failed to import module {module} based on file: {path}")
|
||||
failures.append(module)
|
||||
if not ignore_errors:
|
||||
raise
|
||||
@@ -209,8 +190,7 @@ def _zipwalk(path: str):
|
||||
zip_results[os.path.join(path, os.path.dirname(file.filename))] = (
|
||||
dirlist
|
||||
)
|
||||
for value in zip_results:
|
||||
yield value, zip_results[value]
|
||||
yield from zip_results.items()
|
||||
|
||||
|
||||
def list_plugins() -> Dict[str, Type[interfaces.plugins.PluginInterface]]:
|
||||
@@ -233,4 +213,4 @@ def clear_cache(complete=True):
|
||||
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")
|
||||
vollog.log(constants.LOGLEVEL_VVVV, "Attempting to clear a non-existent cache")
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Optional, Tuple, Type
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from volatility3.framework import constants, interfaces
|
||||
from volatility3.framework.automagic import symbol_cache, symbol_finder
|
||||
@@ -27,16 +26,6 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
progress_callback: constants.ProgressCallback = None,
|
||||
) -> Optional[interfaces.layers.DataLayerInterface]:
|
||||
"""Attempts to identify linux within this layer."""
|
||||
# Version check the SQlite cache
|
||||
required = (1, 0, 0)
|
||||
if not requirements.VersionRequirement.matches_required(
|
||||
required, symbol_cache.SqliteCache.version
|
||||
):
|
||||
vollog.info(
|
||||
f"SQLiteCache version not suitable: required {required} found {symbol_cache.SqliteCache.version}"
|
||||
)
|
||||
return None
|
||||
|
||||
# Bail out by default unless we can stack properly
|
||||
layer = context.layers[layer_name]
|
||||
join = interfaces.configuration.path_join
|
||||
@@ -46,12 +35,9 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
if isinstance(layer, intel.Intel):
|
||||
return None
|
||||
|
||||
identifiers_path = os.path.join(
|
||||
constants.CACHE_PATH, constants.IDENTIFIERS_FILENAME
|
||||
linux_banners = symbol_cache.load_cache_manager().get_identifier_dictionary(
|
||||
operating_system="linux"
|
||||
)
|
||||
linux_banners = symbol_cache.SqliteCache(
|
||||
identifiers_path
|
||||
).get_identifier_dictionary(operating_system="linux")
|
||||
# If we have no banners, don't bother scanning
|
||||
if not linux_banners:
|
||||
vollog.info(
|
||||
@@ -76,18 +62,27 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
isf_url=isf_path,
|
||||
)
|
||||
context.symbol_space.append(table)
|
||||
|
||||
kaslr_shift, aslr_shift = cls.find_aslr(
|
||||
context, table_name, layer_name, progress_callback=progress_callback
|
||||
context,
|
||||
table_name,
|
||||
layer_name,
|
||||
progress_callback=progress_callback,
|
||||
)
|
||||
|
||||
layer_class: Type = intel.Intel
|
||||
if "init_top_pgt" in table.symbols:
|
||||
layer_class = intel.Intel32e
|
||||
layer_class = intel.LinuxIntel32e
|
||||
dtb_symbol_name = "init_top_pgt"
|
||||
elif "init_level4_pgt" in table.symbols:
|
||||
layer_class = intel.Intel32e
|
||||
layer_class = intel.LinuxIntel32e
|
||||
dtb_symbol_name = "init_level4_pgt"
|
||||
elif "pkmap_count" in table.symbols and table.get_symbol(
|
||||
"pkmap_count"
|
||||
).type.count in (512, 2048):
|
||||
layer_class = intel.LinuxIntelPAE
|
||||
dtb_symbol_name = "swapper_pg_dir"
|
||||
else:
|
||||
layer_class = intel.LinuxIntel
|
||||
dtb_symbol_name = "swapper_pg_dir"
|
||||
|
||||
dtb = cls.virtual_to_physical_address(
|
||||
@@ -126,7 +121,17 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
progress_callback: constants.ProgressCallback = None,
|
||||
) -> Tuple[int, int]:
|
||||
"""Determines the offset of the actual DTB in physical space and its
|
||||
symbol offset."""
|
||||
symbol offset.
|
||||
|
||||
Args:
|
||||
context: The context to retrieve required elements (layers, symbol tables) from
|
||||
symbol_table: The name of the kernel module on which to operate
|
||||
layer_name: The layer within the context in which the module exists
|
||||
progress_callback: A function that takes a percentage (and an optional description) that will be called periodically
|
||||
|
||||
Returns:
|
||||
kaslr_shirt and aslr_shift
|
||||
"""
|
||||
init_task_symbol = symbol_table + constants.BANG + "init_task"
|
||||
init_task_json_address = context.symbol_space.get_symbol(
|
||||
init_task_symbol
|
||||
@@ -156,6 +161,18 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
and init_task.state.cast("unsigned int") != 0
|
||||
):
|
||||
continue
|
||||
elif init_task.active_mm.cast("long unsigned int") == module.get_symbol(
|
||||
"init_mm"
|
||||
).address and init_task.tasks.next.cast(
|
||||
"long unsigned int"
|
||||
) == init_task.tasks.prev.cast(
|
||||
"long unsigned int"
|
||||
):
|
||||
# The idle task steals `mm` from previously running task, i.e.,
|
||||
# `init_mm` is only used as long as no CPU has ever been idle.
|
||||
# This catches cases where we found a fragment of the
|
||||
# unrelocated ELF file instead of the running kernel.
|
||||
continue
|
||||
|
||||
# This we get for free
|
||||
aslr_shift = (
|
||||
@@ -174,9 +191,7 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
if aslr_shift & 0xFFF != 0 or kaslr_shift & 0xFFF != 0:
|
||||
continue
|
||||
vollog.debug(
|
||||
"Linux ASLR shift values determined: physical {:0x} virtual {:0x}".format(
|
||||
kaslr_shift, aslr_shift
|
||||
)
|
||||
f"Linux ASLR shift values determined: physical {kaslr_shift:0x} virtual {aslr_shift:0x}"
|
||||
)
|
||||
return kaslr_shift, aslr_shift
|
||||
|
||||
@@ -184,8 +199,8 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
vollog.debug("Scanners could not determine any ASLR shifts, using 0 for both")
|
||||
return 0, 0
|
||||
|
||||
@classmethod
|
||||
def virtual_to_physical_address(cls, addr: int) -> int:
|
||||
@staticmethod
|
||||
def virtual_to_physical_address(addr: int) -> int:
|
||||
"""Converts a virtual linux address to a physical one (does not account
|
||||
of ASLR)"""
|
||||
if addr > 0xFFFFFFFF80000000:
|
||||
@@ -199,5 +214,217 @@ class LinuxSymbolFinder(symbol_finder.SymbolFinder):
|
||||
banner_config_key = "kernel_banner"
|
||||
operating_system = "linux"
|
||||
symbol_class = "volatility3.framework.symbols.linux.LinuxKernelIntermedSymbols"
|
||||
find_aslr = lambda cls, *args: LinuxIntelStacker.find_aslr(*args)[1]
|
||||
exclusion_list = ["mac", "windows"]
|
||||
|
||||
@classmethod
|
||||
def find_aslr(cls, *args):
|
||||
return LinuxIntelStacker.find_aslr(*args)[1]
|
||||
|
||||
|
||||
class LinuxIntelVMCOREINFOStacker(interfaces.automagic.StackerLayerInterface):
|
||||
stack_order = 34
|
||||
exclusion_list = ["mac", "windows"]
|
||||
|
||||
@staticmethod
|
||||
def _check_versions() -> bool:
|
||||
"""Verify the versions of the required modules"""
|
||||
# Check VMCOREINFO API version
|
||||
vmcoreinfo_version_required = (1, 0, 0)
|
||||
if not requirements.VersionRequirement.matches_required(
|
||||
vmcoreinfo_version_required, linux.VMCoreInfo.version
|
||||
):
|
||||
vollog.info(
|
||||
"VMCOREINFO version not suitable: required %s found %s",
|
||||
vmcoreinfo_version_required,
|
||||
linux.VMCoreInfo.version,
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def stack(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
layer_name: str,
|
||||
progress_callback: constants.ProgressCallback = None,
|
||||
) -> Optional[interfaces.layers.DataLayerInterface]:
|
||||
"""Attempts to identify linux within this layer."""
|
||||
|
||||
# Verify the versions of the required modules
|
||||
if not cls._check_versions():
|
||||
return None
|
||||
|
||||
# Bail out by default unless we can stack properly
|
||||
layer = context.layers[layer_name]
|
||||
|
||||
# Never stack on top of an intel layer
|
||||
# FIXME: Find a way to improve this check
|
||||
if isinstance(layer, intel.Intel):
|
||||
return None
|
||||
|
||||
linux_banners = symbol_cache.load_cache_manager().get_identifier_dictionary(
|
||||
operating_system="linux"
|
||||
)
|
||||
if not linux_banners:
|
||||
# If we have no banners, don't bother scanning
|
||||
vollog.info(
|
||||
"No Linux banners found - if this is a linux plugin, please check your "
|
||||
"symbol files location"
|
||||
)
|
||||
return None
|
||||
|
||||
vmcoreinfo_elf_notes_iter = linux.VMCoreInfo.search_vmcoreinfo_elf_note(
|
||||
context=context,
|
||||
layer_name=layer_name,
|
||||
progress_callback=progress_callback,
|
||||
)
|
||||
|
||||
# Iterate through each VMCOREINFO ELF note found, using the first one that is valid.
|
||||
for _vmcoreinfo_offset, vmcoreinfo in vmcoreinfo_elf_notes_iter:
|
||||
shifts = cls._vmcoreinfo_find_aslr(vmcoreinfo)
|
||||
if not shifts:
|
||||
# Let's try the next VMCOREINFO, in case this one isn't correct.
|
||||
continue
|
||||
|
||||
kaslr_shift, aslr_shift = shifts
|
||||
|
||||
dtb = cls._vmcoreinfo_get_dtb(vmcoreinfo, aslr_shift, kaslr_shift)
|
||||
if dtb is None:
|
||||
# Discard this VMCOREINFO immediately
|
||||
continue
|
||||
|
||||
is_32bit, is_pae = cls._vmcoreinfo_is_32bit(vmcoreinfo)
|
||||
if is_32bit:
|
||||
layer_class = intel.IntelPAE if is_pae else intel.Intel
|
||||
else:
|
||||
layer_class = intel.Intel32e
|
||||
|
||||
uts_release = vmcoreinfo["OSRELEASE"]
|
||||
|
||||
# See how linux_banner constant is built in the linux kernel
|
||||
linux_version_prefix = f"Linux version {uts_release} (".encode()
|
||||
valid_banners = [
|
||||
x for x in linux_banners if x and x.startswith(linux_version_prefix)
|
||||
]
|
||||
if not valid_banners:
|
||||
# There's no banner matching this VMCOREINFO, keep trying with the next one
|
||||
continue
|
||||
elif len(valid_banners) == 1:
|
||||
# Usually, we narrow down the Linux banner list to a single element.
|
||||
# Using BytesScanner here is slightly faster than MultiStringScanner.
|
||||
scanner = scanners.BytesScanner(valid_banners[0])
|
||||
else:
|
||||
scanner = scanners.MultiStringScanner(valid_banners)
|
||||
|
||||
join = interfaces.configuration.path_join
|
||||
for match in layer.scan(
|
||||
context=context, scanner=scanner, progress_callback=progress_callback
|
||||
):
|
||||
# Unfortunately, the scanners do not maintain a consistent interface
|
||||
banner = match[1] if isinstance(match, Tuple) else valid_banners[0]
|
||||
|
||||
isf_path = linux_banners.get(banner, None)
|
||||
if not isf_path:
|
||||
vollog.warning(
|
||||
"Identified banner %r, but no matching ISF is available.",
|
||||
banner,
|
||||
)
|
||||
continue
|
||||
|
||||
vollog.debug("Identified banner: %r", banner)
|
||||
table_name = context.symbol_space.free_table_name("LintelStacker")
|
||||
table = linux.LinuxKernelIntermedSymbols(
|
||||
context,
|
||||
f"temporary.{table_name}",
|
||||
name=table_name,
|
||||
isf_url=isf_path,
|
||||
)
|
||||
context.symbol_space.append(table)
|
||||
|
||||
# Build the new layer
|
||||
new_layer_name = context.layers.free_layer_name("primary")
|
||||
config_path = join("vmcoreinfo", new_layer_name)
|
||||
kernel_banner = LinuxSymbolFinder.banner_config_key
|
||||
banner_str = banner.decode(encoding="latin-1")
|
||||
context.config[join(config_path, kernel_banner)] = banner_str
|
||||
context.config[join(config_path, "memory_layer")] = layer_name
|
||||
context.config[join(config_path, "page_map_offset")] = dtb
|
||||
context.config[join(config_path, "kernel_virtual_offset")] = aslr_shift
|
||||
layer = layer_class(
|
||||
context,
|
||||
config_path=config_path,
|
||||
name=new_layer_name,
|
||||
metadata={"os": "Linux"},
|
||||
)
|
||||
|
||||
if layer:
|
||||
vollog.debug(
|
||||
"Values found in VMCOREINFO: KASLR=0x%x, ASLR=0x%x, DTB=0x%x",
|
||||
kaslr_shift,
|
||||
aslr_shift,
|
||||
dtb,
|
||||
)
|
||||
|
||||
return layer
|
||||
|
||||
vollog.debug("No suitable linux banner could be matched")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _vmcoreinfo_find_aslr(vmcoreinfo) -> Tuple[int, int]:
|
||||
phys_base = vmcoreinfo.get("NUMBER(phys_base)")
|
||||
if phys_base is None:
|
||||
# In kernel < 4.10, there may be a SYMBOL(phys_base), but as noted in the
|
||||
# c401721ecd1dcb0a428aa5d6832ee05ffbdbffbbe commit comment, this value
|
||||
# isn't useful for calculating the physical address.
|
||||
# There's nothing we can do here, so let's try with the next VMCOREINFO or
|
||||
# the next Stacker.
|
||||
return None
|
||||
|
||||
# kernels 3.14 (b6085a865762236bb84934161273cdac6dd11c2d) KERNELOFFSET was added
|
||||
kerneloffset = vmcoreinfo.get("KERNELOFFSET")
|
||||
if kerneloffset is None:
|
||||
# kernels < 3.14 if KERNELOFFSET is missing, KASLR might not be implemented.
|
||||
# Oddly, NUMBER(phys_base) is present without it. To be safe, proceed only
|
||||
# if both are present.
|
||||
return None
|
||||
|
||||
aslr_shift = kerneloffset
|
||||
kaslr_shift = phys_base + aslr_shift
|
||||
|
||||
return kaslr_shift, aslr_shift
|
||||
|
||||
@staticmethod
|
||||
def _vmcoreinfo_get_dtb(vmcoreinfo, aslr_shift, kaslr_shift) -> int:
|
||||
"""Returns the page global directory physical address (a.k.a DTB or PGD)"""
|
||||
# In x86-64, since kernels 2.5.22 swapper_pg_dir is a macro to the respective pgd.
|
||||
# First, in e3ebadd95cb621e2c7436f3d3646447ac9d5c16d to init_level4_pgt, and later
|
||||
# in kernels 4.13 in 65ade2f872b474fa8a04c2d397783350326634e6) to init_top_pgt.
|
||||
# In x86-32, the pgd is swapper_pg_dir. So, in any case, for VMCOREINFO
|
||||
# SYMBOL(swapper_pg_dir) will always have the right value.
|
||||
dtb_vaddr = vmcoreinfo.get("SYMBOL(swapper_pg_dir)")
|
||||
if dtb_vaddr is None:
|
||||
# Abort, it should be present
|
||||
return None
|
||||
|
||||
dtb_paddr = (
|
||||
LinuxIntelStacker.virtual_to_physical_address(dtb_vaddr)
|
||||
- aslr_shift
|
||||
+ kaslr_shift
|
||||
)
|
||||
|
||||
return dtb_paddr
|
||||
|
||||
@staticmethod
|
||||
def _vmcoreinfo_is_32bit(vmcoreinfo) -> Tuple[bool, bool]:
|
||||
"""Returns a tuple of booleans with is_32bit and is_pae values"""
|
||||
is_pae = vmcoreinfo.get("CONFIG_X86_PAE", "n") == "y"
|
||||
if is_pae:
|
||||
is_32bit = True
|
||||
else:
|
||||
# Check the swapper_pg_dir virtual address size
|
||||
dtb_vaddr = vmcoreinfo["SYMBOL(swapper_pg_dir)"]
|
||||
is_32bit = dtb_vaddr <= 2**32
|
||||
|
||||
return is_32bit, is_pae
|
||||
|
||||
@@ -3,13 +3,11 @@
|
||||
#
|
||||
|
||||
import logging
|
||||
import os
|
||||
import struct
|
||||
from typing import Optional
|
||||
|
||||
from volatility3.framework import constants, exceptions, interfaces, layers
|
||||
from volatility3.framework.automagic import symbol_cache, symbol_finder
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.layers import intel, scanners
|
||||
from volatility3.framework.symbols import mac
|
||||
|
||||
@@ -28,16 +26,6 @@ class MacIntelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
progress_callback: constants.ProgressCallback = None,
|
||||
) -> Optional[interfaces.layers.DataLayerInterface]:
|
||||
"""Attempts to identify mac within this layer."""
|
||||
# Version check the SQlite cache
|
||||
required = (1, 0, 0)
|
||||
if not requirements.VersionRequirement.matches_required(
|
||||
required, symbol_cache.SqliteCache.version
|
||||
):
|
||||
vollog.info(
|
||||
f"SQLiteCache version not suitable: required {required} found {symbol_cache.SqliteCache.version}"
|
||||
)
|
||||
return None
|
||||
|
||||
# Bail out by default unless we can stack properly
|
||||
layer = context.layers[layer_name]
|
||||
new_layer = None
|
||||
@@ -48,12 +36,9 @@ class MacIntelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
if isinstance(layer, intel.Intel):
|
||||
return None
|
||||
|
||||
identifiers_path = os.path.join(
|
||||
constants.CACHE_PATH, constants.IDENTIFIERS_FILENAME
|
||||
mac_banners = symbol_cache.load_cache_manager().get_identifier_dictionary(
|
||||
operating_system="mac"
|
||||
)
|
||||
mac_banners = symbol_cache.SqliteCache(
|
||||
identifiers_path
|
||||
).get_identifier_dictionary(operating_system="mac")
|
||||
# If we have no banners, don't bother scanning
|
||||
if not mac_banners:
|
||||
vollog.info(
|
||||
@@ -197,7 +182,7 @@ class MacIntelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
aslr_shift = 0
|
||||
|
||||
for offset, banner in offset_generator:
|
||||
banner_major, banner_minor = [int(x) for x in banner[22:].split(b".")[0:2]]
|
||||
banner_major, banner_minor = (int(x) for x in banner[22:].split(b".")[0:2])
|
||||
|
||||
tmp_aslr_shift = offset - cls.virtual_to_physical_address(
|
||||
version_json_address
|
||||
|
||||
@@ -7,6 +7,7 @@ from loaded PE files.
|
||||
This module contains a standalone scanner, and also a :class:`~volatility3.framework.interfaces.layers.ScannerInterface`
|
||||
based scanner for use within the framework by calling :func:`~volatility3.framework.interfaces.layers.DataLayerInterface.scan`.
|
||||
"""
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
import math
|
||||
@@ -17,7 +18,7 @@ from volatility3.framework import constants, exceptions, interfaces, layers
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.layers import intel, scanners
|
||||
from volatility3.framework.symbols import native
|
||||
from volatility3.framework.symbols.windows.pdbutil import PDBUtility
|
||||
from volatility3.framework.symbols.windows import pdbutil
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
@@ -50,6 +51,21 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
max_pdb_size = 0x400000
|
||||
exclusion_list = ["linux", "mac"]
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
return [
|
||||
requirements.VersionRequirement(
|
||||
name="pdb_utility",
|
||||
component=pdbutil.PDBUtility,
|
||||
version=(1, 0, 1),
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="bytes_scanner",
|
||||
component=scanners.BytesScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
]
|
||||
|
||||
def find_virtual_layers_from_req(
|
||||
self,
|
||||
context: interfaces.context.ContextInterface,
|
||||
@@ -120,7 +136,7 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
):
|
||||
raise TypeError("PDB name or GUID not a string value")
|
||||
|
||||
PDBUtility.load_windows_symbol_table(
|
||||
pdbutil.PDBUtility.load_windows_symbol_table(
|
||||
context=context,
|
||||
guid=kernel["GUID"],
|
||||
age=kernel["age"],
|
||||
@@ -215,9 +231,7 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
return (virtual_layer_name, kvo, kernel)
|
||||
else:
|
||||
vollog.debug(
|
||||
"Potential kernel_virtual_offset did not map to expected location: {}".format(
|
||||
hex(kvo)
|
||||
)
|
||||
f"Potential kernel_virtual_offset did not map to expected location: {hex(kvo)}"
|
||||
)
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.debug(
|
||||
@@ -261,7 +275,7 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
bytes(name + ".pdb", "utf-8")
|
||||
for name in constants.windows.KERNEL_MODULE_NAMES
|
||||
]
|
||||
kernels = PDBUtility.pdbname_scan(
|
||||
kernels = pdbutil.PDBUtility.pdbname_scan(
|
||||
ctx=context,
|
||||
layer_name=layer_to_scan,
|
||||
start=start_scan_address,
|
||||
@@ -270,6 +284,10 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
progress_callback=progress_callback,
|
||||
)
|
||||
for kernel in kernels:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVVV,
|
||||
f"Testing potential kernel for {kernel.get('pdb_name', 'Unknown')} at {kernel.get('signature_offset', -1):x} with MZ offset at {(kernel.get('mz_offset', -1) or -1):x}",
|
||||
)
|
||||
valid_kernel = test_kernel(physical_layer_name, virtual_layer_name, kernel)
|
||||
if valid_kernel is not None:
|
||||
break
|
||||
@@ -360,7 +378,7 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
with contextlib.suppress(exceptions.InvalidAddressException):
|
||||
if vlayer.read(address, 0x2) == b"MZ":
|
||||
res = list(
|
||||
PDBUtility.pdbname_scan(
|
||||
pdbutil.PDBUtility.pdbname_scan(
|
||||
ctx=context,
|
||||
layer_name=vlayer.name,
|
||||
page_size=vlayer.page_size,
|
||||
@@ -374,8 +392,74 @@ class KernelPDBScanner(interfaces.automagic.AutomagicInterface):
|
||||
valid_kernel = (virtual_layer_name, address, res[0])
|
||||
return valid_kernel
|
||||
|
||||
def method_low_stub_offset(
|
||||
self,
|
||||
context: interfaces.context.ContextInterface,
|
||||
vlayer: layers.intel.Intel,
|
||||
progress_callback: constants.ProgressCallback = None,
|
||||
) -> Optional[ValidKernelType]:
|
||||
# This method is only valid for x64 systems
|
||||
if not isinstance(vlayer, intel.Intel32e):
|
||||
return None
|
||||
kernel_hint = 0
|
||||
kernel_base = 0
|
||||
physical_layer = context.layers.get("memory_layer")
|
||||
|
||||
# Try locating kernel base via x64 Low Stub in lower 1MB starting from second page (4KB)
|
||||
# If "Discard Low Memory" setting is disabled in BIOS, the Low Stub may be at the third/fourth or further pages
|
||||
for offset in range(0x1000, 0x100000, 0x1000):
|
||||
try:
|
||||
jmp_and_completion_values = int.from_bytes(
|
||||
physical_layer.read(offset, 0x8), "little"
|
||||
)
|
||||
if (
|
||||
0xFFFFFFFFFFFF00FF & jmp_and_completion_values
|
||||
!= constants.windows.JMP_AND_COMPLETION_SIGNATURE
|
||||
):
|
||||
continue
|
||||
cr3_value = int.from_bytes(
|
||||
physical_layer.read(
|
||||
offset + constants.windows.PROCESSOR_START_BLOCK_CR3_OFFSET, 0x8
|
||||
),
|
||||
"little",
|
||||
)
|
||||
|
||||
# Compare previously observed valid page table address that's stored in vlayer._initial_entry
|
||||
# with PROCESSOR_START_BLOCK->ProcessorState->SpecialRegisters->Cr3
|
||||
# which was observed to be an invalid page address, so add 1 (to make it valid too)
|
||||
if (cr3_value + 1) != vlayer._initial_entry:
|
||||
continue
|
||||
potential_kernel_hint = int.from_bytes(
|
||||
physical_layer.read(
|
||||
offset
|
||||
+ constants.windows.PROCESSOR_START_BLOCK_LM_TARGET_OFFSET,
|
||||
0x8,
|
||||
),
|
||||
"little",
|
||||
)
|
||||
if 0x3 & potential_kernel_hint:
|
||||
continue
|
||||
kernel_hint = potential_kernel_hint & 0xFFFFFFFFFFFF
|
||||
kernel_base = kernel_hint & (~0x1FFFFF) & 0xFFFFFFFFFFFF
|
||||
break
|
||||
except exceptions.InvalidAddressException:
|
||||
continue
|
||||
|
||||
if kernel_base:
|
||||
# Scanning 32mb in 2mb chunks for the 'ntoskrnl' base address
|
||||
while (kernel_base + 0x2000000) > kernel_hint:
|
||||
for i in range(0, 0x200000, 0x1000):
|
||||
valid_kernel = self.check_kernel_offset(
|
||||
context, vlayer, kernel_base + i, progress_callback
|
||||
)
|
||||
if valid_kernel:
|
||||
return valid_kernel
|
||||
kernel_base -= 0x200000
|
||||
return None
|
||||
|
||||
# List of methods to be run, in order, to determine the valid kernels
|
||||
methods = [
|
||||
method_low_stub_offset,
|
||||
method_kdbg_offset,
|
||||
method_module_offset,
|
||||
method_fixed_mapping,
|
||||
|
||||
@@ -153,8 +153,9 @@ class LayerStacker(interfaces.automagic.AutomagicInterface):
|
||||
constructor(context, config_path, requirement)
|
||||
|
||||
# Stash the changed config items
|
||||
self._cached = context.config.get(path, None), context.config.branch(
|
||||
path
|
||||
self._cached = (
|
||||
context.config.get(path, None),
|
||||
context.config.branch(path),
|
||||
)
|
||||
vollog.debug(
|
||||
f"physical_layer maximum_address: {physical_layer.maximum_address}"
|
||||
@@ -166,7 +167,9 @@ class LayerStacker(interfaces.automagic.AutomagicInterface):
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
initial_layer: str,
|
||||
stack_set: List[Type[interfaces.automagic.StackerLayerInterface]] = None,
|
||||
stack_set: Optional[
|
||||
List[Type[interfaces.automagic.StackerLayerInterface]]
|
||||
] = None,
|
||||
progress_callback: constants.ProgressCallback = None,
|
||||
):
|
||||
"""Stacks as many possible layers on top of the initial layer as can be done.
|
||||
|
||||
@@ -104,9 +104,11 @@ class CacheManagerInterface(interfaces.configuration.VersionableInterface):
|
||||
for subclazz in framework.class_subclasses(IdentifierProcessor):
|
||||
self._classifiers[subclazz.operating_system] = subclazz
|
||||
|
||||
@abstractmethod
|
||||
def add_identifier(self, location: str, operating_system: str, identifier: str):
|
||||
"""Adds an identifier to the store"""
|
||||
|
||||
@abstractmethod
|
||||
def find_location(
|
||||
self, identifier: bytes, operating_system: Optional[str]
|
||||
) -> Optional[str]:
|
||||
@@ -120,15 +122,18 @@ class CacheManagerInterface(interfaces.configuration.VersionableInterface):
|
||||
The location of the symbols file that matches the identifier
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_local_locations(self) -> Iterable[str]:
|
||||
"""Returns a list of all the local locations"""
|
||||
|
||||
@abstractmethod
|
||||
def update(self):
|
||||
"""Locates all files under the symbol directories. Updates the cache with additions, modifications and removals.
|
||||
This also updates remote locations based on a cache timeout.
|
||||
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_identifier_dictionary(
|
||||
self, operating_system: Optional[str] = None, local_only: bool = False
|
||||
) -> Dict[bytes, str]:
|
||||
@@ -142,12 +147,15 @@ class CacheManagerInterface(interfaces.configuration.VersionableInterface):
|
||||
A dictionary of identifiers mapped to a location
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_identifier(self, location: str) -> Optional[bytes]:
|
||||
"""Returns an identifier based on a specific location or None"""
|
||||
|
||||
@abstractmethod
|
||||
def get_identifiers(self, operating_system: Optional[str]) -> List[bytes]:
|
||||
"""Returns all identifiers for a particular operating system"""
|
||||
|
||||
@abstractmethod
|
||||
def get_location_statistics(
|
||||
self, location: str
|
||||
) -> Optional[Tuple[int, int, int, int]]:
|
||||
@@ -157,6 +165,7 @@ class CacheManagerInterface(interfaces.configuration.VersionableInterface):
|
||||
A tuple of base_types, types, enums, symbols, or None is location not found
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_hash(self, location: str) -> Optional[str]:
|
||||
"""Returns the hash of the JSON from within a location ISF"""
|
||||
|
||||
@@ -296,6 +305,13 @@ class SqliteCache(CacheManagerInterface):
|
||||
This also updates remote locations based on a cache timeout.
|
||||
|
||||
"""
|
||||
if progress_callback is None:
|
||||
|
||||
def dummy_progress(*args, **kargs) -> None:
|
||||
return None
|
||||
|
||||
progress_callback = dummy_progress
|
||||
|
||||
on_disk_locations = set(
|
||||
[
|
||||
filename
|
||||
@@ -501,6 +517,21 @@ class SqliteCache(CacheManagerInterface):
|
||||
return output
|
||||
|
||||
|
||||
def load_cache_manager(cache_file: Optional[str] = None) -> CacheManagerInterface:
|
||||
"""Loads a cache manager based on a specific cache file"""
|
||||
if cache_file is None:
|
||||
cache_file = os.path.join(constants.CACHE_PATH, constants.IDENTIFIERS_FILENAME)
|
||||
# Different implementations of cache
|
||||
if not os.path.exists(cache_file):
|
||||
raise ValueError("Non-existent cache file provided")
|
||||
with open(cache_file, "rb") as fp:
|
||||
header = fp.read(4)
|
||||
if header not in [b"SQLi"]:
|
||||
raise ValueError("Identifier file not in recognized format")
|
||||
# Currently only one choice, so use that
|
||||
return SqliteCache(cache_file)
|
||||
|
||||
|
||||
### Automagic
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, Callable, Iterable, List, Optional, Tuple
|
||||
from typing import Callable, List, Optional, Tuple
|
||||
|
||||
from volatility3.framework import constants, interfaces, layers
|
||||
from volatility3.framework.automagic import symbol_cache
|
||||
@@ -40,7 +40,12 @@ class SymbolFinder(interfaces.automagic.AutomagicInterface):
|
||||
name="SQLiteCache",
|
||||
component=symbol_cache.SqliteCache,
|
||||
version=(1, 0, 0),
|
||||
)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="multi_string_scanner",
|
||||
component=scanners.MultiStringScanner,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
]
|
||||
|
||||
@property
|
||||
@@ -142,11 +147,11 @@ class SymbolFinder(interfaces.automagic.AutomagicInterface):
|
||||
)
|
||||
|
||||
for _, banner in banner_list:
|
||||
vollog.debug(f"Identified banner: {repr(banner)}")
|
||||
symbol_files = self.banners.get(banner, None)
|
||||
if symbol_files:
|
||||
isf_path = symbol_files
|
||||
vollog.debug(f"Using symbol library: {symbol_files}")
|
||||
vollog.debug(f"Identified banner: {banner!r}")
|
||||
symbols_file = self.banners.get(banner, None)
|
||||
if symbols_file:
|
||||
isf_path = symbols_file
|
||||
vollog.debug(f"Using symbol library: {symbols_file}")
|
||||
clazz = self.symbol_class
|
||||
# Set the discovered options
|
||||
path_join = interfaces.configuration.path_join
|
||||
@@ -160,8 +165,31 @@ class SymbolFinder(interfaces.automagic.AutomagicInterface):
|
||||
path_join(config_path, requirement.name, "symbol_mask")
|
||||
] = layer.address_mask
|
||||
|
||||
# Keep track of the existing table names so we know which ones were added
|
||||
old_table_names = set(context.symbol_space)
|
||||
|
||||
# Construct the appropriate symbol table
|
||||
requirement.construct(context, config_path)
|
||||
|
||||
new_table_names = set(context.symbol_space) - old_table_names
|
||||
# It should add only one symbol table. Ignore the next steps if it doesn't
|
||||
if len(new_table_names) == 1:
|
||||
new_table_name = new_table_names.pop()
|
||||
symbol_table = context.symbol_space[new_table_name]
|
||||
producer_metadata = symbol_table.producer
|
||||
vollog.debug(
|
||||
f"producer_name: {producer_metadata.name}, producer_version: {producer_metadata.version_string}"
|
||||
)
|
||||
|
||||
symbol_metadata = symbol_table.metadata
|
||||
vollog.debug("Types:")
|
||||
for types_source_dict in symbol_metadata.get_types_sources():
|
||||
vollog.debug(f"\t{types_source_dict}")
|
||||
|
||||
vollog.debug("Symbols:")
|
||||
for symbol_source_dict in symbol_metadata.get_symbols_sources():
|
||||
vollog.debug(f"\t{symbol_source_dict}")
|
||||
|
||||
break
|
||||
else:
|
||||
vollog.debug(f"Symbol library path not found for: {banner}")
|
||||
|
||||
@@ -26,6 +26,7 @@ The self-referential indices for older versions of windows are listed below:
|
||||
| x64 | 0x1ED |
|
||||
+--------------+-------+
|
||||
"""
|
||||
|
||||
import logging
|
||||
import struct
|
||||
from typing import Generator, Iterable, List, Optional, Tuple, Type
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.configuration import requirements as requirements
|
||||
|
||||
@@ -8,13 +8,14 @@ These requirement types allow plugins to request simple information
|
||||
types (such as strings, integers, etc) as well as indicating what they
|
||||
expect to be in the context (such as particular layers or symboltables).
|
||||
"""
|
||||
|
||||
import abc
|
||||
import logging
|
||||
import os
|
||||
from typing import Any, ClassVar, Dict, List, Optional, Tuple, Type
|
||||
from typing import Any, ClassVar, Dict, List, Optional, Set, Tuple, Type
|
||||
from urllib import parse, request
|
||||
|
||||
from volatility3.framework import constants, interfaces
|
||||
from volatility3.framework import constants, interfaces, deprecation, versionutils
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
@@ -111,7 +112,7 @@ class ListRequirement(interfaces.configuration.RequirementInterface):
|
||||
|
||||
Args:
|
||||
element_type: The (requirement) type of each element within the list
|
||||
max_elements; The maximum number of acceptable elements this list can contain
|
||||
max_elements: The maximum number of acceptable elements this list can contain
|
||||
min_elements: The minimum number of acceptable elements this list can contain
|
||||
"""
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -314,11 +315,11 @@ class TranslationLayerRequirement(
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
description: str = None,
|
||||
description: Optional[str] = None,
|
||||
default: interfaces.configuration.ConfigSimpleType = None,
|
||||
optional: bool = False,
|
||||
oses: List = None,
|
||||
architectures: List = None,
|
||||
oses: Optional[List] = None,
|
||||
architectures: Optional[List[str]] = None,
|
||||
) -> None:
|
||||
"""Constructs a Translation Layer Requirement.
|
||||
|
||||
@@ -526,19 +527,19 @@ class VersionRequirement(interfaces.configuration.RequirementInterface):
|
||||
description: Optional[str] = None,
|
||||
default: bool = False,
|
||||
optional: bool = False,
|
||||
component: Type[interfaces.configuration.VersionableInterface] = None,
|
||||
component: Optional[Type[interfaces.configuration.VersionableInterface]] = None,
|
||||
version: Optional[Tuple[int, ...]] = None,
|
||||
) -> None:
|
||||
if version is None:
|
||||
raise TypeError("Version cannot be None")
|
||||
if component is None:
|
||||
raise TypeError("Component cannot be None")
|
||||
if description is None:
|
||||
description = f"Version {'.'.join([str(x) for x in version])} dependency on {component.__module__}.{component.__name__} unmet"
|
||||
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
|
||||
)
|
||||
if component is None:
|
||||
raise TypeError("Component cannot be None")
|
||||
self._component: Type[interfaces.configuration.VersionableInterface] = component
|
||||
if version is None:
|
||||
raise TypeError("Version cannot be None")
|
||||
self._version = version
|
||||
|
||||
def unsatisfied(
|
||||
@@ -546,12 +547,12 @@ class VersionRequirement(interfaces.configuration.RequirementInterface):
|
||||
context: interfaces.context.ContextInterface,
|
||||
config_path: str,
|
||||
accumulator: Optional[
|
||||
List[interfaces.configuration.VersionableInterface]
|
||||
Set[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):
|
||||
if not versionutils.matches_required(self._version, self._component.version):
|
||||
return {config_path: self}
|
||||
|
||||
recurse = True
|
||||
@@ -580,7 +581,7 @@ class VersionRequirement(interfaces.configuration.RequirementInterface):
|
||||
)
|
||||
|
||||
if result:
|
||||
result.update({config_path: self})
|
||||
result[config_path] = self
|
||||
return result
|
||||
|
||||
context.config[interfaces.configuration.path_join(config_path, self.name)] = (
|
||||
@@ -593,21 +594,22 @@ class VersionRequirement(interfaces.configuration.RequirementInterface):
|
||||
def matches_required(
|
||||
cls, required: Tuple[int, ...], version: Tuple[int, int, int]
|
||||
) -> bool:
|
||||
if len(required) > 0 and version[0] != required[0]:
|
||||
return False
|
||||
if len(required) > 1 and version[1] < required[1]:
|
||||
return False
|
||||
return True
|
||||
return versionutils.matches_required(required, version)
|
||||
|
||||
|
||||
@deprecation.renamed_class(
|
||||
deprecated_class_name="PluginRequirement",
|
||||
removal_date="2026-06-01",
|
||||
message="PluginRequirement is to be deprecated. Use VersionRequirement instead.",
|
||||
)
|
||||
class PluginRequirement(VersionRequirement):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
description: str = None,
|
||||
description: Optional[str] = None,
|
||||
default: bool = False,
|
||||
optional: bool = False,
|
||||
plugin: Type[interfaces.plugins.PluginInterface] = None,
|
||||
plugin: Optional[Type[interfaces.plugins.PluginInterface]] = None,
|
||||
version: Optional[Tuple[int, ...]] = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
@@ -627,7 +629,7 @@ class ModuleRequirement(
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
description: str = None,
|
||||
description: Optional[str] = None,
|
||||
default: bool = False,
|
||||
architectures: Optional[List[str]] = None,
|
||||
optional: bool = False,
|
||||
@@ -664,9 +666,7 @@ class ModuleRequirement(
|
||||
if value is not None:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_V,
|
||||
"TypeError - Module Requirement only accepts string labels: {}".format(
|
||||
repr(value)
|
||||
),
|
||||
f"TypeError - Module Requirement only accepts string labels: {repr(value)}",
|
||||
)
|
||||
return {config_path: self}
|
||||
|
||||
|
||||
@@ -6,22 +6,25 @@
|
||||
Stores all the constant values that are generally fixed throughout
|
||||
volatility This includes default scanning block sizes, etc.
|
||||
"""
|
||||
|
||||
import enum
|
||||
import os.path
|
||||
import sys
|
||||
import warnings
|
||||
from typing import Callable, Optional
|
||||
|
||||
import volatility3.framework.constants.linux
|
||||
import volatility3.framework.constants.windows
|
||||
from volatility3.framework.constants import linux as linux
|
||||
from volatility3.framework.constants import windows as windows
|
||||
from volatility3.framework.constants._version import (
|
||||
PACKAGE_VERSION,
|
||||
VERSION_MAJOR,
|
||||
VERSION_MINOR,
|
||||
VERSION_PATCH,
|
||||
VERSION_SUFFIX,
|
||||
PACKAGE_VERSION as PACKAGE_VERSION,
|
||||
VERSION_MAJOR as VERSION_MAJOR,
|
||||
VERSION_MINOR as VERSION_MINOR,
|
||||
VERSION_PATCH as VERSION_PATCH,
|
||||
VERSION_SUFFIX as VERSION_SUFFIX,
|
||||
)
|
||||
|
||||
REQUIRED_PYTHON_VERSION = (3, 8, 0)
|
||||
|
||||
PLUGINS_PATH = [
|
||||
os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..", "plugins")),
|
||||
os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "plugins")),
|
||||
@@ -37,6 +40,8 @@ SYMBOL_BASEPATHS = [
|
||||
ISF_EXTENSIONS = [".json", ".json.xz", ".json.gz", ".json.bz2"]
|
||||
"""List of accepted extensions for ISF files"""
|
||||
|
||||
SYMBOL_SERVER_URL = "http://msdl.microsoft.com/download/symbols"
|
||||
|
||||
if hasattr(sys, "frozen") and sys.frozen:
|
||||
# Ensure we include the executable's directory as the base for plugins and symbols
|
||||
PLUGINS_PATH = [
|
||||
@@ -65,7 +70,11 @@ LOGLEVEL_VVV = 7
|
||||
LOGLEVEL_VVVV = 6
|
||||
"""Logging level for four levels of detail: -vvvvvv"""
|
||||
|
||||
CACHE_PATH = os.path.join(os.path.expanduser("~"), ".cache", "volatility3")
|
||||
|
||||
CACHE_PATH = os.path.join(
|
||||
os.environ.get("XDG_CACHE_HOME") or os.path.join(os.path.expanduser("~"), ".cache"),
|
||||
"volatility3",
|
||||
)
|
||||
"""Default path to store cached data"""
|
||||
|
||||
SQLITE_CACHE_PERIOD = "-3 days"
|
||||
@@ -113,6 +122,9 @@ OFFLINE = False
|
||||
REMOTE_ISF_URL = None # 'http://localhost:8000/banners.json'
|
||||
"""Remote URL to query for a list of ISF addresses"""
|
||||
|
||||
DOWNLOAD_TIMEOUT = 30
|
||||
"""Length of time (in seconds) to wait for another process to download a resource before using it"""
|
||||
|
||||
###
|
||||
# DEPRECATED VALUES
|
||||
###
|
||||
|
||||
@@ -1,11 +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_MINOR = 28 # 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]])
|
||||
".".join(str(x) for x in [VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH])
|
||||
+ VERSION_SUFFIX
|
||||
)
|
||||
"""The canonical version of the volatility3 package"""
|
||||
|
||||
@@ -5,7 +5,13 @@
|
||||
|
||||
Linux-specific values that aren't found in debug symbols
|
||||
"""
|
||||
from enum import IntEnum, Flag
|
||||
|
||||
import enum
|
||||
from dataclasses import dataclass
|
||||
|
||||
# Exec argument limits
|
||||
# Ref: include/uapi/linux/binfmts.h (linux.git commit f6031913338f1dad5bd8cb7286ff4e53644b6940)
|
||||
MAX_ARG_STRLEN = 32 * 4096
|
||||
|
||||
KERNEL_NAME = "__kernel__"
|
||||
|
||||
@@ -281,8 +287,54 @@ CAPABILITIES = (
|
||||
|
||||
ELF_MAX_EXTRACTION_SIZE = 1024 * 1024 * 1024 * 4 - 1
|
||||
|
||||
# For IFA_* below - Ref: include/net/ipv6.h
|
||||
IPV6_ADDR_LOOPBACK = 0x0010
|
||||
IPV6_ADDR_LINKLOCAL = 0x0020
|
||||
IPV6_ADDR_SITELOCAL = 0x0040
|
||||
# For inet6_ifaddr - Ref: include/net/if_inet6.h
|
||||
IFA_HOST = IPV6_ADDR_LOOPBACK
|
||||
IFA_LINK = IPV6_ADDR_LINKLOCAL
|
||||
IFA_SITE = IPV6_ADDR_SITELOCAL
|
||||
|
||||
class ELF_IDENT(IntEnum):
|
||||
# Only for kernels < 3.15 when the net_device_flags enum didn't exist
|
||||
# ref include/uapi/linux/if.h
|
||||
NET_DEVICE_FLAGS = {
|
||||
"IFF_UP": 0x1,
|
||||
"IFF_BROADCAST": 0x2,
|
||||
"IFF_DEBUG": 0x4,
|
||||
"IFF_LOOPBACK": 0x8,
|
||||
"IFF_POINTOPOINT": 0x10,
|
||||
"IFF_NOTRAILERS": 0x20,
|
||||
"IFF_RUNNING": 0x40,
|
||||
"IFF_NOARP": 0x80,
|
||||
"IFF_PROMISC": 0x100,
|
||||
"IFF_ALLMULTI": 0x200,
|
||||
"IFF_MASTER": 0x400,
|
||||
"IFF_SLAVE": 0x800,
|
||||
"IFF_MULTICAST": 0x1000,
|
||||
"IFF_PORTSEL": 0x2000,
|
||||
"IFF_AUTOMEDIA": 0x4000,
|
||||
"IFF_DYNAMIC": 0x8000,
|
||||
"IFF_LOWER_UP": 0x10000,
|
||||
"IFF_DORMANT": 0x20000,
|
||||
"IFF_ECHO": 0x40000,
|
||||
}
|
||||
|
||||
|
||||
# Kernels >= 2.6.17. See IF_OPER_* in include/uapi/linux/if.h
|
||||
class IF_OPER_STATES(enum.Enum):
|
||||
"""RFC 2863 - Network interface operational status"""
|
||||
|
||||
UNKNOWN = 0
|
||||
NOTPRESENT = 1
|
||||
DOWN = 2
|
||||
LOWERLAYERDOWN = 3
|
||||
TESTING = 4
|
||||
DORMANT = 5
|
||||
UP = 6
|
||||
|
||||
|
||||
class ELF_IDENT(enum.IntEnum):
|
||||
"""ELF header e_ident indexes"""
|
||||
|
||||
EI_MAG0 = 0
|
||||
@@ -296,7 +348,7 @@ class ELF_IDENT(IntEnum):
|
||||
EI_PAD = 8
|
||||
|
||||
|
||||
class ELF_CLASS(IntEnum):
|
||||
class ELF_CLASS(enum.IntEnum):
|
||||
"""ELF header class types"""
|
||||
|
||||
ELFCLASSNONE = 0
|
||||
@@ -319,7 +371,7 @@ PTRACE_O_EXITKILL = 1 << 20
|
||||
PTRACE_O_SUSPEND_SECCOMP = 1 << 21
|
||||
|
||||
|
||||
class PT_FLAGS(Flag):
|
||||
class PT_FLAGS(enum.Flag):
|
||||
"PTrace flags"
|
||||
|
||||
PT_PTRACED = 0x00001
|
||||
@@ -353,3 +405,123 @@ NSEC_PER_SEC = 1e9
|
||||
MODULE_MAXIMUM_CORE_SIZE = 20000000
|
||||
MODULE_MAXIMUM_CORE_TEXT_SIZE = 20000000
|
||||
MODULE_MINIMUM_SIZE = 4096
|
||||
|
||||
# Kallsyms
|
||||
KSYM_NAME_LEN = 512
|
||||
NM_TYPES_DESC = {
|
||||
"a": "Symbol is absolute and doesn't change during linking",
|
||||
"b": "Symbol in the BSS section, typically holding zero-initialized or uninitialized data",
|
||||
"c": "Symbol is common, typically holding uninitialized data",
|
||||
"d": "Symbol is in the initialized data section",
|
||||
"g": "Symbol is in an initialized data section for small objects",
|
||||
"i": "Symbol is an indirect reference to another symbol",
|
||||
"N": "Symbol is a debugging symbol",
|
||||
"n": "Symbol is in a non-data, non-code, non-debug read-only section",
|
||||
"p": "Symbol is in a stack unwind section",
|
||||
"r": "Symbol is in a read only data section",
|
||||
"s": "Symbol is in an uninitialized or zero-initialized data section for small objects",
|
||||
"t": "Symbol is in the text (code) section",
|
||||
"U": "Symbol is undefined",
|
||||
"u": "Symbol is a unique global symbol",
|
||||
"V": "Symbol is a weak object, with a default value",
|
||||
"v": "Symbol is a weak object",
|
||||
"W": "Symbol is a weak symbol but not marked as a weak object symbol, with a default value",
|
||||
"w": "Symbol is a weak symbol but not marked as a weak object symbol",
|
||||
"?": "Symbol type is unknown",
|
||||
}
|
||||
|
||||
# VMCOREINFO
|
||||
VMCOREINFO_MAGIC = b"VMCOREINFO\x00"
|
||||
# Aligned to 4 bytes. See storenote() in kernels < 4.19 or append_kcore_note() in kernels >= 4.19
|
||||
VMCOREINFO_MAGIC_ALIGNED = VMCOREINFO_MAGIC + b"\x00"
|
||||
OSRELEASE_TAG = b"OSRELEASE="
|
||||
|
||||
ATTRIBUTE_NAME_MAX_SIZE = 255
|
||||
"""
|
||||
In 5.9-rc1+, the Linux kernel limits the READ size of a section bin_attribute name to MODULE_SECT_READ_SIZE:
|
||||
|
||||
- https://elixir.bootlin.com/linux/v6.15-rc4/source/kernel/module/sysfs.c#L106
|
||||
- https://github.com/torvalds/linux/commit/11990a5bd7e558e9203c1070fc52fb6f0488e75b
|
||||
|
||||
However, the raw section name loaded from the .ko ELF can in theory be thousands of characters,
|
||||
and unless we do a NULL terminated search we can't set a perfect value.
|
||||
"""
|
||||
|
||||
|
||||
@dataclass
|
||||
class TaintFlag:
|
||||
shift: int
|
||||
desc: str
|
||||
when_present: bool
|
||||
module: bool
|
||||
|
||||
|
||||
TAINT_FLAGS = {
|
||||
"P": TaintFlag(
|
||||
shift=1 << 0, desc="PROPRIETARY_MODULE", when_present=True, module=True
|
||||
),
|
||||
"G": TaintFlag(
|
||||
shift=1 << 0, desc="PROPRIETARY_MODULE", when_present=False, module=True
|
||||
),
|
||||
"F": TaintFlag(shift=1 << 1, desc="FORCED_MODULE", when_present=True, module=False),
|
||||
"S": TaintFlag(
|
||||
shift=1 << 2, desc="CPU_OUT_OF_SPEC", when_present=True, module=False
|
||||
),
|
||||
"R": TaintFlag(shift=1 << 3, desc="FORCED_RMMOD", when_present=True, module=False),
|
||||
"M": TaintFlag(shift=1 << 4, desc="MACHINE_CHECK", when_present=True, module=False),
|
||||
"B": TaintFlag(shift=1 << 5, desc="BAD_PAGE", when_present=True, module=False),
|
||||
"U": TaintFlag(shift=1 << 6, desc="USER", when_present=True, module=False),
|
||||
"D": TaintFlag(shift=1 << 7, desc="DIE", when_present=True, module=False),
|
||||
"A": TaintFlag(
|
||||
shift=1 << 8, desc="OVERRIDDEN_ACPI_TABLE", when_present=True, module=False
|
||||
),
|
||||
"W": TaintFlag(shift=1 << 9, desc="WARN", when_present=True, module=False),
|
||||
"C": TaintFlag(shift=1 << 10, desc="CRAP", when_present=True, module=True),
|
||||
"I": TaintFlag(
|
||||
shift=1 << 11, desc="FIRMWARE_WORKAROUND", when_present=True, module=False
|
||||
),
|
||||
"O": TaintFlag(shift=1 << 12, desc="OOT_MODULE", when_present=True, module=True),
|
||||
"E": TaintFlag(
|
||||
shift=1 << 13, desc="UNSIGNED_MODULE", when_present=True, module=True
|
||||
),
|
||||
"L": TaintFlag(shift=1 << 14, desc="SOFTLOCKUP", when_present=True, module=False),
|
||||
"K": TaintFlag(shift=1 << 15, desc="LIVEPATCH", when_present=True, module=True),
|
||||
"X": TaintFlag(shift=1 << 16, desc="AUX", when_present=True, module=True),
|
||||
"T": TaintFlag(shift=1 << 17, desc="RANDSTRUCT", when_present=True, module=True),
|
||||
"N": TaintFlag(shift=1 << 18, desc="TEST", when_present=True, module=True),
|
||||
}
|
||||
"""Flags used to taint kernel and modules, for debugging purposes.
|
||||
|
||||
Map based on 6.12-rc5.
|
||||
|
||||
Documentation :
|
||||
- https://www.kernel.org/doc/Documentation/admin-guide/sysctl/kernel.rst#:~:text=guide/sysrq.rst.-,tainted,-%3D%3D%3D%3D%3D%3D%3D%0A%0ANon%2Dzero%20if
|
||||
- https://www.kernel.org/doc/Documentation/admin-guide/tainted-kernels.rst#:~:text=More%20detailed%20explanation%20for%20tainting
|
||||
- taint_flag kernel struct
|
||||
- taint_flags kernel constant
|
||||
"""
|
||||
|
||||
## ELF related constants
|
||||
|
||||
# Elf Symbol Bindings
|
||||
STB_LOCAL = 0
|
||||
STB_GLOBAL = 1
|
||||
|
||||
# Elf Symbol Types
|
||||
STT_NOTYPE = 0
|
||||
STT_OBJECT = 1
|
||||
STT_FUNC = 2
|
||||
STT_SECTION = 3
|
||||
|
||||
# Elf Section Types
|
||||
SHT_NULL = 0
|
||||
SHT_PROGBITS = 1
|
||||
SHT_SYMTAB = 2
|
||||
SHT_STRTAB = 3
|
||||
SHT_RELA = 4
|
||||
SHT_NOTE = 7
|
||||
|
||||
# Elf Section Attributes
|
||||
SHF_WRITE = 1
|
||||
SHF_ALLOC = 2
|
||||
SHF_EXECINSTR = 4
|
||||
|
||||
@@ -10,3 +10,23 @@ KERNEL_MODULE_NAMES = ["ntkrnlmp", "ntkrnlpa", "ntkrpamp", "ntoskrnl"]
|
||||
"""The list of names that kernel modules can have within the windows OS"""
|
||||
|
||||
PE_MAX_EXTRACTION_SIZE = 1024 * 1024 * 256
|
||||
|
||||
"""
|
||||
The following constants represent the layout of the Low Stub which exists only on x64 machines with no virtualization/emulation,
|
||||
responsible for transitioning from Real Mode(16 bit) to Protected Mode(32 bit) and Long Mode(64 bit) on boot/return from sleep.
|
||||
Contains offsets to fields and structures within the undocumented structure _PROCESSOR_START_BLOCK.
|
||||
Here's a reference: https://github.com/mic101/windows/blob/master/WRK-v1.2/base/ntos/inc/amd64.h#L3334
|
||||
"""
|
||||
# Expected signature for validation, constructed from:
|
||||
# PROCESSOR_START_BLOCK->Jmp->OpCode | PROCESSOR_START_BLOCK->Jmp->Offset | PROCESSOR_START_BLOCK->CompletionFlag
|
||||
JMP_AND_COMPLETION_SIGNATURE = 0x00000001000600E9
|
||||
|
||||
# Address of LmTarget (Long Mode target)
|
||||
PROCESSOR_START_BLOCK_LM_TARGET_OFFSET = (
|
||||
0x70 # PROCESSOR_START_BLOCK->LmTarget, PVOID 8 bytes
|
||||
)
|
||||
|
||||
# CR3 register within structures describing initial processor state to be started
|
||||
PROCESSOR_START_BLOCK_CR3_OFFSET = 0xA0 # PROCESSOR_START_BLOCK->ProcessorState->SpecialRegisters->Cr3, ULONG64 8 bytes
|
||||
|
||||
MAX_PID = 0xFFFFFFFC
|
||||
|
||||
@@ -8,10 +8,12 @@ This has been made an object to allow quick swapping and changing of
|
||||
contexts, to allow a plugin to act on multiple different contexts
|
||||
without them interfering with each other.
|
||||
"""
|
||||
|
||||
import functools
|
||||
import hashlib
|
||||
import logging
|
||||
from typing import Callable, Iterable, List, Optional, Set, Tuple, Union
|
||||
import re
|
||||
from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple, Union
|
||||
|
||||
from volatility3.framework import constants, interfaces, symbols, exceptions
|
||||
from volatility3.framework.objects import templates
|
||||
@@ -129,7 +131,7 @@ class Context(interfaces.context.ContextInterface):
|
||||
object_info=interfaces.objects.ObjectInformation(
|
||||
layer_name=layer_name,
|
||||
offset=offset,
|
||||
native_layer_name=native_layer_name,
|
||||
native_layer_name=native_layer_name or layer_name,
|
||||
size=object_template.size,
|
||||
),
|
||||
)
|
||||
@@ -229,7 +231,7 @@ class Module(interfaces.context.ModuleInterface):
|
||||
def object(
|
||||
self,
|
||||
object_type: str,
|
||||
offset: int = None,
|
||||
offset: Optional[int] = None,
|
||||
native_layer_name: Optional[str] = None,
|
||||
absolute: bool = False,
|
||||
**kwargs,
|
||||
@@ -286,7 +288,7 @@ class Module(interfaces.context.ModuleInterface):
|
||||
symbol_name: Name of the symbol (within the module) to construct
|
||||
native_layer_name: Name of the layer in which constructed objects are made (for pointers)
|
||||
absolute: whether the symbol's address is absolute or relative to the module
|
||||
object_type: Override for the type from the symobl to use (or if the symbol type is missing)
|
||||
object_type: Override for the type from the symbol to use (or if the symbol type is missing)
|
||||
"""
|
||||
if constants.BANG not in symbol_name:
|
||||
symbol_name = self.symbol_table_name + constants.BANG + symbol_name
|
||||
@@ -337,7 +339,7 @@ class Module(interfaces.context.ModuleInterface):
|
||||
)
|
||||
|
||||
@property
|
||||
def symbols(self):
|
||||
def symbols(self) -> Iterable[str]:
|
||||
return self.context.symbol_space[self.symbol_table_name].symbols
|
||||
|
||||
get_symbol = get_module_wrapper("get_symbol")
|
||||
@@ -356,7 +358,7 @@ class SizedModule(Module):
|
||||
return size or 0
|
||||
|
||||
@property # type: ignore # FIXME: mypy #5107
|
||||
@functools.lru_cache()
|
||||
@functools.lru_cache
|
||||
def hash(self) -> str:
|
||||
"""Hashes the module for equality checks.
|
||||
|
||||
@@ -386,10 +388,8 @@ class ModuleCollection(interfaces.context.ModuleContainer):
|
||||
"""Class to contain a collection of SizedModules and reason about their
|
||||
contents."""
|
||||
|
||||
def __init__(
|
||||
self, modules: Optional[List[interfaces.context.ModuleInterface]] = None
|
||||
) -> None:
|
||||
self._prefix_count = {}
|
||||
def __init__(self, modules: Optional[List[SizedModule]] = None) -> None:
|
||||
self._modules: Dict[str, SizedModule] = {}
|
||||
super().__init__(modules)
|
||||
|
||||
def deduplicate(self) -> "ModuleCollection":
|
||||
@@ -402,20 +402,19 @@ class ModuleCollection(interfaces.context.ModuleContainer):
|
||||
new_modules = []
|
||||
seen: Set[str] = set()
|
||||
for mod in self._modules:
|
||||
if mod.hash not in seen or mod.size == 0:
|
||||
if self._modules[mod].hash not in seen or self._modules[mod].size == 0:
|
||||
new_modules.append(mod)
|
||||
seen.add(mod.hash) # type: ignore # FIXME: mypy #5107
|
||||
seen.add(self._modules[mod].hash)
|
||||
return ModuleCollection(new_modules)
|
||||
|
||||
def free_module_name(self, prefix: str = "module") -> str:
|
||||
"""Returns an unused module name"""
|
||||
if prefix not in self._prefix_count:
|
||||
self._prefix_count[prefix] = 1
|
||||
existing_names = [name for name in self if re.match(rf"^{prefix}[0-9]*$", name)]
|
||||
if not existing_names:
|
||||
return prefix
|
||||
count = self._prefix_count[prefix]
|
||||
count = len(existing_names)
|
||||
while prefix + str(count) in self:
|
||||
count += 1
|
||||
self._prefix_count[prefix] = count
|
||||
return prefix + str(count)
|
||||
|
||||
@property
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
# This file is Copyright 2025 Volatility Foundation and licensed under the Volatility Software License 1.0
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
|
||||
# This file contains the Deprecation class used to deprecate methods in an orderly manner
|
||||
|
||||
import warnings
|
||||
import functools
|
||||
import inspect
|
||||
|
||||
from typing import Callable, Tuple
|
||||
|
||||
from volatility3.framework import interfaces, exceptions, versionutils
|
||||
|
||||
|
||||
def method_being_removed(message: str, removal_date: str):
|
||||
"""A decorator for marking functions as being removed in the future and without a replacement.
|
||||
Callers to this function should explicitly list the API paths that should be used instead.
|
||||
|
||||
Args:
|
||||
message: A message added to the standard deprecation warning. Should include the replacement API paths
|
||||
removal_date: A YYYY-MM-DD formatted date of when the function will be removed from the framework
|
||||
"""
|
||||
|
||||
def decorator(deprecated_func):
|
||||
@functools.wraps(deprecated_func)
|
||||
def wrapper(*args, **kwargs):
|
||||
warnings.warn(
|
||||
f"This API ({deprecated_func.__module__}.{deprecated_func.__qualname__}) will be removed in the first release after {removal_date}. {message}",
|
||||
FutureWarning,
|
||||
)
|
||||
return deprecated_func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def deprecated_method(
|
||||
replacement: Callable,
|
||||
removal_date: str,
|
||||
replacement_version: Tuple[int, int, int] = None,
|
||||
additional_information: str = "",
|
||||
):
|
||||
"""A decorator for marking functions as deprecated.
|
||||
|
||||
Args:
|
||||
replacement: The replacement function overriding the deprecated API, in the form of a Callable (typically a method)
|
||||
removal_date: A YYYY-MM-DD formatted date of when the function will be removed from the framework
|
||||
replacement_version: The "replacement" base class version that the deprecated method expects before proxying to it. This implies that "replacement" is a method from a class that inherits from VersionableInterface.
|
||||
additional_information: Information appended at the end of the deprecation message
|
||||
"""
|
||||
|
||||
def decorator(deprecated_func):
|
||||
@functools.wraps(deprecated_func)
|
||||
def wrapper(*args, **kwargs):
|
||||
nonlocal replacement, replacement_version, additional_information
|
||||
# Prevent version mismatches between deprecated (proxy) methods and the ones they proxy
|
||||
if (
|
||||
replacement_version is not None
|
||||
and callable(replacement)
|
||||
and hasattr(replacement, "__self__")
|
||||
):
|
||||
replacement_base_class = replacement.__self__
|
||||
|
||||
# Verify that the base class inherits from VersionableInterface
|
||||
if inspect.isclass(replacement_base_class) and issubclass(
|
||||
replacement_base_class,
|
||||
interfaces.configuration.VersionableInterface,
|
||||
):
|
||||
# SemVer check
|
||||
if not versionutils.matches_required(
|
||||
replacement_version, replacement_base_class.version
|
||||
):
|
||||
raise exceptions.VersionMismatchException(
|
||||
deprecated_func,
|
||||
replacement_base_class,
|
||||
replacement_version,
|
||||
"This is a bug, the deprecated call needs to be removed and the caller needs to update their code to use the new method.",
|
||||
)
|
||||
|
||||
deprecation_msg = f'Method "{deprecated_func.__module__ + "." + deprecated_func.__qualname__}" is deprecated and will be removed in the first release after {removal_date}, use "{replacement.__module__ + "." + replacement.__qualname__}" instead. {additional_information}'
|
||||
warnings.warn(deprecation_msg, FutureWarning)
|
||||
# Return the wrapped function with its original arguments
|
||||
return deprecated_func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
def renamed_class(deprecated_class_name: str, message: str, removal_date: str):
|
||||
"""A decorator for marking classes as being renamed and removed in the future.
|
||||
Callers to this function should explicitly update to use the other plugins instead.
|
||||
|
||||
Args:
|
||||
deprecated_class_name: The name of the class being deprecated
|
||||
message: A message added to the standard deprecation warning. Should include the replacement API paths
|
||||
removal_date: A YYYY-MM-DD formatted date of when the function will be removed from the framework
|
||||
"""
|
||||
|
||||
def decorator(replacement_func):
|
||||
@functools.wraps(replacement_func)
|
||||
def wrapper(*args, **kwargs):
|
||||
warnings.warn(
|
||||
f"This plugin ({deprecated_class_name}) has been renamed and will be removed in the first release after {removal_date}. {message}",
|
||||
FutureWarning,
|
||||
)
|
||||
return replacement_func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class PluginRenameClass:
|
||||
"""Class to move all classmethod invocations (for when a plugin has been moved)"""
|
||||
|
||||
def __init_subclass__(cls, replacement_class, removal_date, **kwargs):
|
||||
deprecated_class_name = f"{cls.__module__}.{cls.__qualname__}"
|
||||
super().__init_subclass__(**kwargs)
|
||||
for attr, value in replacement_class.__dict__.items():
|
||||
if isinstance(value, classmethod) and attr != "get_requirements":
|
||||
setattr(
|
||||
cls,
|
||||
attr,
|
||||
classmethod(
|
||||
renamed_class(
|
||||
deprecated_class_name=deprecated_class_name,
|
||||
removal_date=removal_date,
|
||||
message=f"Please ensure all method calls to this plugin are replaced with calls to {replacement_class.__module__}.{replacement_class.__qualname__}",
|
||||
)(value.__func__)
|
||||
),
|
||||
)
|
||||
else:
|
||||
if attr == "run":
|
||||
setattr(
|
||||
cls,
|
||||
attr,
|
||||
method_being_removed(
|
||||
removal_date=removal_date,
|
||||
message=f"This plugin has been renamed, please call {replacement_class.__module__}.{replacement_class.__qualname__} rather than {deprecated_class_name}.",
|
||||
)(value),
|
||||
)
|
||||
elif not attr.startswith("__"):
|
||||
setattr(cls, attr, value)
|
||||
return super(PluginRenameClass).__init_subclass__(**kwargs)
|
||||
@@ -8,7 +8,8 @@ space or symbol tables, and by layers when an address is invalid. The
|
||||
:class:`PagedInvalidAddressException` contains information about the
|
||||
size of the invalid page.
|
||||
"""
|
||||
from typing import Dict, Optional
|
||||
|
||||
from typing import Callable, Dict, Optional, Tuple
|
||||
|
||||
from volatility3.framework import interfaces
|
||||
|
||||
@@ -130,3 +131,35 @@ class OfflineException(VolatilityException):
|
||||
|
||||
class RenderException(VolatilityException):
|
||||
"""Thrown if there is an error during rendering"""
|
||||
|
||||
|
||||
class LinuxPageCacheException(VolatilityException):
|
||||
"""Thrown if there is an error during Linux Page Cache processing"""
|
||||
|
||||
|
||||
class VersionMismatchException(VolatilityException):
|
||||
"""Thrown if a version mismatch has been encountered between two components."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
source_component: Callable,
|
||||
target_component: interfaces.configuration.VersionableInterface,
|
||||
target_version: Tuple[int, int, int],
|
||||
failure_reason: str = None,
|
||||
*args,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
source_component: The component that required the target component
|
||||
target_component: The component that is required. Must inherit from interfaces.configuration.VersionableInterface
|
||||
target_version: The version of the target component that was required, and ultimately was not satisfied
|
||||
failure_reason: A detailed failure reason to enhance debugging and bug tracking
|
||||
"""
|
||||
super().__init__(*args)
|
||||
self.source_component = source_component
|
||||
self.target_component = target_component
|
||||
self.target_version = target_version
|
||||
self.failure_reason = failure_reason
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.source_component.__module__ + '.' + self.source_component.__qualname__}: Version {self.target_version} dependency on {self.target_component.__module__ + '.' + self.target_component.__name__} {self.target_component.version} unmet."
|
||||
|
||||
@@ -13,12 +13,12 @@ components of volatility to write plugins.
|
||||
# This will also avoid namespace issues, because people can use interfaces.layers to
|
||||
# avoid clashing with the layers package
|
||||
from volatility3.framework.interfaces import (
|
||||
renderers,
|
||||
configuration,
|
||||
context,
|
||||
layers,
|
||||
objects,
|
||||
plugins,
|
||||
symbols,
|
||||
automagic,
|
||||
renderers as renderers,
|
||||
configuration as configuration,
|
||||
context as context,
|
||||
layers as layers,
|
||||
objects as objects,
|
||||
plugins as plugins,
|
||||
symbols as symbols,
|
||||
automagic as automagic,
|
||||
)
|
||||
|
||||
@@ -7,6 +7,7 @@ runs.
|
||||
Automagic objects attempt to automatically fill configuration values
|
||||
that a user has not filled.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from abc import ABCMeta
|
||||
from typing import Any, List, Optional, Tuple, Type, Union
|
||||
@@ -42,7 +43,7 @@ class AutomagicInterface(
|
||||
priority = 10
|
||||
"""An ordering to indicate how soon this automagic should be run"""
|
||||
|
||||
exclusion_list = []
|
||||
exclusion_list: List[str] = []
|
||||
"""A list of plugin categories (typically operating systems) which the plugin will not operate on"""
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -53,7 +53,7 @@ ConfigSimpleType = Optional[Union[SimpleTypes, List[SimpleTypes]]]
|
||||
def path_join(*args) -> str:
|
||||
"""Joins configuration paths together."""
|
||||
# If a path element (particularly the first) is empty, then remove it from the list
|
||||
args = tuple([arg for arg in args if arg])
|
||||
args = tuple(arg for arg in args if arg)
|
||||
return CONFIG_SEPARATOR.join(args)
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ class HierarchicalDict(collections.abc.Mapping):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
initial_dict: Dict[str, "SimpleTypeRequirement"] = None,
|
||||
initial_dict: Optional[Dict[str, "SimpleTypeRequirement"]] = None,
|
||||
separator: str = CONFIG_SEPARATOR,
|
||||
) -> None:
|
||||
"""
|
||||
@@ -94,7 +94,7 @@ class HierarchicalDict(collections.abc.Mapping):
|
||||
raise TypeError(f"Separator must be a one character string: {separator}")
|
||||
self._separator = separator
|
||||
self._data: Dict[str, ConfigSimpleType] = {}
|
||||
self._subdict: Dict[str, "HierarchicalDict"] = {}
|
||||
self._subdict: Dict[str, HierarchicalDict] = {}
|
||||
if isinstance(initial_dict, str):
|
||||
initial_dict = json.loads(initial_dict)
|
||||
if isinstance(initial_dict, dict):
|
||||
@@ -182,9 +182,7 @@ class HierarchicalDict(collections.abc.Mapping):
|
||||
else:
|
||||
if not isinstance(value, HierarchicalDict):
|
||||
raise TypeError(
|
||||
"HierarchicalDicts can only store HierarchicalDicts within their structure: {}".format(
|
||||
type(value)
|
||||
)
|
||||
f"HierarchicalDicts can only store HierarchicalDicts within their structure: {type(value)}"
|
||||
)
|
||||
self._subdict[key] = value
|
||||
|
||||
@@ -330,7 +328,7 @@ class RequirementInterface(metaclass=ABCMeta):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
description: str = None,
|
||||
description: Optional[str] = None,
|
||||
default: ConfigSimpleType = None,
|
||||
optional: bool = False,
|
||||
) -> None:
|
||||
@@ -498,9 +496,7 @@ class SimpleTypeRequirement(RequirementInterface):
|
||||
if not isinstance(value, self.instance_type):
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_V,
|
||||
"TypeError - {} requirements only accept {} type: {}".format(
|
||||
self.name, self.instance_type.__name__, repr(value)
|
||||
),
|
||||
f"TypeError - {self.name} requirements only accept {self.instance_type.__name__} type: {repr(value)}",
|
||||
)
|
||||
return {config_path: self}
|
||||
return {}
|
||||
@@ -622,7 +618,7 @@ class ConstructableRequirementInterface(RequirementInterface):
|
||||
self,
|
||||
context: "interfaces.context.ContextInterface",
|
||||
config_path: str,
|
||||
requirement_dict: Dict[str, object] = None,
|
||||
requirement_dict: Optional[Dict[str, object]] = None,
|
||||
) -> Optional["interfaces.objects.ObjectInterface"]:
|
||||
"""Constructs the class, handing args and the subrequirements as
|
||||
parameters to __init__"""
|
||||
@@ -656,6 +652,7 @@ class ConstructableRequirementInterface(RequirementInterface):
|
||||
class ConfigurableRequirementInterface(RequirementInterface):
|
||||
"""Simple Abstract class to provide build_required_config."""
|
||||
|
||||
@abstractmethod
|
||||
def build_configuration(
|
||||
self,
|
||||
context: "interfaces.context.ContextInterface",
|
||||
@@ -775,17 +772,16 @@ class ConfigurableInterface(metaclass=ABCMeta):
|
||||
str: The newly generated full configuration path
|
||||
"""
|
||||
random_config_dict = "".join(
|
||||
random.SystemRandom().choice(string.ascii_uppercase + string.digits)
|
||||
for _ in range(8)
|
||||
random.SystemRandom().choices(string.ascii_uppercase + string.digits, k=8)
|
||||
)
|
||||
new_config_path = path_join(base_config_path, random_config_dict)
|
||||
# TODO: Check that the new_config_path is empty, although it's not critical if it's not since the values are merged in
|
||||
|
||||
# This should check that each k corresponds to a requirement and each v is of the appropriate type
|
||||
# This would require knowledge of the new configurable itself to verify, and they should do validation in the
|
||||
# constructor anyway, however, to prevent bad types getting into the config tree we just verify that v is a simple type
|
||||
# constructor anyway, however, to prevent bad types getting into the config tree we just verify that v is a basic type
|
||||
for k, v in kwargs.items():
|
||||
if not isinstance(v, (int, str, bool, float, bytes)):
|
||||
if not isinstance(v, BasicTypes):
|
||||
raise TypeError(
|
||||
"Config values passed to make_subconfig can only be simple types"
|
||||
)
|
||||
|
||||
@@ -11,6 +11,7 @@ convenience functions, most notably the object constructor function,
|
||||
`object`, which will construct a symbol on a layer at a particular
|
||||
offset.
|
||||
"""
|
||||
|
||||
import collections
|
||||
import copy
|
||||
from abc import ABCMeta, abstractmethod
|
||||
@@ -85,7 +86,7 @@ class ContextInterface(metaclass=ABCMeta):
|
||||
object_type: Union[str, "interfaces.objects.Template"],
|
||||
layer_name: str,
|
||||
offset: int,
|
||||
native_layer_name: str = None,
|
||||
native_layer_name: Optional[str] = None,
|
||||
**arguments,
|
||||
) -> "interfaces.objects.ObjectInterface":
|
||||
"""Object factory, takes a context, symbol, offset and optional
|
||||
@@ -114,6 +115,7 @@ class ContextInterface(metaclass=ABCMeta):
|
||||
"""
|
||||
return copy.deepcopy(self)
|
||||
|
||||
@abstractmethod
|
||||
def module(
|
||||
self,
|
||||
module_name: str,
|
||||
@@ -232,7 +234,7 @@ class ModuleInterface(interfaces.configuration.ConfigurableInterface):
|
||||
def object(
|
||||
self,
|
||||
object_type: str,
|
||||
offset: int = None,
|
||||
offset: Optional[int] = None,
|
||||
native_layer_name: Optional[str] = None,
|
||||
absolute: bool = False,
|
||||
**kwargs,
|
||||
@@ -266,7 +268,7 @@ class ModuleInterface(interfaces.configuration.ConfigurableInterface):
|
||||
symbol_name: The name of a symbol (that must be present in the module's symbol table). The symbol's associated type will be used to construct an object at the symbol's offset.
|
||||
native_layer_name: The native layer for objects that reference a different layer (if not the default provided during module construction)
|
||||
absolute: A boolean specifying whether the offset is absolute within the layer, or relative to the start of the module
|
||||
object_type: Override for the type from the symobl to use (or if the symbol type is missing)
|
||||
object_type: Override for the type from the symbol to use (or if the symbol type is missing)
|
||||
|
||||
Returns:
|
||||
The constructed object
|
||||
@@ -277,27 +279,37 @@ class ModuleInterface(interfaces.configuration.ConfigurableInterface):
|
||||
symbol = self.get_symbol(name)
|
||||
return self.offset + symbol.address
|
||||
|
||||
@abstractmethod
|
||||
def get_type(self, name: str) -> "interfaces.objects.Template":
|
||||
"""Returns a type from the module's symbol table."""
|
||||
|
||||
@abstractmethod
|
||||
def get_symbol(self, name: str) -> "interfaces.symbols.SymbolInterface":
|
||||
"""Returns a symbol object from the module's symbol table."""
|
||||
|
||||
@abstractmethod
|
||||
def get_enumeration(self, name: str) -> "interfaces.objects.Template":
|
||||
"""Returns an enumeration from the module's symbol table."""
|
||||
|
||||
@abstractmethod
|
||||
def has_type(self, name: str) -> bool:
|
||||
"""Determines whether a type is present in the module's symbol table."""
|
||||
|
||||
@abstractmethod
|
||||
def has_symbol(self, name: str) -> bool:
|
||||
"""Determines whether a symbol is present in the module's symbol table."""
|
||||
|
||||
@abstractmethod
|
||||
def has_enumeration(self, name: str) -> bool:
|
||||
"""Determines whether an enumeration is present in the module's symbol table."""
|
||||
|
||||
def symbols(self) -> List:
|
||||
"""Lists the symbols contained in the symbol table for this module"""
|
||||
@property
|
||||
@abstractmethod
|
||||
def symbols(self) -> Iterable[str]:
|
||||
"""Returns an iterable of the symbols contained in the symbol table for this module"""
|
||||
raise NotImplementedError("Symbols property has not been implemented.")
|
||||
|
||||
@abstractmethod
|
||||
def get_symbols_by_absolute_location(self, offset: int, size: int = 0) -> List[str]:
|
||||
"""Returns the symbols within table_name (or this module if not specified) that live at the specified
|
||||
absolute offset provided."""
|
||||
@@ -343,6 +355,7 @@ class ModuleContainer(collections.abc.Mapping):
|
||||
def __iter__(self):
|
||||
return iter(self._modules)
|
||||
|
||||
@abstractmethod
|
||||
def free_module_name(self, prefix: str = "module") -> str:
|
||||
"""Returns an unused table name to ensure no collision occurs when
|
||||
inserting a symbol table."""
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
One layer may combine other layers, map data based on the data itself,
|
||||
or map a procedure (such as decryption) across another layer of data.
|
||||
"""
|
||||
|
||||
import collections.abc
|
||||
import functools
|
||||
import logging
|
||||
@@ -136,7 +137,7 @@ class DataLayerInterface(
|
||||
def minimum_address(self) -> int:
|
||||
"""Returns the minimum valid address of the space."""
|
||||
|
||||
@property
|
||||
@functools.cached_property
|
||||
def address_mask(self) -> int:
|
||||
"""Returns a mask which encapsulates all the active bits of an address
|
||||
for this layer."""
|
||||
@@ -188,7 +189,6 @@ class DataLayerInterface(
|
||||
the object unreadable (exceptions will be thrown using a
|
||||
DataLayer after destruction)
|
||||
"""
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -211,7 +211,7 @@ class DataLayerInterface(
|
||||
context: interfaces.context.ContextInterface,
|
||||
scanner: ScannerInterface,
|
||||
progress_callback: constants.ProgressCallback = None,
|
||||
sections: Iterable[Tuple[int, int]] = None,
|
||||
sections: Optional[Iterable[Tuple[int, int]]] = None,
|
||||
) -> Iterable[Any]:
|
||||
"""Scans a Translation layer by chunk.
|
||||
|
||||
@@ -361,9 +361,7 @@ class DataLayerInterface(
|
||||
data += self.context.layers[layer_name].read(address, chunk_size)
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.debug(
|
||||
"Invalid address in layer {} found scanning {} at address {:x}".format(
|
||||
layer_name, self.name, address
|
||||
)
|
||||
f"Invalid address in layer {layer_name} found scanning {self.name} at address {address:x}"
|
||||
)
|
||||
|
||||
if len(data) > scanner.chunk_size + scanner.overlap:
|
||||
@@ -681,7 +679,7 @@ class LayerContainer(collections.abc.Mapping):
|
||||
if name in self._layers[layer].dependencies:
|
||||
raise exceptions.LayerException(
|
||||
self._layers[layer].name,
|
||||
f"Layer {self._layers[layer].name} is depended upon by {layer}",
|
||||
f"Layer {name} is depended upon by {layer}",
|
||||
)
|
||||
# Otherwise, wipe out the layer
|
||||
self._layers[name].destroy()
|
||||
@@ -721,7 +719,7 @@ class LayerContainer(collections.abc.Mapping):
|
||||
raise NotImplementedError("Cycle checking has not yet been implemented")
|
||||
|
||||
|
||||
class DummyProgress(object):
|
||||
class DummyProgress:
|
||||
"""A class to emulate Multiprocessing/threading Value objects."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
#
|
||||
"""Objects are the core of volatility, and provide pythonic access to
|
||||
interpreted values of data from a layer."""
|
||||
|
||||
import abc
|
||||
import collections
|
||||
import collections.abc
|
||||
import contextlib
|
||||
import dataclasses
|
||||
import logging
|
||||
from typing import Any, Dict, List, Mapping, Optional
|
||||
|
||||
@@ -52,7 +54,8 @@ class ReadOnlyMapping(collections.abc.Mapping):
|
||||
return dict(self) == dict(other)
|
||||
|
||||
|
||||
class ObjectInformation(ReadOnlyMapping):
|
||||
@dataclasses.dataclass
|
||||
class ObjectInformation:
|
||||
"""Contains common information useful/pertinent only to an individual
|
||||
object (like an instance)
|
||||
|
||||
@@ -63,35 +66,20 @@ class ObjectInformation(ReadOnlyMapping):
|
||||
in a single place. These values are based on the :class:`ReadOnlyMapping` class, to prevent their modification.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
layer_name: str,
|
||||
offset: int,
|
||||
member_name: Optional[str] = None,
|
||||
parent: Optional["ObjectInterface"] = None,
|
||||
native_layer_name: Optional[str] = None,
|
||||
size: Optional[int] = None,
|
||||
):
|
||||
"""Constructs a container for basic information about an object.
|
||||
layer_name: str
|
||||
offset: int
|
||||
native_layer_name: str
|
||||
member_name: Optional[str] = None
|
||||
parent: Optional["ObjectInterface"] = None
|
||||
size: Optional[int] = None
|
||||
|
||||
Args:
|
||||
layer_name: Layer from which the data for the object will be read
|
||||
offset: Offset within the layer at which the data for the object will be read
|
||||
member_name: If the object was accessed as a member of a parent object, this was the name used to access it
|
||||
parent: If the object was accessed as a member of a parent object, this is the parent object
|
||||
native_layer_name: If this object references other objects (such as a pointer), what layer those objects live in
|
||||
size: The size that the whole structure consumes in bytes
|
||||
"""
|
||||
super().__init__(
|
||||
{
|
||||
"layer_name": layer_name,
|
||||
"offset": offset,
|
||||
"member_name": member_name,
|
||||
"parent": parent,
|
||||
"native_layer_name": native_layer_name or layer_name,
|
||||
"size": size,
|
||||
}
|
||||
)
|
||||
def __getitem__(self, key):
|
||||
if key in self:
|
||||
return getattr(self, key)
|
||||
raise KeyError(f"No {key} present in ObjectInformation")
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in [field.name for field in dataclasses.fields(self)]
|
||||
|
||||
|
||||
class ObjectInterface(metaclass=abc.ABCMeta):
|
||||
@@ -133,7 +121,7 @@ class ObjectInterface(metaclass=abc.ABCMeta):
|
||||
|
||||
def __getattr__(self, attr: str) -> Any:
|
||||
"""Method for ensuring volatility members can be returned."""
|
||||
raise AttributeError
|
||||
raise AttributeError(f"Unable to find {attr} for type {type(self)}")
|
||||
|
||||
@property
|
||||
def vol(self) -> ReadOnlyMapping:
|
||||
@@ -183,7 +171,7 @@ class ObjectInterface(metaclass=abc.ABCMeta):
|
||||
offset=self.vol.offset,
|
||||
member_name=self.vol.member_name,
|
||||
parent=self.vol.parent,
|
||||
native_layer_name=self.vol.native_layer_name,
|
||||
native_layer_name=self.vol.native_layer_name or self.vol.layer_name,
|
||||
size=object_template.size,
|
||||
)
|
||||
return object_template(context=self._context, object_info=object_info)
|
||||
@@ -374,6 +362,7 @@ class Template:
|
||||
f"{self.__class__.__name__} object has no attribute {attr}"
|
||||
)
|
||||
|
||||
@abc.abstractmethod
|
||||
def __call__(
|
||||
self,
|
||||
context: "interfaces.context.ContextInterface",
|
||||
|
||||
@@ -46,7 +46,7 @@ class FileHandlerInterface(io.RawIOBase):
|
||||
def preferred_filename(self, filename: str):
|
||||
"""Sets the preferred filename"""
|
||||
if self.closed:
|
||||
raise IOError("FileHandler name cannot be changed once closed")
|
||||
raise OSError("FileHandler name cannot be changed once closed")
|
||||
if not isinstance(filename, str):
|
||||
raise TypeError("FileHandler preferred filenames must be strings")
|
||||
if os.path.sep in filename:
|
||||
@@ -59,14 +59,14 @@ class FileHandlerInterface(io.RawIOBase):
|
||||
|
||||
@staticmethod
|
||||
def sanitize_filename(filename: str) -> str:
|
||||
"""Sanititizes the filename to ensure only a specific whitelist of characters is allowed through"""
|
||||
allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.- ()[]{}!$%^:#~?<>,|"
|
||||
"""Sanititizes the filename to ensure only a specific allow list of characters is allowed through"""
|
||||
allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.- ()[]{}!$%^#~,"
|
||||
result = ""
|
||||
for char in filename:
|
||||
if char in allowed:
|
||||
result += char
|
||||
else:
|
||||
result += "?"
|
||||
result += "_" # change unwanted chars to an underscore
|
||||
return result
|
||||
|
||||
def __enter__(self):
|
||||
|
||||
@@ -10,7 +10,8 @@ suitable output.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from abc import abstractmethod, ABCMeta
|
||||
import warnings
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from collections import abc
|
||||
from typing import (
|
||||
Any,
|
||||
@@ -20,21 +21,68 @@ from typing import (
|
||||
List,
|
||||
NamedTuple,
|
||||
Optional,
|
||||
TypeVar,
|
||||
Type,
|
||||
Tuple,
|
||||
Type,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
from typing import Dict
|
||||
|
||||
from volatility3.framework import interfaces
|
||||
|
||||
|
||||
class BasicType:
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method for rendering basic types"""
|
||||
return str(self)
|
||||
|
||||
|
||||
class BaseAbsentValue:
|
||||
"""Class that represents values which are not present for some reason."""
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method for rendering basic types"""
|
||||
return "-"
|
||||
|
||||
|
||||
class Column(NamedTuple):
|
||||
name: str
|
||||
type: Any
|
||||
|
||||
Column = NamedTuple("Column", [("name", str), ("type", Any)])
|
||||
|
||||
RenderOption = Any
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class TypeRendererInterface:
|
||||
type = T
|
||||
|
||||
def __init__(
|
||||
self, func: Optional[Callable] = None, options: Optional[Dict[str, Any]] = None
|
||||
):
|
||||
self._options = options or {}
|
||||
setattr(self, "render", func)
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
return self._options
|
||||
|
||||
def render(self, data: Union[T, BaseAbsentValue]) -> Any:
|
||||
"""Renders a specific datatype"""
|
||||
return ""
|
||||
|
||||
def __call__(self, data: Union[T, BaseAbsentValue]) -> Any:
|
||||
"""Shortcut for render"""
|
||||
return self.render(data)
|
||||
|
||||
|
||||
class Renderer(metaclass=ABCMeta):
|
||||
"""Class that defines the interface that all output renderers must
|
||||
support."""
|
||||
|
||||
_type_renderers: Dict[Union[Type, str], Callable]
|
||||
|
||||
def __init__(self, options: Optional[List[RenderOption]] = None) -> None:
|
||||
"""Accepts an options object to configure the renderers."""
|
||||
# FIXME: Once the config option objects are in place, put the _type_check in place
|
||||
@@ -98,11 +146,7 @@ class TreeNode(abc.Sequence, metaclass=ABCMeta):
|
||||
"""
|
||||
|
||||
|
||||
class BaseAbsentValue(object):
|
||||
"""Class that represents values which are not present for some reason."""
|
||||
|
||||
|
||||
class Disassembly(object):
|
||||
class Disassembly(BasicType):
|
||||
"""A class to indicate that the bytes provided should be disassembled
|
||||
(based on the architecture)"""
|
||||
|
||||
@@ -111,6 +155,10 @@ class Disassembly(object):
|
||||
def __init__(
|
||||
self, data: bytes, offset: int = 0, architecture: str = "intel64"
|
||||
) -> None:
|
||||
warnings.warn(
|
||||
"interfaces.renderers.Disassembly is now renderers.Disassembly",
|
||||
FutureWarning,
|
||||
)
|
||||
self.data = data
|
||||
self.architecture = None
|
||||
if architecture in self.possible_architectures:
|
||||
@@ -119,6 +167,10 @@ class Disassembly(object):
|
||||
raise TypeError("Offset must be an integer type")
|
||||
self.offset = offset
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Fallback method of rendering"""
|
||||
return str(self.data)
|
||||
|
||||
|
||||
# We don't class these off a shared base, because the BaseTypes must only
|
||||
# contain the types that the validator will accept (which would not include the base)
|
||||
@@ -131,13 +183,13 @@ BaseTypes = Union[
|
||||
Type[bytes],
|
||||
Type[datetime.datetime],
|
||||
Type[BaseAbsentValue],
|
||||
Type[Disassembly],
|
||||
Type[BasicType],
|
||||
]
|
||||
ColumnsType = List[Tuple[str, BaseTypes]]
|
||||
VisitorSignature = Callable[[TreeNode, _Type], _Type]
|
||||
|
||||
|
||||
class TreeGrid(object, metaclass=ABCMeta):
|
||||
class TreeGrid(metaclass=ABCMeta):
|
||||
"""Class providing the interface for a TreeGrid (which contains TreeNodes)
|
||||
|
||||
The structure of a TreeGrid is designed to maintain the structure of the tree in a single object.
|
||||
@@ -150,16 +202,15 @@ class TreeGrid(object, metaclass=ABCMeta):
|
||||
and to create cycles.
|
||||
"""
|
||||
|
||||
base_types: ClassVar[Tuple] = (
|
||||
int,
|
||||
str,
|
||||
float,
|
||||
bytes,
|
||||
datetime.datetime,
|
||||
Disassembly,
|
||||
)
|
||||
# TODO: Figure out why this isn't just BaseTypes (which includes AbsentValues'
|
||||
base_types: ClassVar[Tuple] = (int, str, float, bytes, datetime.datetime, BasicType)
|
||||
|
||||
def __init__(self, columns: ColumnsType, generator: Generator) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
columns: ColumnsType,
|
||||
generator: Generator,
|
||||
context: Optional["interfaces.context.ContextInterface"] = None,
|
||||
) -> None:
|
||||
"""Constructs a TreeGrid object using a specific set of columns.
|
||||
|
||||
The TreeGrid itself is a root element, that can have children but no values.
|
||||
@@ -170,6 +221,15 @@ class TreeGrid(object, metaclass=ABCMeta):
|
||||
columns: A list of column tuples made up of (name, type).
|
||||
generator: An iterable containing row for a tree grid, each row contains a indent level followed by the values for each column in order.
|
||||
"""
|
||||
self._context = context
|
||||
|
||||
@property
|
||||
def context(self) -> Optional["interfaces.context.ContextInterface"]:
|
||||
"""Returns the context value for the tree grid (to retrieve data items)
|
||||
|
||||
This is a property to ensure the renderers don't try changing the context for any reason
|
||||
"""
|
||||
return self._context
|
||||
|
||||
@staticmethod
|
||||
@abstractmethod
|
||||
@@ -179,7 +239,7 @@ class TreeGrid(object, metaclass=ABCMeta):
|
||||
@abstractmethod
|
||||
def populate(
|
||||
self,
|
||||
function: VisitorSignature = None,
|
||||
function: Optional[VisitorSignature] = None,
|
||||
initial_accumulator: Any = None,
|
||||
fail_on_errors: bool = True,
|
||||
) -> Optional[Exception]:
|
||||
@@ -231,7 +291,7 @@ class TreeGrid(object, metaclass=ABCMeta):
|
||||
node: Optional[TreeNode],
|
||||
function: VisitorSignature,
|
||||
initial_accumulator: _Type,
|
||||
sort_key: ColumnSortKey = None,
|
||||
sort_key: Optional[ColumnSortKey] = None,
|
||||
) -> None:
|
||||
"""Visits all the nodes in a tree, calling function on each one.
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
"""Symbols provide structural information about a set of bytes."""
|
||||
|
||||
import bisect
|
||||
import collections.abc
|
||||
from abc import ABC, abstractmethod
|
||||
@@ -10,7 +11,6 @@ from typing import Any, Dict, Iterable, List, Mapping, Optional, Tuple, Type
|
||||
from volatility3.framework import constants, exceptions, interfaces
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.interfaces import configuration, objects
|
||||
from volatility3.framework.interfaces.configuration import RequirementInterface
|
||||
|
||||
|
||||
class SymbolInterface:
|
||||
@@ -122,7 +122,7 @@ class BaseSymbolTableInterface:
|
||||
|
||||
@property
|
||||
def symbols(self) -> Iterable[str]:
|
||||
"""Returns an iterator of the Symbol names."""
|
||||
"""Returns an iterable of the available symbol names."""
|
||||
raise NotImplementedError(
|
||||
"Abstract property symbols not implemented by subclass."
|
||||
)
|
||||
@@ -131,7 +131,7 @@ class BaseSymbolTableInterface:
|
||||
|
||||
@property
|
||||
def types(self) -> Iterable[str]:
|
||||
"""Returns an iterator of the Symbol type names."""
|
||||
"""Returns an iterable of the available symbol type names."""
|
||||
raise NotImplementedError(
|
||||
"Abstract property types not implemented by subclass."
|
||||
)
|
||||
@@ -149,7 +149,7 @@ class BaseSymbolTableInterface:
|
||||
|
||||
@property
|
||||
def enumerations(self) -> Iterable[Any]:
|
||||
"""Returns an iterator of the Enumeration names."""
|
||||
"""Returns an iterable of the available enumerations."""
|
||||
raise NotImplementedError(
|
||||
"Abstract property enumerations not implemented by subclass."
|
||||
)
|
||||
@@ -250,13 +250,13 @@ class BaseSymbolTableInterface:
|
||||
|
||||
def clear_symbol_cache(self) -> None:
|
||||
"""Clears the symbol cache of this symbol table."""
|
||||
pass
|
||||
|
||||
|
||||
class SymbolSpaceInterface(collections.abc.Mapping):
|
||||
"""An interface for the container that holds all the symbol-containing
|
||||
tables for use within a context."""
|
||||
|
||||
@abstractmethod
|
||||
def free_table_name(self, prefix: str = "layer") -> str:
|
||||
"""Returns an unused table name to ensure no collision occurs when
|
||||
inserting a symbol table."""
|
||||
@@ -347,7 +347,7 @@ class SymbolTableInterface(
|
||||
return config
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[RequirementInterface]:
|
||||
def get_requirements(cls) -> List[configuration.RequirementInterface]:
|
||||
return super().get_requirements() + [
|
||||
requirements.IntRequirement(
|
||||
name="symbol_mask",
|
||||
@@ -366,6 +366,7 @@ class NativeTableInterface(BaseSymbolTableInterface):
|
||||
|
||||
@property
|
||||
def symbols(self) -> Iterable[str]:
|
||||
"""Returns an iterable of the available symbol names."""
|
||||
return []
|
||||
|
||||
def get_enumeration(self, name: str) -> objects.Template:
|
||||
@@ -374,11 +375,17 @@ class NativeTableInterface(BaseSymbolTableInterface):
|
||||
)
|
||||
|
||||
@property
|
||||
def enumerations(self) -> Iterable[str]:
|
||||
def enumerations(self) -> Iterable[Any]:
|
||||
"""Returns an iterable of the available enumerations."""
|
||||
return []
|
||||
|
||||
@property
|
||||
def types(self) -> Iterable[str]:
|
||||
"""Returns an iterable of the available symbol type names."""
|
||||
return []
|
||||
|
||||
|
||||
class MetadataInterface(object):
|
||||
class MetadataInterface:
|
||||
"""Interface for accessing metadata stored within a symbol table."""
|
||||
|
||||
def __init__(self, json_data: Dict) -> None:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user