diff --git a/volatility3/framework/constants/_version.py b/volatility3/framework/constants/_version.py index 21c339a6e..f219fb0af 100644 --- a/volatility3/framework/constants/_version.py +++ b/volatility3/framework/constants/_version.py @@ -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 diff --git a/volatility3/framework/plugins/timeliner.py b/volatility3/framework/plugins/timeliner.py index 26a100f53..f657a2918 100644 --- a/volatility3/framework/plugins/timeliner.py +++ b/volatility3/framework/plugins/timeliner.py @@ -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 diff --git a/volatility3/framework/plugins/windows/filescan.py b/volatility3/framework/plugins/windows/filescan.py index 0f68f39d4..82566361d 100644 --- a/volatility3/framework/plugins/windows/filescan.py +++ b/volatility3/framework/plugins/windows/filescan.py @@ -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(), ) diff --git a/volatility3/framework/plugins/windows/handles.py b/volatility3/framework/plugins/windows/handles.py index a19e7a397..3e5a2fd82 100644 --- a/volatility3/framework/plugins/windows/handles.py +++ b/volatility3/framework/plugins/windows/handles.py @@ -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 diff --git a/volatility3/framework/plugins/windows/ldrmodules.py b/volatility3/framework/plugins/windows/ldrmodules.py index ffd84ea4a..a888f22e1 100644 --- a/volatility3/framework/plugins/windows/ldrmodules.py +++ b/volatility3/framework/plugins/windows/ldrmodules.py @@ -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(), diff --git a/volatility3/framework/plugins/windows/netscan.py b/volatility3/framework/plugins/windows/netscan.py index 62ead3ab7..868bd8bcd 100644 --- a/volatility3/framework/plugins/windows/netscan.py +++ b/volatility3/framework/plugins/windows/netscan.py @@ -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 diff --git a/volatility3/framework/plugins/windows/netstat.py b/volatility3/framework/plugins/windows/netstat.py index 24eb02018..0908767fc 100644 --- a/volatility3/framework/plugins/windows/netstat.py +++ b/volatility3/framework/plugins/windows/netstat.py @@ -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) diff --git a/volatility3/framework/plugins/windows/psxview.py b/volatility3/framework/plugins/windows/psxview.py new file mode 100644 index 000000000..71919c410 --- /dev/null +++ b/volatility3/framework/plugins/windows/psxview.py @@ -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(), + ) diff --git a/volatility3/framework/plugins/windows/thrdscan.py b/volatility3/framework/plugins/windows/thrdscan.py index bbf65cd6c..6e664e052 100644 --- a/volatility3/framework/plugins/windows/thrdscan.py +++ b/volatility3/framework/plugins/windows/thrdscan.py @@ -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), ) diff --git a/volatility3/framework/plugins/windows/threads.py b/volatility3/framework/plugins/windows/threads.py index 83d231abb..ae70e717b 100644 --- a/volatility3/framework/plugins/windows/threads.py +++ b/volatility3/framework/plugins/windows/threads.py @@ -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 diff --git a/volatility3/framework/renderers/conversion.py b/volatility3/framework/renderers/conversion.py index bb18fcc8a..864794860 100644 --- a/volatility3/framework/renderers/conversion.py +++ b/volatility3/framework/renderers/conversion.py @@ -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 diff --git a/volatility3/framework/renderers/format_hints.py b/volatility3/framework/renderers/format_hints.py index 6120b77c9..194e38099 100644 --- a/volatility3/framework/renderers/format_hints.py +++ b/volatility3/framework/renderers/format_hints.py @@ -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 +) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 1b3f75e98..a96fe9d2f 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -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 diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index e7c6b66d7..05679523f 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -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) diff --git a/volatility3/framework/symbols/windows/callbacks-x64.json b/volatility3/framework/symbols/windows/callbacks-x64.json index 3f891b94f..705f2361d 100644 --- a/volatility3/framework/symbols/windows/callbacks-x64.json +++ b/volatility3/framework/symbols/windows/callbacks-x64.json @@ -105,8 +105,11 @@ }, "NotificationRoutine": { "type": { - "kind": "base", - "name": "unsigned int" + "kind": "pointer", + "subtype": { + "kind": "base", + "name": "void" + } }, "offset": 24 } diff --git a/volatility3/framework/symbols/windows/netscan/netscan-win10-20348-x64.json b/volatility3/framework/symbols/windows/netscan/netscan-win10-20348-x64.json new file mode 100644 index 000000000..bd574b2b7 --- /dev/null +++ b/volatility3/framework/symbols/windows/netscan/netscan-win10-20348-x64.json @@ -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" + } +}