Merge branch 'volatilityfoundation:develop' into feature/lsof_inodes

This commit is contained in:
ForensicXlab
2024-08-02 16:21:46 +02:00
committed by GitHub
16 changed files with 1078 additions and 55 deletions
+2 -4
View File
@@ -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
+3 -1
View File
@@ -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
@@ -14,6 +14,7 @@ class FileScan(interfaces.plugins.PluginInterface):
"""Scans for file objects present in a particular windows memory image."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 1)
@classmethod
def get_requirements(cls):
@@ -67,10 +68,10 @@ class FileScan(interfaces.plugins.PluginInterface):
except exceptions.InvalidAddressException:
continue
yield (0, (format_hints.Hex(fileobj.vol.offset), file_name, fileobj.Size))
yield (0, (format_hints.Hex(fileobj.vol.offset), file_name))
def run(self):
return renderers.TreeGrid(
[("Offset", format_hints.Hex), ("Name", str), ("Size", int)],
[("Offset", format_hints.Hex), ("Name", str)],
self._generator(),
)
@@ -25,7 +25,7 @@ class Handles(interfaces.plugins.PluginInterface):
"""Lists process open handles."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 1)
_version = (1, 0, 2)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
@@ -142,6 +142,7 @@ class Handles(interfaces.plugins.PluginInterface):
pointers in the _HANDLE_TABLE_ENTRY which allows us to find the
associated _OBJECT_HEADER.
"""
DEFAULT_SAR_VALUE = 0x10 # to be used only when decoding fails
if self._sar_value is None:
if not has_capstone:
@@ -175,10 +176,11 @@ class Handles(interfaces.plugins.PluginInterface):
virtual_layer_name, func_addr_to_read, num_bytes_to_read
)
except exceptions.InvalidAddressException:
vollog.debug(
f"Failed to read {hex(num_bytes_to_read)} bytes at symbol {hex(func_addr_to_read)}"
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 {hex(DEFAULT_SAR_VALUE)}"
)
return None
self._sar_value = DEFAULT_SAR_VALUE
return self._sar_value
md = capstone.Cs(capstone.CS_ARCH_X86, capstone.CS_MODE_64)
@@ -198,9 +200,10 @@ class Handles(interfaces.plugins.PluginInterface):
break
if self._sar_value is None:
vollog.debug(
f"Failed to to locate SAR value having parsed {instruction_count} instructions"
vollog.warning(
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
return self._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(),
)
@@ -3,7 +3,7 @@
##
import logging
import datetime
from typing import Iterable
from typing import Callable, Iterable
from volatility3.framework import renderers, interfaces, exceptions
from volatility3.framework.configuration import requirements
@@ -19,7 +19,11 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
# version 2.6.0 adds support for scanning for 'Ethread' structures by pool tags
_required_framework_version = (2, 6, 0)
_version = (1, 0, 0)
_version = (1, 1, 0)
def __init__(self, *args, **kwargs):
self.implementation = self.scan_threads
super().__init__(*args, **kwargs)
@classmethod
def get_requirements(cls):
@@ -38,8 +42,7 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
def scan_threads(
cls,
context: interfaces.context.ContextInterface,
layer_name: str,
symbol_table: str,
module_name: str,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Scans for threads using the poolscanner module and constraints.
@@ -52,6 +55,10 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
A list of _ETHREAD objects found by scanning memory for the "Thre" / "Thr\\xE5" pool signatures
"""
module = context.modules[module_name]
layer_name = module.layer_name
symbol_table = module.symbol_table_name
constraints = poolscanner.PoolScanner.builtin_constraints(
symbol_table, [b"Thr\xe5", b"Thre"]
)
@@ -76,7 +83,7 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
ethread.get_exit_time()
) # datetime.datetime object / volatility3.framework.renderers.UnparsableValue object
except exceptions.InvalidAddressException:
vollog.debug("Thread invalid address {:#x}".format(thread.vol.offset))
vollog.debug("Thread invalid address {:#x}".format(ethread.vol.offset))
return None
return (
@@ -88,18 +95,19 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
thread_exit_time,
)
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
def _generator(self, filter_func: Callable):
kernel_name = self.config["kernel"]
for ethread in self.scan_threads(
self.context, kernel.layer_name, kernel.symbol_table_name
):
for ethread in self.implementation(self.context, kernel_name):
info = self.gather_thread_info(ethread)
if info:
yield (0, info)
def generate_timeline(self):
for row in self._generator():
filt_func = self.filter_func(self.config)
for row in self._generator(filt_func):
_depth, row_data = row
row_dict = {}
(
@@ -126,7 +134,14 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
row_dict["ExitTime"],
)
@classmethod
def filter_func(cls, config: interfaces.configuration.HierarchicalDict) -> Callable:
"""Returns a function that can filter this plugin's implementation method based on the config"""
return lambda x: False
def run(self):
filt_func = self.filter_func(self.config)
return renderers.TreeGrid(
[
("Offset", format_hints.Hex),
@@ -136,5 +151,5 @@ class ThrdScan(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface)
("CreateTime", datetime.datetime),
("ExitTime", datetime.datetime),
],
self._generator(),
self._generator(filt_func),
)
@@ -3,7 +3,7 @@
#
import logging
from typing import List, Generator
from typing import Callable, Iterable, List, Generator
from volatility3.framework import interfaces, constants
from volatility3.framework.configuration import requirements
@@ -18,6 +18,10 @@ class Threads(thrdscan.ThrdScan):
_required_framework_version = (2, 4, 0)
_version = (1, 0, 0)
def __init__(self):
self.implementation = self.list_process_threads
super().__init__()
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
# Since we're calling the plugin, make sure we have the plugin's requirements
@@ -34,7 +38,7 @@ class Threads(thrdscan.ThrdScan):
optional=True,
),
requirements.PluginRequirement(
name="thrdscan", plugin=thrdscan.ThrdScan, version=(1, 0, 0)
name="thrdscan", plugin=thrdscan.ThrdScan, version=(1, 1, 0)
),
]
@@ -60,18 +64,27 @@ class Threads(thrdscan.ThrdScan):
seen.add(thread.vol.offset)
yield thread
def _generator(self):
kernel = self.context.modules[self.config["kernel"]]
@classmethod
def filter_func(cls, config: interfaces.configuration.HierarchicalDict) -> Callable:
return pslist.PsList.create_pid_filter(config.get("pid", None))
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))
@classmethod
def list_process_threads(
cls,
context: interfaces.context.ContextInterface,
module_name: str,
filter_func: Callable,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Runs through all processes and lists threads for each process"""
module = context.modules[module_name]
layer_name = module.layer_name
symbol_table_name = module.symbol_table_name
for proc in pslist.PsList.list_processes(
context=self.context,
layer_name=kernel.layer_name,
symbol_table=kernel.symbol_table_name,
context=context,
layer_name=layer_name,
symbol_table=symbol_table_name,
filter_func=filter_func,
):
for thread in self.list_threads(kernel, proc):
info = self.gather_thread_info(thread)
if info:
yield (0, info)
for thread in cls.list_threads(module, proc):
yield thread
@@ -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"
}
}