Merge branch 'develop' into stable

This commit is contained in:
ikelos
2025-05-06 22:37:47 +01:00
committed by GitHub
28 changed files with 647 additions and 262 deletions
+30 -31
View File
@@ -13,10 +13,10 @@ name: "CodeQL"
on:
push:
branches: [ "develop" ]
branches: ["develop"]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "develop" ]
branches: ["develop"]
# schedule:
# - cron: '16 8 * * 0'
@@ -32,43 +32,42 @@ jobs:
strategy:
fail-fast: false
matrix:
language: [ 'python' ]
language: ["python"]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
queries: security-and-quality # ,security-extended
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
queries: security-and-quality # ,security-extended
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v3
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
+104
View File
@@ -0,0 +1,104 @@
Coding Standards
================
The coding standards for volatility are mostly by our linter and our code formatter.
All code submissions will be vetted automatically through tests from both and the submission will not be accepted if either of these fail.
Code Linter: Ruff
Code Formatter: Black
In addition, there are some coding practices that we employ to prevent specific failure cases and ensure consistency across the codebase. These are documented below along with the rationale for the decision.
This is heavily based upon https://google.github.io/styleguide/pyguide.html with minor modifications for volatility use.
Imports
-------
Use import statements for packages and modules only, not for individual types, classes, or functions and ideally not aliased unless the imported name would cause confusion. This is to prevent people from importing something that was itself imported from elsewhere (which can lead to confusion and add in an unnecessary dependency in the import chain).
* Use `import x` for importing packages and modules.
* Use `from x import y` where x is the package prefix and y is the module name with no prefix.
* Use `from x import y as z` in any of the following circumstances:
* Two modules named `y` are to be imported.
* `y` conflicts with a top-level name defined in the current module.
* `y` conflicts with a common parameter name that is part of the public API (e.g., `features`).
* `y` is an inconveniently long name.
* `y` is too generic in the context of your code (e.g., `from storage.file_system import options as fs_options`).
Exemptions from this rule:
* Symbols from the following modules are used to support static analysis and type checking:
* `typing` module
* `collections.abc` module
* `typing_extensions` module
Function calls
--------------
For longer function calls, where line length is no longer an issue, favour using keyword arguments for clarity over unnamed positional arguments.
This helps coders learning the code from examples to know what parameters to pass in and avoids ordering mistakes.
Global Mutable State
--------------------
Avoid mutable global state.
In those rare cases where using global state is warranted, mutable global entities should be declared at the module level or as a class attribute and made internal by prepending an _ to the name. If necessary, external access to mutable global state must be done through public functions or class methods. See Naming below. Please explain the design reasons why mutable global state is being used in a comment or a doc linked to from a comment.
Module-level constants are permitted and encouraged. For example: _MAX_HOLY_HANDGRENADE_COUNT = 3 for an internal use constant or SIR_LANCELOTS_FAVORITE_COLOR = "blue" for a public API constant. Constants must be named using all caps with underscores. See Naming below.
Exceptions
----------
Never use catch-all except: statements, or catch Exception or StandardError, unless you are
* re-raising the exception, or
* creating an isolation point in the program where exceptions are not propagated but are recorded and suppressed instead, such as protecting a thread from crashing by guarding its outermost block.
Python is very tolerant in this regard and except: will really catch everything including misspelled names, sys.exit() calls, Ctrl+C interrupts, unittest failures and all kinds of other exceptions that you simply dont want to catch.
Versioning
----------
Modules that inherit from `VersionableInterface` define a `_version` attribute which states their version. This is a tuple of `(MAJOR, MINOR, PATCH)` numbers, which can then be used for Semantic Versioning (where modifications that change the API in a non-backwards compatible way bump the `MAJOR` version (and set the `MINOR` and `PATCH` to 0) and additive changes increase the `MINOR` version (and set the `PATCH` to 0). Changes that have no effect on the external interface (either input or output form) should have their `PATCH` number incremented. This allows for callers of the interface to determine when changes have happened and whether their code will still work with it. Volatility carries out these checks through the requirements system, where a plugin can define what requirements it has.
Shared functionality
--------------------
Within a plugin, there may be functions that are useful to other plugins. These are created as `classmethod`s so that the plugin can be depended upon by other plugins in their requirements section, without needing to instantiate a whole copy of the plugin. It is not a staticmethod, because the caller may wish to determine information about the class the method is defined in, and this is not easily accessible for staticmethods.
A classmethod usually takes a `context` for its first method (and if it requires one, a configuration string for it second). All other parameters should generally be basic types (such as strings, numbers, etc) so that future work requiring parallelization does not have complex types to have to keep in sync. In particular, the idea was to ensure only one context was used per method (and each object brings its own context with it, meaning the function signature should not include objects to avoid discrepancies).
Comprehensions
--------------
Comprehensions are allowed, however multiple for clauses or filter expressions are not permitted. Optimize for readability, not conciseness.
Lambda functions
----------------
Okay for one-liners. Prefer generator expressions over map() or filter() with a lambda.
Default Arguments
-----------------
Default arguments are fine, but not with mutable types (because they're constructed once at module load time and can lead to confusion/errors.)
Format strings
--------------
Generally f-strings are preferred, and where possible a format modifier should be used over a separate method call. As an example, hex output should be `f"0x{offset:x}"` rather than `f"{hex(offset)}"`.
F-strings should be used over other formatting methods *except* in cases of logging where the f-string gets calculated/executed whether the log message is displayed or not (where as parameters are not evaluated if not needed).
The ruff linter should alert about these situations and exceptions can be maded if needed.
True/False Evaluations
----------------------
Use the “implicit” false if possible, e.g., if foo: rather than if foo != []:. There are a few caveats that you should keep in mind though:
* Always use `if foo is None:` (or `is not None`) to check for a `None` value. E.g., when testing whether a variable or argument that defaults to `None` was set to some other value. The other value might be a value thats false in a boolean context!
* Never compare a boolean variable to `False` using `==`. Use `if not x:` instead. If you need to distinguish `False` from `None` then chain the expressions, such as `if not x and x is not None:`.
* For sequences (strings, lists, tuples), use the fact that empty sequences are false, so `if seq:` and `if not seq:` are preferable to `if len(seq):` and `if not len(seq):` respectively.
Logging
-------
We do allow f-string usage in log messages, although technically it should be avoided since it will be evaluated even if the log message is never emitted.
+31 -27
View File
@@ -14,10 +14,33 @@ technical and performance challenges associated with the original
code base that became apparent over the previous 10 years. Another benefit
of the rewrite is that Volatility 3 could be released under a custom
license that was more aligned with the goals of the Volatility community,
the Volatility Software License (VSL). See the
[LICENSE](https://www.volatilityfoundation.org/license/vsl-v1.0) file for
the Volatility Software License (VSL). See the
[LICENSE](https://www.volatilityfoundation.org/license/vsl-v1.0) file for
more details.
## Quick Start
1. Install the required dependencies:
```shell
pip install --user -e ".[full]"
```
2. See available options:
```shell
vol -h
```
3. To get more information on a Windows memory sample and to make sure Volatility supports that sample type, run `vol -f <imagepath> windows.info`:
```shell
vol -f /home/user/samples/stuxnet.vmem windows.info
```
4. Run some other plugins. The `-f` or `--single-location` is not strictly required, but most plugins expect a single sample.
Some also require/accept other options. Run `vol <plugin> -h` for more information on a particular command.
## Installing
Volatility 3 requires Python 3.8.0 or later and is published on the [PyPi registry](https://pypi.org/project/volatility3).
@@ -38,38 +61,19 @@ python3 -m venv venv && . venv/bin/activate
pip install -e ".[dev]"
```
## Quick Start
1. Install Volatility 3 as documented in the Installing section of the readme.
2. See available options:
```shell
vol -h
```
3. To get more information on a Windows memory sample and to make sure Volatility supports that sample type, run `vol -f <imagepath> windows.info`:
```shell
vol -f /home/user/samples/stuxnet.vmem windows.info
```
4. Run some other plugins. The `-f` or `--single-location` is not strictly required, but most plugins expect a single sample.
Some also require/accept other options. Run `vol <plugin> -h` for more information on a particular command.
## Symbol Tables
Symbol table packs for the various operating systems are available for download at:
<https://downloads.volatilityfoundation.org/volatility3/symbols/windows.zip>
<https://downloads.volatilityfoundation.org/volatility3/symbols/mac.zip>
<https://downloads.volatilityfoundation.org/volatility3/symbols/linux.zip>
<https://downloads.volatilityfoundation.org/volatility3/symbols/windows.zip>
<https://downloads.volatilityfoundation.org/volatility3/symbols/mac.zip>
<https://downloads.volatilityfoundation.org/volatility3/symbols/linux.zip>
The hashes to verify whether any of the symbol pack files have downloaded successfully or have changed can be found at:
<https://downloads.volatilityfoundation.org/volatility3/symbols/SHA256SUMS>
<https://downloads.volatilityfoundation.org/volatility3/symbols/SHA1SUMS>
<https://downloads.volatilityfoundation.org/volatility3/symbols/MD5SUMS>
<https://downloads.volatilityfoundation.org/volatility3/symbols/SHA256SUMS>
<https://downloads.volatilityfoundation.org/volatility3/symbols/SHA1SUMS>
<https://downloads.volatilityfoundation.org/volatility3/symbols/MD5SUMS>
Symbol tables zip files must be placed, as named, into the `volatility3/symbols` directory (or just the symbols directory next to the executable file).
+45
View File
@@ -746,6 +746,51 @@ class TestWindowsKPCRs:
assert test_volatility.count_entries_flat(json.loads(out)) > 0
class TestWindowsSymlinkScan:
def test_windows_generic_symlinkscan(self, volatility, python, image):
rc, out, _err = test_volatility.runvol_plugin(
"windows.symlinkscan.SymlinkScan",
image,
volatility,
python,
globalargs=("-r", "json"),
)
assert rc == 0
assert test_volatility.count_entries_flat(json.loads(out)) > 0
def test_windows_specific_symlinkscan(self, volatility, python):
image = WindowsSamples.WINDOWSXP_GENERIC.value.path
rc, out, _err = test_volatility.runvol_plugin(
"windows.symlinkscan.SymlinkScan",
image,
volatility,
python,
globalargs=("-r", "json"),
)
assert rc == 0
json_out = json.loads(out)
assert test_volatility.count_entries_flat(json_out) > 5
expected_rows = [
{
"CreateTime": "2005-06-25T16:47:28+00:00",
"From Name": "AUX",
"Offset": 453082584,
"To Name": "\\DosDevices\\COM1",
"__children": []
},
{
"CreateTime": "2005-06-25T16:47:28+00:00",
"From Name": "UNC",
"Offset": 453176664,
"To Name": "\\Device\\Mup",
"__children": []
}
]
for expected_row in expected_rows:
assert test_volatility.match_output_row(expected_row, json_out)
class TestWindowsLdrModules:
def test_windows_specific_ldrmodules(self, volatility, python):
image = WindowsSamples.WINDOWSXP_GENERIC.value.path
+3
View File
@@ -505,6 +505,9 @@ class CommandLine:
try:
# Construct and run the plugin
if constructed:
vollog.debug(
f"Successfully constructed {args.plugin} {constructed.version}"
)
grid = constructed.run()
renderer = renderers[args.renderer]()
renderer.filter = text_filter.CLIFilter(grid, args.filters)
+4 -1
View File
@@ -85,7 +85,7 @@ class Volshell(interfaces.plugins.PluginInterface):
return reqs
def run(
self, additional_locals: Dict[str, Any] = {}
self, additional_locals: Dict[str, Any] = None
) -> interfaces.renderers.TreeGrid:
"""Runs the interactive volshell plugin.
@@ -93,6 +93,9 @@ class Volshell(interfaces.plugins.PluginInterface):
Return a TreeGrid but this is always empty since the point of this plugin is to run interactively
"""
if additional_locals is None:
additional_locals = {}
# Try to enable tab completion
if not has_ipython:
try:
+1 -1
View File
@@ -1,7 +1,7 @@
# We use the SemVer 2.0.0 versioning scheme
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
VERSION_MINOR = 26 # Number of changes that only add to the interface
VERSION_PATCH = 0 # Number of changes that do not change the interface
VERSION_PATCH = 2 # Number of changes that do not change the interface
VERSION_SUFFIX = ""
PACKAGE_VERSION = (
@@ -28,3 +28,5 @@ PROCESSOR_START_BLOCK_LM_TARGET_OFFSET = (
# CR3 register within structures describing initial processor state to be started
PROCESSOR_START_BLOCK_CR3_OFFSET = 0xA0 # PROCESSOR_START_BLOCK->ProcessorState->SpecialRegisters->Cr3, ULONG64 8 bytes
MAX_PID = 0xFFFFFFFC
+1 -1
View File
@@ -678,7 +678,7 @@ class LayerContainer(collections.abc.Mapping):
if name in self._layers[layer].dependencies:
raise exceptions.LayerException(
self._layers[layer].name,
f"Layer {self._layers[layer].name} is depended upon by {layer}",
f"Layer {name} is depended upon by {layer}",
)
# Otherwise, wipe out the layer
self._layers[name].destroy()
+2 -2
View File
@@ -102,7 +102,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer):
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
return super().get_requirements() + [
requirements.VersionRequirement(
name="regex_scanner",
component=scanners.RegExScanner,
@@ -115,7 +115,7 @@ class QemuSuspendLayer(segmented.NonLinearlySegmentedLayer):
cls, base_layer: interfaces.layers.DataLayerInterface, name: str = ""
):
header = base_layer.read(0, 8)
if header[:4] != b"\x51\x45\x56\x4D":
if header[:4] != b"\x51\x45\x56\x4d":
raise exceptions.LayerException(name, "No QEMU magic bytes")
if header[4:] != b"\x00\x00\x00\x03":
raise exceptions.LayerException(name, "Unsupported QEMU version found")
@@ -281,7 +281,7 @@ class MountInfo(plugins.PluginInterface):
if sb_ptr in seen_sb_ptr:
continue
seen_sb_ptr.add(sb_ptr)
seen_sb_ptr.add(int(sb_ptr))
superblock = sb_ptr.dereference()
@@ -8,7 +8,7 @@ import datetime
import time
import tarfile
from dataclasses import dataclass, astuple
from typing import IO, List, Set, Type, Iterable, Tuple
from typing import IO, List, Set, Type, Iterable, Tuple, Union
from io import BytesIO
from pathlib import PurePath
@@ -283,9 +283,14 @@ class Files(plugins.PluginInterface, timeliner.TimeLinerInterface):
continue
# Inode already processed?
# Store a primitive int (instead of the pointer value) to track
# addresses we've already seen. Storing the full `objects.Pointer`
# uses too much memory, and we don't need all of the information
# that it contains.
if root_inode_ptr in seen_inodes:
continue
seen_inodes.add(root_inode_ptr)
seen_inodes.add(int(root_inode_ptr))
root_path = mountpoint
@@ -318,9 +323,13 @@ class Files(plugins.PluginInterface, timeliner.TimeLinerInterface):
continue
# Inode already processed?
# Store a primitive int (instead of the pointer value) to track
# addresses we've already seen. Storing the full `objects.Pointer`
# uses too much memory, and we don't need all of the information
# that it contains.
if file_inode_ptr in seen_inodes:
continue
seen_inodes.add(file_inode_ptr)
seen_inodes.add(int(file_inode_ptr))
if follow_symlinks:
file_path = cls._follow_symlink(file_inode_ptr, file_path)
@@ -541,6 +550,7 @@ class InodePages(plugins.PluginInterface):
self,
inode: interfaces.objects.ObjectInterface,
vmlinux_layer: interfaces.layers.TranslationLayerInterface,
filename: Union[renderers.NotApplicableValue, str],
) -> Iterable[Tuple[int, int, int, int, bool, str]]:
inode_size = inode.i_size
try:
@@ -569,6 +579,7 @@ class InodePages(plugins.PluginInterface):
page_index,
dump_safe,
page_flags,
filename,
)
yield 0, fields
@@ -610,6 +621,7 @@ class InodePages(plugins.PluginInterface):
vollog.error("The inode is not a regular file")
return None
filename = renderers.NotApplicableValue()
if self.config["dump"]:
open_method = self.open
inode_address = inode.vol.offset
@@ -618,8 +630,7 @@ class InodePages(plugins.PluginInterface):
self.write_inode_content_to_file(
self.context, vmlinux_layer.name, inode, filename, open_method
)
else:
yield from self._generate_inode_fields(inode, vmlinux_layer)
yield from self._generate_inode_fields(inode, vmlinux_layer, filename)
def run(self):
headers = [
@@ -629,6 +640,7 @@ class InodePages(plugins.PluginInterface):
("Index", int),
("DumpSafe", bool),
("Flags", str),
("Output File", str),
]
return renderers.TreeGrid(
+19 -9
View File
@@ -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, 1, 0)
_version = (4, 1, 1)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -262,17 +262,27 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
init_task = vmlinux.object_from_symbol(symbol_name="init_task")
# 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
seen = set()
for forward in (True, False):
for task in init_task.tasks.to_list(
symbol_type=init_task.vol.type_name,
member="tasks",
forward=forward,
):
if task.vol.offset in seen:
continue
seen.add(task.vol.offset)
if filter_func(task):
continue
if not task.is_valid():
continue
yield task
if filter_func(task):
continue
if include_threads:
yield from task.get_threads()
yield task
if include_threads:
yield from task.get_threads()
def run(self):
pids = self.config.get("pid")
@@ -48,7 +48,7 @@ class Callbacks(interfaces.plugins.PluginInterface):
name="driverirp", component=driverirp.DriverIrp, version=(1, 0, 0)
),
requirements.VersionRequirement(
name="handles", component=handles.Handles, version=(3, 0, 0)
name="handles", component=handles.Handles, version=(4, 0, 0)
),
]
@@ -5,18 +5,12 @@
import logging
import ntpath
import re
from typing import List, Tuple, Type, Optional, Generator
from typing import Generator, List, Optional, Tuple, Type
from volatility3.framework import (
interfaces,
exceptions,
constants,
renderers,
)
from volatility3.framework import constants, exceptions, interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints
from volatility3.plugins.windows import handles
from volatility3.plugins.windows import pslist
from volatility3.plugins.windows import handles, pslist
vollog = logging.getLogger(__name__)
@@ -76,7 +70,7 @@ class DumpFiles(interfaces.plugins.PluginInterface):
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="handles", component=handles.Handles, version=(3, 0, 0)
name="handles", component=handles.Handles, version=(4, 0, 0)
),
]
@@ -231,14 +225,11 @@ class DumpFiles(interfaces.plugins.PluginInterface):
# private variables, so we need an instance (for now, anyway). We _could_ call Handles._generator()
# to do some of the other work that is duplicated here, but then we'd need to parse the TreeGrid
# results instead of just dealing with them as direct objects here.
handles_plugin = handles.Handles(
context=self.context, config_path=self._config_path
)
type_map = handles_plugin.get_type_map(
type_map = handles.Handles.get_type_map(
context=self.context,
kernel_module_name=self.config["kernel"],
)
cookie = handles_plugin.find_cookie(
cookie = handles.Handles.find_cookie(
context=self.context,
kernel_module_name=self.config["kernel"],
)
@@ -255,7 +246,11 @@ class DumpFiles(interfaces.plugins.PluginInterface):
)
continue
for entry in handles_plugin.handles(object_table):
for entry in handles.Handles.handles(
context=self.context,
kernel_module_name=self.config["kernel"],
handle_table=object_table,
):
try:
obj_type = entry.get_object_type(type_map, cookie)
if obj_type == "File":
@@ -0,0 +1,128 @@
# This file is Copyright 2025 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from volatility3.framework import exceptions, interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.plugins.windows import pslist, pe_symbols
vollog = logging.getLogger(__name__)
class EtwPatch(interfaces.plugins.PluginInterface):
"""Identifies ETW (Event Tracing for Windows) patching techniques used by malware to evade detection.
This plugin examines the first opcode of key ETW functions in ntdll.dll and advapi32.dll
to detect common ETW bypass techniques such as return pointer manipulation (RET) or function
redirection (JMP). Attackers often patch these functions to prevent security tools from
receiving telemetry about process execution, API calls, and other system events.
"""
_version = (1, 0, 0)
_required_framework_version = (2, 26, 0)
etw_functions = {
"ntdll.dll": {
pe_symbols.wanted_names_identifier: [
"EtwEventWrite",
"EtwEventWriteFull",
"NtTraceEvent",
],
},
"advapi32.dll": {
pe_symbols.wanted_names_identifier: ["EventWrite"],
},
}
@classmethod
def get_requirements(cls):
return [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="pslist", component=pslist.PsList, version=(3, 0, 0)
),
requirements.VersionRequirement(
name="pe_symbols", component=pe_symbols.PESymbols, version=(3, 0, 0)
),
requirements.ListRequirement(
name="pid",
description="Filter on specific process IDs",
element_type=int,
optional=True,
),
]
def _generator(self):
# Get all ETW function addresses before looping through processes
found_symbols = pe_symbols.PESymbols.addresses_for_process_symbols(
context=self.context,
config_path=self.config_path,
kernel_module_name=self.config["kernel"],
symbols=self.etw_functions,
)
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
for proc in pslist.PsList.list_processes(
context=self.context,
kernel_module_name=self.config["kernel"],
filter_func=filter_func,
):
try:
proc_id = proc.UniqueProcessId
proc_name = utility.array_to_string(proc.ImageFileName)
proc_layer_name = proc.add_process_layer()
except exceptions.InvalidAddressException:
vollog.debug(f"Unable to create process layer for PID {proc_id}")
continue
# Map of opcodes to their instruction names
opcode_map = {
0xC3: "RET",
0xE9: "JMP",
}
for dll_name, functions in found_symbols.items():
for func_name, func_addr in functions:
try:
opcode = self.context.layers[proc_layer_name].read(
func_addr, 1
)[0]
if opcode in opcode_map:
instruction = opcode_map[opcode]
yield (
0,
(
proc_id,
proc_name,
dll_name,
func_name,
format_hints.Hex(func_addr),
f"{opcode:02x} ({instruction})",
),
)
except exceptions.InvalidAddressException:
vollog.debug(
f"Invalid address when reading function {func_name} at {func_addr:#x} in process {proc_id}"
)
def run(self):
return renderers.TreeGrid(
[
("PID", int),
("Process", str),
("DLL", str),
("Function", str),
("Offset", format_hints.Hex),
("Opcode", str),
],
self._generator(),
)
@@ -3,7 +3,7 @@
#
import logging
from typing import Dict, List, Optional
from typing import Dict, Iterator, List, Optional
from volatility3.framework import constants, exceptions, interfaces, renderers, symbols
from volatility3.framework.configuration import requirements
@@ -18,13 +18,9 @@ class Handles(interfaces.plugins.PluginInterface):
"""Lists process open handles."""
_required_framework_version = (2, 0, 0)
_version = (3, 0, 0)
_version = (4, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._type_map = None
self._cookie = None
self._level_mask = 7
LEVEL_MASK = 7
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -54,18 +50,27 @@ class Handles(interfaces.plugins.PluginInterface):
),
]
def _get_item(self, handle_table_entry, handle_value):
"""Given a handle table entry (_HANDLE_TABLE_ENTRY) structure from a
@classmethod
def _get_item(
cls,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
handle_table_entry: interfaces.objects.ObjectInterface,
handle_value: int,
) -> Optional[interfaces.objects.ObjectInterface]:
"""
Given a handle table entry (_HANDLE_TABLE_ENTRY) structure from a
process' handle table, determine where the corresponding object's
_OBJECT_HEADER can be found."""
_OBJECT_HEADER can be found, and construct and return the _OBJECT_HEADER
"""
kernel = self.context.modules[self.config["kernel"]]
kernel = context.modules[kernel_module_name]
virtual = kernel.layer_name
try:
# before windows 7
if not self.context.layers[virtual].is_valid(handle_table_entry.Object):
if not context.layers[virtual].is_valid(handle_table_entry.Object):
return None
fast_ref = handle_table_entry.Object.cast("_EX_FAST_REF")
@@ -78,7 +83,7 @@ class Handles(interfaces.plugins.PluginInterface):
except AttributeError:
# starting with windows 8
is_64bit = symbols.symbol_table_is_64bit(
context=self.context, symbol_table_name=kernel.symbol_table_name
context=context, symbol_table_name=kernel.symbol_table_name
)
if is_64bit:
@@ -104,7 +109,7 @@ class Handles(interfaces.plugins.PluginInterface):
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(
object_header = context.object(
kernel.symbol_table_name + constants.BANG + "_OBJECT_HEADER",
virtual,
offset=offset,
@@ -205,11 +210,23 @@ class Handles(interfaces.plugins.PluginInterface):
offset=symbol_offset,
)
def _make_handle_array(self, offset, level, depth=0):
"""Parse a process' handle table and yield valid handle table entries,
going as deep into the table "levels" as necessary."""
@classmethod
def _make_handle_array(
cls,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
offset: int,
level: int,
depth: int = 0,
) -> Iterator[interfaces.objects.ObjectInterface]:
"""
Parses a process' handle table by constructing an array of
`_HANDLE_TABLE_ENTRY` structures at the given offset, and yields valid
handle table entries, going as deep into the table "levels" as
necessary.
"""
kernel = self.context.modules[self.config["kernel"]]
kernel = context.modules[kernel_module_name]
if level > 0:
subtype = kernel.get_type("pointer")
@@ -218,7 +235,7 @@ class Handles(interfaces.plugins.PluginInterface):
subtype = kernel.get_type("_HANDLE_TABLE_ENTRY")
count = 0x1000 / subtype.size
if not self.context.layers[kernel.layer_name].is_valid(offset):
if not context.layers[kernel.layer_name].is_valid(offset):
return None
table = kernel.object(
@@ -229,7 +246,7 @@ class Handles(interfaces.plugins.PluginInterface):
absolute=True,
)
layer_object = self.context.layers[kernel.layer_name]
layer_object = context.layers[kernel.layer_name]
masked_offset = offset & layer_object.maximum_address
for i in range(len(table)):
@@ -243,11 +260,13 @@ class Handles(interfaces.plugins.PluginInterface):
# 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[kernel.layer_name].is_valid(entry.vol.offset):
if not context.layers[kernel.layer_name].is_valid(entry.vol.offset):
continue
if level > 0:
yield from self._make_handle_array(entry, level - 1, depth)
yield from cls._make_handle_array(
context, kernel_module_name, entry, level - 1, depth
)
depth += 1
else:
handle_multiplier = 4
@@ -258,7 +277,7 @@ class Handles(interfaces.plugins.PluginInterface):
/ (subtype.size / handle_multiplier)
) + handle_level_base
item = self._get_item(entry, handle_value)
item = cls._get_item(context, kernel_module_name, entry, handle_value)
if item is None:
continue
@@ -272,10 +291,21 @@ class Handles(interfaces.plugins.PluginInterface):
except exceptions.InvalidAddressException:
continue
def handles(self, handle_table):
@classmethod
def handles(
cls,
context: interfaces.context.ContextInterface,
kernel_module_name: str,
handle_table: interfaces.objects.ObjectInterface,
) -> Iterator[interfaces.objects.ObjectInterface]:
"""
Takes a context, kernel module name, and handle table structure
(_HANDLE_TABLE), and yields _HANDLE_TABLE_ENTRY structures from the
handle table.
"""
try:
TableCode = handle_table.TableCode & ~self._level_mask
table_levels = handle_table.TableCode & self._level_mask
TableCode = handle_table.TableCode & ~cls.LEVEL_MASK
table_levels = handle_table.TableCode & cls.LEVEL_MASK
except exceptions.InvalidAddressException:
vollog.log(
constants.LOGLEVEL_VVV,
@@ -283,7 +313,9 @@ class Handles(interfaces.plugins.PluginInterface):
)
return None
yield from self._make_handle_array(TableCode, table_levels)
yield from cls._make_handle_array(
context, kernel_module_name, TableCode, table_levels
)
def _generator(self, procs):
type_map = self.get_type_map(
@@ -306,7 +338,9 @@ class Handles(interfaces.plugins.PluginInterface):
process_name = utility.array_to_string(proc.ImageFileName)
for entry in self.handles(object_table):
for entry in self.handles(
self.context, self.config["kernel"], object_table
):
try:
obj_type = entry.get_object_type(type_map, cookie)
if obj_type is None:
@@ -131,7 +131,7 @@ class PoolScanner(plugins.PluginInterface):
"""A generic pool scanner plugin."""
_required_framework_version = (2, 0, 0)
_version = (3, 0, 0)
_version = (3, 0, 1)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -142,7 +142,7 @@ class PoolScanner(plugins.PluginInterface):
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="handles", component=handles.Handles, version=(3, 0, 0)
name="handles", component=handles.Handles, version=(4, 0, 0)
),
requirements.VersionRequirement(
name="pool_header_scanner",
@@ -343,7 +343,7 @@ class PoolScanner(plugins.PluginInterface):
type_name=symbol_table + constants.BANG + "_OBJECT_SYMBOLIC_LINK",
object_type="SymbolicLink",
size=(72, None),
page_type=PoolType.NONPAGED | PoolType.FREE,
page_type=PoolType.PAGED | PoolType.FREE,
),
# symlinks on windows starting with windows 8
PoolConstraint(
@@ -351,7 +351,7 @@ class PoolScanner(plugins.PluginInterface):
type_name=symbol_table + constants.BANG + "_OBJECT_SYMBOLIC_LINK",
object_type="SymbolicLink",
size=(72, None),
page_type=PoolType.NONPAGED | PoolType.FREE,
page_type=PoolType.PAGED | PoolType.FREE,
),
# registry hives
PoolConstraint(
@@ -6,13 +6,13 @@ import datetime
import logging
from typing import Callable, Iterator, List, Optional, Type
from volatility3.framework import renderers, interfaces, layers, exceptions, constants
from volatility3.framework import constants, exceptions, interfaces, layers, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.windows.extensions import pe
from volatility3.framework.symbols.windows import extensions
from volatility3.framework.symbols.windows.extensions import pe
from volatility3.plugins import timeliner
vollog = logging.getLogger(__name__)
@@ -24,7 +24,7 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
_required_framework_version = (2, 0, 0)
# 3.0.0 - changed signature for `list_processes`
_version = (3, 0, 0)
_version = (3, 0, 1)
PHYSICAL_DEFAULT = False
@classmethod
@@ -261,9 +261,18 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
absolute=True,
)
for proc in eproc.ActiveProcessLinks:
if not filter_func(proc):
yield proc
seen = set()
for forward in (True, False):
for proc in eproc.ActiveProcessLinks.to_list(
symbol_type=eproc.vol.type_name,
member="ActiveProcessLinks",
forward=forward,
):
if proc.vol.offset in seen:
continue
seen.add(proc.vol.offset)
if not filter_func(proc):
yield proc
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
@@ -9,12 +9,7 @@ from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols.windows import extensions
from volatility3.plugins.windows import (
handles,
pslist,
psscan,
thrdscan,
)
from volatility3.plugins.windows import handles, pslist, psscan, thrdscan
vollog = logging.getLogger(__name__)
@@ -58,7 +53,7 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter
name="thrdscan", component=thrdscan.ThrdScan, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="handles", component=handles.Handles, version=(3, 0, 0)
name="handles", component=handles.Handles, version=(4, 0, 0)
),
requirements.BooleanRequirement(
name="physical-offsets",
@@ -144,15 +139,11 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter
) -> Dict[int, extensions.EPROCESS]:
ret: List[extensions.EPROCESS] = []
handles_plugin = handles.Handles(
context=self.context, config_path=self.config_path
)
type_map = handles_plugin.get_type_map(
type_map = handles.Handles.get_type_map(
context=self.context, kernel_module_name=self.config["kernel"]
)
cookie = handles_plugin.find_cookie(
cookie = handles.Handles.find_cookie(
context=self.context, kernel_module_name=self.config["kernel"]
)
@@ -164,7 +155,11 @@ We recommend using -r pretty if you are looking at this plugin's output in a ter
try:
ret += [
handle.Body.cast("_EPROCESS")
for handle in handles_plugin.handles(p.ObjectTable)
for handle in handles.Handles.handles(
context=self.context,
kernel_module_name=self.config["kernel"],
handle_table=p.ObjectTable,
)
if handle.get_object_type(type_map, cookie) == "Process"
]
except exceptions.InvalidAddressException:
@@ -174,6 +174,8 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
vad.get_start() + SHIM_NUM_ENTRIES_OFFSET,
)
vollog.debug(f"Found {num_entries} shimcache entries")
if num_entries > SHIM_MAX_ENTRIES:
continue
@@ -204,7 +206,6 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
if physical_addr in seen:
continue
seen.add(physical_addr)
shim_entry = proc_layer.context.object(
shimcache_symbol_table + constants.BANG + "SHIM_CACHE_ENTRY",
@@ -216,6 +217,8 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
if not shim_entry.is_valid():
continue
seen.add(physical_addr)
yield shim_entry
@classmethod
@@ -579,13 +582,19 @@ class ShimcacheMem(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterf
:return: The offset and size of the module, if found; Otherwise, returns `None`
"""
try:
krnl_mod = next(
module
for module in modules.Modules.list_modules(context, kernel_module_name)
if module.BaseDllName.String in module_list
)
except StopIteration:
krnl_mod = None
for module in modules.Modules.list_modules(context, kernel_module_name):
try:
if module.BaseDllName.String in module_list:
krnl_mod = module
break
except exceptions.InvalidAddressException as exc:
vollog.warning(
f"Failed to get kernel module due to {exc.__class__.__name__}: {exc.invalid_address:#x}"
)
if krnl_mod is None:
vollog.warning("Failed to find kernel module")
return None
kernel = context.modules[kernel_module_name]
@@ -1,15 +1,13 @@
# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
# This module attempts to locate skeleton-key like function hooks.
# It does this by locating the CSystems array through a variety of methods,
# and then validating the entry for RC4 HMAC (0x17 / 23)
# This module compares services found through list walking versus scanning,
# with the aim of finding hidden services.
#
# For a thorough walkthrough on how the R&D was performed to develop this plugin,
# please see our blogpost here:
# For background of hidden services and a real-world example of the use of this plugin,
# please see our blogpost:
#
# https://volatility-labs.blogspot.com/2021/10/memory-forensics-r-illustrated.html
# https://volatilityfoundation.org/memory-forensics-rd-illustrated-detecting-hidden-windows-services/
import logging
@@ -1,15 +1,17 @@
##
## plugin for testing addition of threads scan support to poolscanner.py
##
import logging
import datetime
from typing import Callable, Iterable, Tuple, Optional, Dict
import logging
from typing import Callable, Dict, NamedTuple, Optional, Union, Tuple, Iterator
from volatility3.framework import renderers, interfaces, exceptions
from volatility3.framework import exceptions, interfaces, objects, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.constants import windows as windows_constants
from volatility3.framework.renderers import format_hints
from volatility3.plugins.windows import poolscanner, pe_symbols
from volatility3.framework.symbols.windows import extensions as win_extensions
from volatility3.plugins import timeliner
from volatility3.plugins.windows import pe_symbols, poolscanner
vollog = logging.getLogger(__name__)
@@ -19,7 +21,18 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
# version 2.6.0 adds support for scanning for 'Ethread' structures by pool tags
_required_framework_version = (2, 6, 0)
_version = (2, 0, 0)
_version = (2, 1, 0)
class ThreadInfo(NamedTuple):
offset: int
pid: objects.Pointer
tid: objects.Pointer
start_addr: objects.Pointer
start_path: Optional[str]
win32_start_addr: objects.Pointer
win32_start_path: Optional[str]
create_time: Union[datetime.datetime, interfaces.renderers.BaseAbsentValue]
exit_time: Union[datetime.datetime, interfaces.renderers.BaseAbsentValue]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -51,7 +64,7 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
cls,
context: interfaces.context.ContextInterface,
module_name: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
) -> Iterator[win_extensions.ETHREAD]:
"""Scans for threads using the poolscanner module and constraints.
Args:
@@ -77,19 +90,9 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
@classmethod
def gather_thread_info(
cls,
ethread: interfaces.objects.ObjectInterface,
vads_cache: Dict[int, pe_symbols.ranges_type] = None,
) -> Tuple[
int,
int,
int,
int,
Optional[str],
int,
Optional[str],
Optional[datetime.datetime],
Optional[datetime.datetime],
]:
ethread: win_extensions.ETHREAD,
vads_cache: Optional[Dict[int, pe_symbols.ranges_type]] = None,
) -> Optional[ThreadInfo]:
try:
thread_offset = ethread.vol.offset
owner_proc_pid = ethread.Cid.UniqueProcess
@@ -110,11 +113,35 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
vollog.debug(f"Thread invalid address {ethread.vol.offset:#x}")
return None
# don't look for VADs in kernel threads, just let them get reported with empty paths
if owner_proc_pid != 4 and vads_cache is not None:
# Filter junk PIDs
if (
ethread.Cid.UniqueProcess > windows_constants.MAX_PID
or ethread.Cid.UniqueProcess == 0
or ethread.Cid.UniqueProcess % 4 != 0
):
return None
# Get VAD mappings for valid non-system (PID 4) processes
if (
owner_proc
and owner_proc.is_valid()
and owner_proc.UniqueProcessId != 4
and vads_cache is not None
):
vads = pe_symbols.PESymbols.get_vads_for_process_cache(
vads_cache, owner_proc
)
start_path = (
pe_symbols.PESymbols.filepath_for_address(vads, thread_start_addr)
if vads
else None
)
win32start_path = (
pe_symbols.PESymbols.filepath_for_address(vads, thread_win32start_addr)
if vads
else None
)
else:
vads = None
@@ -129,19 +156,19 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
else None
)
return (
format_hints.Hex(thread_offset),
return cls.ThreadInfo(
thread_offset,
owner_proc_pid,
thread_tid,
format_hints.Hex(thread_start_addr),
thread_start_addr,
start_path,
format_hints.Hex(thread_win32start_addr),
thread_win32start_addr,
win32start_path,
thread_create_time,
thread_exit_time,
)
def _generator(self, filter_func: Callable):
def _generator(self, filter_func: Callable) -> Iterator[Tuple[int, Tuple]]:
kernel_name = self.config["kernel"]
vads_cache: Dict[int, pe_symbols.ranges_type] = {}
@@ -150,27 +177,16 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
info = self.gather_thread_info(ethread, vads_cache)
if info:
(
offset,
pid,
tid,
start_addr,
start_path,
win32start_addr,
win32start_path,
create_time,
exit_time,
) = info
yield 0, (
offset,
pid,
tid,
start_addr,
start_path or renderers.NotAvailableValue(),
win32start_addr,
win32start_path or renderers.NotAvailableValue(),
create_time,
exit_time,
format_hints.Hex(info.offset),
info.pid,
info.tid,
format_hints.Hex(info.start_addr),
info.start_path or renderers.NotAvailableValue(),
format_hints.Hex(info.win32_start_addr),
info.win32_start_path or renderers.NotAvailableValue(),
info.create_time,
info.exit_time,
)
def generate_timeline(self):
@@ -471,6 +471,7 @@ class task_struct(generic.GenericIntelProcess):
return True
@functools.lru_cache
def add_process_layer(
self, config_prefix: Optional[str] = None, preferred_name: Optional[str] = None
) -> Optional[str]:
@@ -3064,14 +3065,9 @@ class kernel_symbol(objects.StructType):
else:
raise AttributeError("Unsupported kernel_symbol type implementation")
layer = self._context.layers[self.vol.layer_name]
name_bytes = layer.read(name_offset, linux_constants.KSYM_NAME_LEN)
idx = name_bytes.find(b"\x00")
if idx != -1:
name_bytes = name_bytes[:idx]
return name_bytes.decode("utf-8", errors="ignore")
return utility.pointer_to_string(
name_offset, linux_constants.KSYM_NAME_LEN, errors="ignore"
)
def get_name(self) -> Optional[str]:
try:
@@ -3107,14 +3103,9 @@ class kernel_symbol(objects.StructType):
else:
raise AttributeError("Unsupported kernel_symbol type implementation")
layer = self._context.layers[self.vol.layer_name]
namespace_bytes = layer.read(namespace_offset, linux_constants.KSYM_NAME_LEN)
idx = namespace_bytes.find(b"\x00")
if idx != -1:
namespace_bytes = namespace_bytes[:idx]
return namespace_bytes.decode("utf-8", errors="ignore")
return utility.pointer_to_string(
namespace_offset, linux_constants.KSYM_NAME_LEN, errors="ignore"
)
def get_namespace(self) -> Optional[str]:
try:
@@ -2,6 +2,7 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import contextlib
import functools
import logging
from typing import Generator, Iterable, Optional, Set, Tuple
@@ -17,6 +18,7 @@ class proc(generic.GenericIntelProcess):
def get_task(self):
return self.task.dereference().cast("task")
@functools.lru_cache
def add_process_layer(
self, config_prefix: Optional[str] = None, preferred_name: Optional[str] = None
) -> Optional[str]:
@@ -568,16 +568,20 @@ class ETHREAD(objects.StructType, pool.ExecutiveObject):
# passed all validations
return True
def get_create_time(self):
def get_create_time(
self,
) -> Union[datetime.datetime, interfaces.renderers.BaseAbsentValue]:
# For Windows XPs
if self.has_member("ThreadsProcess"):
return conversion.wintime_to_datetime(self.CreateTime.QuadPart >> 3)
return conversion.wintime_to_datetime(self.CreateTime.QuadPart)
def get_exit_time(self):
def get_exit_time(
self,
) -> Union[datetime.datetime, interfaces.renderers.BaseAbsentValue]:
return conversion.wintime_to_datetime(self.ExitTime.QuadPart)
def owning_process(self) -> interfaces.objects.ObjectInterface:
def owning_process(self) -> "EPROCESS":
"""Return the EPROCESS that owns this thread."""
# For Windows XPs
@@ -705,7 +709,11 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject):
return False
# NT pids are divisible by 4
if self.UniqueProcessId % 4 != 0:
if (
self.UniqueProcessId % 4 != 0
or self.UniqueProcessId == 0
or self.UniqueProcessId > constants.windows.MAX_PID
):
return False
# check for all 0s besides the PCID entries
@@ -732,9 +740,10 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject):
return True
@functools.lru_cache
def add_process_layer(
self, config_prefix: Optional[str] = None, preferred_name: Optional[str] = None
):
) -> str:
"""Constructs a new layer based on the process's DirectoryTableBase."""
parent_layer = self._context.layers[self.vol.layer_name]
@@ -8,6 +8,7 @@ from datetime import datetime
from typing import Dict, Optional, Tuple, Union
from volatility3.framework import constants, exceptions, interfaces, objects, renderers
from volatility3.framework.objects.utility import address_to_string
from volatility3.framework.symbols.windows.extensions import conversion
vollog = logging.getLogger(__name__)
@@ -38,39 +39,49 @@ class SHIM_CACHE_ENTRY(objects.StructType):
if self._exec_flag is not None:
return self._exec_flag
if hasattr(self, "ListEntryDetail") and hasattr(
self.ListEntryDetail, "InsertFlags"
):
self._exec_flag = self.ListEntryDetail.InsertFlags & 0x2 == 2
elif hasattr(self, "InsertFlags"):
self._exec_flag = self.InsertFlags & 0x2 == 2
elif hasattr(self, "ListEntryDetail") and hasattr(
self.ListEntryDetail, "BlobBuffer"
):
blob_offset = self.ListEntryDetail.BlobBuffer
blob_size = self.ListEntryDetail.BlobSize
if not self._context.layers[self.vol.native_layer_name].is_valid(
blob_offset, blob_size
try:
if hasattr(self, "ListEntryDetail") and hasattr(
self.ListEntryDetail, "InsertFlags"
):
self._exec_flag = renderers.UnparsableValue()
self._exec_flag = self.ListEntryDetail.InsertFlags & 0x2 == 2
raw_flag = self._context.layers[self.vol.native_layer_name].read(
blob_offset, blob_size
elif hasattr(self, "InsertFlags"):
self._exec_flag = self.InsertFlags & 0x2 == 2
elif hasattr(self, "ListEntryDetail") and hasattr(
self.ListEntryDetail, "BlobBuffer"
):
blob_offset = self.ListEntryDetail.BlobBuffer
blob_size = self.ListEntryDetail.BlobSize
if not self._context.layers[self.vol.native_layer_name].is_valid(
blob_offset, blob_size
):
self._exec_flag = renderers.UnreadableValue()
return self._exec_flag
raw_flag = self._context.layers[self.vol.native_layer_name].read(
blob_offset, blob_size
)
if not raw_flag:
self._exec_flag = renderers.UnparsableValue()
return self._exec_flag
try:
self._exec_flag = bool(struct.unpack("<I", raw_flag)[0])
except struct.error:
self._exec_flag = renderers.UnparsableValue()
except exceptions.InvalidAddressException:
vollog.debug(
"Failed to read SHIMCACHE_ENTRY exec flag due to invalid address exception"
)
if not raw_flag:
self._exec_flag = renderers.UnparsableValue()
try:
self._exec_flag = bool(struct.unpack("<I", raw_flag)[0])
except struct.error:
self._exec_flag = renderers.UnparsableValue()
self._exec_flag = renderers.UnreadableValue()
else:
# Always set to true for XP/2K3
self._exec_flag = renderers.NotApplicableValue()
return self._exec_flag
@property
@@ -117,6 +128,8 @@ class SHIM_CACHE_ENTRY(objects.StructType):
)
except AttributeError:
self._last_updated = renderers.NotApplicableValue()
except exceptions.InvalidAddressException:
self._last_updated = renderers.UnreadableValue()
return self._last_updated
@@ -126,8 +139,12 @@ class SHIM_CACHE_ENTRY(objects.StructType):
return self._file_path
if not hasattr(self.Path, "Buffer"):
return self.Path.cast(
"string", max_length=self.Path.vol.count, encoding="utf-16le"
return address_to_string(
self._context,
self.Path.vol.layer_name,
self.Path.vol.offset,
self.Path.vol.count,
encoding="utf-16le",
)
try:
@@ -331,23 +331,23 @@
"LastModified": {
"type": {
"kind": "union",
"name": "LARGE_INTEGER"
"name": "_LARGE_INTEGER"
},
"offset": 4
"offset": 528
},
"FileSize": {
"type": {
"kind": "base",
"name": "long long"
},
"offset": 8
"offset": 536
},
"LastUpdate": {
"type": {
"kind": "union",
"name": "LARGE_INTEGER"
"name": "_LARGE_INTEGER"
},
"offset": 12
"offset": 544
}
},
"kind": "struct",