mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-06 09:47:38 +02:00
Merge branch 'develop' into linux_vmcoreinfo_aslr_and_plugin
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
name: build-pyinstaller
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- stable
|
||||
- develop
|
||||
- 'release/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- stable
|
||||
- 'release/**'
|
||||
|
||||
jobs:
|
||||
|
||||
exe:
|
||||
runs-on: windows-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.11"]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- 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
|
||||
|
||||
- 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
|
||||
@@ -42,8 +42,13 @@ jobs:
|
||||
|
||||
- name: Testing...
|
||||
run: |
|
||||
pytest ./test/test_volatility.py --volatility=vol.py --image-dir=./test_images -k test_windows -v
|
||||
pytest ./test/test_volatility.py --volatility=vol.py --image-dir=./test_images -k test_linux -v
|
||||
# VolShell
|
||||
pytest ./test/test_volatility.py --volatility=volshell.py --image-dir=./test_images -k test_windows_volshell -v
|
||||
pytest ./test/test_volatility.py --volatility=volshell.py --image-dir=./test_images -k test_linux_volshell -v
|
||||
|
||||
# Volatility
|
||||
pytest ./test/test_volatility.py --volatility=vol.py --image-dir=./test_images -k "test_windows and not test_windows_volshell" -v
|
||||
pytest ./test/test_volatility.py --volatility=vol.py --image-dir=./test_images -k "test_linux and not test_linux_volshell" -v
|
||||
|
||||
- name: Clean up post-test
|
||||
run: |
|
||||
|
||||
@@ -88,7 +88,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
|
||||
|
||||
|
||||
+1
-1
@@ -167,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
|
||||
|
||||
+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.PluginRequirement(
|
||||
name = 'pslist',
|
||||
plugin = 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
|
||||
@@ -180,16 +200,24 @@ that will be output as part of the :py:class:`~volatility3.framework.interfaces.
|
||||
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(
|
||||
self.context,
|
||||
kernel.layer_name,
|
||||
kernel.symbol_table_name,
|
||||
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
|
||||
@@ -281,5 +309,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.
|
||||
|
||||
|
||||
|
||||
+57
-4
@@ -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
|
||||
|
||||
::
|
||||
|
||||
@@ -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.
|
||||
|
||||
+26
-21
@@ -1,7 +1,15 @@
|
||||
[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" },
|
||||
@@ -10,9 +18,7 @@ requires-python = ">=3.8.0"
|
||||
license = { text = "VSL" }
|
||||
dynamic = ["version"]
|
||||
|
||||
dependencies = [
|
||||
"pefile>=2024.8.26",
|
||||
]
|
||||
dependencies = ["pefile>=2024.8.26"]
|
||||
|
||||
[project.optional-dependencies]
|
||||
full = [
|
||||
@@ -20,18 +26,20 @@ full = [
|
||||
"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",
|
||||
]
|
||||
cloud = ["gcsfs>=2024.10.0", "s3fs>=2024.10.0"]
|
||||
|
||||
dev = [
|
||||
"volatility3[full,cloud]",
|
||||
"jsonschema>=4.23.0,<5",
|
||||
"pyinstaller>=6.11.0,<7",
|
||||
"pyinstaller>=6.5.0,<7",
|
||||
"pyinstaller-hooks-contrib>=2024.9",
|
||||
"types-jsonschema>=4.23.0,<5",
|
||||
]
|
||||
|
||||
test = [
|
||||
@@ -43,8 +51,8 @@ test = [
|
||||
|
||||
docs = [
|
||||
"volatility3[dev]",
|
||||
"sphinx>=8.0.0,<7",
|
||||
"sphinx-autodoc-typehints>=2.5.0,<3",
|
||||
"sphinx>=4.0.0,<9",
|
||||
"sphinx-autodoc-typehints>=2.0.0,<3",
|
||||
"sphinx-rtd-theme>=3.0.1,<4",
|
||||
]
|
||||
|
||||
@@ -68,25 +76,22 @@ include = ["volatility3*"]
|
||||
mypy_path = "./stubs"
|
||||
show_traceback = true
|
||||
|
||||
[tool.mypy.overrides]
|
||||
ignore_missing_imports = 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
|
||||
"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
|
||||
"E501", # ignore due to conflict with formatter
|
||||
]
|
||||
|
||||
[build-system]
|
||||
|
||||
+86
-7
@@ -39,7 +39,9 @@ def runvol(args, volatility, python):
|
||||
return p.returncode, stdout, stderr
|
||||
|
||||
|
||||
def runvol_plugin(plugin, img, volatility, python, pluginargs=[], globalargs=[]):
|
||||
def runvol_plugin(plugin, img, volatility, python, pluginargs=None, globalargs=None):
|
||||
pluginargs = pluginargs or []
|
||||
globalargs = globalargs or []
|
||||
args = (
|
||||
globalargs
|
||||
+ [
|
||||
@@ -54,13 +56,68 @@ def runvol_plugin(plugin, img, volatility, python, pluginargs=[], globalargs=[])
|
||||
return runvol(args, volatility, python)
|
||||
|
||||
|
||||
def runvolshell(img, volshell, python, volshellargs=None, globalargs=None):
|
||||
volshellargs = volshellargs or []
|
||||
globalargs = globalargs or []
|
||||
args = (
|
||||
globalargs
|
||||
+ [
|
||||
"--single-location",
|
||||
img,
|
||||
"-q",
|
||||
]
|
||||
+ volshellargs
|
||||
)
|
||||
|
||||
return runvol(args, volshell, python)
|
||||
|
||||
|
||||
#
|
||||
# TESTS
|
||||
#
|
||||
|
||||
|
||||
def basic_volshell_test(image, volatility, python, globalargs):
|
||||
# Basic VolShell test to verify requirements and ensure VolShell runs without crashing
|
||||
|
||||
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:
|
||||
volshell_script = "\n".join(volshell_commands)
|
||||
with os.fdopen(fd, "w") as f:
|
||||
f.write(volshell_script)
|
||||
|
||||
rc, out, _err = runvolshell(
|
||||
img=image,
|
||||
volshell=volatility,
|
||||
python=python,
|
||||
volshellargs=["--script", filename],
|
||||
globalargs=globalargs,
|
||||
)
|
||||
finally:
|
||||
with contextlib.suppress(FileNotFoundError):
|
||||
os.remove(filename)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
return out
|
||||
|
||||
|
||||
# WINDOWS
|
||||
|
||||
|
||||
def test_windows_volshell(image, volatility, python):
|
||||
out = basic_volshell_test(image, volatility, python, globalargs=["-w"])
|
||||
assert out.count(b"<EPROCESS") > 40
|
||||
|
||||
|
||||
def test_windows_pslist(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin("windows.pslist.PsList", image, volatility, python)
|
||||
out = out.lower()
|
||||
@@ -332,6 +389,11 @@ def test_windows_vadyarascan_yara_string(image, volatility, python):
|
||||
# LINUX
|
||||
|
||||
|
||||
def test_linux_volshell(image, volatility, python):
|
||||
out = basic_volshell_test(image, volatility, python, globalargs=["-l"])
|
||||
assert out.count(b"<task_struct") > 100
|
||||
|
||||
|
||||
def test_linux_pslist(image, volatility, python):
|
||||
rc, out, _err = runvol_plugin("linux.pslist.PsList", image, volatility, python)
|
||||
|
||||
@@ -646,6 +708,24 @@ def test_linux_page_cache_inodepages(image, volatility, python):
|
||||
|
||||
inode_address = hex(0x88001AB5C270)
|
||||
inode_dump_filename = f"inode_{inode_address}.dmp"
|
||||
|
||||
rc, out, _err = 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 = runvol_plugin(
|
||||
"linux.pagecache.InodePages",
|
||||
@@ -656,13 +736,8 @@ def test_linux_page_cache_inodepages(image, volatility, python):
|
||||
)
|
||||
|
||||
assert rc == 0
|
||||
assert out.count(b"\n") > 4
|
||||
assert out.count(b"\n") >= 4
|
||||
|
||||
# PageVAddr PagePAddr MappingAddr .. DumpSafe
|
||||
assert re.search(
|
||||
rb"0xea000054c5f8\s0x18389000\s0x88001ab5c3b0.*?True",
|
||||
out,
|
||||
)
|
||||
assert os.path.exists(inode_dump_filename)
|
||||
with open(inode_dump_filename, "rb") as fp:
|
||||
inode_contents = fp.read()
|
||||
@@ -770,6 +845,10 @@ def test_linux_hidden_modules(image, volatility, python):
|
||||
# MAC
|
||||
|
||||
|
||||
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)
|
||||
out = out.lower()
|
||||
|
||||
+27
-12
@@ -19,7 +19,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:
|
||||
@@ -64,7 +64,7 @@ class PrintedProgress:
|
||||
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,7 +81,7 @@ class PrintedProgress:
|
||||
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
|
||||
|
||||
|
||||
@@ -363,10 +363,21 @@ class CommandLine:
|
||||
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])
|
||||
|
||||
@@ -572,6 +583,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):
|
||||
@@ -616,9 +629,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}"
|
||||
@@ -629,13 +640,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
|
||||
|
||||
@@ -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,14 +68,14 @@ 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 OSError:
|
||||
return False
|
||||
|
||||
@@ -240,7 +240,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 +250,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 +262,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:
|
||||
@@ -347,7 +347,7 @@ class Volshell(interfaces.plugins.PluginInterface):
|
||||
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 not isinstance(
|
||||
@@ -479,7 +479,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")
|
||||
@@ -553,17 +553,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)
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -20,7 +31,7 @@ class Volshell(generic.Volshell):
|
||||
name="kernel", description="Linux kernel module"
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
|
||||
name="pslist", plugin=pslist.PsList, version=(4, 0, 0)
|
||||
),
|
||||
requirements.IntRequirement(
|
||||
name="pid", description="Process ID", optional=True
|
||||
@@ -40,6 +51,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 +126,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 +138,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 +146,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
|
||||
@@ -63,7 +63,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 +71,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
|
||||
@@ -44,11 +44,67 @@ class Volshell(generic.Volshell):
|
||||
)
|
||||
)
|
||||
|
||||
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 +116,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 +124,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
|
||||
|
||||
@@ -5,17 +5,30 @@
|
||||
# Check the python version to ensure it's suitable
|
||||
import glob
|
||||
import sys
|
||||
from volatility3.framework import check_python_version as check_python_version
|
||||
import zipfile
|
||||
import importlib
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
import traceback
|
||||
from typing import Any, Dict, Generator, List, Tuple, Type, TypeVar
|
||||
import functools
|
||||
import warnings
|
||||
from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Type, TypeVar
|
||||
|
||||
from volatility3.framework import constants, interfaces
|
||||
from volatility3.framework import constants, exceptions, interfaces
|
||||
from volatility3.framework.configuration import requirements
|
||||
|
||||
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,12 +66,67 @@ def require_interface_version(*args) -> None:
|
||||
)
|
||||
|
||||
|
||||
class Deprecation:
|
||||
"""Deprecation related methods."""
|
||||
|
||||
@staticmethod
|
||||
def deprecated_method(
|
||||
replacement: Callable,
|
||||
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)
|
||||
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 requirements.VersionRequirement.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, 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
|
||||
|
||||
|
||||
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:
|
||||
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)
|
||||
@@ -185,8 +253,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]]:
|
||||
|
||||
@@ -76,6 +76,11 @@ class LinuxIntelStacker(interfaces.automagic.StackerLayerInterface):
|
||||
elif "init_level4_pgt" in table.symbols:
|
||||
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"
|
||||
|
||||
@@ -376,8 +376,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, 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,
|
||||
|
||||
@@ -166,7 +166,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"""
|
||||
|
||||
@@ -292,6 +301,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
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import sys
|
||||
|
||||
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(
|
||||
f"Volatility framework requires python version {required_python_version[0]}.{required_python_version[1]}.{required_python_version[2]} or greater"
|
||||
)
|
||||
@@ -11,7 +11,7 @@ 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
|
||||
@@ -314,11 +314,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,18 +526,18 @@ 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"
|
||||
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
|
||||
self._version = version
|
||||
|
||||
@@ -546,7 +546,7 @@ 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
|
||||
@@ -580,7 +580,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)] = (
|
||||
@@ -604,10 +604,10 @@ 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 +627,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,
|
||||
|
||||
@@ -23,6 +23,8 @@ from volatility3.framework.constants._version import (
|
||||
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")),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# We use the SemVer 2.0.0 versioning scheme
|
||||
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
|
||||
VERSION_MINOR = 13 # Number of changes that only add to the interface
|
||||
VERSION_MINOR = 19 # Number of changes that only add to the interface
|
||||
VERSION_PATCH = 0 # Number of changes that do not change the interface
|
||||
VERSION_SUFFIX = ""
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
Linux-specific values that aren't found in debug symbols
|
||||
"""
|
||||
from enum import IntEnum, Flag
|
||||
from dataclasses import dataclass
|
||||
|
||||
KERNEL_NAME = "__kernel__"
|
||||
|
||||
@@ -358,3 +359,57 @@ 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="
|
||||
|
||||
|
||||
@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
|
||||
"""
|
||||
|
||||
@@ -10,3 +10,21 @@ 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
|
||||
|
||||
@@ -11,7 +11,8 @@ 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
|
||||
@@ -229,7 +230,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,
|
||||
@@ -337,7 +338,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")
|
||||
@@ -386,10 +387,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 +401,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
|
||||
|
||||
@@ -8,9 +8,10 @@ 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
|
||||
from volatility3.framework.interfaces.configuration import VersionableInterface
|
||||
|
||||
|
||||
class VolatilityException(Exception):
|
||||
@@ -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: 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 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."
|
||||
|
||||
@@ -42,7 +42,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:
|
||||
"""
|
||||
@@ -328,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:
|
||||
@@ -618,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__"""
|
||||
@@ -652,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",
|
||||
@@ -771,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"
|
||||
)
|
||||
|
||||
@@ -85,7 +85,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 +114,7 @@ class ContextInterface(metaclass=ABCMeta):
|
||||
"""
|
||||
return copy.deepcopy(self)
|
||||
|
||||
@abstractmethod
|
||||
def module(
|
||||
self,
|
||||
module_name: str,
|
||||
@@ -232,7 +233,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,
|
||||
@@ -277,27 +278,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 +354,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."""
|
||||
|
||||
@@ -210,7 +210,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.
|
||||
|
||||
|
||||
@@ -374,6 +374,7 @@ class Template:
|
||||
f"{self.__class__.__name__} object has no attribute {attr}"
|
||||
)
|
||||
|
||||
@abc.abstractmethod
|
||||
def __call__(
|
||||
self,
|
||||
context: "interfaces.context.ContextInterface",
|
||||
|
||||
@@ -183,7 +183,7 @@ class TreeGrid(metaclass=ABCMeta):
|
||||
@abstractmethod
|
||||
def populate(
|
||||
self,
|
||||
function: VisitorSignature = None,
|
||||
function: Optional[VisitorSignature] = None,
|
||||
initial_accumulator: Any = None,
|
||||
fail_on_errors: bool = True,
|
||||
) -> Optional[Exception]:
|
||||
@@ -235,7 +235,7 @@ class TreeGrid(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.
|
||||
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
@@ -256,6 +256,7 @@ 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."""
|
||||
@@ -365,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:
|
||||
@@ -373,7 +375,13 @@ 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 []
|
||||
|
||||
|
||||
|
||||
@@ -129,6 +129,8 @@ if HAS_LEECHCORE:
|
||||
|
||||
def readline(self, __size: Optional[int] = ...) -> bytes:
|
||||
data = b""
|
||||
if not __size:
|
||||
__size = 0
|
||||
while __size > self._chunk_size or __size < 0:
|
||||
data += self.read(self._chunk_size)
|
||||
index = data.find(b"\n")
|
||||
|
||||
@@ -194,7 +194,7 @@ class PdbMSFStream(linear.LinearlyMappedLayer):
|
||||
) -> None:
|
||||
super().__init__(context, config_path, name, metadata)
|
||||
self._base_layer = self.config["base_layer"]
|
||||
self._pages = self.config.get("pages", None)
|
||||
self._pages = self.config.get("pages", [])
|
||||
self._pages_len = len(self._pages)
|
||||
if not self._pages:
|
||||
raise PDBFormatException(name, "Invalid/no pages specified")
|
||||
|
||||
@@ -140,7 +140,13 @@ class RegistryHive(linear.LinearlyMappedLayer):
|
||||
"""Returns the appropriate Node, interpreted from the Cell based on its
|
||||
Signature."""
|
||||
cell = self.get_cell(cell_offset)
|
||||
signature = cell.cast("string", max_length=2, encoding="latin-1")
|
||||
try:
|
||||
signature = cell.cast("string", max_length=2, encoding="latin-1")
|
||||
except (RegistryInvalidIndex, exceptions.InvalidAddressException):
|
||||
vollog.debug(
|
||||
f"Failed to get cell signature for cell (0x{cell.vol.offset:x})"
|
||||
)
|
||||
return cell
|
||||
if signature == "nk":
|
||||
return cell.u.KeyNode
|
||||
elif signature == "sk":
|
||||
@@ -186,9 +192,9 @@ class RegistryHive(linear.LinearlyMappedLayer):
|
||||
while key_array and node_key:
|
||||
subkeys = node_key[-1].get_subkeys()
|
||||
for subkey in subkeys:
|
||||
# registry keys are not case sensitive so compare lowercase
|
||||
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms724946(v=vs.85).aspx
|
||||
if subkey.get_name().lower() == key_array[0].lower():
|
||||
# registry keys are not case sensitive so compare likewise
|
||||
# https://learn.microsoft.com/en-us/windows/win32/sysinfo/structure-of-the-registry
|
||||
if subkey.get_name().casefold() == key_array[0].casefold():
|
||||
node_key = node_key + [subkey]
|
||||
found_key, key_array = found_key + [key_array[0]], key_array[1:]
|
||||
break
|
||||
|
||||
@@ -72,7 +72,7 @@ class MultiStringScanner(layers.ScannerInterface):
|
||||
return None
|
||||
|
||||
for char in value:
|
||||
trie[char] = trie.get(char, {})
|
||||
trie.setdefault(char, {})
|
||||
trie = trie[char]
|
||||
|
||||
# Mark the end of a string
|
||||
|
||||
@@ -57,6 +57,10 @@ class VmwareLayer(segmented.SegmentedLayer):
|
||||
)
|
||||
|
||||
meta_layer = self.context.layers.get(self._meta_layer, None)
|
||||
if meta_layer is None:
|
||||
raise exceptions.LayerException(
|
||||
self._meta_layer, "VMware: Meta layer not found"
|
||||
)
|
||||
header_size = struct.calcsize(self.header_structure)
|
||||
data = meta_layer.read(0, header_size)
|
||||
magic, unknown, groupCount = struct.unpack(self.header_structure, data)
|
||||
|
||||
@@ -54,6 +54,7 @@ class XenCoreDumpLayer(elf.Elf64Layer):
|
||||
|
||||
segments = []
|
||||
self._segment_headers = []
|
||||
segment_names = None
|
||||
|
||||
for sindex in range(ehdr.e_shnum):
|
||||
shdr = self.context.object(
|
||||
|
||||
@@ -152,7 +152,7 @@ class PrimitiveObject(interfaces.objects.ObjectInterface):
|
||||
type_name: str,
|
||||
object_info: interfaces.objects.ObjectInformation,
|
||||
data_format: DataFormatInfo,
|
||||
new_value: TUnion[int, float, bool, bytes, str] = None,
|
||||
new_value: Optional[TUnion[int, float, bool, bytes, str]] = None,
|
||||
**kwargs,
|
||||
) -> "PrimitiveObject":
|
||||
"""Creates the appropriate class and returns it so that the native type
|
||||
@@ -601,7 +601,7 @@ class Enumeration(interfaces.objects.ObjectInterface, int):
|
||||
inverse_choices[v] = k
|
||||
return inverse_choices
|
||||
|
||||
def lookup(self, value: int = None) -> str:
|
||||
def lookup(self, value: Optional[int] = None) -> str:
|
||||
"""Looks up an individual value and returns the associated name.
|
||||
|
||||
If multiple identifiers map to the same value, the first matching identifier will be returned
|
||||
@@ -690,7 +690,7 @@ class Array(interfaces.objects.ObjectInterface, collections.abc.Sequence):
|
||||
type_name: str,
|
||||
object_info: interfaces.objects.ObjectInformation,
|
||||
count: int = 0,
|
||||
subtype: templates.ObjectTemplate = None,
|
||||
subtype: Optional[templates.ObjectTemplate] = None,
|
||||
) -> None:
|
||||
super().__init__(context=context, type_name=type_name, object_info=object_info)
|
||||
self._vol["count"] = count
|
||||
|
||||
@@ -33,11 +33,12 @@ def array_to_string(
|
||||
) -> interfaces.objects.ObjectInterface:
|
||||
"""Takes a volatility Array of characters and returns a string."""
|
||||
# TODO: Consider checking the Array's target is a native char
|
||||
if count is None:
|
||||
count = array.vol.count
|
||||
if not isinstance(array, objects.Array):
|
||||
raise TypeError("Array_to_string takes an Array of char")
|
||||
|
||||
if count is None:
|
||||
count = array.vol.count
|
||||
|
||||
return array.cast("string", max_length=count, errors=errors)
|
||||
|
||||
|
||||
@@ -45,8 +46,10 @@ def pointer_to_string(pointer: "objects.Pointer", count: int, errors: str = "rep
|
||||
"""Takes a volatility Pointer to characters and returns a string."""
|
||||
if not isinstance(pointer, objects.Pointer):
|
||||
raise TypeError("pointer_to_string takes a Pointer")
|
||||
|
||||
if count < 1:
|
||||
raise ValueError("pointer_to_string requires a positive count")
|
||||
|
||||
char = pointer.dereference()
|
||||
return char.cast("string", max_length=count, errors=errors)
|
||||
|
||||
|
||||
@@ -14,8 +14,8 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ConfigWriter(plugins.PluginInterface):
|
||||
"""Runs the automagics and both prints and outputs configuration in the
|
||||
output directory."""
|
||||
"""Runs the automagics and both prints and outputs configuration in the \
|
||||
output directory."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
|
||||
@@ -132,6 +132,7 @@ class IsfInfo(plugins.PluginInterface):
|
||||
valid = check_valid(data)
|
||||
except (UnicodeDecodeError, json.decoder.JSONDecodeError):
|
||||
vollog.warning(f"Invalid ISF: {entry}")
|
||||
continue
|
||||
yield (
|
||||
0,
|
||||
(
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 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
|
||||
#
|
||||
"""A module containing a collection of plugins that produce data typically
|
||||
found in Linux's /proc file system."""
|
||||
"""A module containing a plugin that recovers bash command history
|
||||
from bash process memory."""
|
||||
|
||||
import datetime
|
||||
import struct
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 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
|
||||
#
|
||||
"""A module containing a collection of plugins that produce data typically
|
||||
found in Linux's /proc file system."""
|
||||
"""A module containing a plugin that verifies the operation function
|
||||
pointers of network protocols."""
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
|
||||
from volatility3.framework import interfaces, renderers, symbols
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.renderers import format_hints
|
||||
@@ -27,6 +28,11 @@ class Check_idt(interfaces.plugins.PluginInterface):
|
||||
description="Linux kernel",
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="linux_utilities_modules",
|
||||
component=linux_utilities_modules.Modules,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0)
|
||||
),
|
||||
@@ -99,8 +105,10 @@ class Check_idt(interfaces.plugins.PluginInterface):
|
||||
|
||||
idt_addr = idt_addr & address_mask
|
||||
|
||||
module_name, symbol_name = linux.LinuxUtilities.lookup_module_address(
|
||||
vmlinux, handlers, idt_addr
|
||||
module_name, symbol_name = (
|
||||
linux_utilities_modules.Modules.lookup_module_address(
|
||||
self.context, vmlinux.name, handlers, idt_addr
|
||||
)
|
||||
)
|
||||
|
||||
yield (
|
||||
|
||||
@@ -18,6 +18,7 @@ vollog = logging.getLogger(__name__)
|
||||
class Check_modules(plugins.PluginInterface):
|
||||
"""Compares module list to sysfs info, if available"""
|
||||
|
||||
_version = (1, 0, 0)
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# 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
|
||||
#
|
||||
"""A module containing a collection of plugins that produce data typically
|
||||
found in Linux's /proc file system."""
|
||||
"""A module containing a plugin that checks the system call table for hooks."""
|
||||
import contextlib
|
||||
import logging
|
||||
from typing import List
|
||||
@@ -83,7 +82,7 @@ class Check_syscall(plugins.PluginInterface):
|
||||
|
||||
return table_size
|
||||
|
||||
def _get_table_info_disassembly(self, ptr_sz, vmlinux):
|
||||
def _get_table_info_disassembly(self, ptr_sz, vmlinux) -> int:
|
||||
"""Find the size of the system call table by disassembling functions
|
||||
that immediately reference it in their first instruction This is in the
|
||||
form 'cmp reg,NR_syscalls'."""
|
||||
@@ -108,9 +107,13 @@ class Check_syscall(plugins.PluginInterface):
|
||||
return 0
|
||||
|
||||
vmlinux = self.context.modules[self.config["kernel"]]
|
||||
data = self.context.layers.read(vmlinux.layer_name, func_addr, 6)
|
||||
vmlinux_layer = self.context.layers[vmlinux.layer_name]
|
||||
try:
|
||||
data = vmlinux_layer.read(func_addr, 6)
|
||||
except exceptions.InvalidAddressException:
|
||||
return 0
|
||||
|
||||
for address, size, mnemonic, op_str in md.disasm_lite(data, func_addr):
|
||||
for _address, _size, mnemonic, op_str in md.disasm_lite(data, func_addr):
|
||||
if mnemonic == "CMP":
|
||||
table_size = int(op_str.split(",")[1].strip()) & 0xFFFF
|
||||
break
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# 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
|
||||
#
|
||||
"""A module containing a collection of plugins that produce data typically
|
||||
found in Linux's /proc file system."""
|
||||
"""A module containing a plugin for enumerating memory-mapped
|
||||
ELF files across all processes."""
|
||||
|
||||
import logging
|
||||
from typing import List, Optional, Type
|
||||
|
||||
@@ -18,7 +18,7 @@ class Envars(plugins.PluginInterface):
|
||||
"""Lists processes with their environment variables"""
|
||||
|
||||
_required_framework_version = (2, 13, 0)
|
||||
_version = (2, 0, 0)
|
||||
_version = (2, 0, 1)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -40,8 +40,9 @@ class Envars(plugins.PluginInterface):
|
||||
),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def get_task_env_variables(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
task: interfaces.objects.ObjectInterface,
|
||||
env_area_max_size: int = 8192,
|
||||
|
||||
@@ -0,0 +1,334 @@
|
||||
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
import logging
|
||||
import io
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Type, List, Dict, Tuple
|
||||
from volatility3.framework import constants, exceptions, interfaces
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.renderers import (
|
||||
format_hints,
|
||||
TreeGrid,
|
||||
NotAvailableValue,
|
||||
UnreadableValue,
|
||||
)
|
||||
from volatility3.framework.objects import utility
|
||||
from volatility3.framework.constants import architectures
|
||||
from volatility3.framework.symbols import linux
|
||||
|
||||
# Image manipulation functions are kept in the plugin,
|
||||
# to prevent a general exit on missing PIL (pillow) dependency.
|
||||
try:
|
||||
from PIL import Image
|
||||
|
||||
has_pil = True
|
||||
except ImportError:
|
||||
has_pil = False
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Framebuffer:
|
||||
"""Framebuffer object internal representation. This is useful to unify a framebuffer with precalculated
|
||||
properties and pass it through functions conveniently."""
|
||||
|
||||
id: str
|
||||
xres_virtual: int
|
||||
yres_virtual: int
|
||||
line_length: int
|
||||
bpp: int
|
||||
"""Bits Per Pixel"""
|
||||
size: int
|
||||
color_fields: Dict[str, Tuple[int, int, int]]
|
||||
fb_info: interfaces.objects.ObjectInterface
|
||||
|
||||
|
||||
class Fbdev(interfaces.plugins.PluginInterface):
|
||||
"""Extract framebuffers from the fbdev graphics subsystem"""
|
||||
|
||||
_version = (1, 0, 0)
|
||||
_required_framework_version = (2, 11, 0)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
return [
|
||||
requirements.ModuleRequirement(
|
||||
name="kernel",
|
||||
description="Linux kernel",
|
||||
architectures=architectures.LINUX_ARCHS,
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="linuxutils", component=linux.LinuxUtilities, version=(2, 2, 0)
|
||||
),
|
||||
requirements.BooleanRequirement(
|
||||
name="dump",
|
||||
description="Dump framebuffers",
|
||||
default=False,
|
||||
optional=True,
|
||||
),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def parse_fb_pixel_bitfields(
|
||||
cls, fb_var_screeninfo: interfaces.objects.ObjectInterface
|
||||
) -> Dict[str, Tuple[int, int, int]]:
|
||||
"""Organize a framebuffer pixel format into a dictionary.
|
||||
This is needed to know the position and bitlength of a color inside
|
||||
a pixel.
|
||||
|
||||
Args:
|
||||
fb_var_screeninfo: a fb_var_screeninfo kernel object instance
|
||||
|
||||
Returns:
|
||||
The color fields mappings
|
||||
|
||||
Documentation:
|
||||
include/uapi/linux/fb.h:
|
||||
struct fb_bitfield {
|
||||
__u32 offset; /* beginning of bitfield */
|
||||
__u32 length; /* length of bitfield */
|
||||
__u32 msb_right; /* != 0 : Most significant bit is right */
|
||||
};
|
||||
"""
|
||||
# Naturally order by RGBA
|
||||
color_mappings = [
|
||||
("R", fb_var_screeninfo.red),
|
||||
("G", fb_var_screeninfo.green),
|
||||
("B", fb_var_screeninfo.blue),
|
||||
("A", fb_var_screeninfo.transp),
|
||||
]
|
||||
color_fields = {}
|
||||
for color_code, fb_bitfield in color_mappings:
|
||||
color_fields[color_code] = (
|
||||
int(fb_bitfield.offset),
|
||||
int(fb_bitfield.length),
|
||||
int(fb_bitfield.msb_right),
|
||||
)
|
||||
return color_fields
|
||||
|
||||
@classmethod
|
||||
def convert_fb_raw_buffer_to_image(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
kernel_name: str,
|
||||
fb: Framebuffer,
|
||||
):
|
||||
"""Convert raw framebuffer pixels to an image.
|
||||
|
||||
Args:
|
||||
fb: the relevant Framebuffer object
|
||||
|
||||
Returns:
|
||||
A PIL Image object
|
||||
|
||||
Documentation:
|
||||
include/uapi/linux/fb.h:
|
||||
/* Interpretation of offset for color fields: All offsets are from the right,
|
||||
* inside a "pixel" value, which is exactly 'bits_per_pixel' wide (means: you
|
||||
* can use the offset as right argument to <<). A pixel afterwards is a bit
|
||||
* stream and is written to video memory as that unmodified.
|
||||
"""
|
||||
kernel = context.modules[kernel_name]
|
||||
kernel_layer = context.layers[kernel.layer_name]
|
||||
|
||||
raw_pixels = io.BytesIO(kernel_layer.read(fb.fb_info.screen_base, fb.size))
|
||||
bytes_per_pixel = fb.bpp // 8
|
||||
image = Image.new("RGBA", (fb.xres_virtual, fb.yres_virtual))
|
||||
|
||||
# This is not designed to be extremely fast (numpy isn't available),
|
||||
# but convenient and dynamic for any color field layout.
|
||||
for y in range(fb.yres_virtual):
|
||||
for x in range(fb.xres_virtual):
|
||||
raw_pixel = int.from_bytes(raw_pixels.read(bytes_per_pixel), "little")
|
||||
pixel = [0, 0, 0, 255]
|
||||
# The framebuffer is expected to have been correctly constructed,
|
||||
# especially by parse_fb_pixel_bitfields, to get the needed RGBA mappings.
|
||||
for i, color_code in enumerate(["R", "G", "B", "A"]):
|
||||
offset, length, msb_right = fb.color_fields[color_code]
|
||||
if length == 0:
|
||||
continue
|
||||
color_value = (raw_pixel >> offset) & (2**length - 1)
|
||||
if msb_right:
|
||||
# Reverse bit order
|
||||
color_value = int(
|
||||
"{:0{length}b}".format(color_value, length=length)[::-1], 2
|
||||
)
|
||||
pixel[i] = color_value
|
||||
image.putpixel((x, y), tuple(pixel))
|
||||
|
||||
return image
|
||||
|
||||
@classmethod
|
||||
def dump_fb(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
kernel_name: str,
|
||||
open_method: Type[interfaces.plugins.FileHandlerInterface],
|
||||
fb: Framebuffer,
|
||||
convert_to_png_image: bool,
|
||||
) -> str:
|
||||
"""Dump a Framebuffer buffer to disk.
|
||||
|
||||
Args:
|
||||
fb: the relevant Framebuffer object
|
||||
convert_to_image: a boolean specifying if the buffer should be converted to an image
|
||||
|
||||
Returns:
|
||||
The filename of the dumped buffer.
|
||||
"""
|
||||
kernel = context.modules[kernel_name]
|
||||
kernel_layer = context.layers[kernel.layer_name]
|
||||
id = "N-A" if isinstance(fb.id, NotAvailableValue) else fb.id
|
||||
base_filename = f"{id}_{fb.xres_virtual}x{fb.yres_virtual}_{fb.bpp}bpp"
|
||||
if convert_to_png_image:
|
||||
image_object = cls.convert_fb_raw_buffer_to_image(context, kernel_name, fb)
|
||||
raw_io_output = io.BytesIO()
|
||||
image_object.save(raw_io_output, "PNG")
|
||||
final_fb_buffer = raw_io_output.getvalue()
|
||||
filename = f"{base_filename}.png"
|
||||
else:
|
||||
final_fb_buffer = kernel_layer.read(fb.fb_info.screen_base, fb.size)
|
||||
filename = f"{base_filename}.raw"
|
||||
|
||||
with open_method(filename) as f:
|
||||
f.write(final_fb_buffer)
|
||||
return f.preferred_filename
|
||||
|
||||
@classmethod
|
||||
def parse_fb_info(
|
||||
cls,
|
||||
fb_info: interfaces.objects.ObjectInterface,
|
||||
) -> Framebuffer:
|
||||
"""Parse an fb_info struct
|
||||
Args:
|
||||
fb_info: an fb_info kernel object live instance
|
||||
|
||||
Returns:
|
||||
A Framebuffer object
|
||||
|
||||
Documentation:
|
||||
https://docs.kernel.org/fb/api.html:
|
||||
- struct fb_fix_screeninfo stores device independent unchangeable information about the frame buffer device and the current format.
|
||||
Those information can't be directly modified by applications, but can be changed by the driver when an application modifies the format.
|
||||
- struct fb_var_screeninfo stores device independent changeable information about a frame buffer device, its current format and video mode,
|
||||
as well as other miscellaneous parameters.
|
||||
"""
|
||||
id = utility.array_to_string(fb_info.fix.id) or NotAvailableValue()
|
||||
color_fields = None
|
||||
|
||||
# 0 = color, 1 = grayscale, >1 = FOURCC
|
||||
if fb_info.var.grayscale in [0, 1]:
|
||||
color_fields = cls.parse_fb_pixel_bitfields(fb_info.var)
|
||||
|
||||
# There a lot of tricky pixel formats used by drivers and vendors in include/uapi/linux/videodev2.h.
|
||||
# As Volatility3 is not a video format converter, it is best to play it safe and let the user parse
|
||||
# the raw data manually (with ffmpeg for example).
|
||||
elif fb_info.var.grayscale > 1:
|
||||
fourcc = linux.LinuxUtilities.convert_fourcc_code(fb_info.var.grayscale)
|
||||
warn_msg = f"""Framebuffer "{id}" uses a FOURCC pixel format "{fourcc}" that isn't natively supported.
|
||||
You can try using ffmpeg to decode the raw buffer. Example usage:
|
||||
"ffmpeg -pix_fmts" to list supported formats, then
|
||||
"ffmpeg -f rawvideo -video_size {fb_info.var.xres_virtual}x{fb_info.var.yres_virtual} -i <FILENAME>.raw -pix_fmt <FORMAT> output.png"."""
|
||||
vollog.warning(warn_msg)
|
||||
|
||||
# Prefer using the virtual resolution, instead of the visible one.
|
||||
# This prevents missing non-visible data stored in the framebuffer.
|
||||
fb = Framebuffer(
|
||||
id,
|
||||
xres_virtual=fb_info.var.xres_virtual,
|
||||
yres_virtual=fb_info.var.yres_virtual,
|
||||
line_length=fb_info.fix.line_length,
|
||||
bpp=fb_info.var.bits_per_pixel,
|
||||
size=fb_info.var.yres_virtual * fb_info.fix.line_length,
|
||||
color_fields=color_fields,
|
||||
fb_info=fb_info,
|
||||
)
|
||||
|
||||
return fb
|
||||
|
||||
def _generator(self):
|
||||
|
||||
if not has_pil:
|
||||
vollog.error(
|
||||
"PIL (pillow) module is required to use this plugin. Please install it manually or through pyproject.toml."
|
||||
)
|
||||
return None
|
||||
|
||||
kernel_name = self.config["kernel"]
|
||||
kernel = self.context.modules[kernel_name]
|
||||
|
||||
if not kernel.has_symbol("num_registered_fb"):
|
||||
raise exceptions.SymbolError(
|
||||
"num_registered_fb",
|
||||
kernel.symbol_table_name,
|
||||
"The provided symbol does not exist in the symbol table. This means you are either analyzing an unsupported kernel version or that your symbol table is corrupt.",
|
||||
)
|
||||
|
||||
num_registered_fb = kernel.object_from_symbol("num_registered_fb")
|
||||
if num_registered_fb < 1:
|
||||
vollog.info("No registered framebuffer in the fbdev API.")
|
||||
return None
|
||||
|
||||
registered_fb = kernel.object_from_symbol("registered_fb")
|
||||
fb_info_list = utility.array_of_pointers(
|
||||
registered_fb,
|
||||
num_registered_fb,
|
||||
kernel.symbol_table_name + constants.BANG + "fb_info",
|
||||
self.context,
|
||||
)
|
||||
|
||||
for fb_info in fb_info_list:
|
||||
fb = self.parse_fb_info(fb_info)
|
||||
file_output = "Disabled"
|
||||
if self.config["dump"]:
|
||||
try:
|
||||
file_output = self.dump_fb(
|
||||
self.context, kernel_name, self.open, fb, bool(fb.color_fields)
|
||||
)
|
||||
file_output = str(file_output)
|
||||
except exceptions.InvalidAddressException as excp:
|
||||
vollog.error(
|
||||
f'Layer {excp.layer_name} failed to read address {hex(excp.invalid_address)} when dumping framebuffer "{fb.id}".'
|
||||
)
|
||||
file_output = UnreadableValue()
|
||||
|
||||
try:
|
||||
fb_device_name = utility.pointer_to_string(
|
||||
fb.fb_info.dev.kobj.name, 256
|
||||
)
|
||||
except exceptions.InvalidAddressException:
|
||||
fb_device_name = NotAvailableValue()
|
||||
|
||||
yield (
|
||||
0,
|
||||
(
|
||||
format_hints.Hex(fb.fb_info.screen_base),
|
||||
fb_device_name,
|
||||
fb.id,
|
||||
fb.size,
|
||||
f"{fb.xres_virtual}x{fb.yres_virtual}",
|
||||
fb.bpp,
|
||||
"RUNNING" if fb.fb_info.state == 0 else "SUSPENDED",
|
||||
file_output,
|
||||
),
|
||||
)
|
||||
|
||||
def run(self):
|
||||
columns = [
|
||||
("Address", format_hints.Hex),
|
||||
("Device", str),
|
||||
("ID", str),
|
||||
("Size", int),
|
||||
("Virtual resolution", str),
|
||||
("BPP", int),
|
||||
("State", str),
|
||||
("Filename", str),
|
||||
]
|
||||
|
||||
return TreeGrid(
|
||||
columns,
|
||||
self._generator(),
|
||||
)
|
||||
@@ -16,8 +16,7 @@ class Hidden_modules(interfaces.plugins.PluginInterface):
|
||||
"""Carves memory to find hidden kernel modules"""
|
||||
|
||||
_required_framework_version = (2, 10, 0)
|
||||
|
||||
_version = (1, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -32,8 +31,9 @@ class Hidden_modules(interfaces.plugins.PluginInterface):
|
||||
),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def get_modules_memory_boundaries(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
vmlinux_module_name: str,
|
||||
) -> Tuple[int]:
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
import logging
|
||||
|
||||
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
|
||||
from volatility3.framework import interfaces, renderers, exceptions
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.renderers import format_hints
|
||||
@@ -26,6 +27,11 @@ class Keyboard_notifiers(interfaces.plugins.PluginInterface):
|
||||
description="Linux kernel",
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="linux_utilities_modules",
|
||||
component=linux_utilities_modules.Modules,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0)
|
||||
),
|
||||
@@ -66,8 +72,10 @@ class Keyboard_notifiers(interfaces.plugins.PluginInterface):
|
||||
):
|
||||
call_addr = call_back.notifier_call
|
||||
|
||||
module_name, symbol_name = linux.LinuxUtilities.lookup_module_address(
|
||||
vmlinux, handlers, call_addr
|
||||
module_name, symbol_name = (
|
||||
linux_utilities_modules.Modules.lookup_module_address(
|
||||
self.context, vmlinux.name, handlers, call_addr
|
||||
)
|
||||
)
|
||||
|
||||
yield (0, [format_hints.Hex(call_addr), module_name, symbol_name])
|
||||
|
||||
@@ -5,7 +5,7 @@ import re
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
from enum import Enum
|
||||
from typing import Generator, Iterator, List, Tuple
|
||||
from typing import Generator, Iterator, List, Tuple, Union
|
||||
|
||||
from volatility3.framework import (
|
||||
class_subclasses,
|
||||
@@ -135,8 +135,14 @@ class ABCKmsg(ABC):
|
||||
bool: True if the kernel being analyzed fulfill the class requirements.
|
||||
"""
|
||||
|
||||
def get_string(self, addr: int, length: int) -> str:
|
||||
txt = self._context.layers[self.layer_name].read(addr, length) # type: ignore
|
||||
def get_string(self, addr: int, length: int) -> Union[str, None]:
|
||||
layer = self._context.layers[self.layer_name]
|
||||
if not layer.is_valid(addr, length):
|
||||
vollog.warning("Failed to read log record at address 0x%x", addr)
|
||||
return None
|
||||
|
||||
txt = layer.read(addr, length)
|
||||
|
||||
return txt.decode(encoding="utf8", errors="replace")
|
||||
|
||||
def nsec_to_sec_str(self, nsec: int) -> str:
|
||||
@@ -149,7 +155,7 @@ class ABCKmsg(ABC):
|
||||
# This might seem insignificant but it could cause some issues
|
||||
# when compared with userland tool results or when used in
|
||||
# timelines.
|
||||
return f"{nsec / 1000000000:lu}.{(nsec % 1000000000) / 1000:06lu}"
|
||||
return f"{nsec // 1000000000}.{(nsec % 1000000000) // 1000:06}"
|
||||
|
||||
def get_timestamp_in_sec_str(self, obj) -> str:
|
||||
# obj could be log, printk_log or printk_info
|
||||
@@ -166,7 +172,7 @@ class ABCKmsg(ABC):
|
||||
|
||||
def get_caller_text(self, caller_id):
|
||||
caller_name = "CPU" if caller_id & 0x80000000 else "Task"
|
||||
caller = f"{caller_name}({caller_id & ~0x80000000:u})"
|
||||
caller = f"{caller_name}({caller_id & ~0x80000000})"
|
||||
return caller
|
||||
|
||||
def get_prefix(self, obj) -> Tuple[int, int, str, str]:
|
||||
@@ -263,7 +269,7 @@ class Kmsg_3_5_to_3_11(ABCKmsg):
|
||||
def _get_log_struct_name(self):
|
||||
return "log"
|
||||
|
||||
def get_text_from_log(self, msg) -> str:
|
||||
def get_text_from_log(self, msg) -> Union[str, None]:
|
||||
log_struct_name = self._get_log_struct_name()
|
||||
log_struct_size = self.vmlinux.get_type(log_struct_name).size
|
||||
msg_offset = msg.vol.offset + log_struct_size
|
||||
@@ -272,7 +278,8 @@ class Kmsg_3_5_to_3_11(ABCKmsg):
|
||||
def get_log_lines(self, msg) -> Generator[str, None, None]:
|
||||
if msg.text_len > 0:
|
||||
text = self.get_text_from_log(msg)
|
||||
yield from text.splitlines()
|
||||
if text:
|
||||
yield from text.splitlines()
|
||||
|
||||
def get_dict_lines(self, msg) -> Generator[str, None, None]:
|
||||
if msg.dict_len == 0:
|
||||
@@ -281,9 +288,13 @@ class Kmsg_3_5_to_3_11(ABCKmsg):
|
||||
log_struct_name = self._get_log_struct_name()
|
||||
log_struct_size = self.vmlinux.get_type(log_struct_name).size
|
||||
dict_offset = msg.vol.offset + log_struct_size + msg.text_len
|
||||
dict_data = self._context.layers[self.layer_name].read(
|
||||
dict_offset, msg.dict_len
|
||||
)
|
||||
layer = self._context.layers[self.layer_name]
|
||||
try:
|
||||
dict_data = layer.read(dict_offset, msg.dict_len)
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.debug("Unable to read kmsg dict from 0x%x", dict_offset)
|
||||
return None
|
||||
|
||||
for chunk in dict_data.split(b"\x00"):
|
||||
yield " " + chunk.decode()
|
||||
|
||||
@@ -317,23 +328,27 @@ class Kmsg_3_5_to_3_11(ABCKmsg):
|
||||
while cur_idx < end_idx:
|
||||
msg_offset = log_buf_ptr + cur_idx # type: ignore
|
||||
msg = self.vmlinux.object(object_type=log_struct_name, offset=msg_offset)
|
||||
if msg.len == 0:
|
||||
# As per kernel/printk.c:
|
||||
# A length == 0 for the next message indicates a wrap-around to
|
||||
# the beginning of the buffer.
|
||||
cur_idx = 0
|
||||
end_idx = log_next_idx
|
||||
else:
|
||||
facility, level, timestamp, caller = self.get_prefix(msg)
|
||||
level_txt = self.get_level_text(level)
|
||||
facility_txt = self.get_facility_text(facility)
|
||||
try:
|
||||
if msg.len == 0:
|
||||
# As per kernel/printk.c:
|
||||
# A length == 0 for the next message indicates a wrap-around to
|
||||
# the beginning of the buffer.
|
||||
cur_idx = 0
|
||||
end_idx = log_next_idx
|
||||
else:
|
||||
facility, level, timestamp, caller = self.get_prefix(msg)
|
||||
level_txt = self.get_level_text(level)
|
||||
facility_txt = self.get_facility_text(facility)
|
||||
|
||||
for line in self.get_log_lines(msg):
|
||||
yield facility_txt, level_txt, timestamp, caller, line
|
||||
for line in self.get_dict_lines(msg):
|
||||
yield facility_txt, level_txt, timestamp, caller, line
|
||||
for line in self.get_log_lines(msg):
|
||||
yield facility_txt, level_txt, timestamp, caller, line
|
||||
for line in self.get_dict_lines(msg):
|
||||
yield facility_txt, level_txt, timestamp, caller, line
|
||||
|
||||
cur_idx += msg.len
|
||||
cur_idx += msg.len
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.warning("Kmsg buffer msg length could not be read")
|
||||
return
|
||||
|
||||
|
||||
class Kmsg_3_11_to_5_10(Kmsg_3_5_to_3_11):
|
||||
@@ -399,7 +414,7 @@ class Kmsg_5_10_to_(ABCKmsg):
|
||||
def symtab_checks(cls, vmlinux) -> bool:
|
||||
return vmlinux.has_symbol("prb")
|
||||
|
||||
def get_text_from_data_ring(self, text_data_ring, desc, info) -> str:
|
||||
def get_text_from_data_ring(self, text_data_ring, desc, info) -> Union[str, None]:
|
||||
text_data_sz = text_data_ring.size_bits
|
||||
text_data_mask = 1 << text_data_sz
|
||||
|
||||
@@ -427,7 +442,8 @@ class Kmsg_5_10_to_(ABCKmsg):
|
||||
|
||||
def get_log_lines(self, text_data_ring, desc, info) -> Generator[str, None, None]:
|
||||
text = self.get_text_from_data_ring(text_data_ring, desc, info)
|
||||
yield from text.splitlines()
|
||||
if text:
|
||||
yield from text.splitlines()
|
||||
|
||||
def get_dict_lines(self, info) -> Generator[str, None, None]:
|
||||
dict_text = utility.array_to_string(info.dev_info.subsystem)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
|
||||
from volatility3.framework import constants, exceptions, interfaces, renderers
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.interfaces import plugins
|
||||
@@ -20,7 +21,7 @@ class Kthreads(plugins.PluginInterface):
|
||||
"""Enumerates kthread functions"""
|
||||
|
||||
_required_framework_version = (2, 11, 0)
|
||||
_version = (1, 0, 2)
|
||||
_version = (1, 0, 3)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -30,6 +31,11 @@ class Kthreads(plugins.PluginInterface):
|
||||
description="Linux kernel",
|
||||
architectures=architectures.LINUX_ARCHS,
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="linux_utilities_modules",
|
||||
component=linux_utilities_modules.Modules,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="linuxutils", component=linux.LinuxUtilities, version=(2, 1, 0)
|
||||
),
|
||||
@@ -88,8 +94,10 @@ class Kthreads(plugins.PluginInterface):
|
||||
if kthread.has_member("full_name")
|
||||
else task_name
|
||||
)
|
||||
module_name, symbol_name = linux.LinuxUtilities.lookup_module_address(
|
||||
vmlinux, handlers, threadfn
|
||||
module_name, symbol_name = (
|
||||
linux_utilities_modules.Modules.lookup_module_address(
|
||||
self.context, vmlinux.name, handlers, threadfn
|
||||
)
|
||||
)
|
||||
|
||||
fields = [
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
# 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
|
||||
#
|
||||
"""A module containing a collection of plugins that produce data typically
|
||||
found in Linux's /proc file system."""
|
||||
"""A module containing a plugin that lists loaded kernel modules."""
|
||||
|
||||
import logging
|
||||
from typing import List, Iterable
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
import logging
|
||||
from typing import List, Dict, Iterator
|
||||
from volatility3.plugins.linux import lsmod, check_modules, hidden_modules
|
||||
from volatility3.framework import interfaces
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.renderers import format_hints, TreeGrid, NotAvailableValue
|
||||
from volatility3.framework.symbols.linux import extensions
|
||||
from volatility3.framework.constants import architectures
|
||||
from volatility3.framework.symbols.linux.utilities import tainting
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Modxview(interfaces.plugins.PluginInterface):
|
||||
"""Centralize lsmod, check_modules and hidden_modules results to efficiently \
|
||||
spot modules presence and taints."""
|
||||
|
||||
_version = (1, 0, 0)
|
||||
_required_framework_version = (2, 17, 0)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
return [
|
||||
requirements.ModuleRequirement(
|
||||
name="kernel",
|
||||
description="Linux kernel",
|
||||
architectures=architectures.LINUX_ARCHS,
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="linux-tainting", component=tainting.Tainting, version=(1, 0, 0)
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0)
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="check_modules",
|
||||
plugin=check_modules.Check_modules,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="hidden_modules",
|
||||
plugin=hidden_modules.Hidden_modules,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
requirements.BooleanRequirement(
|
||||
name="plain_taints",
|
||||
description="Display the plain taints string for each module.",
|
||||
optional=True,
|
||||
default=False,
|
||||
),
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def flatten_run_modules_results(
|
||||
cls, run_results: Dict[str, List[extensions.module]], deduplicate: bool = True
|
||||
) -> Iterator[extensions.module]:
|
||||
"""Flatten a dictionary mapping plugin names and modules list, to a single merged list.
|
||||
This is useful to get a generic lookup list of all the detected modules.
|
||||
|
||||
Args:
|
||||
run_results: dictionary of plugin names mapping a list of detected modules
|
||||
deduplicate: remove duplicate modules, based on their offsets
|
||||
|
||||
Returns:
|
||||
Iterator of modules objects
|
||||
"""
|
||||
seen_addresses = set()
|
||||
for modules in run_results.values():
|
||||
for module in modules:
|
||||
if deduplicate and module.vol.offset in seen_addresses:
|
||||
continue
|
||||
seen_addresses.add(module.vol.offset)
|
||||
yield module
|
||||
|
||||
@classmethod
|
||||
def run_modules_scanners(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
kernel_name: str,
|
||||
run_hidden_modules: bool = True,
|
||||
) -> Dict[str, List[extensions.module]]:
|
||||
"""Run module scanning plugins and aggregate the results. It is designed
|
||||
to not operate any inter-plugin results triage.
|
||||
|
||||
Args:
|
||||
run_hidden_modules: specify if the hidden_modules plugin should be run
|
||||
Returns:
|
||||
Dictionary mapping each plugin to its corresponding result
|
||||
"""
|
||||
|
||||
kernel = context.modules[kernel_name]
|
||||
run_results = {}
|
||||
# lsmod
|
||||
run_results["lsmod"] = list(lsmod.Lsmod.list_modules(context, kernel_name))
|
||||
# check_modules
|
||||
sysfs_modules: dict = check_modules.Check_modules.get_kset_modules(
|
||||
context, kernel_name
|
||||
)
|
||||
## Convert get_kset_modules() offsets back to module objects
|
||||
run_results["check_modules"] = [
|
||||
kernel.object(object_type="module", offset=m_offset, absolute=True)
|
||||
for m_offset in sysfs_modules.values()
|
||||
]
|
||||
# hidden_modules
|
||||
if run_hidden_modules:
|
||||
known_modules_addresses = set(
|
||||
context.layers[kernel.layer_name].canonicalize(module.vol.offset)
|
||||
for module in run_results["lsmod"] + run_results["check_modules"]
|
||||
)
|
||||
modules_memory_boundaries = (
|
||||
hidden_modules.Hidden_modules.get_modules_memory_boundaries(
|
||||
context, kernel_name
|
||||
)
|
||||
)
|
||||
run_results["hidden_modules"] = list(
|
||||
hidden_modules.Hidden_modules.get_hidden_modules(
|
||||
context,
|
||||
kernel_name,
|
||||
known_modules_addresses,
|
||||
modules_memory_boundaries,
|
||||
)
|
||||
)
|
||||
|
||||
return run_results
|
||||
|
||||
def _generator(self):
|
||||
kernel_name = self.config["kernel"]
|
||||
run_results = self.run_modules_scanners(self.context, kernel_name)
|
||||
aggregated_modules = {}
|
||||
# We want to be explicit on the plugins results we are interested in
|
||||
for plugin_name in ["lsmod", "check_modules", "hidden_modules"]:
|
||||
# Iterate over each recovered module
|
||||
for module in run_results[plugin_name]:
|
||||
# Use offsets as unique keys, whether a module
|
||||
# appears in many plugin runs or not
|
||||
if aggregated_modules.get(module.vol.offset, None) is not None:
|
||||
# Append the plugin to the list of originating plugins
|
||||
aggregated_modules[module.vol.offset][1].append(plugin_name)
|
||||
else:
|
||||
aggregated_modules[module.vol.offset] = (module, [plugin_name])
|
||||
|
||||
for module_offset, (module, originating_plugins) in aggregated_modules.items():
|
||||
# Tainting parsing capabilities applied to the module
|
||||
if self.config.get("plain_taints"):
|
||||
taints = tainting.Tainting.get_taints_as_plain_string(
|
||||
self.context,
|
||||
kernel_name,
|
||||
module.taints,
|
||||
True,
|
||||
)
|
||||
else:
|
||||
taints = ",".join(
|
||||
tainting.Tainting.get_taints_parsed(
|
||||
self.context,
|
||||
kernel_name,
|
||||
module.taints,
|
||||
True,
|
||||
)
|
||||
)
|
||||
|
||||
yield (
|
||||
0,
|
||||
(
|
||||
module.get_name() or NotAvailableValue(),
|
||||
format_hints.Hex(module_offset),
|
||||
"lsmod" in originating_plugins,
|
||||
"check_modules" in originating_plugins,
|
||||
"hidden_modules" in originating_plugins,
|
||||
taints or NotAvailableValue(),
|
||||
),
|
||||
)
|
||||
|
||||
def run(self):
|
||||
columns = [
|
||||
("Name", str),
|
||||
("Address", format_hints.Hex),
|
||||
("In procfs", bool),
|
||||
("In sysfs", bool),
|
||||
("Hidden", bool),
|
||||
("Taints", str),
|
||||
]
|
||||
|
||||
return TreeGrid(
|
||||
columns,
|
||||
self._generator(),
|
||||
)
|
||||
@@ -36,7 +36,7 @@ class MountInfo(plugins.PluginInterface):
|
||||
"""Lists mount points on processes mount namespaces"""
|
||||
|
||||
_required_framework_version = (2, 2, 0)
|
||||
_version = (1, 2, 3)
|
||||
_version = (1, 2, 4)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -152,9 +152,11 @@ class MountInfo(plugins.PluginInterface):
|
||||
if not (
|
||||
task
|
||||
and task.fs
|
||||
and task.fs.root
|
||||
and task.fs.is_readable()
|
||||
and task.nsproxy
|
||||
and task.nsproxy.is_readable()
|
||||
and task.nsproxy.mnt_ns
|
||||
and task.nsproxy.mnt_ns.is_readable()
|
||||
):
|
||||
# This task doesn't have all the information required.
|
||||
# It should be a kernel < 2.6.30
|
||||
|
||||
@@ -5,6 +5,7 @@ from dataclasses import dataclass, field
|
||||
from abc import ABC, abstractmethod
|
||||
import logging
|
||||
|
||||
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
|
||||
from typing import Iterator, List, Tuple
|
||||
from volatility3 import framework
|
||||
from volatility3.framework import (
|
||||
@@ -98,6 +99,20 @@ class AbstractNetfilter(ABC):
|
||||
f"linux.LinuxUtilities version not suitable: required {linuxutils_required_version} found {linuxutils_current_version}"
|
||||
)
|
||||
|
||||
linux_utilities_modules_required_version = (
|
||||
Netfilter._required_linux_utilities_modules_version
|
||||
)
|
||||
linux_utilities_modules_current_version = (
|
||||
linux_utilities_modules.Modules._version
|
||||
)
|
||||
if not requirements.VersionRequirement.matches_required(
|
||||
linux_utilities_modules_required_version,
|
||||
linux_utilities_modules_current_version,
|
||||
):
|
||||
raise exceptions.PluginRequirementException(
|
||||
f"linux_utilities_modules.Modules version not suitable: required {linux_utilities_modules_required_version} found {linux_utilities_modules_current_version}"
|
||||
)
|
||||
|
||||
modules = lsmod.Lsmod.list_modules(context, kernel_module_name)
|
||||
self.handlers = linux.LinuxUtilities.generate_kernel_handler_info(
|
||||
context, kernel_module_name, modules
|
||||
@@ -263,8 +278,10 @@ class AbstractNetfilter(ABC):
|
||||
"""Helper to obtain the module and symbol name in the format needed for the
|
||||
output of this plugin.
|
||||
"""
|
||||
module_name, symbol_name = linux.LinuxUtilities.lookup_module_address(
|
||||
self.vmlinux, self.handlers, addr
|
||||
module_name, symbol_name = (
|
||||
linux_utilities_modules.Modules.lookup_module_address(
|
||||
self._context, self.vmlinux.name, self.handlers, addr
|
||||
)
|
||||
)
|
||||
|
||||
if module_name == "UNKNOWN":
|
||||
@@ -677,6 +694,7 @@ class Netfilter(interfaces.plugins.PluginInterface):
|
||||
|
||||
_version = (1, 1, 0)
|
||||
|
||||
_required_linux_utilities_modules_version = (1, 0, 0)
|
||||
_required_linuxutils_version = (2, 1, 0)
|
||||
_required_lsmod_version = (2, 0, 0)
|
||||
|
||||
@@ -688,6 +706,11 @@ class Netfilter(interfaces.plugins.PluginInterface):
|
||||
description="Linux kernel",
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="linux_utilities_modules",
|
||||
component=linux_utilities_modules.Modules,
|
||||
version=cls._required_linux_utilities_modules_version,
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="lsmod", plugin=lsmod.Lsmod, version=cls._required_lsmod_version
|
||||
),
|
||||
|
||||
@@ -6,9 +6,9 @@ import math
|
||||
import logging
|
||||
import datetime
|
||||
from dataclasses import dataclass, astuple
|
||||
from typing import List, Set, Type, Iterable
|
||||
from typing import List, Set, Type, Iterable, Tuple
|
||||
|
||||
from volatility3.framework import renderers, interfaces
|
||||
from volatility3.framework import renderers, interfaces, exceptions
|
||||
from volatility3.framework.renderers import format_hints
|
||||
from volatility3.framework.interfaces import plugins
|
||||
from volatility3.framework.configuration import requirements
|
||||
@@ -104,7 +104,7 @@ class Files(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
_version = (1, 0, 1)
|
||||
_version = (1, 0, 3)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -147,7 +147,13 @@ class Files(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
Otherwise, it returns the same symlink_path
|
||||
"""
|
||||
# i_link (fast symlinks) were introduced in 4.2
|
||||
if inode and inode.is_link and inode.has_member("i_link") and inode.i_link:
|
||||
if (
|
||||
inode
|
||||
and inode.is_link
|
||||
and inode.has_member("i_link")
|
||||
and inode.i_link
|
||||
and inode.i_link.is_readable()
|
||||
):
|
||||
i_link_str = inode.i_link.dereference().cast(
|
||||
"string", max_length=255, encoding="utf-8", errors="replace"
|
||||
)
|
||||
@@ -253,6 +259,10 @@ class Files(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
if not root_inode.is_valid():
|
||||
continue
|
||||
|
||||
if not (root_inode.i_mapping and root_inode.i_mapping.is_readable()):
|
||||
# Retrieving data from the page cache requires a valid address space
|
||||
continue
|
||||
|
||||
# Inode already processed?
|
||||
if root_inode_ptr in seen_inodes:
|
||||
continue
|
||||
@@ -284,6 +294,10 @@ class Files(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
if not file_inode.is_valid():
|
||||
continue
|
||||
|
||||
if not (file_inode.i_mapping and file_inode.i_mapping.is_readable()):
|
||||
# Retrieving data from the page cache requires a valid address space
|
||||
continue
|
||||
|
||||
# Inode already processed?
|
||||
if file_inode_ptr in seen_inodes:
|
||||
continue
|
||||
@@ -316,10 +330,12 @@ class Files(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
if self.config["find"]:
|
||||
if inode_in.path == self.config["find"]:
|
||||
inode_out = inode_in.to_user(vmlinux_layer)
|
||||
|
||||
yield (0, astuple(inode_out))
|
||||
break # Only the first match
|
||||
else:
|
||||
inode_out = inode_in.to_user(vmlinux_layer)
|
||||
|
||||
yield (0, astuple(inode_out))
|
||||
|
||||
def generate_timeline(self):
|
||||
@@ -344,8 +360,8 @@ class Files(plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
yield description, timeliner.TimeLinerType.MODIFIED, inode_out.modification_time
|
||||
yield description, timeliner.TimeLinerType.CHANGED, inode_out.change_time
|
||||
|
||||
@staticmethod
|
||||
def format_fields_with_headers(headers, generator):
|
||||
@classmethod
|
||||
def format_fields_with_headers(cls, headers, generator):
|
||||
"""Uses the headers type to cast the fields obtained from the generator"""
|
||||
for level, fields in generator:
|
||||
formatted_fields = []
|
||||
@@ -389,7 +405,7 @@ class InodePages(plugins.PluginInterface):
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
_version = (2, 0, 0)
|
||||
_version = (2, 0, 2)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -420,8 +436,9 @@ class InodePages(plugins.PluginInterface):
|
||||
),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def write_inode_content_to_file(
|
||||
cls,
|
||||
inode: interfaces.objects.ObjectInterface,
|
||||
filename: str,
|
||||
open_method: Type[interfaces.plugins.FileHandlerInterface],
|
||||
@@ -443,28 +460,80 @@ class InodePages(plugins.PluginInterface):
|
||||
# created, saving both disk space and I/O time.
|
||||
# Additionally, using the page index will guarantee that each page is written at the
|
||||
# appropriate file position.
|
||||
inode_size = inode.i_size
|
||||
try:
|
||||
with open_method(filename) as f:
|
||||
inode_size = inode.i_size
|
||||
f.truncate(inode_size)
|
||||
|
||||
file_initialized = False
|
||||
with open_method(filename) as file_obj:
|
||||
for page_idx, page_content in inode.get_contents():
|
||||
current_fp = page_idx * vmlinux_layer.page_size
|
||||
max_length = inode_size - current_fp
|
||||
page_bytes = page_content[:max_length]
|
||||
if current_fp + len(page_bytes) > inode_size:
|
||||
page_bytes_len = min(max_length, len(page_content))
|
||||
if (
|
||||
current_fp >= inode_size
|
||||
or current_fp + page_bytes_len > inode_size
|
||||
):
|
||||
vollog.error(
|
||||
"Page out of file bounds: inode 0x%x, inode size %d, page index %d",
|
||||
inode.vol.offset,
|
||||
inode_size,
|
||||
page_idx,
|
||||
)
|
||||
f.seek(current_fp)
|
||||
f.write(page_bytes)
|
||||
continue
|
||||
page_bytes = page_content[:page_bytes_len]
|
||||
|
||||
if not file_initialized:
|
||||
# Lazy initialization to avoid truncating the file until we are
|
||||
# certain there is something to write
|
||||
file_obj.truncate(inode_size)
|
||||
file_initialized = True
|
||||
|
||||
file_obj.seek(current_fp)
|
||||
file_obj.write(page_bytes)
|
||||
except exceptions.LinuxPageCacheException:
|
||||
vollog.error(
|
||||
f"Error dumping cached pages for inode at {inode.vol.offset:#x}"
|
||||
)
|
||||
except OSError as e:
|
||||
vollog.error("Unable to write to file (%s): %s", filename, e)
|
||||
|
||||
def _generate_inode_fields(
|
||||
self,
|
||||
inode: interfaces.objects.ObjectInterface,
|
||||
vmlinux_layer: interfaces.layers.TranslationLayerInterface,
|
||||
) -> Iterable[Tuple[int, int, int, int, bool, str]]:
|
||||
inode_size = inode.i_size
|
||||
try:
|
||||
for page_obj in inode.get_pages():
|
||||
if page_obj.mapping != inode.i_mapping:
|
||||
vollog.warning(
|
||||
f"Cached page at {page_obj.vol.offset:#x} has a mismatched address space with the inode. Skipping page"
|
||||
)
|
||||
continue
|
||||
page_vaddr = page_obj.vol.offset
|
||||
page_paddr = page_obj.to_paddr()
|
||||
page_mapping_addr = page_obj.mapping
|
||||
page_index = page_obj.index
|
||||
page_file_offset = page_index * vmlinux_layer.page_size
|
||||
dump_safe = (
|
||||
page_file_offset < inode_size
|
||||
and page_mapping_addr
|
||||
and page_mapping_addr.is_readable()
|
||||
)
|
||||
page_flags_list = page_obj.get_flags_list()
|
||||
page_flags = ",".join([x.replace("PG_", "") for x in page_flags_list])
|
||||
fields = (
|
||||
page_vaddr,
|
||||
page_paddr,
|
||||
page_mapping_addr,
|
||||
page_index,
|
||||
dump_safe,
|
||||
page_flags,
|
||||
)
|
||||
|
||||
yield 0, fields
|
||||
except exceptions.LinuxPageCacheException:
|
||||
vollog.warning(f"Page cache for inode at {inode.vol.offset:#x} is corrupt")
|
||||
|
||||
def _generator(self):
|
||||
vmlinux_module_name = self.config["kernel"]
|
||||
vmlinux = self.context.modules[vmlinux_module_name]
|
||||
@@ -486,7 +555,6 @@ class InodePages(plugins.PluginInterface):
|
||||
else:
|
||||
vollog.error("Unable to find inode with path %s", self.config["find"])
|
||||
return None
|
||||
|
||||
elif self.config["inode"]:
|
||||
inode = vmlinux.object("inode", self.config["inode"], absolute=True)
|
||||
else:
|
||||
@@ -501,27 +569,6 @@ class InodePages(plugins.PluginInterface):
|
||||
vollog.error("The inode is not a regular file")
|
||||
return None
|
||||
|
||||
inode_size = inode.i_size
|
||||
for page_obj in inode.get_pages():
|
||||
page_vaddr = page_obj.vol.offset
|
||||
page_paddr = page_obj.to_paddr()
|
||||
page_mapping_addr = page_obj.mapping
|
||||
page_index = int(page_obj.index)
|
||||
page_file_offset = page_index * vmlinux_layer.page_size
|
||||
dump_safe = page_file_offset < inode_size
|
||||
page_flags_list = page_obj.get_flags_list()
|
||||
page_flags = ",".join([x.replace("PG_", "") for x in page_flags_list])
|
||||
fields = (
|
||||
page_vaddr,
|
||||
page_paddr,
|
||||
page_mapping_addr,
|
||||
page_index,
|
||||
dump_safe,
|
||||
page_flags,
|
||||
)
|
||||
|
||||
yield 0, fields
|
||||
|
||||
if self.config["dump"]:
|
||||
open_method = self.open
|
||||
inode_address = inode.vol.offset
|
||||
@@ -530,6 +577,8 @@ class InodePages(plugins.PluginInterface):
|
||||
self.write_inode_content_to_file(
|
||||
inode, filename, open_method, vmlinux_layer
|
||||
)
|
||||
else:
|
||||
yield from self._generate_inode_fields(inode, vmlinux_layer)
|
||||
|
||||
def run(self):
|
||||
headers = [
|
||||
|
||||
@@ -21,7 +21,7 @@ class Maps(plugins.PluginInterface):
|
||||
"""Lists all memory maps for all processes."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 2)
|
||||
_version = (1, 0, 3)
|
||||
|
||||
MAXSIZE_DEFAULT = 1024 * 1024 * 1024 # 1 Gb
|
||||
|
||||
@@ -83,18 +83,24 @@ class Maps(plugins.PluginInterface):
|
||||
Returns:
|
||||
Yields vmas based on the task and filtered based on the filter function
|
||||
"""
|
||||
if task.mm:
|
||||
for vma in task.mm.get_vma_iter():
|
||||
if filter_func(vma):
|
||||
yield vma
|
||||
else:
|
||||
vollog.debug(
|
||||
f"Excluded vma at offset {vma.vol.offset:#x} for pid {task.pid} due to filter_func"
|
||||
)
|
||||
else:
|
||||
mm_pointer = task.mm
|
||||
if not mm_pointer:
|
||||
vollog.debug(
|
||||
f"Excluded pid {task.pid} as there is no mm member. It is likely a kernel thread."
|
||||
f"Excluded pid {task.pid} as there is no mm member. It is likely a kernel thread"
|
||||
)
|
||||
return
|
||||
|
||||
if not mm_pointer.is_readable():
|
||||
vollog.error(f"Task {task.pid} has an invalid mm member")
|
||||
return
|
||||
|
||||
for vma in mm_pointer.get_vma_iter():
|
||||
if filter_func(vma):
|
||||
yield vma
|
||||
else:
|
||||
vollog.debug(
|
||||
f"Excluded vma at offset {vma.vol.offset:#x} for pid {task.pid} due to filter_func"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def vma_dump(
|
||||
@@ -174,31 +180,32 @@ class Maps(plugins.PluginInterface):
|
||||
]
|
||||
|
||||
# if any of the user supplied addresses would fall within this vma return true
|
||||
if addrs_in_vma:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return bool(addrs_in_vma)
|
||||
|
||||
vma_filter_func = vma_filter_function
|
||||
|
||||
for task in tasks:
|
||||
if not task.mm:
|
||||
if not (task.mm and task.mm.is_readable()):
|
||||
continue
|
||||
name = utility.array_to_string(task.comm)
|
||||
|
||||
for vma in self.list_vmas(task, filter_func=vma_filter_func):
|
||||
flags = vma.get_protection()
|
||||
page_offset = vma.get_page_offset()
|
||||
major = 0
|
||||
minor = 0
|
||||
inode = 0
|
||||
|
||||
if vma.vm_file != 0:
|
||||
inode_num = None
|
||||
try:
|
||||
dentry = vma.vm_file.get_dentry()
|
||||
if dentry != 0:
|
||||
inode_object = dentry.d_inode
|
||||
major = inode_object.i_sb.major
|
||||
minor = inode_object.i_sb.minor
|
||||
inode = inode_object.i_ino
|
||||
inode_ptr = dentry.d_inode
|
||||
inode_num = inode_ptr.i_ino
|
||||
major = inode_ptr.i_sb.major
|
||||
minor = inode_ptr.i_sb.minor
|
||||
except exceptions.InvalidAddressException:
|
||||
if not inode_num:
|
||||
inode_num = 0
|
||||
major = 0
|
||||
minor = 0
|
||||
|
||||
path = vma.get_name(self.context, task)
|
||||
|
||||
file_output = "Disabled"
|
||||
@@ -238,7 +245,7 @@ class Maps(plugins.PluginInterface):
|
||||
format_hints.Hex(page_offset),
|
||||
major,
|
||||
minor,
|
||||
inode,
|
||||
inode_num,
|
||||
path,
|
||||
file_output,
|
||||
),
|
||||
|
||||
@@ -34,7 +34,7 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
"""Lists the processes present in a particular linux memory image."""
|
||||
|
||||
_required_framework_version = (2, 13, 0)
|
||||
_version = (4, 0, 0)
|
||||
_version = (4, 1, 0)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -74,7 +74,9 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def create_pid_filter(cls, pid_list: List[int] = None) -> Callable[[Any], bool]:
|
||||
def create_pid_filter(
|
||||
cls, pid_list: Optional[List[int]] = None
|
||||
) -> Callable[[Any], bool]:
|
||||
"""Constructs a filter function for process IDs.
|
||||
|
||||
Args:
|
||||
@@ -177,6 +179,10 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
file_output = "VMA start matching task start_code not found"
|
||||
return file_output
|
||||
|
||||
@staticmethod
|
||||
def _format_cred(cred):
|
||||
return renderers.NotAvailableValue() if cred is None else cred
|
||||
|
||||
def _generator(
|
||||
self,
|
||||
pid_filter: Callable[[Any], bool],
|
||||
@@ -210,16 +216,21 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
|
||||
task_fields = self.get_task_fields(task, decorate_comm)
|
||||
|
||||
task_uid = self._format_cred(task_fields.uid)
|
||||
task_gid = self._format_cred(task_fields.gid)
|
||||
task_euid = self._format_cred(task_fields.euid)
|
||||
task_egid = self._format_cred(task_fields.egid)
|
||||
|
||||
yield 0, (
|
||||
format_hints.Hex(task_fields.offset),
|
||||
task_fields.user_pid,
|
||||
task_fields.user_tid,
|
||||
task_fields.user_ppid,
|
||||
task_fields.name,
|
||||
task_fields.uid or renderers.NotAvailableValue(),
|
||||
task_fields.gid or renderers.NotAvailableValue(),
|
||||
task_fields.euid or renderers.NotAvailableValue(),
|
||||
task_fields.egid or renderers.NotAvailableValue(),
|
||||
task_uid,
|
||||
task_gid,
|
||||
task_euid,
|
||||
task_egid,
|
||||
task_fields.creation_time or renderers.NotAvailableValue(),
|
||||
file_output,
|
||||
)
|
||||
@@ -248,6 +259,9 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
|
||||
# Note that the init_task itself is not yielded, since "ps" also never shows it.
|
||||
for task in init_task.tasks:
|
||||
if not task.is_valid():
|
||||
continue
|
||||
|
||||
if filter_func(task):
|
||||
continue
|
||||
|
||||
|
||||
@@ -9,8 +9,7 @@ from volatility3.plugins.linux import pslist
|
||||
|
||||
|
||||
class PsTree(interfaces.plugins.PluginInterface):
|
||||
"""Plugin for listing processes in a tree based on their parent process
|
||||
ID."""
|
||||
"""Plugin for listing processes in a tree based on their parent process ID."""
|
||||
|
||||
_required_framework_version = (2, 13, 0)
|
||||
_version = (1, 1, 1)
|
||||
|
||||
@@ -438,7 +438,7 @@ class Sockstat(plugins.PluginInterface):
|
||||
"""Lists all network connections for all processes."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (3, 0, 2)
|
||||
_version = (3, 0, 3)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -514,25 +514,28 @@ class Sockstat(plugins.PluginInterface):
|
||||
fd_num, filp, _full_path = fd_internal.fd_fields
|
||||
task = fd_internal.task
|
||||
|
||||
if not (filp.f_op and filp.f_op.is_readable()):
|
||||
continue
|
||||
|
||||
if filp.f_op not in (sfop_addr, dfop_addr):
|
||||
continue
|
||||
|
||||
dentry = filp.get_dentry()
|
||||
if not dentry:
|
||||
if not (dentry and dentry.is_readable()):
|
||||
continue
|
||||
|
||||
d_inode = dentry.d_inode
|
||||
if not d_inode:
|
||||
if not (d_inode and d_inode.is_readable()):
|
||||
continue
|
||||
|
||||
socket_alloc = linux.LinuxUtilities.container_of(
|
||||
d_inode, "socket_alloc", "vfs_inode", vmlinux
|
||||
)
|
||||
socket = socket_alloc.socket
|
||||
|
||||
if not (socket and socket.sk):
|
||||
if not socket_alloc:
|
||||
continue
|
||||
socket = socket_alloc.socket
|
||||
if not (socket.sk and socket.sk.is_readable()):
|
||||
continue
|
||||
|
||||
sock = socket.sk.dereference()
|
||||
|
||||
sock_type = sock.get_type()
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import logging
|
||||
from typing import List
|
||||
|
||||
import volatility3.framework.symbols.linux.utilities.modules as linux_utilities_modules
|
||||
from volatility3.framework import interfaces, renderers, exceptions, constants
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.interfaces import plugins
|
||||
@@ -29,6 +30,11 @@ class tty_check(plugins.PluginInterface):
|
||||
description="Linux kernel",
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="linux_utilities_modules",
|
||||
component=linux_utilities_modules.Modules,
|
||||
version=(1, 0, 0),
|
||||
),
|
||||
requirements.PluginRequirement(
|
||||
name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0)
|
||||
),
|
||||
@@ -79,8 +85,10 @@ class tty_check(plugins.PluginInterface):
|
||||
|
||||
recv_buf = tty_dev.ldisc.ops.receive_buf
|
||||
|
||||
module_name, symbol_name = linux.LinuxUtilities.lookup_module_address(
|
||||
vmlinux, handlers, recv_buf
|
||||
module_name, symbol_name = (
|
||||
linux_utilities_modules.Modules.lookup_module_address(
|
||||
self.context, vmlinux.name, handlers, recv_buf
|
||||
)
|
||||
)
|
||||
|
||||
yield (0, (name, format_hints.Hex(recv_buf), module_name, symbol_name))
|
||||
|
||||
@@ -18,7 +18,7 @@ class VmaYaraScan(interfaces.plugins.PluginInterface):
|
||||
"""Scans all virtual memory areas for tasks using yara."""
|
||||
|
||||
_required_framework_version = (2, 4, 0)
|
||||
_version = (1, 0, 2)
|
||||
_version = (1, 0, 3)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -105,8 +105,9 @@ class VmaYaraScan(interfaces.plugins.PluginInterface):
|
||||
value,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def get_vma_maps(
|
||||
cls,
|
||||
task: interfaces.objects.ObjectInterface,
|
||||
) -> Iterable[Tuple[int, int]]:
|
||||
"""Creates a map of start/end addresses for each virtual memory area in a task.
|
||||
|
||||
@@ -11,8 +11,8 @@ from volatility3.framework.symbols import mac
|
||||
|
||||
|
||||
class Mount(plugins.PluginInterface):
|
||||
"""A module containing a collection of plugins that produce data typically
|
||||
found in Mac's mount command"""
|
||||
"""A module containing a collection of plugins that produce data typically \
|
||||
found in Mac's mount command"""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
from typing import Callable, Dict, Iterable, List
|
||||
from typing import Callable, Dict, Iterable, List, Optional
|
||||
|
||||
from volatility3.framework import exceptions, interfaces, renderers
|
||||
from volatility3.framework.configuration import requirements
|
||||
@@ -82,7 +82,9 @@ class PsList(interfaces.plugins.PluginInterface):
|
||||
return list_tasks
|
||||
|
||||
@classmethod
|
||||
def create_pid_filter(cls, pid_list: List[int] = None) -> Callable[[int], bool]:
|
||||
def create_pid_filter(
|
||||
cls, pid_list: Optional[List[int]] = None
|
||||
) -> Callable[[int], bool]:
|
||||
def filter_func(_):
|
||||
return False
|
||||
|
||||
|
||||
@@ -10,8 +10,7 @@ from volatility3.plugins.mac import pslist
|
||||
|
||||
|
||||
class PsTree(plugins.PluginInterface):
|
||||
"""Plugin for listing processes in a tree based on their parent process
|
||||
ID."""
|
||||
"""Plugin for listing processes in a tree based on their parent process ID."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
|
||||
@@ -41,8 +41,8 @@ class TimeLinerInterface(metaclass=abc.ABCMeta):
|
||||
|
||||
|
||||
class Timeliner(interfaces.plugins.PluginInterface):
|
||||
"""Runs all relevant plugins that provide time related information and
|
||||
orders the results by time."""
|
||||
"""Runs all relevant plugins that provide time related information and \
|
||||
orders the results by time."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 1, 0)
|
||||
@@ -54,7 +54,9 @@ class Timeliner(interfaces.plugins.PluginInterface):
|
||||
self.automagics: Optional[List[interfaces.automagic.AutomagicInterface]] = None
|
||||
|
||||
@classmethod
|
||||
def get_usable_plugins(cls, selected_list: List[str] = None) -> List[Type]:
|
||||
def get_usable_plugins(
|
||||
cls, selected_list: Optional[List[str]] = None
|
||||
) -> List[Type]:
|
||||
# Initialize for the run
|
||||
plugin_list = list(framework.class_subclasses(TimeLinerInterface))
|
||||
|
||||
|
||||
@@ -543,7 +543,7 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
amcache.get_key("Root\\InventoryDriverBinary") # type: ignore
|
||||
)
|
||||
)
|
||||
except KeyError:
|
||||
except (KeyError, registry.RegistryFormatException):
|
||||
# Registry key not found
|
||||
pass
|
||||
|
||||
@@ -554,7 +554,7 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
amcache.get_key("Root\\Programs")
|
||||
) # type: ignore
|
||||
}
|
||||
except KeyError:
|
||||
except (KeyError, registry.RegistryFormatException):
|
||||
programs = {}
|
||||
|
||||
try:
|
||||
@@ -564,7 +564,7 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
),
|
||||
key=_entry_sort_key,
|
||||
)
|
||||
except KeyError:
|
||||
except (KeyError, registry.RegistryFormatException):
|
||||
files = []
|
||||
|
||||
for program_id, file_entries in itertools.groupby(
|
||||
@@ -593,7 +593,7 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
amcache.get_key("Root\\InventoryApplication") # type: ignore
|
||||
)
|
||||
)
|
||||
except KeyError:
|
||||
except (KeyError, registry.RegistryFormatException):
|
||||
programs = {}
|
||||
|
||||
try:
|
||||
@@ -603,7 +603,7 @@ class Amcache(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
),
|
||||
key=_entry_sort_key,
|
||||
)
|
||||
except KeyError:
|
||||
except (KeyError, registry.RegistryFormatException):
|
||||
files = []
|
||||
|
||||
for program_id, file_entries in itertools.groupby(
|
||||
|
||||
@@ -8,7 +8,7 @@ from typing import Tuple
|
||||
from Crypto.Cipher import ARC4, AES
|
||||
from Crypto.Hash import HMAC
|
||||
|
||||
from volatility3.framework import interfaces, renderers
|
||||
from volatility3.framework import interfaces, renderers, exceptions
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.layers import registry
|
||||
from volatility3.framework.symbols.windows import versions
|
||||
@@ -22,7 +22,7 @@ class Cachedump(interfaces.plugins.PluginInterface):
|
||||
"""Dumps lsa secrets from memory"""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -43,16 +43,16 @@ class Cachedump(interfaces.plugins.PluginInterface):
|
||||
),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def get_nlkm(
|
||||
sechive: registry.RegistryHive, lsakey: bytes, is_vista_or_later: bool
|
||||
cls, sechive: registry.RegistryHive, lsakey: bytes, is_vista_or_later: bool
|
||||
):
|
||||
return lsadump.Lsadump.get_secret_by_name(
|
||||
sechive, "NL$KM", lsakey, is_vista_or_later
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def decrypt_hash(edata: bytes, nlkm: bytes, ch, xp: bool):
|
||||
@classmethod
|
||||
def decrypt_hash(cls, edata: bytes, nlkm: bytes, ch, xp: bool):
|
||||
if xp:
|
||||
hmac_md5 = HMAC.new(nlkm, ch)
|
||||
rc4key = hmac_md5.digest()
|
||||
@@ -69,8 +69,8 @@ class Cachedump(interfaces.plugins.PluginInterface):
|
||||
data += aes.decrypt(buf)
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def parse_cache_entry(cache_data: bytes) -> Tuple[int, int, int, bytes, bytes]:
|
||||
@classmethod
|
||||
def parse_cache_entry(cls, cache_data: bytes) -> Tuple[int, int, int, bytes, bytes]:
|
||||
(uname_len, domain_len) = unpack("<HH", cache_data[:4])
|
||||
if len(cache_data[60:62]) == 0:
|
||||
return (uname_len, domain_len, 0, b"", b"")
|
||||
@@ -79,9 +79,9 @@ class Cachedump(interfaces.plugins.PluginInterface):
|
||||
enc_data = cache_data[96:]
|
||||
return (uname_len, domain_len, domain_name_len, enc_data, ch)
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def parse_decrypted_cache(
|
||||
dec_data: bytes, uname_len: int, domain_len: int, domain_name_len: int
|
||||
cls, dec_data: bytes, uname_len: int, domain_len: int, domain_name_len: int
|
||||
) -> Tuple[str, str, str, bytes]:
|
||||
"""Get the data from the cache and separate it into the username, domain name, and hash data"""
|
||||
uname_offset = 72
|
||||
@@ -140,9 +140,14 @@ class Cachedump(interfaces.plugins.PluginInterface):
|
||||
if cache_item.Name == "NL$Control":
|
||||
continue
|
||||
|
||||
data = sechive.read(cache_item.Data + 4, cache_item.DataLength)
|
||||
if data is None:
|
||||
try:
|
||||
data = sechive.read(cache_item.Data + 4, cache_item.DataLength)
|
||||
except exceptions.InvalidAddressException:
|
||||
continue
|
||||
|
||||
if not data:
|
||||
continue
|
||||
|
||||
(
|
||||
uname_len,
|
||||
domain_len,
|
||||
|
||||
@@ -67,6 +67,7 @@ class CmdScan(interfaces.plugins.PluginInterface):
|
||||
|
||||
Args:
|
||||
conhost_proc: the process object for conhost.exe
|
||||
size_filter: size above which vads will not be returned
|
||||
|
||||
Returns:
|
||||
A list of tuples of:
|
||||
@@ -99,8 +100,8 @@ class CmdScan(interfaces.plugins.PluginInterface):
|
||||
kernel_layer_name: The name of the layer on which to operate
|
||||
kernel_symbol_table_name: The name of the table containing the kernel symbols
|
||||
config_path: The config path where to find symbol files
|
||||
procs: list of process objects
|
||||
max_history: an initial set of CommandHistorySize values
|
||||
procs: List of process objects
|
||||
max_history: An initial set of CommandHistorySize values
|
||||
|
||||
Returns:
|
||||
The conhost process object, the command history structure, a dictionary of properties for
|
||||
@@ -227,7 +228,6 @@ class CmdScan(interfaces.plugins.PluginInterface):
|
||||
"data": command_history.CommandCountMax,
|
||||
}
|
||||
)
|
||||
|
||||
command_history_properties.append(
|
||||
{
|
||||
"level": 1,
|
||||
@@ -236,6 +236,7 @@ class CmdScan(interfaces.plugins.PluginInterface):
|
||||
"data": "",
|
||||
}
|
||||
)
|
||||
|
||||
for (
|
||||
cmd_index,
|
||||
bucket_cmd,
|
||||
@@ -352,7 +353,7 @@ class CmdScan(interfaces.plugins.PluginInterface):
|
||||
|
||||
def _conhost_proc_filter(self, proc: interfaces.objects.ObjectInterface):
|
||||
"""
|
||||
Used to filter to only conhost.exe processes
|
||||
Used to filter only conhost.exe processes
|
||||
"""
|
||||
process_name = utility.array_to_string(proc.ImageFileName)
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ class DirectSystemCalls(interfaces.plugins.PluginInterface):
|
||||
"""Detects the Direct System Call technique used to bypass EDRs"""
|
||||
|
||||
_required_framework_version = (2, 4, 0)
|
||||
_version = (1, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
|
||||
# DLLs that are expected to host system call invocations
|
||||
valid_syscall_handlers = ("ntdll.dll", "win32u.dll")
|
||||
@@ -200,8 +200,8 @@ class DirectSystemCalls(interfaces.plugins.PluginInterface):
|
||||
|
||||
return disasm_bytes, end_inst
|
||||
|
||||
@staticmethod
|
||||
def get_disasm_function(architecture: str) -> Callable:
|
||||
@classmethod
|
||||
def get_disasm_function(cls, architecture: str) -> Callable:
|
||||
"""
|
||||
Returns the disassembly handler for the given architecture
|
||||
.detail is used to get full instruction information
|
||||
@@ -284,8 +284,9 @@ class DirectSystemCalls(interfaces.plugins.PluginInterface):
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def get_vad_maps(
|
||||
cls,
|
||||
task: interfaces.objects.ObjectInterface,
|
||||
) -> List[Tuple[int, int, str]]:
|
||||
"""Creates a map of start/end addresses within a virtual address
|
||||
@@ -310,9 +311,9 @@ class DirectSystemCalls(interfaces.plugins.PluginInterface):
|
||||
|
||||
return vads
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def get_range_path(
|
||||
ranges: List[Tuple[int, int, str]], address: int
|
||||
cls, ranges: List[Tuple[int, int, str]], address: int
|
||||
) -> Optional[str]:
|
||||
"""
|
||||
Returns the path for the range holding `address`, if found
|
||||
@@ -433,6 +434,8 @@ class DirectSystemCalls(interfaces.plugins.PluginInterface):
|
||||
proc_layer = self.context.layers[proc_layer_name]
|
||||
|
||||
vads = self.get_vad_maps(proc)
|
||||
if not vads:
|
||||
continue
|
||||
|
||||
# for each valid process, look for malicious syscall invocations
|
||||
for address, vad_path in self._get_rule_hits(
|
||||
|
||||
@@ -19,7 +19,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
"""Lists the loaded modules in a particular windows memory image."""
|
||||
"""Lists the loaded DLLs in a particular windows memory image."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (3, 0, 0)
|
||||
@@ -39,6 +39,9 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
requirements.VersionRequirement(
|
||||
name="psscan", component=psscan.PsScan, version=(1, 1, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="pedump", component=pedump.PEDump, version=(1, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="info", component=info.Info, version=(1, 0, 0)
|
||||
),
|
||||
@@ -53,16 +56,16 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
description="Process offset in the physical address space",
|
||||
optional=True,
|
||||
),
|
||||
requirements.StringRequirement(
|
||||
name="name",
|
||||
description="Specify a regular expression to match dll name(s)",
|
||||
optional=True,
|
||||
),
|
||||
requirements.IntRequirement(
|
||||
name="base",
|
||||
description="Specify a base virtual address in process memory",
|
||||
optional=True,
|
||||
),
|
||||
requirements.StringRequirement(
|
||||
name="name",
|
||||
description="Specify a regular expression to match dll name(s)",
|
||||
optional=True,
|
||||
),
|
||||
requirements.BooleanRequirement(
|
||||
name="ignore-case",
|
||||
description="Specify case insensitivity for the regular expression name matching",
|
||||
@@ -75,9 +78,6 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
default=False,
|
||||
optional=True,
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="pedump", component=pedump.PEDump, version=(1, 0, 0)
|
||||
),
|
||||
]
|
||||
|
||||
def _generator(self, procs):
|
||||
@@ -90,12 +90,15 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
kuser = info.Info.get_kuser_structure(
|
||||
self.context, kernel.layer_name, kernel.symbol_table_name
|
||||
)
|
||||
|
||||
nt_major_version = int(kuser.NtMajorVersion)
|
||||
nt_minor_version = int(kuser.NtMinorVersion)
|
||||
|
||||
# LoadTime only applies to versions higher or equal to Window 7 (6.1 and higher)
|
||||
dll_load_time_field = (nt_major_version > 6) or (
|
||||
nt_major_version == 6 and nt_minor_version >= 1
|
||||
)
|
||||
|
||||
for proc in procs:
|
||||
proc_id = proc.UniqueProcessId
|
||||
proc_layer_name = proc.add_process_layer()
|
||||
@@ -135,7 +138,7 @@ class DllList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
|
||||
if dll_load_time_field:
|
||||
# Versions prior to 6.1 won't have the LoadTime attribute
|
||||
# and 32bit version shouldn't have the Quadpart according to MSDN
|
||||
# and 32-bit version shouldn't have the Quadpart according to MSDN
|
||||
try:
|
||||
DllLoadTime = conversion.wintime_to_datetime(
|
||||
entry.LoadTime.QuadPart
|
||||
|
||||
@@ -64,7 +64,7 @@ class DriverScan(interfaces.plugins.PluginInterface):
|
||||
names associated with a driver
|
||||
|
||||
Args:
|
||||
driver: A Eriver object
|
||||
driver: A Driver object
|
||||
|
||||
Returns:
|
||||
A tuple of strings of (driver name, service key, driver alt. name)
|
||||
|
||||
@@ -76,14 +76,14 @@ class Envars(interfaces.plugins.PluginInterface):
|
||||
"CurrentControlSet\\Control\\Session Manager\\Environment"
|
||||
)
|
||||
sys = True
|
||||
except KeyError:
|
||||
with contextlib.suppress(KeyError):
|
||||
except (KeyError, registry.RegistryFormatException):
|
||||
with contextlib.suppress(KeyError, registry.RegistryFormatException):
|
||||
key = hive.get_key(
|
||||
"ControlSet001\\Control\\Session Manager\\Environment"
|
||||
)
|
||||
sys = True
|
||||
if sys:
|
||||
with contextlib.suppress(KeyError):
|
||||
with contextlib.suppress(KeyError, registry.RegistryFormatException):
|
||||
for node in key.get_values():
|
||||
try:
|
||||
value_node_name = node.get_name()
|
||||
@@ -100,11 +100,11 @@ class Envars(interfaces.plugins.PluginInterface):
|
||||
continue
|
||||
|
||||
## The user-specific variables
|
||||
with contextlib.suppress(KeyError):
|
||||
with contextlib.suppress(KeyError, registry.RegistryFormatException):
|
||||
key = hive.get_key("Environment")
|
||||
ntuser = True
|
||||
if ntuser:
|
||||
with contextlib.suppress(KeyError):
|
||||
with contextlib.suppress(KeyError, registry.RegistryFormatException):
|
||||
for node in key.get_values():
|
||||
try:
|
||||
value_node_name = node.get_name()
|
||||
@@ -123,7 +123,7 @@ class Envars(interfaces.plugins.PluginInterface):
|
||||
## The volatile user variables
|
||||
try:
|
||||
key = hive.get_key("Volatile Environment")
|
||||
except KeyError:
|
||||
except (KeyError, registry.RegistryFormatException):
|
||||
continue
|
||||
try:
|
||||
for node in key.get_values():
|
||||
|
||||
@@ -10,6 +10,7 @@ from typing import List
|
||||
|
||||
from volatility3.framework import renderers, interfaces, constants, exceptions
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.layers import registry
|
||||
from volatility3.plugins.windows.registry import hivelist
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
@@ -86,10 +87,18 @@ class GetServiceSIDs(interfaces.plugins.PluginInterface):
|
||||
# Get ControlSet\Services.
|
||||
try:
|
||||
services = hive.get_key(r"CurrentControlSet\Services")
|
||||
except (KeyError, exceptions.InvalidAddressException):
|
||||
except (
|
||||
KeyError,
|
||||
exceptions.InvalidAddressException,
|
||||
registry.RegistryFormatException,
|
||||
):
|
||||
try:
|
||||
services = hive.get_key(r"ControlSet001\Services")
|
||||
except (KeyError, exceptions.InvalidAddressException):
|
||||
except (
|
||||
KeyError,
|
||||
exceptions.InvalidAddressException,
|
||||
registry.RegistryFormatException,
|
||||
):
|
||||
continue
|
||||
|
||||
if services:
|
||||
|
||||
@@ -158,7 +158,11 @@ class GetSIDs(interfaces.plugins.PluginInterface):
|
||||
layers.registry.RegistryFormatException,
|
||||
):
|
||||
continue
|
||||
except (KeyError, exceptions.InvalidAddressException):
|
||||
except (
|
||||
KeyError,
|
||||
exceptions.InvalidAddressException,
|
||||
layers.registry.RegistryFormatException,
|
||||
):
|
||||
continue
|
||||
|
||||
return sids
|
||||
|
||||
@@ -68,7 +68,12 @@ class Handles(interfaces.plugins.PluginInterface):
|
||||
if not self.context.layers[virtual].is_valid(handle_table_entry.Object):
|
||||
return None
|
||||
fast_ref = handle_table_entry.Object.cast("_EX_FAST_REF")
|
||||
object_header = fast_ref.dereference().cast("_OBJECT_HEADER")
|
||||
|
||||
try:
|
||||
object_header = fast_ref.dereference().cast("_OBJECT_HEADER")
|
||||
except exceptions.InvalidAddressException:
|
||||
return None
|
||||
|
||||
object_header.GrantedAccess = handle_table_entry.GrantedAccess
|
||||
except AttributeError:
|
||||
# starting with windows 8
|
||||
@@ -77,16 +82,26 @@ class Handles(interfaces.plugins.PluginInterface):
|
||||
)
|
||||
|
||||
if is_64bit:
|
||||
if handle_table_entry.ObjectPointerBits == 0:
|
||||
try:
|
||||
pointer_bits = handle_table_entry.ObjectPointerBits
|
||||
except exceptions.InvalidAddressException:
|
||||
return None
|
||||
|
||||
offset = handle_table_entry.ObjectPointerBits << 4
|
||||
if pointer_bits == 0:
|
||||
return None
|
||||
|
||||
offset = pointer_bits << 4
|
||||
|
||||
else:
|
||||
if handle_table_entry.InfoTable == 0:
|
||||
try:
|
||||
info_table = handle_table_entry.InfoTable
|
||||
except exceptions.InvalidAddressException:
|
||||
return None
|
||||
|
||||
offset = handle_table_entry.InfoTable & ~7
|
||||
if info_table == 0:
|
||||
return None
|
||||
|
||||
offset = info_table & ~7
|
||||
|
||||
# print("LowValue: {0:#x} Magic: {1:#x} Offset: {2:#x}".format(handle_table_entry.InfoTable, magic, offset))
|
||||
object_header = self.context.object(
|
||||
@@ -94,7 +109,10 @@ class Handles(interfaces.plugins.PluginInterface):
|
||||
virtual,
|
||||
offset=offset,
|
||||
)
|
||||
object_header.GrantedAccess = handle_table_entry.GrantedAccessBits
|
||||
try:
|
||||
object_header.GrantedAccess = handle_table_entry.GrantedAccessBits
|
||||
except exceptions.InvalidAddressException:
|
||||
return None
|
||||
|
||||
object_header.HandleValue = handle_value
|
||||
return object_header
|
||||
@@ -160,7 +178,7 @@ class Handles(interfaces.plugins.PluginInterface):
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVV,
|
||||
f"Cannot access _OBJECT_HEADER Name at {objt.vol.offset:#x}",
|
||||
f"Cannot access _OBJECT_HEADER Name at {ptr.vol.offset:#x}",
|
||||
)
|
||||
continue
|
||||
|
||||
@@ -226,6 +244,14 @@ class Handles(interfaces.plugins.PluginInterface):
|
||||
masked_offset = offset & layer_object.maximum_address
|
||||
|
||||
for entry in table:
|
||||
# This triggered a backtrace in many testing samples
|
||||
# in the level == 0 path
|
||||
# The code above this calls `is_valid` on the `offset`
|
||||
# It is sent but then does not validate `entry` before
|
||||
# sending it to `_get_item`
|
||||
if not self.context.layers[virtual].is_valid(entry.vol.offset):
|
||||
continue
|
||||
|
||||
if level > 0:
|
||||
yield from self._make_handle_array(entry, level - 1, depth)
|
||||
depth += 1
|
||||
@@ -315,7 +341,7 @@ class Handles(interfaces.plugins.PluginInterface):
|
||||
try:
|
||||
obj_name = entry.NameInfo.Name.String
|
||||
except (ValueError, exceptions.InvalidAddressException):
|
||||
obj_name = ""
|
||||
obj_name = None
|
||||
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.log(
|
||||
@@ -333,7 +359,7 @@ class Handles(interfaces.plugins.PluginInterface):
|
||||
format_hints.Hex(entry.HandleValue),
|
||||
obj_type,
|
||||
format_hints.Hex(entry.GrantedAccess),
|
||||
obj_name,
|
||||
obj_name or renderers.NotAvailableValue(),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@@ -332,7 +332,7 @@ class Hashdump(interfaces.plugins.PluginInterface):
|
||||
try:
|
||||
if hive:
|
||||
result = hive.get_key(key)
|
||||
except KeyError:
|
||||
except (KeyError, registry.RegistryFormatException):
|
||||
vollog.info(
|
||||
f"Unable to load the required registry key {hive.get_name()}\\{key} from this memory image"
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ from typing import Optional
|
||||
from Crypto.Cipher import ARC4, DES, AES
|
||||
from Crypto.Hash import MD5, SHA256
|
||||
|
||||
from volatility3.framework import interfaces, renderers
|
||||
from volatility3.framework import interfaces, renderers, exceptions
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.layers import registry
|
||||
from volatility3.framework.symbols.windows import versions
|
||||
@@ -81,7 +81,10 @@ class Lsadump(interfaces.plugins.PluginInterface):
|
||||
if not enc_reg_value:
|
||||
return None
|
||||
|
||||
obf_lsa_key = sechive.read(enc_reg_value.Data + 4, enc_reg_value.DataLength)
|
||||
try:
|
||||
obf_lsa_key = sechive.read(enc_reg_value.Data + 4, enc_reg_value.DataLength)
|
||||
except exceptions.InvalidAddressException:
|
||||
return None
|
||||
|
||||
if not obf_lsa_key:
|
||||
return None
|
||||
|
||||
@@ -120,8 +120,7 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
vadinfo.winnt_protections,
|
||||
)
|
||||
write_exec = "EXECUTE" in protection_string and "WRITE" in protection_string
|
||||
dirty_page_check = False
|
||||
|
||||
dirty_page = None
|
||||
if not write_exec:
|
||||
"""
|
||||
# Inspect "PAGE_EXECUTE_READ" VAD pages to detect
|
||||
@@ -135,12 +134,12 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
try:
|
||||
# If we have a dirty page in a non writable "EXECUTE" region, it is suspicious.
|
||||
if proc_layer.is_dirty(page):
|
||||
dirty_page_check = True
|
||||
dirty_page = page
|
||||
break
|
||||
except exceptions.InvalidAddressException:
|
||||
# Abort as it is likely that other addresses in the same range will also fail.
|
||||
break
|
||||
if not dirty_page_check:
|
||||
if dirty_page is None:
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
@@ -152,10 +151,10 @@ class Malfind(interfaces.plugins.PluginInterface):
|
||||
if cls.is_vad_empty(proc_layer, vad):
|
||||
continue
|
||||
|
||||
if dirty_page_check:
|
||||
if dirty_page is not None:
|
||||
# Useful information to investigate the page content with volshell afterwards.
|
||||
vollog.warning(
|
||||
f"[proc_id {proc_id}] Found suspicious DIRTY + {protection_string} page at {hex(page)}",
|
||||
f"[proc_id {proc_id}] Found suspicious DIRTY + {protection_string} page at {hex(dirty_page)}",
|
||||
)
|
||||
data = proc_layer.read(vad.get_start(), 64, pad=True)
|
||||
yield vad, data
|
||||
|
||||
@@ -22,7 +22,7 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
_version = (2, 0, 0)
|
||||
_version = (2, 0, 1)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -37,8 +37,9 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def enumerate_mft_records(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
config_path: str,
|
||||
primary_layer_name: str,
|
||||
@@ -128,8 +129,9 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
layer_name=layer.name,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def parse_mft_records(
|
||||
cls,
|
||||
record_map: Dict[int, Tuple[str, int, int]],
|
||||
mft_record: interfaces.objects.ObjectInterface,
|
||||
attr: interfaces.objects.ObjectInterface,
|
||||
@@ -191,8 +193,9 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
file_name,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def parse_data_record(
|
||||
cls,
|
||||
mft_record: interfaces.objects.ObjectInterface,
|
||||
attr: interfaces.objects.ObjectInterface,
|
||||
record_map: Dict[int, Tuple[str, int, int]],
|
||||
@@ -325,7 +328,7 @@ class ADS(interfaces.plugins.PluginInterface):
|
||||
|
||||
_required_framework_version = (2, 7, 0)
|
||||
|
||||
_version = (1, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -343,8 +346,9 @@ class ADS(interfaces.plugins.PluginInterface):
|
||||
),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def parse_ads_data_records(
|
||||
cls,
|
||||
record_map: Dict[int, Tuple[str, int, int]],
|
||||
mft_record: interfaces.objects.ObjectInterface,
|
||||
attr: interfaces.objects.ObjectInterface,
|
||||
@@ -394,7 +398,7 @@ class ResidentData(interfaces.plugins.PluginInterface):
|
||||
|
||||
_required_framework_version = (2, 7, 0)
|
||||
|
||||
_version = (1, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -412,8 +416,9 @@ class ResidentData(interfaces.plugins.PluginInterface):
|
||||
),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def parse_first_data_records(
|
||||
cls,
|
||||
record_map: Dict[int, Tuple[str, int, int]],
|
||||
mft_record: interfaces.objects.ObjectInterface,
|
||||
attr: interfaces.objects.ObjectInterface,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
#
|
||||
import logging
|
||||
from typing import Generator, Iterable, List
|
||||
from typing import Generator, Iterable, List, Optional
|
||||
|
||||
from volatility3.framework import constants, exceptions, interfaces, renderers
|
||||
from volatility3.framework.configuration import requirements
|
||||
@@ -133,7 +133,7 @@ class Modules(interfaces.plugins.PluginInterface):
|
||||
context: interfaces.context.ContextInterface,
|
||||
layer_name: str,
|
||||
symbol_table: str,
|
||||
pids: List[int] = None,
|
||||
pids: Optional[List[int]] = None,
|
||||
) -> Generator[str, None, None]:
|
||||
"""Build a cache of possible virtual layers, in priority starting with
|
||||
the primary/kernel layer. Then keep one layer per session by cycling
|
||||
|
||||
@@ -23,7 +23,7 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
"""Scans for network objects present in a particular windows memory image."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
@@ -50,9 +50,9 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
),
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def create_netscan_constraints(
|
||||
context: interfaces.context.ContextInterface, symbol_table: str
|
||||
cls, context: interfaces.context.ContextInterface, symbol_table: str
|
||||
) -> List[poolscanner.PoolConstraint]:
|
||||
"""Creates a list of Pool Tag Constraints for network objects.
|
||||
|
||||
|
||||
@@ -111,8 +111,21 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
The list of indices at which a 1 was found.
|
||||
"""
|
||||
ret = []
|
||||
# This value is broken in many samples and was causing essentially infinite loops
|
||||
# Testing showed that 8192 is the current size across all Windows versions
|
||||
# We give some leeway in case it increases in later versions, while still keeping it sane
|
||||
# The problematic samples had values that looked like addresses, so in the billions
|
||||
if bitmap_size_in_byte > 8192 * 10:
|
||||
return ret
|
||||
|
||||
for idx in range(bitmap_size_in_byte):
|
||||
current_byte = context.layers[layer_name].read(bitmap_offset + idx, 1)[0]
|
||||
try:
|
||||
current_byte = context.layers[layer_name].read(bitmap_offset + idx, 1)[
|
||||
0
|
||||
]
|
||||
except exceptions.InvalidAddressException:
|
||||
continue
|
||||
|
||||
current_offs = idx * 8
|
||||
for bit in range(8):
|
||||
if current_byte & (1 << bit) != 0:
|
||||
@@ -154,32 +167,37 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
)
|
||||
else:
|
||||
# invalid argument.
|
||||
return None
|
||||
return
|
||||
|
||||
vollog.debug(f"Current Port: {port}")
|
||||
# the given port serves as a shifted index into the port pool lists
|
||||
list_index = port >> 8
|
||||
truncated_port = port & 0xFF
|
||||
|
||||
# constructing port_pool object here so callers don't have to
|
||||
port_pool = context.object(
|
||||
net_symbol_table + constants.BANG + "_INET_PORT_POOL",
|
||||
layer_name=layer_name,
|
||||
offset=port_pool_addr,
|
||||
)
|
||||
try:
|
||||
# constructing port_pool object here so callers don't have to
|
||||
port_pool = context.object(
|
||||
net_symbol_table + constants.BANG + "_INET_PORT_POOL",
|
||||
layer_name=layer_name,
|
||||
offset=port_pool_addr,
|
||||
)
|
||||
# first, grab the given port's PortAssignment (`_PORT_ASSIGNMENT`)
|
||||
inpa = port_pool.PortAssignments[list_index]
|
||||
|
||||
# first, grab the given port's PortAssignment (`_PORT_ASSIGNMENT`)
|
||||
inpa = port_pool.PortAssignments[list_index]
|
||||
|
||||
# then parse the port assignment list (`_PORT_ASSIGNMENT_LIST`) and grab the correct entry
|
||||
assignment = inpa.InPaBigPoolBase.Assignments[truncated_port]
|
||||
# then parse the port assignment list (`_PORT_ASSIGNMENT_LIST`) and grab the correct entry
|
||||
assignment = inpa.InPaBigPoolBase.Assignments[truncated_port]
|
||||
except exceptions.InvalidAddressException:
|
||||
return
|
||||
|
||||
if not assignment:
|
||||
return None
|
||||
return
|
||||
|
||||
# the value within assignment.Entry is a) masked and b) points inside of the network object
|
||||
# first decode the pointer
|
||||
netw_inside = cls._decode_pointer(assignment.Entry)
|
||||
try:
|
||||
netw_inside = cls._decode_pointer(assignment.Entry)
|
||||
except exceptions.InvalidAddressException:
|
||||
return
|
||||
|
||||
if netw_inside:
|
||||
# if the value is valid, calculate the actual object address by subtracting the offset
|
||||
@@ -188,16 +206,30 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
)
|
||||
yield curr_obj
|
||||
|
||||
try:
|
||||
next_obj_address = cls._decode_pointer(curr_obj.Next)
|
||||
except exceptions.InvalidAddressException:
|
||||
return
|
||||
|
||||
# if the same port is used on different interfaces multiple objects are created
|
||||
# those can be found by following the pointer within the object's `Next` field until it is empty
|
||||
while curr_obj.Next:
|
||||
curr_obj = context.object(
|
||||
obj_name,
|
||||
layer_name=layer_name,
|
||||
offset=cls._decode_pointer(curr_obj.Next) - ptr_offset,
|
||||
)
|
||||
while next_obj_address:
|
||||
try:
|
||||
curr_obj = context.object(
|
||||
obj_name,
|
||||
layer_name=layer_name,
|
||||
offset=next_obj_address - ptr_offset,
|
||||
)
|
||||
except exceptions.InvalidAddressException:
|
||||
return
|
||||
|
||||
yield curr_obj
|
||||
|
||||
try:
|
||||
next_obj_address = cls._decode_pointer(curr_obj.Next)
|
||||
except exceptions.InvalidAddressException:
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def get_tcpip_module(
|
||||
cls,
|
||||
@@ -243,16 +275,25 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
The hash table entries which are _not_ empty
|
||||
"""
|
||||
# we are looking for entries whose values are not their own address
|
||||
# smear sanity check from mass testing
|
||||
if ht_length > 4096:
|
||||
return
|
||||
|
||||
for index in range(ht_length):
|
||||
current_addr = ht_offset + index * alignment
|
||||
current_pointer = context.object(
|
||||
net_symbol_table + constants.BANG + "pointer",
|
||||
layer_name=layer_name,
|
||||
offset=current_addr,
|
||||
)
|
||||
try:
|
||||
current_pointer = context.object(
|
||||
net_symbol_table + constants.BANG + "pointer",
|
||||
layer_name=layer_name,
|
||||
offset=current_addr,
|
||||
)
|
||||
except exceptions.InvalidAddressException:
|
||||
continue
|
||||
|
||||
# check if addr of pointer is equal to the value pointed to
|
||||
if current_pointer.vol.offset == current_pointer:
|
||||
continue
|
||||
|
||||
yield current_pointer
|
||||
|
||||
@classmethod
|
||||
@@ -292,11 +333,15 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
tcpip_symbol_table + constants.BANG + "PartitionCount"
|
||||
).address
|
||||
|
||||
part_table_addr = context.object(
|
||||
net_symbol_table + constants.BANG + "pointer",
|
||||
layer_name=layer_name,
|
||||
offset=tcpip_module_offset + part_table_symbol,
|
||||
)
|
||||
try:
|
||||
part_table_addr = context.object(
|
||||
net_symbol_table + constants.BANG + "pointer",
|
||||
layer_name=layer_name,
|
||||
offset=tcpip_module_offset + part_table_symbol,
|
||||
)
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.debug("`PartitionTable` not present in memory.")
|
||||
return
|
||||
|
||||
# part_table is the actual partition table offset and consists out of a dynamic amount of _PARTITION objects
|
||||
part_table = context.object(
|
||||
@@ -304,10 +349,18 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
layer_name=layer_name,
|
||||
offset=part_table_addr,
|
||||
)
|
||||
part_count = int.from_bytes(
|
||||
context.layers[layer_name].read(tcpip_module_offset + part_count_symbol, 1),
|
||||
"little",
|
||||
)
|
||||
|
||||
try:
|
||||
part_count = int.from_bytes(
|
||||
context.layers[layer_name].read(
|
||||
tcpip_module_offset + part_count_symbol, 1
|
||||
),
|
||||
"little",
|
||||
)
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.debug("`PartitionCount` not present in memory.")
|
||||
return
|
||||
|
||||
part_table.Partitions.count = part_count
|
||||
|
||||
vollog.debug(
|
||||
@@ -316,9 +369,21 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
entry_offset = context.symbol_space.get_type(obj_name).relative_child_offset(
|
||||
"ListEntry"
|
||||
)
|
||||
for ctr, partition in enumerate(part_table.Partitions):
|
||||
|
||||
try:
|
||||
partitions = part_table.Partitions
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.debug("Partitions member not present in memory")
|
||||
return
|
||||
|
||||
for ctr, partition in enumerate(partitions):
|
||||
vollog.debug(f"Parsing partition {ctr}")
|
||||
if partition.Endpoints.NumEntries > 0:
|
||||
try:
|
||||
num_entries = partition.Endpoints.NumEntries
|
||||
except exceptions.InvalidAddressException:
|
||||
continue
|
||||
|
||||
if num_entries > 0:
|
||||
for endpoint_entry in cls.parse_hashtable(
|
||||
context,
|
||||
layer_name,
|
||||
@@ -402,6 +467,7 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
upp_symbol = context.symbol_space.get_symbol(
|
||||
tcpip_symbol_table + constants.BANG + "UdpPortPool"
|
||||
).address
|
||||
|
||||
upp_addr = context.object(
|
||||
net_symbol_table + constants.BANG + "pointer",
|
||||
layer_name=layer_name,
|
||||
@@ -498,13 +564,16 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
|
||||
# then, towards the UDP and TCP port pools
|
||||
# first, find their addresses
|
||||
upp_addr, tpp_addr = cls.find_port_pools(
|
||||
context,
|
||||
layer_name,
|
||||
net_symbol_table,
|
||||
tcpip_symbol_table,
|
||||
tcpip_module_offset,
|
||||
)
|
||||
try:
|
||||
upp_addr, tpp_addr = cls.find_port_pools(
|
||||
context,
|
||||
layer_name,
|
||||
net_symbol_table,
|
||||
tcpip_symbol_table,
|
||||
tcpip_module_offset,
|
||||
)
|
||||
except (exceptions.SymbolError, exceptions.InvalidAddressException):
|
||||
vollog.debug("Unable to reconstruct port pools")
|
||||
|
||||
# create port pool objects at the detected address and parse the port bitmap
|
||||
upp_obj = context.object(
|
||||
|
||||
@@ -158,7 +158,7 @@ class PESymbolFinder:
|
||||
|
||||
class PDBSymbolFinder(PESymbolFinder):
|
||||
"""
|
||||
PESymbolFinder implementation for PDB modules
|
||||
PESymbolFinder implementation for PDB modules
|
||||
"""
|
||||
|
||||
def _do_get_address(self, name: str) -> Optional[int]:
|
||||
@@ -195,7 +195,7 @@ class PDBSymbolFinder(PESymbolFinder):
|
||||
|
||||
class ExportSymbolFinder(PESymbolFinder):
|
||||
"""
|
||||
PESymbolFinder implementation for PDB modules
|
||||
PESymbolFinder implementation for PDB modules
|
||||
"""
|
||||
|
||||
def _get_name(self, export: pefile.ExportData) -> Optional[str]:
|
||||
@@ -244,7 +244,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
|
||||
|
||||
_required_framework_version = (2, 7, 0)
|
||||
|
||||
_version = (1, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
|
||||
# used for special handling of the kernel PDB file. See later notes
|
||||
os_module_name = "ntoskrnl.exe"
|
||||
@@ -300,7 +300,7 @@ class PESymbols(interfaces.plugins.PluginInterface):
|
||||
base_address: int,
|
||||
) -> Optional[pefile.PE]:
|
||||
"""
|
||||
Attempts to pefile object from the bytes of the PE file
|
||||
Attempts to create a pefile object from the bytes of the PE file
|
||||
|
||||
Args:
|
||||
pe_table_name: name of the pe types table
|
||||
@@ -330,9 +330,9 @@ class PESymbols(interfaces.plugins.PluginInterface):
|
||||
|
||||
return pe_ret
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def range_info_for_address(
|
||||
ranges: ranges_type, address: int
|
||||
cls, ranges: ranges_type, address: int
|
||||
) -> Optional[range_type]:
|
||||
"""
|
||||
Helper for getting the range information for an address.
|
||||
@@ -351,8 +351,8 @@ class PESymbols(interfaces.plugins.PluginInterface):
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def filepath_for_address(ranges: ranges_type, address: int) -> Optional[str]:
|
||||
@classmethod
|
||||
def filepath_for_address(cls, ranges: ranges_type, address: int) -> Optional[str]:
|
||||
"""
|
||||
Helper to get the file path for an address
|
||||
|
||||
@@ -369,8 +369,8 @@ class PESymbols(interfaces.plugins.PluginInterface):
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def filename_for_path(filepath: str) -> str:
|
||||
@classmethod
|
||||
def filename_for_path(cls, filepath: str) -> str:
|
||||
"""
|
||||
Consistent way to get the filename regardless of platform
|
||||
|
||||
@@ -382,8 +382,9 @@ class PESymbols(interfaces.plugins.PluginInterface):
|
||||
"""
|
||||
return ntpath.basename(filepath).lower()
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def addresses_for_process_symbols(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
config_path: str,
|
||||
layer_name: str,
|
||||
@@ -416,8 +417,9 @@ class PESymbols(interfaces.plugins.PluginInterface):
|
||||
|
||||
return found_symbols
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def path_and_symbol_for_address(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
config_path: str,
|
||||
collected_modules: collected_modules_type,
|
||||
@@ -733,8 +735,9 @@ class PESymbols(interfaces.plugins.PluginInterface):
|
||||
|
||||
return found, remaining
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def find_symbols(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
config_path: str,
|
||||
wanted_modules: PESymbolFinder.cached_value_dict,
|
||||
@@ -775,8 +778,9 @@ class PESymbols(interfaces.plugins.PluginInterface):
|
||||
|
||||
return found_symbols, missing_symbols
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def get_kernel_modules(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
layer_name: str,
|
||||
symbol_table: str,
|
||||
@@ -837,8 +841,9 @@ class PESymbols(interfaces.plugins.PluginInterface):
|
||||
|
||||
return found_modules
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def get_vads_for_process_cache(
|
||||
cls,
|
||||
vads_cache: Dict[int, ranges_type],
|
||||
owner_proc: interfaces.objects.ObjectInterface,
|
||||
) -> Optional[ranges_type]:
|
||||
@@ -865,8 +870,9 @@ class PESymbols(interfaces.plugins.PluginInterface):
|
||||
|
||||
return vads
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def get_proc_vads_with_file_paths(
|
||||
cls,
|
||||
proc: interfaces.objects.ObjectInterface,
|
||||
) -> ranges_type:
|
||||
"""
|
||||
@@ -928,8 +934,9 @@ class PESymbols(interfaces.plugins.PluginInterface):
|
||||
|
||||
yield proc, proc_layer_name, vads
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def get_process_modules(
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
layer_name: str,
|
||||
symbol_table: str,
|
||||
|
||||
@@ -64,30 +64,27 @@ class PEDump(interfaces.plugins.PluginInterface):
|
||||
"""
|
||||
Returns the filename of the dump file or None
|
||||
"""
|
||||
try:
|
||||
file_handle = open_method(file_name)
|
||||
with open_method(file_name) as file_handle:
|
||||
try:
|
||||
dos_header = context.object(
|
||||
pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER",
|
||||
offset=base,
|
||||
layer_name=layer_name,
|
||||
)
|
||||
|
||||
dos_header = context.object(
|
||||
pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER",
|
||||
offset=base,
|
||||
layer_name=layer_name,
|
||||
)
|
||||
for offset, data in dos_header.reconstruct():
|
||||
file_handle.seek(offset)
|
||||
file_handle.write(data)
|
||||
except (
|
||||
OSError,
|
||||
exceptions.VolatilityException,
|
||||
OverflowError,
|
||||
ValueError,
|
||||
) as excp:
|
||||
vollog.debug(f"Unable to dump PE file at offset {base}: {excp}")
|
||||
return None
|
||||
|
||||
for offset, data in dos_header.reconstruct():
|
||||
file_handle.seek(offset)
|
||||
file_handle.write(data)
|
||||
except (
|
||||
OSError,
|
||||
exceptions.VolatilityException,
|
||||
OverflowError,
|
||||
ValueError,
|
||||
) as excp:
|
||||
vollog.debug(f"Unable to dump PE file at offset {base}: {excp}")
|
||||
return None
|
||||
finally:
|
||||
file_handle.close()
|
||||
|
||||
return file_handle.preferred_filename
|
||||
return file_handle.preferred_filename
|
||||
|
||||
@classmethod
|
||||
def dump_ldr_entry(
|
||||
@@ -96,7 +93,7 @@ class PEDump(interfaces.plugins.PluginInterface):
|
||||
pe_table_name: str,
|
||||
ldr_entry: interfaces.objects.ObjectInterface,
|
||||
open_method: Type[interfaces.plugins.FileHandlerInterface],
|
||||
layer_name: str = None,
|
||||
layer_name: Optional[str] = None,
|
||||
prefix: str = "",
|
||||
) -> Optional[str]:
|
||||
"""Extracts the PE file referenced an LDR_DATA_TABLE_ENTRY (DLL, kernel module) instance
|
||||
|
||||
@@ -127,8 +127,8 @@ class PoolHeaderScanner(interfaces.layers.ScannerInterface):
|
||||
class PoolScanner(plugins.PluginInterface):
|
||||
"""A generic pool scanner plugin."""
|
||||
|
||||
_version = (1, 0, 0)
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 1)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
|
||||
@@ -181,9 +181,9 @@ class PoolScanner(plugins.PluginInterface):
|
||||
),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@classmethod
|
||||
def builtin_constraints(
|
||||
symbol_table: str, tags_filter: List[bytes] = None
|
||||
cls, symbol_table: str, tags_filter: Optional[List[bytes]] = None
|
||||
) -> List[PoolConstraint]:
|
||||
"""Get built-in PoolConstraints given a list of pool tags.
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
from typing import Callable, Iterator, List, Type
|
||||
from typing import Callable, Iterator, List, Optional, Type
|
||||
|
||||
from volatility3.framework import renderers, interfaces, layers, exceptions, constants
|
||||
from volatility3.framework.configuration import requirements
|
||||
@@ -114,7 +114,7 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
|
||||
@classmethod
|
||||
def create_pid_filter(
|
||||
cls, pid_list: List[int] = None, exclude: bool = False
|
||||
cls, pid_list: Optional[List[int]] = None, exclude: bool = False
|
||||
) -> Callable[[interfaces.objects.ObjectInterface], bool]:
|
||||
"""A factory for producing filter functions that filter based on a list
|
||||
of process IDs.
|
||||
@@ -171,7 +171,7 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
|
||||
@classmethod
|
||||
def create_name_filter(
|
||||
cls, name_list: List[str] = None, exclude: bool = False
|
||||
cls, name_list: Optional[List[str]] = None, exclude: bool = False
|
||||
) -> Callable[[interfaces.objects.ObjectInterface], bool]:
|
||||
"""A factory for producing filter functions that filter based on a list
|
||||
of process names.
|
||||
|
||||
@@ -89,7 +89,7 @@ class PsScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
cls,
|
||||
context: interfaces.context.ContextInterface,
|
||||
layer_name: str,
|
||||
offset: int = None,
|
||||
offset: Optional[int] = None,
|
||||
physical: bool = True,
|
||||
exclude: bool = False,
|
||||
) -> Callable[[interfaces.objects.ObjectInterface], bool]:
|
||||
|
||||
@@ -14,8 +14,7 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PsTree(interfaces.plugins.PluginInterface):
|
||||
"""Plugin for listing processes in a tree based on their parent process
|
||||
ID."""
|
||||
"""Plugin for listing processes in a tree based on their parent process ID."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
|
||||
|
||||
@@ -21,11 +21,12 @@ vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PsXView(plugins.PluginInterface):
|
||||
"""Lists all processes found via four of the methods described in \"The Art of Memory Forensics,\" which may help
|
||||
identify processes that are trying to hide themselves. I recommend using -r pretty if you are looking at this
|
||||
plugin's output in a terminal."""
|
||||
"""Lists all processes found via four of the methods described in \"The Art of Memory Forensics\" which may help \
|
||||
identify processes that are trying to hide themselves.
|
||||
|
||||
# I've omitted the desktop thread scanning method because Volatility3 doesn't appear to have the funcitonality
|
||||
We recommend using -r pretty if you are looking at this plugin's output in a terminal."""
|
||||
|
||||
# I've omitted the desktop thread scanning method because Volatility3 doesn't appear to have the functionality
|
||||
# which the original plugin used to do it.
|
||||
|
||||
# The sessions method is omitted because it begins with the list of processes found by Pslist anyway.
|
||||
|
||||
@@ -12,8 +12,7 @@ from volatility3.plugins.windows import poolscanner, bigpools
|
||||
|
||||
|
||||
class HiveScan(interfaces.plugins.PluginInterface):
|
||||
"""Scans for registry hives present in a particular windows memory
|
||||
image."""
|
||||
"""Scans for registry hives present in a particular windows memory image."""
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 0)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
from typing import List, Sequence, Iterable, Tuple, Union
|
||||
from typing import List, Optional, Sequence, Iterable, Tuple, Union
|
||||
|
||||
from volatility3.framework import objects, renderers, exceptions, interfaces, constants
|
||||
from volatility3.framework.configuration import requirements
|
||||
@@ -51,7 +51,7 @@ class PrintKey(interfaces.plugins.PluginInterface):
|
||||
def key_iterator(
|
||||
cls,
|
||||
hive: RegistryHive,
|
||||
node_path: Sequence[objects.StructType] = None,
|
||||
node_path: Optional[Sequence[objects.StructType]] = None,
|
||||
recurse: bool = False,
|
||||
) -> Iterable[
|
||||
Tuple[
|
||||
@@ -121,7 +121,7 @@ class PrintKey(interfaces.plugins.PluginInterface):
|
||||
def _printkey_iterator(
|
||||
self,
|
||||
hive: RegistryHive,
|
||||
node_path: Sequence[objects.StructType] = None,
|
||||
node_path: Optional[Sequence[objects.StructType]] = None,
|
||||
recurse: bool = False,
|
||||
):
|
||||
"""Method that wraps the more generic key_iterator, to provide output
|
||||
@@ -242,8 +242,8 @@ class PrintKey(interfaces.plugins.PluginInterface):
|
||||
self,
|
||||
layer_name: str,
|
||||
symbol_table: str,
|
||||
hive_offsets: List[int] = None,
|
||||
key: str = None,
|
||||
hive_offsets: Optional[List[int]] = None,
|
||||
key: Optional[str] = None,
|
||||
recurse: bool = False,
|
||||
):
|
||||
for hive in hivelist.HiveList.list_hives(
|
||||
|
||||
@@ -13,7 +13,7 @@ from typing import Any, Generator, List, Tuple
|
||||
from volatility3.framework import constants, exceptions, interfaces, renderers
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.layers.physical import BufferDataLayer
|
||||
from volatility3.framework.layers.registry import RegistryHive
|
||||
from volatility3.framework.layers.registry import RegistryHive, RegistryFormatException
|
||||
from volatility3.framework.renderers import conversion, format_hints
|
||||
from volatility3.framework.symbols import intermed
|
||||
from volatility3.plugins.windows.registry import hivelist
|
||||
@@ -167,10 +167,21 @@ class UserAssist(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterfac
|
||||
|
||||
self._determine_userassist_type()
|
||||
|
||||
userassist_node_path = hive.get_key(
|
||||
"software\\microsoft\\windows\\currentversion\\explorer\\userassist",
|
||||
return_list=True,
|
||||
)
|
||||
try:
|
||||
userassist_node_path = hive.get_key(
|
||||
"software\\microsoft\\windows\\currentversion\\explorer\\userassist",
|
||||
return_list=True,
|
||||
)
|
||||
except RegistryFormatException as e:
|
||||
vollog.warning(
|
||||
f"Error accessing UserAssist key in {hive_name} at {hive.hive_offset:#x}: {e}"
|
||||
)
|
||||
return None
|
||||
except KeyError:
|
||||
vollog.warning(
|
||||
f"UserAssist key not found in {hive_name} at {hive.hive_offset:#x}"
|
||||
)
|
||||
return None
|
||||
|
||||
if not userassist_node_path:
|
||||
vollog.warning("list_userassist did not find a valid node_path (or None)")
|
||||
|
||||
@@ -270,7 +270,6 @@ class _ScheduledTasksReader(io.BytesIO):
|
||||
return val
|
||||
|
||||
def read_aligned_bstring_expand_sz(self) -> Optional[str]:
|
||||
# type: () -> Optional[str]
|
||||
sz = self.read_aligned_u4()
|
||||
if sz is None:
|
||||
return None
|
||||
@@ -1100,9 +1099,8 @@ class DynamicInfo:
|
||||
|
||||
|
||||
class ScheduledTasks(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
"""Decodes scheduled task information from the Windows registry, including
|
||||
information about triggers, actions, run times, and creation times.
|
||||
"""
|
||||
"""Decodes scheduled task information from the Windows registry, including \
|
||||
information about triggers, actions, run times, and creation times."""
|
||||
|
||||
_required_framework_version = (2, 11, 0)
|
||||
_version = (1, 0, 0)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user