Merge branch 'develop' into issue_985

This commit is contained in:
ikelos
2023-12-12 23:06:45 +00:00
committed by GitHub
71 changed files with 1034 additions and 285 deletions
+31
View File
@@ -0,0 +1,31 @@
name: Install Volatility3 test
on: [push, pull_request]
jobs:
install_test:
runs-on: ${{ matrix.host }}
strategy:
fail-fast: false
matrix:
host: [ ubuntu-latest, windows-latest ]
python-version: [ "3.7", "3.8", "3.9", "3.10", "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: Setup python-pip
run: python -m pip install --upgrade pip
- name: Install dependencies
run: |
pip install -r requirements.txt
- name: Install volatility3
run: pip install .
- name: Run volatility3
run: vol --help
+37
View File
@@ -0,0 +1,37 @@
# This CITATION.cff file was generated with cffinit.
# Visit https://bit.ly/cffinit to generate yours today!
cff-version: 1.2.0
title: Volatility 3
message: >-
If you reference this software, please feel free to cite
it using the information below.
type: software
authors:
- name: Volatility Foundation
country: US
website: 'https://www.volatilityfoundation.org/'
identifiers:
- type: url
value: 'https://github.com/volatilityfoundation/volatility3'
description: Volatility 3 source code respository
repository-code: 'https://github.com/volatilityfoundation/volatility3'
url: 'https://github.com/volatilityfoundation/volatility3'
abstract: >-
Volatility is the world's most widely used framework for
extracting digital artifacts from volatile memory (RAM)
samples. The extraction techniques are performed
completely independent of the system being investigated
but offer visibility into the runtime state of the system.
The framework is intended to introduce people to the
techniques and complexities associated with extracting
digital artifacts from volatile memory samples and provide
a platform for further work into this exciting area of
research.
keywords:
- malware
- forensics
- memory
- python
- ram
- volatility
+1 -1
View File
@@ -27,7 +27,7 @@ def setup(app):
source_dir = os.path.abspath(os.path.dirname(__file__))
sphinx.ext.apidoc.main(
argv=["-e", "-M", "-f", "-T", "-o", source_dir, volatility_directory]
["-e", "-M", "-f", "-T", "-o", source_dir, volatility_directory]
)
# Go through the volatility3.framework.plugins files and change them to volatility3.plugins
+6
View File
@@ -54,6 +54,12 @@ also be included, which can be found in `volatility3.constants.PLUGINS_PATH`.
volatility3.plugins.__path__ = <new_plugin_path> + constants.PLUGINS_PATH
failures = framework.import_files(volatility3.plugins, True)
.. note::
Volatility uses the `volatility3.plugins` namespace for all plugins (including those in `volatility3.framework.plugins`).
Please ensure you only use `volatility3.plugins` and only ever import plugins from this namespace.
This ensures the ability of users to override core plugins without needing write access to the framework directory.
Once the plugins have been imported, we can interrogate which plugins are available. The
:py:func:`~volatility3.framework.list_plugins` call will
return a dictionary of plugin names and the plugin classes.
+1 -1
View File
@@ -1,5 +1,5 @@
# The following packages are required for core functionality.
pefile>=2017.8.1
pefile>=2023.2.7
# The following packages are optional.
# If certain packages are not necessary, place a comment (#) at the start of the line.
+1 -1
View File
@@ -1,2 +1,2 @@
# These packages are required for core functionality.
pefile>=2017.8.1 #foo
pefile>=2023.2.7 #foo
+5 -1
View File
@@ -1,5 +1,5 @@
# The following packages are required for core functionality.
pefile>=2017.8.1
pefile>=2023.2.7
# The following packages are optional.
# If certain packages are not necessary, place a comment (#) at the start of the line.
@@ -16,3 +16,7 @@ pycryptodome
# This is required for memory acquisition via leechcore/pcileech.
leechcorepyc>=2.4.0
# This is required for memory analysis on a Amazon/MinIO S3 and Google Cloud object storage
gcsfs>=2023.1.0
s3fs>=2023.1.0
+6 -5
View File
@@ -12,7 +12,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
def get_install_requires():
requirements = []
with open("requirements-minimal.txt", "r", encoding = "utf-8") as fh:
with open("requirements-minimal.txt", "r", encoding="utf-8") as fh:
for line in fh.readlines():
stripped_line = line.strip()
if stripped_line == "" or stripped_line.startswith("#"):
@@ -20,6 +20,7 @@ def get_install_requires():
requirements.append(stripped_line)
return requirements
setuptools.setup(
name="volatility3",
description="Memory forensics framework",
@@ -36,12 +37,12 @@ setuptools.setup(
"Documentation": "https://volatility3.readthedocs.io/",
"Source Code": "https://github.com/volatilityfoundation/volatility3",
},
packages=setuptools.find_namespace_packages(
include=["volatility3", "volatility3.*"]
),
package_dir={"volatility3": "volatility3"},
python_requires=">=3.7.0",
include_package_data=True,
exclude_package_data={"": ["development", "development.*"], "development": ["*"]},
packages=setuptools.find_namespace_packages(
exclude=["development", "development.*"]
),
entry_points={
"console_scripts": [
"vol = volatility3.cli:main",
+2 -2
View File
@@ -662,7 +662,7 @@ class CommandLine:
def close(self):
# Don't overcommit
if self.closed:
return
return None
self.seek(0)
@@ -712,7 +712,7 @@ class CommandLine:
"""Closes and commits the file (by moving the temporary file to the correct name"""
# Don't overcommit
if self._file.closed:
return
return None
self._file.close()
output_filename = self._get_final_filename()
+3 -3
View File
@@ -108,7 +108,7 @@ class Volshell(interfaces.plugins.PluginInterface):
"""Describes the available commands"""
if args:
help(*args)
return
return None
variables = []
print("\nMethods:")
@@ -325,7 +325,7 @@ class Volshell(interfaces.plugins.PluginInterface):
(str, interfaces.objects.ObjectInterface, interfaces.objects.Template),
):
print("Cannot display information about non-type object")
return
return None
if not isinstance(object, str):
# Mypy requires us to order things this way
@@ -453,7 +453,7 @@ class Volshell(interfaces.plugins.PluginInterface):
"""Prints an alphabetical list of symbols for a symbol table"""
if symbol_table is None:
print("No symbol table provided")
return
return None
longest_offset = longest_name = 0
table = self.context.symbol_space[symbol_table]
+2 -2
View File
@@ -35,9 +35,9 @@ class Volshell(generic.Volshell):
process_layer = task.add_process_layer()
if process_layer is not None:
self.change_layer(process_layer)
return
return None
print(f"Layer for task ID {pid} could not be constructed")
return
return None
print(f"No task with task ID {pid} found")
def list_tasks(self):
+2 -2
View File
@@ -35,9 +35,9 @@ class Volshell(generic.Volshell):
process_layer = task.add_process_layer()
if process_layer is not None:
self.change_layer(process_layer)
return
return None
print(f"Layer for task ID {pid} could not be constructed")
return
return None
print(f"No task with task ID {pid} found")
def list_tasks(self, method=None):
+1 -1
View File
@@ -32,7 +32,7 @@ class Volshell(generic.Volshell):
if process.UniqueProcessId == pid:
process_layer = process.add_process_layer()
self.change_layer(process_layer)
return
return None
print(f"No process with process ID {pid} found")
def list_processes(self):
+3 -3
View File
@@ -29,9 +29,9 @@ class KernelModule(interfaces.automagic.AutomagicInterface):
requirement.requirements[req],
progress_callback,
)
return
return None
if not requirement.unsatisfied(context, config_path):
return
return None
# The requirement is unfulfilled and is a ModuleRequirement
context.config[
@@ -43,7 +43,7 @@ class KernelModule(interfaces.automagic.AutomagicInterface):
requirement.requirements[req].unsatisfied(context, new_config_path)
and req != "offset"
):
return
return None
# We now just have the offset requirement, but the layer requirement has been fulfilled.
# Unfortunately we don't know the layer name requirement's exact name
+4 -1
View File
@@ -103,7 +103,7 @@ class LayerStacker(interfaces.automagic.AutomagicInterface):
appropriate_config_path, layer_name = result
context.config.merge(appropriate_config_path, subconfig)
context.config[appropriate_config_path] = top_layer_name
return
return None
self._cached = None
new_context = context.clone()
@@ -156,6 +156,9 @@ class LayerStacker(interfaces.automagic.AutomagicInterface):
self._cached = context.config.get(path, None), context.config.branch(
path
)
vollog.debug(
f"physical_layer maximum_address: {physical_layer.maximum_address}"
)
vollog.debug(f"Stacked layers: {stacked_layers}")
@classmethod
@@ -429,7 +429,7 @@ class SqliteCache(CacheManagerInterface):
progress_callback(0, "Reading remote ISF list")
cursor = self._database.cursor()
cursor.execute(
f"SELECT cached FROM cache WHERE local = 0 and cached < datetime('now', {self.cache_period})"
f"SELECT cached FROM cache WHERE local = 0 and cached < datetime('now', '{self.cache_period}')"
)
remote_identifiers = RemoteIdentifierFormat(constants.REMOTE_ISF_URL)
progress_callback(50, "Reading remote ISF list")
@@ -438,9 +438,13 @@ class SqliteCache(CacheManagerInterface):
{}, operating_system=operating_system
)
for identifier, location in identifiers:
identifier = identifier.rstrip()
identifier = (
identifier[:-1] if identifier.endswith(b"\x00") else identifier
) # Linux banners dumped by dwarf2json end with "\x00\n". If not stripped, the banner cannot match.
cursor.execute(
"INSERT OR REPLACE INTO cache(identifier, location, operating_system, local, cached) VALUES (?, ?, ?, ?, datetime('now'))",
(location, identifier, operating_system, False),
(identifier, location, operating_system, False),
)
progress_callback(100, "Reading remote ISF list")
self._database.commit()
@@ -69,7 +69,7 @@ class SymbolFinder(interfaces.automagic.AutomagicInterface):
# Bomb out early if our details haven't been configured
if self.symbol_class is None:
return
return None
self._requirements = self.find_requirements(
context,
@@ -120,7 +120,7 @@ class SymbolFinder(interfaces.automagic.AutomagicInterface):
# Bomb out early if there's no banners
if not self.banners:
return
return None
mss = scanners.MultiStringScanner([x for x in self.banners if x is not None])
+1 -1
View File
@@ -45,7 +45,7 @@ BANG = "!"
# We use the SemVer 2.0.0 versioning scheme
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
VERSION_MINOR = 5 # Number of changes that only add to the interface
VERSION_PATCH = 0 # Number of changes that do not change the interface
VERSION_PATCH = 2 # Number of changes that do not change the interface
VERSION_SUFFIX = ""
# TODO: At version 2.0.0, remove the symbol_shift feature
@@ -279,3 +279,5 @@ CAPABILITIES = (
"bpf",
"checkpoint_restore",
)
ELF_MAX_EXTRACTION_SIZE = 1024 * 1024 * 1024 * 4 - 1
+3 -7
View File
@@ -678,16 +678,12 @@ class LayerContainer(collections.abc.Mapping):
name: The name of the layer to delete
"""
for layer in self._layers:
depend_list = [
superlayer
for superlayer in self._layers
if name in self._layers[layer].dependencies
]
if depend_list:
if name in self._layers[layer].dependencies:
raise exceptions.LayerException(
self._layers[layer].name,
f"Layer {self._layers[layer].name} is depended upon: {', '.join(depend_list)}",
f"Layer {self._layers[layer].name} is depended upon by {layer}",
)
# Otherwise, wipe out the layer
self._layers[name].destroy()
del self._layers[name]
+13 -1
View File
@@ -43,7 +43,7 @@ class FileHandlerInterface(io.RawIOBase):
return self._preferred_filename
@preferred_filename.setter
def preferred_filename(self, filename):
def preferred_filename(self, filename: str):
"""Sets the preferred filename"""
if self.closed:
raise IOError("FileHandler name cannot be changed once closed")
@@ -57,6 +57,18 @@ class FileHandlerInterface(io.RawIOBase):
def close(self):
"""Method that commits the file and fixes the final filename for use"""
@staticmethod
def sanitize_filename(filename: str) -> str:
"""Sanititizes the filename to ensure only a specific whitelist of characters is allowed through"""
allowed = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.- ()[]\{\}!$%^:#~?<>,|"
result = ""
for char in filename:
if char in allowed:
result += char
else:
result += "?"
return result
def __enter__(self):
return self
@@ -0,0 +1,57 @@
# This file is Copyright 2022 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
import urllib.parse
from typing import Optional, Any, List
try:
import s3fs
HAS_S3FS = True
except ImportError:
HAS_S3FS = False
try:
import gcsfs
HAS_GCSFS = True
except ImportError:
HAS_GCSFS = False
from volatility3.framework import exceptions
from volatility3.framework.layers import resources
vollog = logging.getLogger(__file__)
if HAS_S3FS:
class S3FileSystemHandler(resources.VolatilityHandler):
@classmethod
def non_cached_schemes(cls) -> List[str]:
return ["s3"]
@staticmethod
def default_open(req: urllib.request.Request) -> Optional[Any]:
"""Handles the request if it's the s3 scheme."""
if req.type == "s3":
object_uri = "://".join(req.full_url.split("://")[1:])
return s3fs.S3FileSystem().open(object_uri)
return None
if HAS_GCSFS:
class GSFileSystemHandler(resources.VolatilityHandler):
@classmethod
def non_cached_schemes(cls) -> List[str]:
return ["gs"]
@staticmethod
def default_open(req: urllib.request.Request) -> Optional[Any]:
"""Handles the request if it's the gs scheme."""
if req.type == "gs":
object_uri = "://".join(req.full_url.split("://")[1:])
return gcsfs.GCSFileSystem().open(object_uri)
return None
+11 -2
View File
@@ -111,6 +111,11 @@ class Intel(linear.LinearlyMappedLayer):
"""Returns whether a particular page is valid based on its entry."""
return bool(entry & 1)
@staticmethod
def _page_is_dirty(entry: int) -> bool:
"""Returns whether a particular page is dirty based on its entry."""
return bool(entry & (1 << 6))
def canonicalize(self, addr: int) -> int:
"""Canonicalizes an address by performing an appropiate sign extension on the higher addresses"""
if self._bits_per_register <= self._maxvirtaddr:
@@ -259,6 +264,10 @@ class Intel(linear.LinearlyMappedLayer):
except exceptions.InvalidAddressException:
return False
def is_dirty(self, offset: int) -> bool:
"""Returns whether the page at offset is marked dirty"""
return self._page_is_dirty(self._translate_entry(offset)[0])
def mapping(
self, offset: int, length: int, ignore_errors: bool = False
) -> Iterable[Tuple[int, int, int, int, str]]:
@@ -322,9 +331,9 @@ class Intel(linear.LinearlyMappedLayer):
except exceptions.InvalidAddressException:
if not ignore_errors:
raise
return
return None
yield offset, length, mapped_offset, length, layer_name
return
return None
while length > 0:
try:
chunk_offset, page_size, layer_name = self._translate(offset)
+1 -1
View File
@@ -47,7 +47,7 @@ class PdbMultiStreamFormat(linear.LinearlyMappedLayer):
def read_streams(self):
# Shortcut in case they've already been read
if self._streams:
return
return None
# Recover the root table, by recovering the root table index table...
module = self.context.module(self.pdb_symbol_table, self._base_layer, offset=0)
+9 -1
View File
@@ -171,7 +171,15 @@ class RegistryHive(linear.LinearlyMappedLayer):
node (default) or a list of nodes from root to the current node
(if return_list is true).
"""
node_key = [self.get_node(self.root_cell_offset)]
root_node = self.get_node(self.root_cell_offset)
if not root_node.vol.type_name.endswith(constants.BANG + "_CM_KEY_NODE"):
raise RegistryFormatException(
self.name,
"Encountered {} instead of _CM_KEY_NODE".format(
root_node.vol.type_name
),
)
node_key = [root_node]
if key.endswith("\\"):
key = key[:-1]
key_array = key.split("\\")
+2 -2
View File
@@ -126,9 +126,9 @@ class NonLinearlySegmentedLayer(
current_offset = logical_offset
# If it starts too late then we're done
if logical_offset > offset + length:
return
return None
except exceptions.InvalidAddressException:
return
return None
# Crop it to the amount we need left
chunk_size = min(size, length + offset - logical_offset)
yield logical_offset, chunk_size, mapped_offset, mapped_size, self._base_layer
+6
View File
@@ -4,6 +4,7 @@
import contextlib
import logging
import struct
import os
from typing import Any, Dict, List, Optional
from volatility3.framework import constants, exceptions, interfaces
@@ -232,6 +233,11 @@ class VmwareStacker(interfaces.automagic.StackerLayerInterface):
)
if not vmss_success and not vmsn_success:
vmem_file_basename = os.path.basename(location)
example_vmss_file_basename = os.path.basename(vmss)
vollog.warning(
f"No metadata file found alongside VMEM file. A VMSS or VMSN file may be required to correctly process a VMEM file. These should be placed in the same directory with the same file name, e.g. {vmem_file_basename} and {example_vmss_file_basename}.",
)
return None
new_layer_name = context.layers.free_layer_name("VmwareLayer")
context.config[
+2 -1
View File
@@ -44,8 +44,9 @@ def array_of_pointers(
raise TypeError(
"Subtype must be a valid template (or string name of an object template)"
)
# We have to clone the pointer class, or we'll be defining the pointer subtype for all future pointers
subtype_pointer = context.symbol_space.get_type(
symbol_table + constants.BANG + "pointer"
)
).clone()
subtype_pointer.update_vol(subtype=subtype)
return array.cast("array", count=count, subtype=subtype_pointer)
+7 -2
View File
@@ -75,11 +75,16 @@ class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface):
bang_addrs = []
# get task memory sections to be used by scanners
task_memory_sections = [
section for section in task.get_process_memory_sections(heap_only=True)
]
# find '#' values on the heap
for address in proc_layer.scan(
self.context,
scanners.BytesScanner(b"#"),
sections=task.get_process_memory_sections(heap_only=True),
sections=task_memory_sections,
):
bang_addrs.append(struct.pack(pack_format, address))
@@ -89,7 +94,7 @@ class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface):
for address, _ in proc_layer.scan(
self.context,
scanners.MultiStringScanner(bang_addrs),
sections=task.get_process_memory_sections(heap_only=True),
sections=task_memory_sections,
):
hist = self.context.object(
bash_table_name + constants.BANG + "hist_entry",
@@ -88,7 +88,7 @@ class Capabilities(plugins.PluginInterface):
kernel_cap_last_cap = vmlinux.object_from_symbol(symbol_name="cap_last_cap")
except exceptions.SymbolError:
# It should be a kernel < 3.2
return
return None
vol2_last_cap = extensions.kernel_cap_struct.get_last_cap_value()
if kernel_cap_last_cap > vol2_last_cap:
@@ -51,10 +51,22 @@ class Check_afinfo(plugins.PluginInterface):
yield check, addr
def _check_afinfo(self, var_name, var, op_members, seq_members):
for hooked_member, hook_address in self._check_members(
var.seq_fops, var_name, op_members
):
yield var_name, hooked_member, hook_address
# check if object has a least one of the members used for analysis by this function
required_members = ["seq_fops", "seq_ops", "seq_show"]
has_required_member = any(
[var.has_member(member) for member in required_members]
)
if not has_required_member:
vollog.debug(
f"{var_name} object at {hex(var.vol.offset)} had none of the required members: {', '.join([member for member in required_members])}"
)
raise exceptions.PluginRequirementException
if var.has_member("seq_fops"):
for hooked_member, hook_address in self._check_members(
var.seq_fops, var_name, op_members
):
yield var_name, hooked_member, hook_address
# newer kernels
if var.has_member("seq_ops"):
@@ -64,8 +76,10 @@ class Check_afinfo(plugins.PluginInterface):
yield var_name, hooked_member, hook_address
# this is the most commonly hooked member by rootkits, so a force a check on it
elif not self._is_known_address(var.seq_show):
yield var_name, "show", var.seq_show
else:
if var.has_member("seq_show"):
if not self._is_known_address(var.seq_show):
yield var_name, "show", var.seq_show
def _generator(self):
vmlinux = self.context.modules[self.config["kernel"]]
@@ -85,6 +99,12 @@ class Check_afinfo(plugins.PluginInterface):
)
protocols = [tcp, udp]
# used to track the calls to _check_afinfo and the
# number of errors produced due to missing members
symbols_checked = set()
symbols_with_errors = set()
# loop through all symbols
for struct_type, global_vars in protocols:
for global_var_name in global_vars:
# this will lookup fail for the IPv6 protocols on kernels without IPv6 support
@@ -97,10 +117,20 @@ class Check_afinfo(plugins.PluginInterface):
object_type=struct_type, offset=global_var.address
)
for name, member, address in self._check_afinfo(
global_var_name, global_var, op_members, seq_members
):
yield 0, (name, member, format_hints.Hex(address))
symbols_checked.add(global_var_name)
try:
for name, member, address in self._check_afinfo(
global_var_name, global_var, op_members, seq_members
):
yield 0, (name, member, format_hints.Hex(address))
except exceptions.PluginRequirementException:
symbols_with_errors.add(global_var_name)
# if every call to _check_afinfo failed show a warning
if symbols_checked == symbols_with_errors:
vollog.warning(
"This plugin was not able to check for hooks. This means you are either analyzing an unsupported kernel version or that your symbol table is corrupt."
)
def run(self):
return renderers.TreeGrid(
@@ -145,7 +145,7 @@ class Check_syscall(plugins.PluginInterface):
table_info = self._get_table_info(vmlinux, "sys_call_table", ptr_sz)
except exceptions.SymbolError:
vollog.error("Unable to find the system call table. Exiting.")
return
return None
tables = [(table_name, table_info)]
+109 -2
View File
@@ -4,20 +4,26 @@
"""A module containing a collection of plugins that produce data typically
found in Linux's /proc file system."""
from typing import List
import logging
from typing import List, Optional, Type
from volatility3.framework import renderers, interfaces
from volatility3.framework import constants, interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.linux.extensions import elf
from volatility3.plugins.linux import pslist
vollog = logging.getLogger(__name__)
class Elfs(plugins.PluginInterface):
"""Lists all memory mapped ELF files for all processes."""
_required_framework_version = (2, 0, 0)
_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -36,9 +42,93 @@ class Elfs(plugins.PluginInterface):
element_type=int,
optional=True,
),
requirements.BooleanRequirement(
name="dump",
description="Extract listed processes",
default=False,
optional=True,
),
]
@classmethod
def elf_dump(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
elf_table_name: str,
vma: interfaces.objects.ObjectInterface,
task: interfaces.objects.ObjectInterface,
open_method: Type[interfaces.plugins.FileHandlerInterface],
) -> Optional[interfaces.plugins.FileHandlerInterface]:
"""Extracts an ELF as a FileHandlerInterface
Args:
context: the context to operate upon
layer_name: The name of the layer on which to operate
elf_table_name: the name for the symbol table containing the symbols for ELF-files
vma: virtual memory allocation of ELF
task: the task object whose memory should be output
open_method: class to provide context manager for opening the file
Returns:
An open FileHandlerInterface object containing the complete data for the task or None in the case of failure
"""
proc_layer = context.layers[layer_name]
file_handle = None
elf_object = context.object(
elf_table_name + constants.BANG + "Elf",
offset=vma.vm_start,
layer_name=layer_name,
)
if not elf_object.is_valid():
return None
sections = {}
# TODO: Apply more effort to reconstruct ELF, e.g.: https://github.com/enbarberis/core2ELF64 ?
for phdr in elf_object.get_program_headers():
if phdr.p_type != 1: # PT_LOAD = 1
continue
start = phdr.p_vaddr
size = phdr.p_memsz
end = start + size
# Use complete memory pages for dumping
# If start isn't a multiple of 4096, stick to the highest multiple < start
# If end isn't a multiple of 4096, stick to the lowest multiple > end
if start % 4096:
start = start & ~0xFFF
if end % 4096:
end = (end & ~0xFFF) + 4096
real_size = end - start
# Check if ELF has a legitimate size
if real_size < 0 or real_size > constants.linux.ELF_MAX_EXTRACTION_SIZE:
raise ValueError(f"The claimed size of the ELF is invalid: {real_size}")
sections[start] = real_size
elf_data = b""
for section_start in sorted(sections.keys()):
read_size = sections[section_start]
buf = proc_layer.read(vma.vm_start + section_start, read_size, pad=True)
elf_data = elf_data + buf
file_handle = open_method(
f"pid.{task.pid}.{utility.array_to_string(task.comm)}.{vma.vm_start:#x}.dmp"
)
file_handle.write(elf_data)
return file_handle
def _generator(self, tasks):
elf_table_name = intermed.IntermediateSymbolTable.create(
self.context, self.config_path, "linux", "elf", class_types=elf.class_types
)
for task in tasks:
proc_layer_name = task.add_process_layer()
if not proc_layer_name:
@@ -60,6 +150,21 @@ class Elfs(plugins.PluginInterface):
path = vma.get_name(self.context, task)
file_output = "Disabled"
if self.config["dump"]:
file_handle = self.elf_dump(
self.context,
proc_layer_name,
elf_table_name,
vma,
task,
self.open,
)
file_output = "Error outputting file"
if file_handle:
file_handle.close()
file_output = str(file_handle.preferred_filename)
yield (
0,
(
@@ -68,6 +173,7 @@ class Elfs(plugins.PluginInterface):
format_hints.Hex(vma.vm_start),
format_hints.Hex(vma.vm_end),
path,
file_output,
),
)
@@ -81,6 +187,7 @@ class Elfs(plugins.PluginInterface):
("Start", format_hints.Hex),
("End", format_hints.Hex),
("File Path", str),
("File Output", str),
],
self._generator(
pslist.PsList.list_tasks(
+2 -2
View File
@@ -16,7 +16,7 @@ class IOMem(interfaces.plugins.PluginInterface):
"""Generates an output similar to /proc/iomem on a running system."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
_version = (1, 0, 1)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
@@ -53,7 +53,7 @@ class IOMem(interfaces.plugins.PluginInterface):
# create the resource object with protection against memory smear
try:
resource = vmlinux.object("resource", resource_offset)
resource = vmlinux.object("resource", resource_offset, absolute=True)
except exceptions.InvalidAddressException:
vollog.warning(
f"Unable to create resource object at {resource_offset:#x}. This resource, "
+12 -3
View File
@@ -3,7 +3,7 @@
#
from typing import List
import logging
from volatility3.framework import constants, interfaces
from volatility3.framework import renderers
from volatility3.framework.configuration import requirements
@@ -11,6 +11,8 @@ from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.plugins.linux import pslist
vollog = logging.getLogger(__name__)
class Malfind(interfaces.plugins.PluginInterface):
"""Lists process memory ranges that potentially contain injected code."""
@@ -42,12 +44,19 @@ class Malfind(interfaces.plugins.PluginInterface):
proc_layer_name = task.add_process_layer()
if not proc_layer_name:
return
return None
proc_layer = self.context.layers[proc_layer_name]
for vma in task.mm.get_vma_iter():
if vma.is_suspicious() and vma.get_name(self.context, task) != "[vdso]":
vma_name = vma.get_name(self.context, task)
vollog.debug(
f"Injections : processing PID {task.pid} : VMA {vma_name} : {hex(vma.vm_start)}-{hex(vma.vm_end)}"
)
if (
vma.is_suspicious(proc_layer)
and vma.get_name(self.context, task) != "[vdso]"
):
data = proc_layer.read(vma.vm_start, 64, pad=True)
yield vma, data
+69 -32
View File
@@ -1,12 +1,15 @@
# This file is Copyright 2021 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from typing import Callable, Iterable, List, Any, Tuple
from typing import Any, Callable, Iterable, List
from volatility3.framework import renderers, interfaces
from volatility3.framework import interfaces, 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.linux.extensions import elf
from volatility3.plugins.linux import elfs
class PsList(interfaces.plugins.PluginInterface):
@@ -24,6 +27,9 @@ class PsList(interfaces.plugins.PluginInterface):
description="Linux kernel",
architectures=["Intel32", "Intel64"],
),
requirements.PluginRequirement(
name="elfs", plugin=elfs.Elfs, version=(2, 0, 0)
),
requirements.ListRequirement(
name="pid",
description="Filter on specific process IDs",
@@ -42,6 +48,12 @@ class PsList(interfaces.plugins.PluginInterface):
optional=True,
default=False,
),
requirements.BooleanRequirement(
name="dump",
description="Extract listed processes",
optional=True,
default=False,
),
]
@classmethod
@@ -66,38 +78,12 @@ class PsList(interfaces.plugins.PluginInterface):
else:
return lambda _: False
def _get_task_fields(
self, task: interfaces.objects.ObjectInterface, decorate_comm: bool = False
) -> Tuple[int, int, int, str]:
"""Extract the fields needed for the final output
Args:
task: A task object from where to get the fields.
decorate_comm: If True, it decorates the comm string of
- User threads: in curly brackets,
- Kernel threads: in square brackets
Defaults to False.
Returns:
A tuple with the fields to show in the plugin output.
"""
pid = task.tgid
tid = task.pid
ppid = task.parent.tgid if task.parent else 0
name = utility.array_to_string(task.comm)
if decorate_comm:
if task.is_kernel_thread:
name = f"[{name}]"
elif task.is_user_thread:
name = f"{{{name}}}"
task_fields = (format_hints.Hex(task.vol.offset), pid, tid, ppid, name)
return task_fields
def _generator(
self,
pid_filter: Callable[[Any], bool],
include_threads: bool = False,
decorate_comm: bool = False,
dump: bool = False,
):
"""Generates the tasks list.
@@ -110,14 +96,63 @@ class PsList(interfaces.plugins.PluginInterface):
- User threads: in curly brackets,
- Kernel threads: in square brackets
Defaults to False.
dump: If True, the main executable of the process is written to a file
Defaults to False.
Yields:
Each rows
"""
for task in self.list_tasks(
self.context, self.config["kernel"], pid_filter, include_threads
):
row = self._get_task_fields(task, decorate_comm)
yield (0, row)
elf_table_name = intermed.IntermediateSymbolTable.create(
self.context,
self.config_path,
"linux",
"elf",
class_types=elf.class_types,
)
file_output = "Disabled"
if dump:
proc_layer_name = task.add_process_layer()
if not proc_layer_name:
continue
# Find the vma that belongs to the main ELF of the process
file_output = "Error outputting file"
for v in task.mm.get_mmap_iter():
if v.vm_start == task.mm.start_code:
file_handle = elfs.Elfs.elf_dump(
self.context,
proc_layer_name,
elf_table_name,
v,
task,
self.open,
)
if file_handle:
file_output = str(file_handle.preferred_filename)
file_handle.close()
break
pid = task.tgid
tid = task.pid
ppid = task.parent.tgid if task.parent else 0
name = utility.array_to_string(task.comm)
if decorate_comm:
if task.is_kernel_thread:
name = f"[{name}]"
elif task.is_user_thread:
name = f"{{{name}}}"
yield 0, (
format_hints.Hex(task.vol.offset),
pid,
tid,
ppid,
name,
file_output,
)
@classmethod
def list_tasks(
@@ -155,6 +190,7 @@ class PsList(interfaces.plugins.PluginInterface):
pids = self.config.get("pid")
include_threads = self.config.get("threads")
decorate_comm = self.config.get("decorate_comm")
dump = self.config.get("dump")
filter_func = self.create_pid_filter(pids)
columns = [
@@ -163,7 +199,8 @@ class PsList(interfaces.plugins.PluginInterface):
("TID", int),
("PPID", int),
("COMM", str),
("File output", str),
]
return renderers.TreeGrid(
columns, self._generator(filter_func, include_threads, decorate_comm)
columns, self._generator(filter_func, include_threads, decorate_comm, dump)
)
@@ -147,7 +147,7 @@ class SockHandlers(interfaces.configuration.VersionableInterface):
socket_filter["bpf_filter_type"] = "cBPF"
if not sock_filter.has_member("prog") or not sock_filter.prog:
return
return None
bpfprog = sock_filter.prog
@@ -158,13 +158,13 @@ class SockHandlers(interfaces.configuration.VersionableInterface):
return # cBPF filter
except AttributeError:
# kernel < 3.18.140, it's a cBPF filter
return
return None
BPF_PROG_TYPE_SOCKET_FILTER = 1 # eBPF filter
if bpfprog_type != BPF_PROG_TYPE_SOCKET_FILTER:
socket_filter["bpf_filter_type"] = f"UNK({bpfprog_type})"
vollog.warning(f"Unexpected BPF type {bpfprog_type} for a socket")
return
return None
socket_filter["bpf_filter_type"] = "eBPF"
if not bpfprog.has_member("aux") or not bpfprog.aux:
@@ -329,17 +329,17 @@ class SockHandlers(interfaces.configuration.VersionableInterface):
xdp_sock = sock.cast("xdp_sock")
device = xdp_sock.dev
if not device:
return
return None
src_addr = utility.array_to_string(device.name)
src_port = dst_addr = dst_port = None
bpfprog = device.xdp_prog
if not bpfprog:
return
return None
if not bpfprog.has_member("aux") or not bpfprog.aux:
return
return None
bpfprog_aux = bpfprog.aux
if bpfprog_aux.has_member("id"):
@@ -0,0 +1,113 @@
# This file is Copyright 2023 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from typing import Iterable, List, Tuple
from volatility3.framework import interfaces, renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.renderers import format_hints
from volatility3.plugins import yarascan
from volatility3.plugins.linux import pslist
class VmaYaraScan(interfaces.plugins.PluginInterface):
"""Scans all virtual memory areas for tasks using yara."""
_required_framework_version = (2, 4, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# create a list of requirements for vmayarascan
vmayarascan_requirements = [
requirements.ListRequirement(
name="pid",
element_type=int,
description="Process IDs to include (all other processes are excluded)",
optional=True,
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="yarascan", plugin=yarascan.YaraScan, version=(1, 2, 0)
),
requirements.VersionRequirement(
name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0)
),
requirements.ModuleRequirement(
name="kernel",
description="Linux kernel",
architectures=["Intel32", "Intel64"],
),
]
# get base yarascan requirements for command line options
yarascan_requirements = yarascan.YaraScan.get_yarascan_option_requirements()
# return the combined requirements
return yarascan_requirements + vmayarascan_requirements
def _generator(self):
# use yarascan to parse the yara options provided and create the rules
rules = yarascan.YaraScan.process_yara_options(dict(self.config))
# filter based on the pid option if provided
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
for task in pslist.PsList.list_tasks(
context=self.context,
vmlinux_module_name=self.config["kernel"],
filter_func=filter_func,
):
# attempt to create a process layer for each task and skip those
# that cannot (e.g. kernel threads)
proc_layer_name = task.add_process_layer()
if not proc_layer_name:
continue
# get the proc_layer object from the context
proc_layer = self.context.layers[proc_layer_name]
# scan the process layer with the yarascanner
for offset, rule_name, name, value in proc_layer.scan(
context=self.context,
scanner=yarascan.YaraScanner(rules=rules),
sections=self.get_vma_maps(task),
):
yield 0, (
format_hints.Hex(offset),
task.tgid,
rule_name,
name,
value,
)
@staticmethod
def get_vma_maps(
task: interfaces.objects.ObjectInterface,
) -> Iterable[Tuple[int, int]]:
"""Creates a map of start/end addresses for each virtual memory area in a task.
Args:
task: The task object of which to read the vmas from
Returns:
An iterable of tuples containing start and end addresses for each descriptor
"""
if task.mm:
for vma in task.mm.get_vma_iter():
vm_size = vma.vm_end - vma.vm_start
yield (vma.vm_start, vm_size)
def run(self):
return renderers.TreeGrid(
[
("Offset", format_hints.Hex),
("PID", int),
("Rule", str),
("Component", str),
("Value", bytes),
],
self._generator(),
)
@@ -69,7 +69,7 @@ class Check_sysctl(plugins.PluginInterface):
try:
sysctl = sysctl.oid_link.sle_next.dereference()
except exceptions.InvalidAddressException:
return
return None
while sysctl:
try:
+2 -2
View File
@@ -116,7 +116,7 @@ class Kevents(interfaces.plugins.PluginInterface):
)
except exceptions.InvalidAddressException:
return
return None
for klist in klist_array:
for kn in mac.MacUtilities.walk_slist(klist, "kn_link"):
@@ -140,7 +140,7 @@ class Kevents(interfaces.plugins.PluginInterface):
try:
p_klist = task.p_klist
except exceptions.InvalidAddressException:
return
return None
for kn in mac.MacUtilities.walk_slist(p_klist, "kn_link"):
yield kn
+1 -1
View File
@@ -75,7 +75,7 @@ class Lsmod(plugins.PluginInterface):
try:
kmod = kmod.next
except exceptions.InvalidAddressException:
return
return None
return # Generation finished
def _generator(self):
+1 -1
View File
@@ -40,7 +40,7 @@ class Malfind(interfaces.plugins.PluginInterface):
proc_layer_name = task.add_process_layer()
if proc_layer_name is None:
return
return None
proc_layer = self.context.layers[proc_layer_name]
@@ -108,12 +108,12 @@ class Cachedump(interfaces.plugins.PluginInterface):
vollog.warning("Unable to locate SYSTEM hive")
if sechive is None:
vollog.warning("Unable to locate SECURITY hive")
return
return None
bootkey = hashdump.Hashdump.get_bootkey(syshive)
if not bootkey:
vollog.warning("Unable to find bootkey")
return
return None
kernel = self.context.modules[self.config["kernel"]]
@@ -124,17 +124,17 @@ class Cachedump(interfaces.plugins.PluginInterface):
lsakey = lsadump.Lsadump.get_lsa_key(sechive, bootkey, vista_or_later)
if not lsakey:
vollog.warning("Unable to find lsa key")
return
return None
nlkm = self.get_nlkm(sechive, lsakey, vista_or_later)
if not nlkm:
vollog.warning("Unable to find nlkma key")
return
return None
cache = hashdump.Hashdump.get_hive_key(sechive, "Cache")
if not cache:
vollog.warning("Unable to find cache key")
return
return None
for cache_item in cache.get_values():
if cache_item.Name == "NL$Control":
@@ -157,7 +157,7 @@ class Callbacks(interfaces.plugins.PluginInterface):
)
if callback_count == 0:
return
return None
fast_refs = ntkrnlmp.object(
object_type="array",
@@ -199,7 +199,7 @@ class Callbacks(interfaces.plugins.PluginInterface):
)
if callback_count == 0:
return
return None
callback_list = ntkrnlmp.object(object_type="_LIST_ENTRY", offset=symbol_offset)
for callback in callback_list.to_list(full_type_name, "Link"):
@@ -256,7 +256,7 @@ class Callbacks(interfaces.plugins.PluginInterface):
symbol_status = "exists"
vollog.debug(f"symbol {symbol_name} {symbol_status}.")
return
return None
@classmethod
def list_bugcheck_reason_callbacks(
@@ -287,7 +287,7 @@ class Callbacks(interfaces.plugins.PluginInterface):
).address
except exceptions.SymbolError:
vollog.debug("Cannot find KeBugCheckReasonCallbackListHead")
return
return None
full_type_name = (
callback_table_name + constants.BANG + "_KBUGCHECK_REASON_CALLBACK_RECORD"
@@ -343,7 +343,7 @@ class Callbacks(interfaces.plugins.PluginInterface):
list_offset = ntkrnlmp.get_symbol("KeBugCheckCallbackListHead").address
except exceptions.SymbolError:
vollog.debug("Cannot find KeBugCheckCallbackListHead")
return
return None
full_type_name = (
callback_table_name + constants.BANG + "_KBUGCHECK_CALLBACK_RECORD"
@@ -130,7 +130,7 @@ class DumpFiles(interfaces.plugins.PluginInterface):
constants.LOGLEVEL_VVV,
f"The file object at {file_obj.vol.offset:#x} is not a file on disk",
)
return
return None
# Depending on the type of object (DataSection, ImageSection, SharedCacheMap) we may need to
# read from the memory layer or the primary layer.
@@ -285,7 +285,7 @@ class Handles(interfaces.plugins.PluginInterface):
count = 0x1000 / subtype.size
if not self.context.layers[virtual].is_valid(offset):
return
return None
table = ntkrnlmp.object(
object_type="array",
@@ -335,7 +335,7 @@ class Handles(interfaces.plugins.PluginInterface):
constants.LOGLEVEL_VVV,
"Handle table parsing was aborted due to an invalid address exception",
)
return
return None
for handle_table_entry in self._make_handle_array(TableCode, table_levels):
yield handle_table_entry
@@ -168,16 +168,16 @@ class Lsadump(interfaces.plugins.PluginInterface):
lsakey = self.get_lsa_key(sechive, bootkey, vista_or_later)
if not bootkey:
vollog.warning("Unable to find bootkey")
return
return None
if not lsakey:
vollog.warning("Unable to find lsa key")
return
return None
secrets_key = hashdump.Hashdump.get_hive_key(sechive, "Policy\\Secrets")
if not secrets_key:
vollog.warning("Unable to find secrets key")
return
return None
for key in secrets_key.get_subkeys():
sec_val_key = hashdump.Hashdump.get_hive_key(
@@ -110,7 +110,7 @@ class Malfind(interfaces.plugins.PluginInterface):
proc_id, excp.invalid_address, excp.layer_name
)
)
return
return None
proc_layer = context.layers[proc_layer_name]
+153 -32
View File
@@ -67,9 +67,8 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
)
# We will update this on each pass in the next loop and use it as the new offset.
attr_base_offset = mft_record.FirstAttrOffset
attr_header = self.context.object(
header_object,
attr = self.context.object(
attribute_object,
offset=offset + attr_base_offset,
layer_name=layer.name,
)
@@ -77,17 +76,8 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
# There is no field that has a count of Attributes
# Keep Attempting to read attributes until we get an invalid attr_header.AttrType
while attr_header.AttrType.is_valid_choice:
vollog.debug(f"Attr Type: {attr_header.AttrType.lookup()}")
# Offset past the headers to the attribute data
attr_data_offset = (
offset
+ attr_base_offset
+ self.context.symbol_space.get_type(
attribute_object
).relative_child_offset("Attr_Data")
)
while attr.Attr_Header.AttrType.is_valid_choice:
vollog.debug(f"Attr Type: {attr.Attr_Header.AttrType.lookup()}")
# MFT Flags determine the file type or dir
# If we don't have a valid enum, coerce to hex so we can keep the record
@@ -97,19 +87,16 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
mft_flag = hex(mft_record.Flags)
# Standard Information Attribute
if attr_header.AttrType.lookup() == "STANDARD_INFORMATION":
attr_data = self.context.object(
si_object, offset=attr_data_offset, layer_name=layer.name
)
if attr.Attr_Header.AttrType.lookup() == "STANDARD_INFORMATION":
attr_data = attr.Attr_Data.cast(si_object)
yield 0, (
format_hints.Hex(attr_data_offset),
format_hints.Hex(attr_data.vol.offset),
mft_record.get_signature(),
mft_record.RecordNumber,
mft_record.LinkCount,
mft_flag,
renderers.NotApplicableValue(),
attr_header.AttrType.lookup(),
attr.Attr_Header.AttrType.lookup(),
conversion.wintime_to_datetime(attr_data.CreationTime),
conversion.wintime_to_datetime(attr_data.ModifiedTime),
conversion.wintime_to_datetime(attr_data.UpdatedTime),
@@ -118,10 +105,8 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
)
# File Name Attribute
if attr_header.AttrType.lookup() == "FILE_NAME":
attr_data = self.context.object(
fn_object, offset=attr_data_offset, layer_name=layer.name
)
if attr.Attr_Header.AttrType.lookup() == "FILE_NAME":
attr_data = attr.Attr_Data.cast(fn_object)
file_name = attr_data.get_full_name()
# If we don't have a valid enum, coerce to hex so we can keep the record
@@ -131,13 +116,13 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
permissions = hex(attr_data.Flags)
yield 1, (
format_hints.Hex(attr_data_offset),
format_hints.Hex(attr_data.vol.offset),
mft_record.get_signature(),
mft_record.RecordNumber,
mft_record.LinkCount,
mft_flag,
permissions,
attr_header.AttrType.lookup(),
attr.Attr_Header.AttrType.lookup(),
conversion.wintime_to_datetime(attr_data.CreationTime),
conversion.wintime_to_datetime(attr_data.ModifiedTime),
conversion.wintime_to_datetime(attr_data.UpdatedTime),
@@ -146,14 +131,13 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
)
# If there's no advancement the loop will never end, so break it now
if attr_header.Length == 0:
if attr.Attr_Header.Length == 0:
break
# Update the base offset to point to the next attribute
attr_base_offset += attr_header.Length
# Get the next attribute
attr_header = self.context.object(
header_object,
attr_base_offset += attr.Attr_Header.Length
attr = self.context.object(
attribute_object,
offset=offset + attr_base_offset,
layer_name=layer.name,
)
@@ -189,3 +173,140 @@ class MFTScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
],
self._generator(),
)
class ADS(interfaces.plugins.PluginInterface):
"""Scans for Alternate Data Stream"""
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
return [
requirements.TranslationLayerRequirement(
name="primary",
description="Memory layer for the kernel",
architectures=["Intel32", "Intel64"],
),
requirements.VersionRequirement(
name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0)
),
]
def _generator(self):
layer = self.context.layers[self.config["primary"]]
# Yara Rule to scan for MFT Header Signatures
rules = yarascan.YaraScan.process_yara_options(
{"yara_rules": "/FILE0|FILE\*|BAAD/"}
)
# Read in the Symbol File
symbol_table = intermed.IntermediateSymbolTable.create(
context=self.context,
config_path=self.config_path,
sub_path="windows",
filename="mft",
class_types={
"MFT_ENTRY": mft.MFTEntry,
"FILE_NAME_ENTRY": mft.MFTFileName,
"ATTRIBUTE": mft.MFTAttribute,
},
)
# get each of the individual Field Sets
mft_object = symbol_table + constants.BANG + "MFT_ENTRY"
attribute_object = symbol_table + constants.BANG + "ATTRIBUTE"
fn_object = symbol_table + constants.BANG + "FILE_NAME_ENTRY"
# Scan the layer for Raw MFT records and parse the fields
for offset, _rule_name, _name, _value in layer.scan(
context=self.context, scanner=yarascan.YaraScanner(rules=rules)
):
with contextlib.suppress(exceptions.PagedInvalidAddressException):
mft_record = self.context.object(
mft_object, offset=offset, layer_name=layer.name
)
# We will update this on each pass in the next loop and use it as the new offset.
attr_base_offset = mft_record.FirstAttrOffset
attr = self.context.object(
attribute_object,
offset=offset + attr_base_offset,
layer_name=layer.name,
)
# There is no field that has a count of Attributes
# Keep Attempting to read attributes until we get an invalid attr.AttrType
is_ads = False
file_name = renderers.NotAvailableValue
# The First $DATA Attr is the 'principal' file itself not the ADS
while attr.Attr_Header.AttrType.is_valid_choice:
if attr.Attr_Header.AttrType.lookup() == "FILE_NAME":
attr_data = attr.Attr_Data.cast(fn_object)
file_name = attr_data.get_full_name()
if attr.Attr_Header.AttrType.lookup() == "DATA":
if is_ads:
if not attr.Attr_Header.NonResidentFlag:
# Resident files are the most interesting.
if attr.Attr_Header.NameLength > 0:
ads_name = attr.get_resident_filename()
if not ads_name:
ads_name = renderers.NotAvailableValue
content = attr.get_resident_filecontent()
if content:
# Preparing for Disassembly
disasm = interfaces.renderers.BaseAbsentValue
architecture = layer.metadata.get(
"architecture", None
)
if architecture:
disasm = interfaces.renderers.Disassembly(
content, 0, architecture.lower()
)
else:
content = renderers.NotAvailableValue
disasm = interfaces.renderers.BaseAbsentValue
yield 0, (
format_hints.Hex(attr_data.vol.offset),
mft_record.get_signature(),
mft_record.RecordNumber,
attr.Attr_Header.AttrType.lookup(),
file_name,
ads_name,
format_hints.HexBytes(content),
disasm,
)
else:
is_ads = True
# If there's no advancement the loop will never end, so break it now
if attr.Attr_Header.Length == 0:
break
# Update the base offset to point to the next attribute
attr_base_offset += attr.Attr_Header.Length
# Get the next attribute
attr = self.context.object(
attribute_object,
offset=offset + attr_base_offset,
layer_name=layer.name,
)
def run(self):
return renderers.TreeGrid(
[
("Offset", format_hints.Hex),
("Record Type", str),
("Record Number", int),
("MFT Type", str),
("Filename", str),
("ADS Filename", str),
("Hexdump", format_hints.HexBytes),
("Disasm", interfaces.renderers.Disassembly),
],
self._generator(),
)
@@ -154,7 +154,7 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
)
else:
# invalid argument.
return
return None
vollog.debug(f"Current Port: {port}")
# the given port serves as a shifted index into the port pool lists
@@ -175,7 +175,7 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
assignment = inpa.InPaBigPoolBase.Assignments[truncated_port]
if not assignment:
return
return None
# the value within assignment.Entry is a) masked and b) points inside of the network object
# first decode the pointer
@@ -90,9 +90,19 @@ class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
offset=peb.ImageBaseAddress,
layer_name=proc_layer_name,
)
file_handle = open_method(
f"pid.{proc.UniqueProcessId}.{peb.ImageBaseAddress:#x}.dmp"
process_name = proc.ImageFileName.cast(
"string",
max_length=proc.ImageFileName.vol.count,
errors="replace",
)
file_handle = open_method(
open_method.sanitize_filename(
f"{proc.UniqueProcessId}.{process_name}.{peb.ImageBaseAddress:#x}.dmp"
)
)
for offset, data in dos_header.reconstruct():
file_handle.seek(offset)
file_handle.write(data)
@@ -108,13 +108,13 @@ class PsTree(interfaces.plugins.PluginInterface):
def yield_processes(pid, descendant: bool = False):
if pid in process_pids:
vollog.debug(f"Pid cycle: already processed pid {pid}")
return
return None
process_pids.add(pid)
if pid not in self._ancestors and not descendant:
vollog.debug(f"Pid cycle: pid {pid} not in filtered tree")
return
return None
proc, offset = self._processes[pid]
row = (
@@ -30,7 +30,7 @@ class HiveGenerator:
):
if not hive.is_valid():
self._invalid = hive.vol.offset
return
return None
yield hive
@property
@@ -74,7 +74,7 @@ class PrintKey(interfaces.plugins.PluginInterface):
node_path = [hive.get_node(hive.root_cell_offset)]
if not isinstance(node_path, list) or len(node_path) < 1:
vollog.warning("Hive walker was not passed a valid node_path (or None)")
return
return None
node = node_path[-1]
key_path_items = [hive] + node_path[1:]
key_path = "\\".join([k.get_name() for k in key_path_items])
@@ -153,6 +153,11 @@ class PrintKey(interfaces.plugins.PluginInterface):
vollog.debug(excp)
key_node_name = renderers.UnreadableValue()
# if the item is a subkey, use the LastWriteTime of that subkey
last_write_time = conversion.wintime_to_datetime(
node.LastWriteTime.QuadPart
)
yield (
depth,
(
@@ -173,11 +173,11 @@ class UserAssist(interfaces.plugins.PluginInterface):
if not userassist_node_path:
vollog.warning("list_userassist did not find a valid node_path (or None)")
return
return None
if not isinstance(userassist_node_path, list):
vollog.warning("userassist_node_path did not return a list as expected")
return
return None
userassist_node = userassist_node_path[-1]
# iterate through the GUIDs under the userassist key
for guidkey in userassist_node.get_subkeys():
@@ -601,21 +601,21 @@ class Skeleton_Key_Check(interfaces.plugins.PluginInterface):
if not symbols.symbol_table_is_64bit(self.context, kernel.symbol_table_name):
vollog.info("This plugin only supports 64bit Windows memory samples")
return
return None
lsass_proc, proc_layer_name = self._find_lsass_proc(procs)
if not lsass_proc:
vollog.info(
"Unable to find a valid lsass.exe process in the process list. This should never happen. Analysis cannot proceed."
)
return
return None
cryptdll_base, cryptdll_size = self._find_cryptdll(lsass_proc)
if not cryptdll_base:
vollog.info(
"Unable to find the location of cryptdll.dll inside of lsass.exe. Analysis cannot proceed."
)
return
return None
# the custom type information from binary analysis
cryptdll_types = self._get_cryptdll_types(
@@ -649,7 +649,7 @@ class Skeleton_Key_Check(interfaces.plugins.PluginInterface):
vollog.info(
"Unable to find CSystems inside of cryptdll.dll. Analysis cannot proceed."
)
return
return None
for csystem in csystems:
if not self.context.layers[proc_layer_name].is_valid(
@@ -18,47 +18,26 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
"""Scans all the Virtual Address Descriptor memory maps using yara."""
_required_framework_version = (2, 4, 0)
_version = (1, 0, 0)
_version = (1, 0, 1)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
# create a list of requirements for vadyarascan
vadyarascan_requirements = [
requirements.ModuleRequirement(
name="kernel",
description="Windows kernel",
architectures=["Intel32", "Intel64"],
),
requirements.BooleanRequirement(
name="wide",
description="Match wide (unicode) strings",
default=False,
optional=True,
),
requirements.StringRequirement(
name="yara_rules", description="Yara rules (as a string)", optional=True
),
requirements.URIRequirement(
name="yara_file", description="Yara rules (as a file)", optional=True
),
# This additional requirement is to follow suit with upstream, who feel that compiled rules could potentially be used to execute malicious code
# As such, there's a separate option to run compiled files, as happened with yara-3.9 and later
requirements.URIRequirement(
name="yara_compiled_file",
description="Yara compiled rules (as a file)",
optional=True,
),
requirements.IntRequirement(
name="max_size",
default=0x40000000,
description="Set the maximum size (default is 1GB)",
optional=True,
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
),
requirements.VersionRequirement(
name="yarascanner", component=yarascan.YaraScanner, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="yarascan", plugin=yarascan.YaraScan, version=(1, 2, 0)
),
requirements.ListRequirement(
name="pid",
element_type=int,
@@ -67,6 +46,12 @@ class VadYaraScan(interfaces.plugins.PluginInterface):
),
]
# get base yarascan requirements for command line options
yarascan_requirements = yarascan.YaraScan.get_yarascan_option_requirements()
# return the combined requirements
return yarascan_requirements + vadyarascan_requirements
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
+15 -3
View File
@@ -61,19 +61,31 @@ class YaraScan(plugins.PluginInterface):
"""Scans kernel memory using yara rules (string or file)."""
_required_framework_version = (2, 0, 0)
_version = (1, 1, 0)
_version = (1, 2, 0)
# TODO: When the major version is bumped, take the opportunity to rename the yara_rules config to yara_string
# or something that makes more sense
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
"""Returns the requirements needed to run yarascan directly, combining the TranslationLayerRequirement
and the requirements from get_yarascan_option_requirements."""
return cls.get_yarascan_option_requirements() + [
requirements.TranslationLayerRequirement(
name="primary",
description="Memory layer for the kernel",
architectures=["Intel32", "Intel64"],
),
)
]
@classmethod
def get_yarascan_option_requirements(
cls,
) -> List[interfaces.configuration.RequirementInterface]:
"""Returns the requirements needed for the command lines options used by yarascan. This can
then also be used by other plugins that are using yarascan. This does not include a
TranslationLayerRequirement or a ModuleRequirement."""
return [
requirements.BooleanRequirement(
name="insensitive",
description="Makes the search case insensitive",
+5 -1
View File
@@ -10,7 +10,7 @@ import collections
import collections.abc
import datetime
import logging
from typing import Any, Callable, Iterable, List, Optional, Tuple, TypeVar, Union
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, TypeVar, Union
from volatility3.framework import interfaces
from volatility3.framework.interfaces import renderers
@@ -96,6 +96,10 @@ class TreeNode(interfaces.renderers.TreeNode):
# if isinstance(val, datetime.datetime):
# tznaive = val.tzinfo is None or val.tzinfo.utcoffset(val) is None
def asdict(self) -> Dict[str, Any]:
"""Returns the contents of the node as a dictionary"""
return self._values._asdict()
@property
def values(self) -> List[interfaces.renderers.BaseTypes]:
"""Returns the list of values from the particular node, based on column
@@ -59,7 +59,8 @@ class MultiTypeData(bytes):
def __eq__(self, other):
return (
super(self) == super(other)
isinstance(other, self.__class__)
and super() == super(self.__class__, other)
and self.converted_int == other.converted_int
and self.encoding == other.encoding
and self.split_nulls == other.split_nulls
@@ -29,6 +29,7 @@ class LinuxKernelIntermedSymbols(intermed.IntermediateSymbolTable):
self.set_type_class("files_struct", extensions.files_struct)
self.set_type_class("kobject", extensions.kobject)
self.set_type_class("cred", extensions.cred)
self.set_type_class("kernel_cap_struct", extensions.kernel_cap_struct)
# Might not exist in the current symbols
self.optional_set_type_class("module", extensions.module)
self.optional_set_type_class("bpf_prog", extensions.bpf_prog)
@@ -244,17 +245,17 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
):
# task.files can be null
if not task.files:
return
return None
fd_table = task.files.get_fds()
if fd_table == 0:
return
return None
max_fds = task.files.get_max_fds()
# corruption check
if max_fds > 500000:
return
return None
file_type = symbol_table + constants.BANG + "file"
@@ -379,7 +380,7 @@ class LinuxUtilities(interfaces.configuration.VersionableInterface):
"""
if not addr:
return
return None
type_dec = vmlinux.get_type(type_name)
member_offset = type_dec.relative_child_offset(member_name)
@@ -71,11 +71,11 @@ class module(generic.GenericIntelProcess):
def _get_sect_count(self, grp):
"""Try to determine the number of valid sections"""
arr = self._context.object(
self.get_symbol_table().name + constants.BANG + "array",
self.get_symbol_table_name() + constants.BANG + "array",
layer_name=self.vol.layer_name,
offset=grp.attrs,
subtype=self._context.symbol_space.get_type(
self.get_symbol_table().name + constants.BANG + "pointer"
self.get_symbol_table_name() + constants.BANG + "pointer"
),
count=25,
)
@@ -92,11 +92,11 @@ class module(generic.GenericIntelProcess):
else:
num_sects = self._get_sect_count(self.sect_attrs.grp)
arr = self._context.object(
self.get_symbol_table().name + constants.BANG + "array",
self.get_symbol_table_name() + constants.BANG + "array",
layer_name=self.vol.layer_name,
offset=self.sect_attrs.attrs.vol.offset,
subtype=self._context.symbol_space.get_type(
self.get_symbol_table().name + constants.BANG + "module_sect_attr"
self.get_symbol_table_name() + constants.BANG + "module_sect_attr"
),
count=num_sects,
)
@@ -104,41 +104,82 @@ class module(generic.GenericIntelProcess):
for attr in arr:
yield attr
def get_symbols(self):
if symbols.symbol_table_is_64bit(self._context, self.get_symbol_table().name):
prefix = "Elf64_"
else:
prefix = "Elf32_"
def get_elf_table_name(self):
elf_table_name = intermed.IntermediateSymbolTable.create(
self.context,
self.config_path,
self._context,
"elf_symbol_table",
"linux",
"elf",
native_types=None,
class_types=elf.class_types,
)
return elf_table_name
def get_symbols(self):
"""Get symbols of the module
Yields:
A symbol object
"""
if not hasattr(self, "_elf_table_name"):
self._elf_table_name = self.get_elf_table_name()
if symbols.symbol_table_is_64bit(self._context, self.get_symbol_table_name()):
prefix = "Elf64_"
else:
prefix = "Elf32_"
syms = self._context.object(
self.get_symbol_table().name + constants.BANG + "array",
self.get_symbol_table_name() + constants.BANG + "array",
layer_name=self.vol.layer_name,
offset=self.section_symtab,
subtype=self._context.symbol_space.get_type(
elf_table_name + constants.BANG + prefix + "Sym"
self._elf_table_name + constants.BANG + prefix + "Sym"
),
count=self.num_symtab + 1,
)
if self.section_strtab:
for sym in syms:
sym.set_cached_strtab(self.section_strtab)
yield sym
def get_symbol(self, wanted_sym_name):
"""Get value for a given symbol name"""
def get_symbols_names_and_addresses(self) -> Tuple[str, int]:
"""Get names and addresses for each symbol of the module
Yields:
A tuple for each symbol containing the symbol name and its corresponding value
"""
for sym in self.get_symbols():
sym_name = sym.get_name()
sym_addr = sym.st_value
sym_arr = self._context.object(
self.get_symbol_table_name() + constants.BANG + "array",
layer_name=self.vol.native_layer_name,
offset=self.section_strtab + sym.st_name,
)
try:
sym_name = utility.array_to_string(
sym_arr, 512
) # 512 is the value of KSYM_NAME_LEN kernel constant
except exceptions.InvalidAddressException:
continue
if sym_name != "":
# Normalize sym.st_value offset, which is an address pointing to the symbol value
mask = self._context.layers[self.vol.layer_name].address_mask
sym_address = sym.st_value & mask
yield (sym_name, sym_address)
def get_symbol(self, wanted_sym_name):
"""Get symbol value for a given symbol name"""
for sym_name, sym_address in self.get_symbols_names_and_addresses():
if wanted_sym_name == sym_name:
return sym_addr
return sym_address
return None
def get_symbol_by_address(self, wanted_sym_address):
"""Get symbol name for a given symbol address"""
for sym_name, sym_address in self.get_symbols_names_and_addresses():
if wanted_sym_address == sym_address:
return sym_name
return None
@property
@@ -203,7 +244,7 @@ class task_struct(generic.GenericIntelProcess):
) -> Generator[Tuple[int, int], None, None]:
"""Returns a list of sections based on the memory manager's view of
this task's virtual memory."""
for vma in self.mm.get_mmap_iter():
for vma in self.mm.get_vma_iter():
start = int(vma.vm_start)
end = int(vma.vm_end)
@@ -309,19 +350,30 @@ class maple_tree(objects.StructType):
maple_tree_entry,
parent,
expected_maple_tree_depth,
seen=set(),
seen=None,
current_depth=1,
):
"""Recursively parse Maple Tree Nodes and yield all non empty slots"""
# Create seen set if it does not exist, e.g. on the first call into this recursive function. This
# must be None or an existing set of addresses for MTEs that have already been processed or that
# should otherwise be ignored. If parsing from the root node for example this should be None on the
# first call. If you needed to parse all nodes downwards from part of the tree this should still be
# None. If however you wanted to parse from a node, but ignore some parts of the tree below it then
# this could be populated with the addresses of the nodes you wish to ignore.
if seen == None:
seen = set()
# protect against unlikely loop
if maple_tree_entry in seen:
vollog.warning(
f"The mte {hex(maple_tree_entry)} has all ready been seen, no further results will be produced for this node."
)
return
return None
else:
seen.add(maple_tree_entry)
# check if we have exceeded the expected depth of this maple tree.
# e.g. when current_depth is larger than expected_maple_tree_depth there may be an issue.
# it is normal that expected_maple_tree_depth is equal to current_depth.
@@ -330,6 +382,7 @@ class maple_tree(objects.StructType):
f"The depth for the maple tree at {hex(self.vol.offset)} is {expected_maple_tree_depth}, however when parsing the nodes "
f"a depth of {current_depth} was reached. This is unexpected and may lead to incorrect results."
)
# parse the mte to extract the pointer value, node type, and leaf status
pointer = maple_tree_entry & ~(self.MAPLE_NODE_POINTER_MASK)
node_type = (
@@ -402,7 +455,7 @@ class mm_struct(objects.StructType):
"get_mmap_iter called on mm_struct where no mmap member exists."
)
if not self.mmap:
return
return None
yield self.mmap
seen = {self.mmap.vol.offset}
@@ -578,7 +631,7 @@ class vm_area_struct(objects.StructType):
return fname
# used by malfind
def is_suspicious(self):
def is_suspicious(self, proclayer=None):
ret = False
flags_str = self.get_protection()
@@ -587,6 +640,24 @@ class vm_area_struct(objects.StructType):
ret = True
elif flags_str == "r-x" and self.vm_file.dereference().vol.offset == 0:
ret = True
elif proclayer and "x" in flags_str:
for i in range(self.vm_start, self.vm_end, 1 << constants.linux.PAGE_SHIFT):
try:
if proclayer.is_dirty(i):
vollog.warning(
f"Found malicious (dirty+exec) page at {hex(i)} !"
)
# We do not attempt to find other dirty+exec pages once we have found one
ret = True
break
except (
exceptions.PagedInvalidAddressException,
exceptions.InvalidAddressException,
) as excp:
vollog.debug(f"Unable to translate address {hex(i)} : {excp}")
# Abort as it is likely that other addresses in the same range will also fail
ret = False
break
return ret
@@ -705,7 +776,7 @@ class list_head(objects.StructType, collections.abc.Iterable):
try:
link = getattr(self, direction).dereference()
except exceptions.InvalidAddressException:
return
return None
if not sentinel:
yield self._context.object(
symbol_type, layer, offset=self.vol.offset - relative_offset
@@ -1114,7 +1185,7 @@ class vfsmount(objects.StructType):
class kobject(objects.StructType):
def reference_count(self):
refcnt = self.kref.refcount
if self.has_member("counter"):
if refcnt.has_member("counter"):
ret = refcnt.counter
else:
ret = refcnt.refs.counter
@@ -1200,7 +1271,7 @@ class sock(objects.StructType):
return self.sk_socket.get_inode()
def get_protocol(self):
return
return None
def get_state(self):
# Return the generic socket state
@@ -1212,13 +1283,13 @@ class sock(objects.StructType):
class unix_sock(objects.StructType):
def get_name(self):
if not self.addr:
return
return None
sockaddr_un = self.addr.name.cast("sockaddr_un")
saddr = str(utility.array_to_string(sockaddr_un.sun_path))
return saddr
def get_protocol(self):
return
return None
def get_state(self):
"""Return a string representing the sock state."""
@@ -1277,7 +1348,7 @@ class inet_sock(objects.StructType):
elif hasattr(sk_common, "skc_dport"):
dport_le = sk_common.skc_dport
else:
return
return None
return socket_module.htons(dport_le)
def get_src_addr(self):
@@ -1295,7 +1366,7 @@ class inet_sock(objects.StructType):
addr_size = 16
saddr = self.pinet6.saddr
else:
return
return None
parent_layer = self._context.layers[self.vol.layer_name]
try:
addr_bytes = parent_layer.read(saddr.vol.offset, addr_size)
@@ -1303,7 +1374,7 @@ class inet_sock(objects.StructType):
vollog.debug(
f"Unable to read socket src address from {saddr.vol.offset:#x}"
)
return
return None
return socket_module.inet_ntop(family, addr_bytes)
def get_dst_addr(self):
@@ -1324,7 +1395,7 @@ class inet_sock(objects.StructType):
daddr = sk_common.skc_v6_daddr
addr_size = 16
else:
return
return None
parent_layer = self._context.layers[self.vol.layer_name]
try:
addr_bytes = parent_layer.read(daddr.vol.offset, addr_size)
@@ -1332,7 +1403,7 @@ class inet_sock(objects.StructType):
vollog.debug(
f"Unable to read socket dst address from {daddr.vol.offset:#x}"
)
return
return None
return socket_module.inet_ntop(family, addr_bytes)
@@ -1370,7 +1441,7 @@ class netlink_sock(objects.StructType):
class vsock_sock(objects.StructType):
def get_protocol(self):
# The protocol should always be 0 for vsocks
return
return None
def get_state(self):
# Return the generic socket state
@@ -1381,7 +1452,7 @@ class packet_sock(objects.StructType):
def get_protocol(self):
eth_proto = socket_module.htons(self.num)
if eth_proto == 0:
return
return None
elif eth_proto in ETH_PROTOCOLS:
return ETH_PROTOCOLS[eth_proto]
else:
@@ -1407,7 +1478,7 @@ class bt_sock(objects.StructType):
class xdp_sock(objects.StructType):
def get_protocol(self):
# The protocol should always be 0 for xdp_sock
return
return None
def get_state(self):
# xdp_sock.state is an enum
@@ -3,9 +3,12 @@
#
from typing import Dict, Tuple
import logging
from volatility3.framework import constants
from volatility3.framework import objects, interfaces
from volatility3.framework import objects, interfaces, exceptions
vollog = logging.getLogger(__name__)
class elf(objects.StructType):
@@ -33,14 +36,23 @@ class elf(objects.StructType):
layer_name = self.vol.layer_name
symbol_table_name = self.get_symbol_table_name()
# We read the MAGIC: (0x0 to 0x4) 0x7f 0x45 0x4c 0x46
magic = self._context.object(
symbol_table_name + constants.BANG + "unsigned long",
layer_name=layer_name,
offset=object_info.offset,
)
try:
magic = self._context.object(
symbol_table_name + constants.BANG + "unsigned long",
layer_name=layer_name,
offset=object_info.offset,
)
except (
exceptions.PagedInvalidAddressException,
exceptions.InvalidAddressException,
) as excp:
vollog.debug(
f"Unable to check magic bytes for ELF file at offset {hex(object_info.offset)} in layer {layer_name}: {excp}"
)
return None
# Check validity
if magic != 0x464C457F:
if magic != 0x464C457F: # e.g. ELF
return None
# We need to read the EI_CLASS (0x4 offset)
@@ -72,7 +84,10 @@ class elf(objects.StructType):
"""
Determine whether it is a valid object
"""
return self._type_prefix is not None and self._hdr is not None
if hasattr(self, "_type_prefix") and hasattr(self, "_hdr"):
return self._type_prefix is not None and self._hdr is not None
else:
return False
def __getattr__(self, name):
# Just redirect to the corresponding header
@@ -171,7 +186,7 @@ class elf(objects.StructType):
self._find_symbols()
if self._cached_symtab is None:
return
return None
symtab_arr = self._context.object(
self.get_symbol_table_name() + constants.BANG + "array",
@@ -169,7 +169,7 @@ class MacUtilities(interfaces.configuration.VersionableInterface):
try:
table_addr = task.p_fd.fd_ofiles.dereference()
except exceptions.InvalidAddressException:
return
return None
fds = objects.utility.array_of_pointers(
table_addr, count=num_fds, subtype=file_type, context=context
@@ -204,7 +204,7 @@ class MacUtilities(interfaces.configuration.VersionableInterface):
try:
current = queue.member(attr=list_head_member)
except exceptions.InvalidAddressException:
return
return None
while current:
if current.vol.offset in seen:
@@ -50,7 +50,7 @@ class proc(generic.GenericIntelProcess):
task = self.get_task()
current_map = task.map.hdr.links.next
except exceptions.InvalidAddressException:
return
return None
seen: Set[int] = set()
@@ -138,13 +138,13 @@ class vm_map_object(objects.StructType):
class vnode(objects.StructType):
def _do_calc_path(self, ret, vnodeobj, vname):
if vnodeobj is None:
return
return None
if vname:
try:
ret.append(utility.pointer_to_string(vname, 255))
except exceptions.InvalidAddressException:
return
return None
if int(vnodeobj.v_flag) & 0x000001 != 0 and int(vnodeobj.v_mount) != 0:
if int(vnodeobj.v_mount.mnt_vnodecovered) != 0:
@@ -158,7 +158,7 @@ class vnode(objects.StructType):
parent = vnodeobj.v_parent
parent_name = parent.v_name
except exceptions.InvalidAddressException:
return
return None
self._do_calc_path(ret, parent, parent_name)
@@ -502,7 +502,7 @@ class queue_entry(objects.StructType):
yielded = yielded + 1
if yielded == max_size:
return
return None
n = (
getattr(n.member(attr=member_name), attr)
@@ -91,7 +91,7 @@ class MMVAD_SHORT(objects.StructType):
if vad_address in visited:
vollog.log(constants.LOGLEVEL_VVV, "VAD node already seen!")
return
return None
visited.add(vad_address)
tag = self.get_tag()
@@ -111,7 +111,7 @@ class MMVAD_SHORT(objects.StructType):
constants.LOGLEVEL_VVV,
f"Skipping VAD at {self.vol.offset} depth {depth} with tag {tag}",
)
return
return None
if target:
vad_object = self.cast(target)
@@ -665,7 +665,7 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject):
):
yield entry
except exceptions.InvalidAddressException:
return
return None
def init_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]:
"""Generator for DLLs in the order that they were initialized"""
@@ -678,7 +678,7 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject):
):
yield entry
except exceptions.InvalidAddressException:
return
return None
def mem_order_modules(self) -> Iterable[interfaces.objects.ObjectInterface]:
"""Generator for DLLs in the order that they appear in memory"""
@@ -691,7 +691,7 @@ class EPROCESS(generic.GenericIntelProcess, pool.ExecutiveObject):
):
yield entry
except exceptions.InvalidAddressException:
return
return None
def get_handle_count(self):
try:
@@ -841,11 +841,11 @@ class LIST_ENTRY(objects.StructType, collections.abc.Iterable):
try:
is_valid = trans_layer.is_valid(self.vol.offset)
if not is_valid:
return
return None
link = getattr(self, direction).dereference()
except exceptions.InvalidAddressException:
return
return None
if not sentinel:
yield self._context.object(
@@ -860,7 +860,7 @@ class LIST_ENTRY(objects.StructType, collections.abc.Iterable):
obj_offset = link.vol.offset - relative_offset
if not trans_layer.is_valid(obj_offset):
return
return None
obj = self._context.object(
symbol_type,
@@ -875,7 +875,7 @@ class LIST_ENTRY(objects.StructType, collections.abc.Iterable):
try:
link = getattr(link, direction).dereference()
except exceptions.InvalidAddressException:
return
return None
def __iter__(self) -> Iterator[interfaces.objects.ObjectInterface]:
return self.to_list(self.vol.parent.vol.type_name, self.vol.member_name)
@@ -905,10 +905,10 @@ class TOKEN(objects.StructType):
sid = sid_and_attr.Sid.dereference().cast("_SID")
# catch invalid pointers (UserAndGroupCount is too high)
if sid is None:
return
return None
# this mimics the windows API IsValidSid
if sid.Revision & 0xF != 1 or sid.SubAuthorityCount > 15:
return
return None
id_auth = ""
for i in sid.IdentifierAuthority.Value:
id_auth = i
@@ -2,7 +2,7 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from volatility3.framework import objects
from volatility3.framework import objects, constants, exceptions
class MFTEntry(objects.StructType):
@@ -21,3 +21,36 @@ class MFTFileName(objects.StructType):
"string", encoding="utf16", max_length=self.NameLength * 2, errors="replace"
)
return output
class MFTAttribute(objects.StructType):
"""This represents an MFT ATTRIBUTE"""
def get_resident_filename(self) -> str:
# To get the resident name, we jump to relative name offset and read name length * 2 bytes of data
try:
name = self._context.object(
self.vol.type_name.split(constants.BANG)[0] + constants.BANG + "string",
layer_name=self.vol.layer_name,
offset=self.vol.offset + self.Attr_Header.NameOffset,
max_length=self.Attr_Header.NameLength * 2,
errors="replace",
encoding="utf16",
)
return name
except exceptions.InvalidAddressException:
return None
def get_resident_filecontent(self) -> bytes:
# To get the resident content, we jump to relative content offset and read name length * 2 bytes of data
try:
bytesobj = self._context.object(
self.vol.type_name.split(constants.BANG)[0] + constants.BANG + "bytes",
layer_name=self.vol.layer_name,
offset=self.vol.offset + self.Attr_Header.ContentOffset,
native_layer_name=self.vol.native_layer_name,
length=self.Attr_Header.ContentLength,
)
return bytesobj
except exceptions.InvalidAddressException:
return None
@@ -162,7 +162,7 @@ class CM_KEY_NODE(objects.StructType):
try:
signature = node.cast("string", max_length=2, encoding="latin-1")
except (exceptions.InvalidAddressException, RegistryFormatException):
return
return None
listjump = None
if signature == "ri":
@@ -220,7 +220,7 @@ class CM_KEY_NODE(objects.StructType):
yield node
except (exceptions.InvalidAddressException, RegistryFormatException) as excp:
vollog.debug(f"Invalid address in get_values iteration: {excp}")
return
return None
def get_name(self) -> interfaces.objects.ObjectInterface:
"""Gets the name for the current key node"""
@@ -110,7 +110,7 @@ class SERVICE_RECORD(objects.StructType):
yield rec
rec = rec.ServiceList.Blink.dereference()
except exceptions.InvalidAddressException:
return
return None
class SERVICE_HEADER(objects.StructType):
+18 -4
View File
@@ -230,21 +230,21 @@
"offset": 0,
"type": {
"kind": "struct",
"name": "mft!ATTR_HEADER"
"name": "ATTR_HEADER"
}
},
"Resident_Header": {
"offset": 16,
"type": {
"kind": "struct",
"name": "mft!RESIDENT_HEADER"
"name": "RESIDENT_HEADER"
}
},
"Attr_Data": {
"offset": 24,
"type": {
"kind": "struct",
"name": "mft!ATTR_HEADER"
"name": "ATTR_HEADER"
}
}
},
@@ -300,10 +300,24 @@
"kind": "base",
"name": "unsigned short"
}
},
"ContentLength": {
"offset": 16,
"type": {
"kind": "base",
"name": "unsigned int"
}
},
"ContentOffset": {
"offset": 20,
"type": {
"kind": "base",
"name": "unsigned short"
}
}
},
"kind": "struct",
"size": 16
"size": 24
},"RESIDENT_HEADER": {
"fields": {
"AttrSize": {
@@ -2,13 +2,13 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import binascii
import json
import logging
import lzma
import os
import re
import struct
from pathlib import PureWindowsPath
from typing import Any, Dict, Generator, List, Optional, Tuple, Union
from urllib import parse, request
@@ -226,13 +226,12 @@ class PDBUtility(interfaces.configuration.VersionableInterface):
return None
pdb_name = debug_entry.PdbFileName.decode("utf-8").strip("\x00")
# Let pathlib do the filename extraction. This will likely always be a Windows path though.
pdb_name = PureWindowsPath(pdb_name).name
age = debug_entry.Age
guid = "{:08x}{:04x}{:04x}{}".format(
debug_entry.Signature_Data1,
debug_entry.Signature_Data2,
debug_entry.Signature_Data3,
binascii.hexlify(debug_entry.Signature_Data4).decode("utf-8"),
)
guid = debug_entry.Signature_String[:32] # Removes the Age from the GUID
return guid, age, pdb_name
@classmethod