mirror of
https://github.com/volatilityfoundation/volatility3.git
synced 2026-09-13 05:07:38 +02:00
Merge branch 'volatilityfoundation:develop' into develop
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
|
||||
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
|
||||
@@ -22,6 +22,13 @@ import traceback
|
||||
from typing import Any, Dict, List, Tuple, Type, Union
|
||||
from urllib import parse, request
|
||||
|
||||
try:
|
||||
import argcomplete
|
||||
|
||||
HAS_ARGCOMPLETE = True
|
||||
except ImportError:
|
||||
HAS_ARGCOMPLETE = False
|
||||
|
||||
from volatility3.cli import text_filter
|
||||
import volatility3.plugins
|
||||
import volatility3.symbols
|
||||
@@ -351,6 +358,10 @@ class CommandLine:
|
||||
# Hand the plugin requirements over to the CLI (us) and let it construct the config tree
|
||||
|
||||
# Run the argparser
|
||||
if HAS_ARGCOMPLETE:
|
||||
# The autocompletion line must be after the partial_arg handling, so that it doesn't trip it
|
||||
# before all the plugins have been added
|
||||
argcomplete.autocomplete(parser)
|
||||
args = parser.parse_args()
|
||||
if args.plugin is None:
|
||||
parser.error("Please select a plugin to run")
|
||||
|
||||
@@ -21,8 +21,6 @@ class HelpfulSubparserAction(argparse._SubParsersAction):
|
||||
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
super().__init__(*args, **kwargs)
|
||||
# We don't want the action self-check to kick in, so we remove the choices list, the check happens in __call__
|
||||
self.choices = None
|
||||
|
||||
def __call__(
|
||||
self,
|
||||
@@ -100,3 +98,20 @@ class HelpfulArgParser(argparse.ArgumentParser):
|
||||
|
||||
# return the number of arguments matched
|
||||
return len(match.group(1))
|
||||
|
||||
def _check_value(self, action: argparse.Action, value: Any) -> None:
|
||||
"""This is called to ensure a value is correct/valid
|
||||
|
||||
In normal operation, it would check that a value provided is valid and return None
|
||||
If it was not valid, it would throw an ArgumentError
|
||||
|
||||
When people provide a partial plugin name, we want to look for a matching plugin name
|
||||
which happens in the HelpfulSubparserAction's __call_method
|
||||
|
||||
To get there without tripping the check_value failure, we have to prevent the exception
|
||||
being thrown when the value is a HelpfulSubparserAction. This therefore affects no other
|
||||
checks for normal parameters.
|
||||
"""
|
||||
if not isinstance(action, HelpfulSubparserAction):
|
||||
super()._check_value(action, value)
|
||||
return None
|
||||
|
||||
@@ -21,6 +21,14 @@ from volatility3.framework import (
|
||||
plugins,
|
||||
)
|
||||
|
||||
try:
|
||||
import argcomplete
|
||||
|
||||
HAS_ARGCOMPLETE = True
|
||||
except ImportError:
|
||||
HAS_ARGCOMPLETE = False
|
||||
|
||||
|
||||
# Make sure we log everything
|
||||
|
||||
rootlog = logging.getLogger()
|
||||
@@ -276,6 +284,10 @@ class VolShell(cli.CommandLine):
|
||||
# Hand the plugin requirements over to the CLI (us) and let it construct the config tree
|
||||
|
||||
# Run the argparser
|
||||
if HAS_ARGCOMPLETE:
|
||||
# The autocompletion line must be after the partial_arg handling, so that it doesn't trip it
|
||||
# before all the plugins have been added
|
||||
argcomplete.autocomplete(parser)
|
||||
args = parser.parse_args()
|
||||
|
||||
vollog.log(
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
# We use the SemVer 2.0.0 versioning scheme
|
||||
VERSION_MAJOR = 2 # Number of releases of the library with a breaking change
|
||||
VERSION_MINOR = 7 # Number of changes that only add to the interface
|
||||
VERSION_PATCH = 2 # Number of changes that do not change the interface
|
||||
VERSION_MINOR = 8 # Number of changes that only add to the interface
|
||||
VERSION_PATCH = 0 # Number of changes that do not change the interface
|
||||
VERSION_SUFFIX = ""
|
||||
|
||||
# TODO: At version 2.0.0, remove the symbol_shift feature
|
||||
|
||||
PACKAGE_VERSION = (
|
||||
".".join([str(x) for x in [VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH]])
|
||||
+ VERSION_SUFFIX
|
||||
|
||||
@@ -105,7 +105,9 @@ class Timeliner(interfaces.plugins.PluginInterface):
|
||||
data = item[1]
|
||||
|
||||
def sortable(timestamp):
|
||||
max_date = datetime.datetime(day=1, month=12, year=datetime.MAXYEAR)
|
||||
max_date = datetime.datetime(
|
||||
day=1, month=12, year=datetime.MAXYEAR, tzinfo=datetime.timezone.utc
|
||||
)
|
||||
if isinstance(timestamp, interfaces.renderers.BaseAbsentValue):
|
||||
return max_date
|
||||
return timestamp
|
||||
|
||||
@@ -177,7 +177,7 @@ class Handles(interfaces.plugins.PluginInterface):
|
||||
)
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.warning(
|
||||
f"Failed to read {hex(num_bytes_to_read)} bytes at symbol {hex(func_addr_to_read)}. Unable to decode SAR value. Failing back to a common value of 0x10"
|
||||
f"Failed to read {hex(num_bytes_to_read)} bytes at symbol {hex(func_addr_to_read)}. Unable to decode SAR value. Failing back to a common value of {hex(DEFAULT_SAR_VALUE)}"
|
||||
)
|
||||
self._sar_value = DEFAULT_SAR_VALUE
|
||||
return self._sar_value
|
||||
@@ -201,7 +201,7 @@ class Handles(interfaces.plugins.PluginInterface):
|
||||
|
||||
if self._sar_value is None:
|
||||
vollog.warning(
|
||||
f"Failed to to locate SAR value having parsed {instruction_count} instructions, failing back to a common value of 0x10"
|
||||
f"Failed to to locate SAR value having parsed {instruction_count} instructions, failing back to a common value of {hex(DEFAULT_SAR_VALUE)}"
|
||||
)
|
||||
self._sar_value = DEFAULT_SAR_VALUE
|
||||
|
||||
|
||||
@@ -47,14 +47,6 @@ class LdrModules(interfaces.plugins.PluginInterface):
|
||||
self.context, self.config_path, "windows", "pe", class_types=pe.class_types
|
||||
)
|
||||
|
||||
def filter_function(x: interfaces.objects.ObjectInterface) -> bool:
|
||||
try:
|
||||
return not (x.get_private_memory() == 0 and x.ControlArea)
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
filter_func = filter_function
|
||||
|
||||
for proc in procs:
|
||||
proc_layer_name = proc.add_process_layer()
|
||||
|
||||
@@ -69,7 +61,7 @@ class LdrModules(interfaces.plugins.PluginInterface):
|
||||
|
||||
# Build dictionary of mapped files, where the VAD start address is the key and value is the file name of the mapped file
|
||||
mapped_files = {}
|
||||
for vad in vadinfo.VadInfo.list_vads(proc, filter_func=filter_func):
|
||||
for vad in vadinfo.VadInfo.list_vads(proc):
|
||||
dos_header = self.context.object(
|
||||
pe_table_name + constants.BANG + "_IMAGE_DOS_HEADER",
|
||||
offset=vad.get_start(),
|
||||
|
||||
@@ -218,6 +218,7 @@ class NetScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
(10, 0, 18362, 0): "netscan-win10-18362-x64",
|
||||
(10, 0, 18363, 0): "netscan-win10-18363-x64",
|
||||
(10, 0, 19041, 0): "netscan-win10-19041-x64",
|
||||
(10, 0, 20348, 0): "netscan-win10-20348-x64",
|
||||
}
|
||||
|
||||
# we do not need to check for tcpip's specific FileVersion in every case
|
||||
|
||||
@@ -35,7 +35,7 @@ class NetStat(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface):
|
||||
name="netscan", component=netscan.NetScan, version=(1, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="modules", component=modules.Modules, version=(1, 0, 0)
|
||||
name="modules", component=modules.Modules, version=(2, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="pdbutil", component=pdbutil.PDBUtility, version=(1, 0, 0)
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
import datetime, logging, string
|
||||
|
||||
from volatility3.framework import constants, exceptions
|
||||
from volatility3.framework.interfaces import plugins
|
||||
from volatility3.framework.configuration import requirements
|
||||
from volatility3.framework.renderers import format_hints, TreeGrid
|
||||
from volatility3.plugins.windows import (
|
||||
handles,
|
||||
info,
|
||||
pslist,
|
||||
psscan,
|
||||
sessions,
|
||||
thrdscan,
|
||||
)
|
||||
|
||||
vollog = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class PsXView(plugins.PluginInterface):
|
||||
"""Lists all processes found via four of the methods described in \"The Art of Memory Forensics,\" which may help
|
||||
identify processes that are trying to hide themselves. I recommend using -r pretty if you are looking at this
|
||||
plugin's output in a terminal."""
|
||||
|
||||
# I've omitted the desktop thread scanning method because Volatility3 doesn't appear to have the funcitonality
|
||||
# which the original plugin used to do it.
|
||||
|
||||
# The sessions method is omitted because it begins with the list of processes found by Pslist anyway.
|
||||
|
||||
# Lastly, I've omitted the pspcid method because I could not for the life of me get it to work. I saved the
|
||||
# code I do have from it, and will happily share it if anyone else wants to add it.
|
||||
|
||||
_required_framework_version = (2, 0, 0)
|
||||
_version = (1, 0, 0)
|
||||
|
||||
valid_proc_name_chars = set(
|
||||
string.ascii_lowercase + string.ascii_uppercase + "." + " "
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_requirements(cls):
|
||||
return [
|
||||
requirements.ModuleRequirement(
|
||||
name="kernel",
|
||||
description="Windows kernel",
|
||||
architectures=["Intel32", "Intel64"],
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="info", component=info.Info, version=(1, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="pslist", component=pslist.PsList, version=(2, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="psscan", component=psscan.PsScan, version=(1, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="thrdscan", component=thrdscan.ThrdScan, version=(1, 0, 0)
|
||||
),
|
||||
requirements.VersionRequirement(
|
||||
name="handles", component=handles.Handles, version=(1, 0, 0)
|
||||
),
|
||||
requirements.BooleanRequirement(
|
||||
name="physical-offsets",
|
||||
description="List processes with physical offsets instead of virtual offsets.",
|
||||
optional=True,
|
||||
),
|
||||
]
|
||||
|
||||
def _proc_name_to_string(self, proc):
|
||||
return proc.ImageFileName.cast(
|
||||
"string", max_length=proc.ImageFileName.vol.count, errors="replace"
|
||||
)
|
||||
|
||||
def _is_valid_proc_name(self, str):
|
||||
for c in str:
|
||||
if not c in self.valid_proc_name_chars:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _filter_garbage_procs(self, proc_list):
|
||||
return [
|
||||
p
|
||||
for p in proc_list
|
||||
if p.is_valid() and self._is_valid_proc_name(self._proc_name_to_string(p))
|
||||
]
|
||||
|
||||
def _translate_offset(self, offset):
|
||||
if not self.config["physical-offsets"]:
|
||||
return offset
|
||||
|
||||
kernel = self.context.modules[self.config["kernel"]]
|
||||
layer_name = kernel.layer_name
|
||||
|
||||
try:
|
||||
_original_offset, _original_length, offset, _length, _layer_name = list(
|
||||
self.context.layers[layer_name].mapping(offset=offset, length=0)
|
||||
)[0]
|
||||
except exceptions.PagedInvalidAddressException:
|
||||
vollog.debug(f"Page fault: unable to translate {offset:0x}")
|
||||
|
||||
return offset
|
||||
|
||||
def _proc_list_to_dict(self, tasks):
|
||||
tasks = self._filter_garbage_procs(tasks)
|
||||
return {self._translate_offset(proc.vol.offset): proc for proc in tasks}
|
||||
|
||||
def _check_pslist(self, tasks):
|
||||
return self._proc_list_to_dict(tasks)
|
||||
|
||||
def _check_psscan(self, layer_name, symbol_table):
|
||||
res = psscan.PsScan.scan_processes(
|
||||
context=self.context, layer_name=layer_name, symbol_table=symbol_table
|
||||
)
|
||||
|
||||
return self._proc_list_to_dict(res)
|
||||
|
||||
def _check_thrdscan(self):
|
||||
ret = []
|
||||
|
||||
for ethread in thrdscan.ThrdScan.scan_threads(
|
||||
self.context, module_name="kernel"
|
||||
):
|
||||
process = None
|
||||
try:
|
||||
process = ethread.owning_process()
|
||||
if not process.is_valid():
|
||||
continue
|
||||
|
||||
ret.append(process)
|
||||
except AttributeError:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVV,
|
||||
"Unable to find the owning process of ethread",
|
||||
)
|
||||
|
||||
return self._proc_list_to_dict(ret)
|
||||
|
||||
def _check_csrss_handles(self, tasks, layer_name, symbol_table):
|
||||
ret = []
|
||||
|
||||
for p in tasks:
|
||||
name = self._proc_name_to_string(p)
|
||||
if name == "csrss.exe":
|
||||
try:
|
||||
if p.has_member("ObjectTable"):
|
||||
handles_plugin = handles.Handles(
|
||||
context=self.context, config_path=self.config_path
|
||||
)
|
||||
hndls = list(handles_plugin.handles(p.ObjectTable))
|
||||
for h in hndls:
|
||||
if (
|
||||
h.get_object_type(
|
||||
handles_plugin.get_type_map(
|
||||
self.context, layer_name, symbol_table
|
||||
)
|
||||
)
|
||||
== "Process"
|
||||
):
|
||||
ret.append(h.Body.cast("_EPROCESS"))
|
||||
|
||||
except exceptions.InvalidAddressException:
|
||||
vollog.log(
|
||||
constants.LOGLEVEL_VVV, "Cannot access eprocess object table"
|
||||
)
|
||||
|
||||
return self._proc_list_to_dict(ret)
|
||||
|
||||
def _generator(self):
|
||||
kernel = self.context.modules[self.config["kernel"]]
|
||||
|
||||
layer_name = kernel.layer_name
|
||||
symbol_table = kernel.symbol_table_name
|
||||
|
||||
kdbg_list_processes = list(
|
||||
pslist.PsList.list_processes(
|
||||
context=self.context, layer_name=layer_name, symbol_table=symbol_table
|
||||
)
|
||||
)
|
||||
|
||||
# get processes from each source
|
||||
processes = {}
|
||||
|
||||
processes["pslist"] = self._check_pslist(kdbg_list_processes)
|
||||
processes["psscan"] = self._check_psscan(layer_name, symbol_table)
|
||||
processes["thrdscan"] = self._check_thrdscan()
|
||||
processes["csrss"] = self._check_csrss_handles(
|
||||
kdbg_list_processes, layer_name, symbol_table
|
||||
)
|
||||
|
||||
# print results
|
||||
|
||||
# list of lists of offsets
|
||||
offsets = [list(processes[source].keys()) for source in processes]
|
||||
|
||||
# flatten to one list
|
||||
offsets = sum(offsets, [])
|
||||
|
||||
# remove duplicates
|
||||
offsets = set(offsets)
|
||||
|
||||
for offset in offsets:
|
||||
proc = None
|
||||
|
||||
in_sources = {src: False for src in processes}
|
||||
|
||||
for source in processes:
|
||||
if offset in processes[source]:
|
||||
in_sources[source] = True
|
||||
if not proc:
|
||||
proc = processes[source][offset]
|
||||
|
||||
pid = proc.UniqueProcessId
|
||||
name = self._proc_name_to_string(proc)
|
||||
|
||||
exit_time = proc.get_exit_time()
|
||||
if type(exit_time) != datetime.datetime:
|
||||
exit_time = ""
|
||||
else:
|
||||
exit_time = str(exit_time)
|
||||
|
||||
yield (
|
||||
0,
|
||||
(
|
||||
format_hints.Hex(offset),
|
||||
name,
|
||||
pid,
|
||||
in_sources["pslist"],
|
||||
in_sources["psscan"],
|
||||
in_sources["thrdscan"],
|
||||
in_sources["csrss"],
|
||||
exit_time,
|
||||
),
|
||||
)
|
||||
|
||||
def run(self):
|
||||
offset_type = "(Physical)" if self.config["physical-offsets"] else "(Virtual)"
|
||||
offset_str = "Offset" + offset_type
|
||||
|
||||
return TreeGrid(
|
||||
[
|
||||
(offset_str, format_hints.Hex),
|
||||
("Name", str),
|
||||
("PID", int),
|
||||
("pslist", bool),
|
||||
("psscan", bool),
|
||||
("thrdscan", bool),
|
||||
("csrss", bool),
|
||||
("Exit Time", str),
|
||||
],
|
||||
self._generator(),
|
||||
)
|
||||
@@ -19,9 +19,11 @@ def wintime_to_datetime(
|
||||
return renderers.NotApplicableValue()
|
||||
unix_time = unix_time - 11644473600
|
||||
try:
|
||||
return datetime.datetime.utcfromtimestamp(unix_time)
|
||||
# Windows sometimes throws OSErrors rather than ValueErrors when it can't convert a value
|
||||
except (ValueError, OSError):
|
||||
return datetime.datetime.fromtimestamp(unix_time, datetime.timezone.utc)
|
||||
# Windows sometimes throws OSErrors rather than ValueError/OverflowError when it can't convert a value
|
||||
# Since Python 3.3, this should raise OverflowError instead of ValueError. However, it was observed
|
||||
# that even in Python 3.7.17, ValueError is still being raised.
|
||||
except (ValueError, OverflowError, OSError):
|
||||
return renderers.UnparsableValue()
|
||||
|
||||
|
||||
@@ -33,8 +35,10 @@ def unixtime_to_datetime(
|
||||
)
|
||||
|
||||
if unixtime > 0:
|
||||
with contextlib.suppress(ValueError):
|
||||
ret = datetime.datetime.utcfromtimestamp(unixtime)
|
||||
# Since Python 3.3, this should raise OverflowError instead of ValueError. However, it was observed
|
||||
# that even in Python 3.7.17, ValueError is still being raised. OSError is also raised on Linux
|
||||
with contextlib.suppress(ValueError, OverflowError, OSError):
|
||||
ret = datetime.datetime.fromtimestamp(unixtime, datetime.timezone.utc)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ Text renderers should attempt to honour all hints provided in this module where
|
||||
"""
|
||||
from typing import Type, Union
|
||||
|
||||
from volatility3.framework import interfaces
|
||||
|
||||
|
||||
class Bin(int):
|
||||
"""A class to indicate that the integer value should be represented as a
|
||||
@@ -66,3 +68,17 @@ class MultiTypeData(bytes):
|
||||
and self.split_nulls == other.split_nulls
|
||||
and self.show_hex == other.show_hex
|
||||
)
|
||||
|
||||
|
||||
BinOrAbsent = lambda x: (
|
||||
Bin(x) if not isinstance(x, interfaces.renderers.BaseAbsentValue) else x
|
||||
)
|
||||
HexOrAbsent = lambda x: (
|
||||
Hex(x) if not isinstance(x, interfaces.renderers.BaseAbsentValue) else x
|
||||
)
|
||||
HexBytesOrAbsent = lambda x: (
|
||||
HexBytes(x) if not isinstance(x, interfaces.renderers.BaseAbsentValue) else x
|
||||
)
|
||||
MultiTypeDataOrAbsent = lambda x: (
|
||||
MultiTypeData(x) if not isinstance(x, interfaces.renderers.BaseAbsentValue) else x
|
||||
)
|
||||
|
||||
@@ -29,12 +29,18 @@ 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("inode", extensions.inode)
|
||||
# Might not exist in the current symbols
|
||||
self.optional_set_type_class("module", extensions.module)
|
||||
self.optional_set_type_class("bpf_prog", extensions.bpf_prog)
|
||||
self.optional_set_type_class("kernel_cap_struct", extensions.kernel_cap_struct)
|
||||
self.optional_set_type_class("kernel_cap_t", extensions.kernel_cap_t)
|
||||
|
||||
# kernels >= 4.18
|
||||
self.optional_set_type_class("timespec64", extensions.timespec64)
|
||||
# kernels < 4.18. Reuses timespec64 obj extension, since both has the same members
|
||||
self.optional_set_type_class("timespec", extensions.timespec64)
|
||||
|
||||
# Mount
|
||||
self.set_type_class("vfsmount", extensions.vfsmount)
|
||||
# Might not exist in older kernels or the current symbols
|
||||
|
||||
@@ -4,10 +4,13 @@
|
||||
|
||||
import collections.abc
|
||||
import logging
|
||||
import stat
|
||||
from datetime import datetime
|
||||
import socket as socket_module
|
||||
from typing import Generator, Iterable, Iterator, Optional, Tuple, List
|
||||
from typing import Generator, Iterable, Iterator, Optional, Tuple, List, Union
|
||||
|
||||
from volatility3.framework import constants, exceptions, objects, interfaces, symbols
|
||||
from volatility3.framework.renderers import conversion
|
||||
from volatility3.framework.constants.linux import SOCK_TYPES, SOCK_FAMILY
|
||||
from volatility3.framework.constants.linux import IP_PROTOCOLS, IPV6_PROTOCOLS
|
||||
from volatility3.framework.constants.linux import TCP_STATES, NETLINK_PROTOCOLS
|
||||
@@ -1761,3 +1764,136 @@ class kernel_cap_t(kernel_cap_struct):
|
||||
)
|
||||
|
||||
return cap_value & self.get_kernel_cap_full()
|
||||
|
||||
|
||||
class timespec64(objects.StructType):
|
||||
def to_datetime(self) -> datetime:
|
||||
"""Returns the respective aware datetime"""
|
||||
|
||||
dt = conversion.unixtime_to_datetime(self.tv_sec + self.tv_nsec / 1e9)
|
||||
return dt
|
||||
|
||||
|
||||
class inode(objects.StructType):
|
||||
def is_valid(self) -> bool:
|
||||
# i_count is a 'signed' counter (atomic_t). Smear, or essentially a wrong inode
|
||||
# pointer, will easily cause an integer overflow here.
|
||||
return self.i_ino > 0 and self.i_count.counter >= 0
|
||||
|
||||
@property
|
||||
def is_dir(self) -> bool:
|
||||
"""Returns True if the inode is a directory"""
|
||||
return stat.S_ISDIR(self.i_mode) != 0
|
||||
|
||||
@property
|
||||
def is_reg(self) -> bool:
|
||||
"""Returns True if the inode is a regular file"""
|
||||
return stat.S_ISREG(self.i_mode) != 0
|
||||
|
||||
@property
|
||||
def is_link(self) -> bool:
|
||||
"""Returns True if the inode is a symlink"""
|
||||
return stat.S_ISLNK(self.i_mode) != 0
|
||||
|
||||
@property
|
||||
def is_fifo(self) -> bool:
|
||||
"""Returns True if the inode is a FIFO"""
|
||||
return stat.S_ISFIFO(self.i_mode) != 0
|
||||
|
||||
@property
|
||||
def is_sock(self) -> bool:
|
||||
"""Returns True if the inode is a socket"""
|
||||
return stat.S_ISSOCK(self.i_mode) != 0
|
||||
|
||||
@property
|
||||
def is_block(self) -> bool:
|
||||
"""Returns True if the inode is a block device"""
|
||||
return stat.S_ISBLK(self.i_mode) != 0
|
||||
|
||||
@property
|
||||
def is_char(self) -> bool:
|
||||
"""Returns True if the inode is a char device"""
|
||||
return stat.S_ISCHR(self.i_mode) != 0
|
||||
|
||||
@property
|
||||
def is_sticky(self) -> bool:
|
||||
"""Returns True if the sticky bit is set"""
|
||||
return (self.i_mode & stat.S_ISVTX) != 0
|
||||
|
||||
def get_inode_type(self) -> Union[str, None]:
|
||||
"""Returns inode type name
|
||||
|
||||
Returns:
|
||||
The inode type name
|
||||
"""
|
||||
if self.is_dir:
|
||||
return "DIR"
|
||||
elif self.is_reg:
|
||||
return "REG"
|
||||
elif self.is_link:
|
||||
return "LNK"
|
||||
elif self.is_fifo:
|
||||
return "FIFO"
|
||||
elif self.is_sock:
|
||||
return "SOCK"
|
||||
elif self.is_char:
|
||||
return "CHR"
|
||||
elif self.is_block:
|
||||
return "BLK"
|
||||
else:
|
||||
return None
|
||||
|
||||
def _time_member_to_datetime(self, member) -> datetime:
|
||||
if self.has_member(f"{member}_sec") and self.has_member(f"{member}_nsec"):
|
||||
# kernels >= 6.11 it's i_*_sec -> time64_t and i_*_nsec -> u32
|
||||
# Ref Linux commit 3aa63a569c64e708df547a8913c84e64a06e7853
|
||||
return conversion.unixtime_to_datetime(
|
||||
self.member(f"{member}_sec") + self.has_member(f"{member}_nsec") / 1e9
|
||||
)
|
||||
elif self.has_member(f"__{member}"):
|
||||
# 6.6 <= kernels < 6.11 it's a timespec64
|
||||
# Ref Linux commit 13bc24457850583a2e7203ded05b7209ab4bc5ef / 12cd44023651666bd44baa36a5c999698890debb
|
||||
return self.member(f"__{member}").to_datetime()
|
||||
elif self.has_member(member):
|
||||
# In kernels < 6.6 it's a timespec64 or timespec
|
||||
return self.member(member).to_datetime()
|
||||
else:
|
||||
raise exceptions.VolatilityException(
|
||||
"Unsupported kernel inode type implementation"
|
||||
)
|
||||
|
||||
def get_access_time(self) -> datetime:
|
||||
"""Returns the inode's last access time
|
||||
This is updated when inode contents are read
|
||||
|
||||
Returns:
|
||||
A datetime with the inode's last access time
|
||||
"""
|
||||
return self._time_member_to_datetime("i_atime")
|
||||
|
||||
def get_modification_time(self) -> datetime:
|
||||
"""Returns the inode's last modification time
|
||||
This is updated when the inode contents change
|
||||
|
||||
Returns:
|
||||
A datetime with the inode's last data modification time
|
||||
"""
|
||||
|
||||
return self._time_member_to_datetime("i_mtime")
|
||||
|
||||
def get_change_time(self) -> datetime:
|
||||
"""Returns the inode's last change time
|
||||
This is updated when the inode metadata changes
|
||||
|
||||
Returns:
|
||||
A datetime with the inode's last change time
|
||||
"""
|
||||
return self._time_member_to_datetime("i_ctime")
|
||||
|
||||
def get_file_mode(self) -> str:
|
||||
"""Returns the inode's file mode as string of the form '-rwxrwxrwx'.
|
||||
|
||||
Returns:
|
||||
The inode's file mode string
|
||||
"""
|
||||
return stat.filemode(self.i_mode)
|
||||
|
||||
@@ -105,8 +105,11 @@
|
||||
},
|
||||
"NotificationRoutine": {
|
||||
"type": {
|
||||
"kind": "base",
|
||||
"name": "unsigned int"
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "base",
|
||||
"name": "void"
|
||||
}
|
||||
},
|
||||
"offset": 24
|
||||
}
|
||||
|
||||
@@ -0,0 +1,582 @@
|
||||
{
|
||||
"base_types": {
|
||||
"unsigned long": {
|
||||
"kind": "int",
|
||||
"size": 4,
|
||||
"signed": false,
|
||||
"endian": "little"
|
||||
},
|
||||
"unsigned char": {
|
||||
"kind": "char",
|
||||
"size": 1,
|
||||
"signed": false,
|
||||
"endian": "little"
|
||||
},
|
||||
"pointer": {
|
||||
"kind": "int",
|
||||
"size": 8,
|
||||
"signed": false,
|
||||
"endian": "little"
|
||||
},
|
||||
"unsigned int": {
|
||||
"kind": "int",
|
||||
"size": 4,
|
||||
"signed": false,
|
||||
"endian": "little"
|
||||
},
|
||||
"unsigned short": {
|
||||
"kind": "int",
|
||||
"size": 2,
|
||||
"signed": false,
|
||||
"endian": "little"
|
||||
},
|
||||
"unsigned be short": {
|
||||
"kind": "int",
|
||||
"size": 2,
|
||||
"signed": false,
|
||||
"endian": "big"
|
||||
},
|
||||
"long long": {
|
||||
"endian": "little",
|
||||
"kind": "int",
|
||||
"signed": true,
|
||||
"size": 8
|
||||
},
|
||||
"long": {
|
||||
"kind": "int",
|
||||
"size": 4,
|
||||
"signed": false,
|
||||
"endian": "little"
|
||||
}
|
||||
},
|
||||
"symbols": {},
|
||||
"user_types": {
|
||||
"_UDP_ENDPOINT": {
|
||||
"fields": {
|
||||
"Owner": {
|
||||
"offset": 40,
|
||||
"type": {
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "nt_symbols!_EPROCESS"
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
"CreateTime": {
|
||||
"offset": 88,
|
||||
"type": {
|
||||
"kind": "union",
|
||||
"name": "_LARGE_INTEGER"
|
||||
}
|
||||
},
|
||||
"Next": {
|
||||
"offset": 112,
|
||||
"type":{
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "_UDP_ENDPOINT"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LocalAddr": {
|
||||
"offset": 168,
|
||||
"type":{
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "_LOCAL_ADDRESS_WIN10_UDP"
|
||||
}
|
||||
}
|
||||
},
|
||||
"InetAF": {
|
||||
"offset": 32,
|
||||
"type":{
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "_INETAF"
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
"Port": {
|
||||
"offset": 160,
|
||||
"type": {
|
||||
"kind": "base",
|
||||
"name": "unsigned be short"
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 168
|
||||
},
|
||||
"_TCP_LISTENER": {
|
||||
"fields": {
|
||||
"Owner": {
|
||||
"offset": 48,
|
||||
"type": {
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "nt_symbols!_EPROCESS"
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
"CreateTime": {
|
||||
"offset": 64,
|
||||
"type": {
|
||||
"kind": "union",
|
||||
"name": "_LARGE_INTEGER"
|
||||
}
|
||||
},
|
||||
"LocalAddr": {
|
||||
"offset": 96,
|
||||
"type":{
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "_LOCAL_ADDRESS"
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
"InetAF": {
|
||||
"offset": 40,
|
||||
"type":{
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "_INETAF"
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
"Next": {
|
||||
"offset": 120,
|
||||
"type":{
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "_TCP_LISTENER"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Port": {
|
||||
"offset": 114,
|
||||
"type": {
|
||||
"kind": "base",
|
||||
"name": "unsigned be short"
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 128
|
||||
},
|
||||
"_TCP_ENDPOINT": {
|
||||
"fields": {
|
||||
"Owner": {
|
||||
"offset": 752,
|
||||
"type": {
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "nt_symbols!_EPROCESS"
|
||||
}
|
||||
}
|
||||
},
|
||||
"CreateTime": {
|
||||
"offset": 776,
|
||||
"type": {
|
||||
"kind": "union",
|
||||
"name": "_LARGE_INTEGER"
|
||||
}
|
||||
},
|
||||
"AddrInfo": {
|
||||
"offset": 24,
|
||||
"type":{
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "_ADDRINFO"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ListEntry": {
|
||||
"offset": 40,
|
||||
"type": {
|
||||
"kind": "union",
|
||||
"name": "nt_symbols!_LIST_ENTRY"
|
||||
}
|
||||
},
|
||||
"InetAF": {
|
||||
"offset": 16,
|
||||
"type":{
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "_INETAF"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LocalPort": {
|
||||
"offset": 112,
|
||||
"type": {
|
||||
"kind": "base",
|
||||
"name": "unsigned be short"
|
||||
}
|
||||
},
|
||||
"RemotePort": {
|
||||
"offset": 114,
|
||||
"type": {
|
||||
"kind": "base",
|
||||
"name": "unsigned be short"
|
||||
}
|
||||
},
|
||||
"State": {
|
||||
"offset": 108,
|
||||
"type": {
|
||||
"kind": "enum",
|
||||
"name": "TCPStateEnum"
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 632
|
||||
},
|
||||
"_LOCAL_ADDRESS": {
|
||||
"fields": {
|
||||
"pData": {
|
||||
"offset": 16,
|
||||
"type": {
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "_IN_ADDR"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 20
|
||||
},
|
||||
"_LOCAL_ADDRESS_WIN10_UDP": {
|
||||
"fields": {
|
||||
"pData": {
|
||||
"offset": 0,
|
||||
"type": {
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "_IN_ADDR"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 4
|
||||
},
|
||||
"_ADDRINFO": {
|
||||
"fields": {
|
||||
"Local": {
|
||||
"offset": 0,
|
||||
"type": {
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "_LOCAL_ADDRESS"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Remote": {
|
||||
"offset": 16,
|
||||
"type": {
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "_IN_ADDR"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 4
|
||||
},
|
||||
"_IN_ADDR": {
|
||||
"fields": {
|
||||
"addr4": {
|
||||
"offset": 0,
|
||||
"type": {
|
||||
"count": 4,
|
||||
"subtype": {
|
||||
"kind": "base",
|
||||
"name": "unsigned char"
|
||||
},
|
||||
"kind": "array"
|
||||
}
|
||||
},
|
||||
"addr6": {
|
||||
"offset": 0,
|
||||
"type": {
|
||||
"count": 16,
|
||||
"subtype": {
|
||||
"kind": "base",
|
||||
"name": "unsigned char"
|
||||
},
|
||||
"kind": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 6
|
||||
},
|
||||
"_INETAF": {
|
||||
"fields": {
|
||||
"AddressFamily": {
|
||||
"offset": 24,
|
||||
"type": {
|
||||
"kind": "base",
|
||||
"name": "unsigned short"
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 26
|
||||
},
|
||||
"_LARGE_INTEGER": {
|
||||
"fields": {
|
||||
"HighPart": {
|
||||
"offset": 4,
|
||||
"type": {
|
||||
"kind": "base",
|
||||
"name": "long"
|
||||
}
|
||||
},
|
||||
"LowPart": {
|
||||
"offset": 0,
|
||||
"type": {
|
||||
"kind": "base",
|
||||
"name": "unsigned long"
|
||||
}
|
||||
},
|
||||
"QuadPart": {
|
||||
"offset": 0,
|
||||
"type": {
|
||||
"kind": "base",
|
||||
"name": "long long"
|
||||
}
|
||||
},
|
||||
"u": {
|
||||
"offset": 0,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "__unnamed_2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "union",
|
||||
"size": 8
|
||||
},
|
||||
"_INET_COMPARTMENT_SET": {
|
||||
"fields": {
|
||||
"InetCompartment": {
|
||||
"offset": 328,
|
||||
"type": {
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "_INET_COMPARTMENT"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 384
|
||||
},
|
||||
"_INET_COMPARTMENT": {
|
||||
"fields": {
|
||||
"ProtocolCompartment": {
|
||||
"offset": 32,
|
||||
"type": {
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "_PROTOCOL_COMPARTMENT"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 48
|
||||
},
|
||||
"_PROTOCOL_COMPARTMENT": {
|
||||
"fields": {
|
||||
"PortPool": {
|
||||
"offset": 0,
|
||||
"type": {
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "_INET_PORT_POOL"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 16
|
||||
},
|
||||
"_PORT_ASSIGNMENT_ENTRY": {
|
||||
"fields": {
|
||||
"Entry": {
|
||||
"offset": 16,
|
||||
"type": {
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "base",
|
||||
"name": "void"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 32
|
||||
},
|
||||
"_PORT_ASSIGNMENT_LIST": {
|
||||
"fields": {
|
||||
"Assignments": {
|
||||
"offset": 0,
|
||||
"type": {
|
||||
"count": 256,
|
||||
"kind": "array",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "_PORT_ASSIGNMENT_ENTRY"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 6144
|
||||
},
|
||||
"_PORT_ASSIGNMENT": {
|
||||
"fields": {
|
||||
"InPaBigPoolBase": {
|
||||
"offset": 24,
|
||||
"type": {
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "_PORT_ASSIGNMENT_LIST"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 32
|
||||
},
|
||||
"_INET_PORT_POOL": {
|
||||
"fields": {
|
||||
"PortAssignments": {
|
||||
"offset": 224,
|
||||
"type": {
|
||||
"count": 256,
|
||||
"kind": "array",
|
||||
"subtype": {
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "_PORT_ASSIGNMENT"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"PortBitMap": {
|
||||
"offset": 208,
|
||||
"type": {
|
||||
"kind": "struct",
|
||||
"name": "nt_symbols!_RTL_BITMAP"
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 11200
|
||||
},
|
||||
"_PARTITION": {
|
||||
"fields": {
|
||||
"Endpoints" : {
|
||||
"offset": 8,
|
||||
"type": {
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "nt_symbols!_RTL_DYNAMIC_HASH_TABLE"
|
||||
}
|
||||
}
|
||||
},
|
||||
"UnknownHashTable" : {
|
||||
"offset": 16,
|
||||
"type": {
|
||||
"kind": "pointer",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "nt_symbols!_RTL_DYNAMIC_HASH_TABLE"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 192
|
||||
},
|
||||
"_PARTITION_TABLE": {
|
||||
"fields": {
|
||||
"Partitions": {
|
||||
"offset": 0,
|
||||
"type": {
|
||||
"count": 1,
|
||||
"kind": "array",
|
||||
"subtype": {
|
||||
"kind": "struct",
|
||||
"name": "_PARTITION"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"kind": "struct",
|
||||
"size": 128
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"TCPStateEnum": {
|
||||
"base": "long",
|
||||
"constants": {
|
||||
"CLOSED": 0,
|
||||
"LISTENING": 1,
|
||||
"SYN_SENT": 2,
|
||||
"SYN_RCVD": 3,
|
||||
"ESTABLISHED": 4,
|
||||
"FIN_WAIT1": 5,
|
||||
"FIN_WAIT2": 6,
|
||||
"CLOSE_WAIT": 7,
|
||||
"CLOSING": 8,
|
||||
"LAST_ACK": 9,
|
||||
"TIME_WAIT": 12,
|
||||
"DELETE_TCB": 13
|
||||
},
|
||||
"size": 4
|
||||
}
|
||||
},
|
||||
"metadata": {
|
||||
"producer": {
|
||||
"version": "0.0.1",
|
||||
"name": "dgmcdona-by-hand",
|
||||
"datetime": "2024-07-30T13:00:00"
|
||||
},
|
||||
"format": "6.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# PYTHON_ARGCOMPLETE_OK
|
||||
|
||||
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
|
||||
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
|
||||
|
||||
Reference in New Issue
Block a user