From 18f7f035ee83080d2ea660acfef05453c8e04ab5 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Fri, 18 Oct 2024 12:29:09 +1100 Subject: [PATCH 01/15] linux: fix datetime import and remove unused ones --- .../framework/symbols/linux/extensions/__init__.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 0dab0db433..d9392b9ba3 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -7,13 +7,12 @@ import functools import binascii import stat -from datetime import datetime +import datetime import socket as socket_module from typing import Generator, Iterable, Iterator, Optional, Tuple, List, Union, Dict from volatility3.framework import constants, exceptions, objects, interfaces, symbols from volatility3.framework.renderers import conversion -from volatility3.framework.configuration import requirements 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 @@ -1882,7 +1881,7 @@ def get_capabilities(self) -> int: class timespec64(objects.StructType): - def to_datetime(self) -> datetime: + def to_datetime(self) -> datetime.datetime: """Returns the respective aware datetime""" dt = conversion.unixtime_to_datetime(self.tv_sec + self.tv_nsec / 1e9) @@ -1958,7 +1957,7 @@ def get_inode_type(self) -> Union[str, None]: else: return None - def _time_member_to_datetime(self, member) -> datetime: + def _time_member_to_datetime(self, member) -> datetime.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 @@ -1977,7 +1976,7 @@ def _time_member_to_datetime(self, member) -> datetime: "Unsupported kernel inode type implementation" ) - def get_access_time(self) -> datetime: + def get_access_time(self) -> datetime.datetime: """Returns the inode's last access time This is updated when inode contents are read @@ -1986,7 +1985,7 @@ def get_access_time(self) -> datetime: """ return self._time_member_to_datetime("i_atime") - def get_modification_time(self) -> datetime: + def get_modification_time(self) -> datetime.datetime: """Returns the inode's last modification time This is updated when the inode contents change @@ -1996,7 +1995,7 @@ def get_modification_time(self) -> datetime: return self._time_member_to_datetime("i_mtime") - def get_change_time(self) -> datetime: + def get_change_time(self) -> datetime.datetime: """Returns the inode's last change time This is updated when the inode metadata changes From f8224684078dd9937da4b0924f504a0bfb25b8bf Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Fri, 18 Oct 2024 12:40:02 +1100 Subject: [PATCH 02/15] linux: Implement boot time support in the Volatility 3 core framework --- .../framework/constants/linux/__init__.py | 3 + .../framework/symbols/linux/__init__.py | 98 +++++++++ .../symbols/linux/extensions/__init__.py | 197 ++++++++++++++++++ 3 files changed, 298 insertions(+) diff --git a/volatility3/framework/constants/linux/__init__.py b/volatility3/framework/constants/linux/__init__.py index 3eabc2341a..a481e1ba96 100644 --- a/volatility3/framework/constants/linux/__init__.py +++ b/volatility3/framework/constants/linux/__init__.py @@ -302,3 +302,6 @@ class ELF_CLASS(IntEnum): ELFCLASSNONE = 0 ELFCLASS32 = 1 ELFCLASS64 = 2 + + +NSEC_PER_SEC = 1e9 diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 219f120ee7..95b4762634 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -3,11 +3,15 @@ # import math import contextlib +import datetime +import dataclasses from abc import ABC, abstractmethod from typing import Iterator, List, Tuple, Optional, Union from volatility3 import framework from volatility3.framework import constants, exceptions, interfaces, objects +from volatility3.framework.renderers import conversion +from volatility3.framework.constants.linux import NSEC_PER_SEC from volatility3.framework.objects import utility from volatility3.framework.symbols import intermed from volatility3.framework.symbols.linux import extensions @@ -830,3 +834,97 @@ def get_cached_pages(self) -> interfaces.objects.ObjectInterface: page = self.vmlinux.object("page", offset=page_addr, absolute=True) if page: yield page + + +@dataclasses.dataclass +class TimespecVol3(object): + """Internal helper class to handle all required timespec operations, convertions and + adjustments. + + NOTE: This is intended for exclusive use with get_boottime() and its related functions. + """ + + tv_sec: int = 0 + tv_nsec: int = 0 + + @classmethod + def new_from_timespec(cls, timespec) -> "TimespecVol3": + """Creates a new instance from a TimespecVol3 or timespec64 object""" + if not isinstance(timespec, (TimespecVol3, extensions.timespec64)): + raise TypeError("It requires either a TimespecVol3 or timespec64 type") + + tv_sec = int(timespec.tv_sec) + tv_nsec = int(timespec.tv_nsec) + return cls(tv_sec=tv_sec, tv_nsec=tv_nsec) + + @classmethod + def new_from_nsec(cls, nsec) -> "TimespecVol3": + """Creates a new instance from an integer in nanoseconds""" + + # Based on ns_to_timespec64() + if nsec > 0: + tv_sec = nsec // NSEC_PER_SEC + tv_nsec = nsec % NSEC_PER_SEC + elif nsec < 0: + tv_sec = -((-nsec - 1) // NSEC_PER_SEC) - 1 + rem = (-nsec - 1) % NSEC_PER_SEC + tv_nsec = NSEC_PER_SEC - rem - 1 + else: + tv_sec = tv_nsec = 0 + + return cls(tv_sec=tv_sec, tv_nsec=tv_nsec) + + def to_datetime(self) -> datetime.datetime: + """Converts this TimespecVol3 to a UTC aware datetime""" + return conversion.unixtime_to_datetime( + self.tv_sec + self.tv_nsec / NSEC_PER_SEC + ) + + def to_timedelta(self) -> datetime.timedelta: + """Converts this TimespecVol3 to timedelta""" + return datetime.timedelta(seconds=self.tv_sec + self.tv_nsec / NSEC_PER_SEC) + + def __add__(self, timespec) -> "TimespecVol3": + """Returns a new TimespecVol3 object that sums the current values with those + in the timespec argument""" + if not isinstance(timespec, (TimespecVol3, extensions.timespec64)): + raise TypeError("Cannot add a TimespecVol3 to this object") + + result = TimespecVol3( + tv_sec=self.tv_sec + timespec.tv_sec, + tv_nsec=self.tv_nsec + timespec.tv_nsec, + ) + + result.normalize() + + return result + + def __sub__(self, timespec) -> "TimespecVol3": + """Returns a new TimespecVol3 object that subtracts the values in the timespec + argument from the current object's values""" + if not isinstance(timespec, (TimespecVol3, extensions.timespec64)): + raise TypeError("Cannot add a TimespecVol3 to this object") + + result = TimespecVol3( + tv_sec=self.tv_sec - timespec.tv_sec, + tv_nsec=self.tv_nsec - timespec.tv_nsec, + ) + result.normalize() + + return result + + def normalize(self): + """Normalize any overflow in tv_sec and tv_nsec after previous addition or subtractions""" + # Based on kernel's set_normalized_timespec64() + while self.tv_nsec >= NSEC_PER_SEC: + self.tv_nsec -= NSEC_PER_SEC + self.tv_sec += 1 + + while self.tv_nsec < 0: + self.tv_nsec += NSEC_PER_SEC + self.tv_sec -= 1 + + def negate(self): + """Negates the sign of both tv_sec and tv_nsec""" + self.tv_sec = -self.tv_sec + self.tv_nsec = -self.tv_nsec diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index d9392b9ba3..b3e8361025 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -382,6 +382,203 @@ def get_threads(self) -> Iterable[interfaces.objects.ObjectInterface]: threads_seen.add(task.vol.offset) yield task + def _get_task_start_time(self) -> datetime.timedelta: + """Returns the task's monotonic start_time as a timedelta. + + Returns: + The task's start time as a timedelta object. + """ + for member_name in ("start_boottime", "real_start_time", "start_time"): + if self.has_member(member_name): + start_time_obj = self.member(member_name) + start_time_obj_type = start_time_obj.vol.type_name + start_time_obj_type_name = start_time_obj_type.split(constants.BANG)[1] + if start_time_obj_type_name != "timespec": + # kernels >= 3.17 real_start_time and start_time are u64 + # kernels >= 5.5 uses start_boottime which is also a u64 + start_time = linux.TimespecVol3.new_from_nsec(start_time_obj) + else: + # kernels < 3.17 real_start_time and start_time are timespec + start_time = linux.TimespecVol3.new_from_timespec(start_time_obj) + + # This is relative to the boot time so it makes sense to be a timedelta. + return start_time.to_timedelta() + + raise AttributeError("Unsupported task_struct start_time member") + + def get_time_namespace(self) -> Optional[interfaces.objects.ObjectInterface]: + """Returns the task's time namespace""" + vmlinux = linux.LinuxUtilities.get_module_from_volobj_type(self._context, self) + if not self.has_member("nsproxy"): + # kernels < 2.6.19: ab516013ad9ca47f1d3a936fa81303bfbf734d52 + return None + + if not vmlinux.get_type("nsproxy").has_member("time_ns"): + # kernels < 5.6 769071ac9f20b6a447410c7eaa55d1a5233ef40c + return None + + return self.nsproxy.time_ns + + def get_time_namespace_id(self) -> int: + """Returns the task's time namespace ID.""" + time_ns = self.get_time_namespace() + if not time_ns: + # kernels < 5.6 + return + + # We are good. ns_common (ns) was introduced in kernels 3.19. So by the time the + # time namespace was added in kernels 5.6, it already included the ns member. + return time_ns.ns.inum + + def _get_time_namespace_offsets( + self, + ) -> Optional[interfaces.objects.ObjectInterface]: + """Returns the time offsets from the task's time namespace.""" + time_ns = self.get_time_namespace() + if not time_ns: + # kernels < 5.6 + return + + if not time_ns.has_member("offsets"): + # kernels < 5.6 af993f58d69ee9c1f421dfc87c3ed231c113989c + return None + + return time_ns.offsets + + def get_time_namespace_monotonic_offset( + self, + ) -> Optional[interfaces.objects.ObjectInterface]: + """Gets task's time namespace monotonic offset + + Returns: + a kernel's timespec64 object with the monotonic offset + """ + time_namespace_offsets = self._get_time_namespace_offsets() + if not time_namespace_offsets: + return None + + return time_namespace_offsets.monotonic + + def _get_time_namespace_boottime_offset( + self, + ) -> Optional[interfaces.objects.ObjectInterface]: + """Gets task's time namespace boottime offset + + Returns: + a kernel's timespec64 object with the boottime offset + """ + time_namespace_offsets = self._get_time_namespace_offsets() + if not time_namespace_offsets: + return None + + return time_namespace_offsets.boottime + + def _get_boottime_raw(self) -> "linux.TimespecVol3": + """Returns the boot time in a TimespecVol3.""" + + vmlinux = linux.LinuxUtilities.get_module_from_volobj_type(self._context, self) + if vmlinux.has_symbol("tk_core"): + # kernels >= 3.17 | tk_core | 3fdb14fd1df70325e1e91e1203a699a4803ed741 + tk_core = vmlinux.object_from_symbol("tk_core") + timekeeper = tk_core.timekeeper + if not timekeeper.offs_real.has_member("tv64"): + # kernels >= 4.10 - Tested on Ubuntu 6.8.0-41 + boottime_nsec = timekeeper.offs_real - timekeeper.offs_boot + else: + # 3.17 <= kernels < 4.10 - Tested on Ubuntu 4.4.0-142 + boottime_nsec = timekeeper.offs_real.tv64 - timekeeper.offs_boot.tv64 + return linux.TimespecVol3.new_from_nsec(boottime_nsec) + + elif vmlinux.has_symbol("timekeeper") and vmlinux.get_type( + "timekeeper" + ).has_member("wall_to_monotonic"): + # 3.4 <= kernels < 3.17 - Tested on Ubuntu 3.13.0-185 + timekeeper = vmlinux.object_from_symbol("timekeeper") + + # timekeeper.wall_to_monotonic is timespec + boottime = linux.TimespecVol3.new_from_timespec( + timekeeper.wall_to_monotonic + ) + + boottime += timekeeper.total_sleep_time + + boottime.negate() + boottime.normalize() + + return boottime + + elif vmlinux.has_symbol("wall_to_monotonic"): + # kernels < 3.4 - Tested on Debian7 3.2.0-4 (3.2.57-3+deb7u2) + wall_to_monotonic = vmlinux.object_from_symbol("wall_to_monotonic") + boottime = linux.TimespecVol3.new_from_timespec(wall_to_monotonic) + if vmlinux.has_symbol("total_sleep_time"): + # 2.6.23 <= kernels < 3.4 7c3f1a573237b90ef331267260358a0ec4ac9079 + total_sleep_time = vmlinux.object_from_symbol("total_sleep_time") + full_type_name = total_sleep_time.vol.type_name + type_name = full_type_name.split(constants.BANG)[1] + if type_name == "timespec": + # kernels >= 2.6.32 total_sleep_time is a timespec + boottime += total_sleep_time + else: + # kernels < 2.6.32 total_sleep_time is an unsigned long as seconds + boottime.tv_sec += total_sleep_time + + boottime.negate() + boottime.normalize() + + return boottime + + raise exceptions.VolatilityException("Unsupported") + + def get_boottime(self, root_time_namespace: bool = True) -> datetime.datetime: + """Returns the boot time in UTC as a datetime. + + Args: + root_time_namespace: If True, it returns the boot time as seen from the root + time namespace. Otherwise, it returns the boot time relative to the + task's time namespace. + + Returns: + A datetime with the UTC boot time. + """ + boottime = self._get_boottime_raw() + if not boottime: + return None + + if not root_time_namespace: + # Shift boot timestamp according to the task's time namespace offset + boottime_offset_timespec = self._get_time_namespace_boottime_offset() + if boottime_offset_timespec: + # Time namespace support is from kernels 5.6 + boottime -= boottime_offset_timespec + + return boottime.to_datetime() + + def get_create_time(self) -> datetime.datetime: + """Retrieves the task's start time from its time namespace. + Args: + context: The context to retrieve required elements (layers, symbol tables) from + vmlinux_module_name: The name of the kernel module on which to operate + task: A reference task + + Returns: + A datetime with task's start time + """ + # Typically, we want to see the creation time seen from the root time namespace + boottime = self.get_boottime(root_time_namespace=True) + + # The kernel exports only tv_sec to procfs, see kernel's show_stat(). + # This means user-space tools, like those in the procps package (e.g., ps, top, etc.), + # only use the boot time seconds to compute dates relatives to this. + boottime = boottime.replace(microsecond=0) + + task_start_time_timedelta = self._get_task_start_time() + + # NOTE: Do NOT apply the task's time namespace offsets here. While the kernel uses + # timens_add_boottime_ns(), it's not needed here since we're seeing it from the + # root time namespace, not within the task's own time namespace + return boottime + task_start_time_timedelta + class fs_struct(objects.StructType): def get_root_dentry(self): From 69512dc9911fc6ce8d0e1ab65926663341513974 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Fri, 18 Oct 2024 12:58:02 +1100 Subject: [PATCH 03/15] Linux: pslist: Add creation time column and timeline support to the linux.pslist plugin --- volatility3/framework/plugins/linux/pslist.py | 31 ++++++++++++++++--- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/volatility3/framework/plugins/linux/pslist.py b/volatility3/framework/plugins/linux/pslist.py index 1888bd7b83..b05d69c7ac 100644 --- a/volatility3/framework/plugins/linux/pslist.py +++ b/volatility3/framework/plugins/linux/pslist.py @@ -1,6 +1,7 @@ # This file is Copyright 2021 Volatility Foundation and licensed under the Volatility Software License 1.0 # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # +import datetime from typing import Any, Callable, Iterable, List, Tuple from volatility3.framework import interfaces, renderers @@ -9,15 +10,16 @@ from volatility3.framework.renderers import format_hints from volatility3.framework.symbols import intermed from volatility3.framework.symbols.linux.extensions import elf +from volatility3.plugins import timeliner from volatility3.plugins.linux import elfs -class PsList(interfaces.plugins.PluginInterface): +class PsList(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): """Lists the processes present in a particular linux memory image.""" _required_framework_version = (2, 0, 0) - _version = (2, 2, 1) + _version = (2, 3, 0) @classmethod def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: @@ -81,7 +83,7 @@ def filter_func(x): @classmethod def get_task_fields( cls, task: interfaces.objects.ObjectInterface, decorate_comm: bool = False - ) -> Tuple[int, int, int, str]: + ) -> Tuple[int, int, int, int, str, datetime.datetime]: """Extract the fields needed for the final output Args: @@ -96,13 +98,14 @@ def get_task_fields( tid = task.pid ppid = task.parent.tgid if task.parent else 0 name = utility.array_to_string(task.comm) + start_time = task.get_create_time() if decorate_comm: if task.is_kernel_thread: name = f"[{name}]" elif task.is_user_thread: name = f"{{{name}}}" - task_fields = (task.vol.offset, pid, tid, ppid, name) + task_fields = (task.vol.offset, pid, tid, ppid, name, start_time) return task_fields def _get_file_output(self, task: interfaces.objects.ObjectInterface) -> str: @@ -177,7 +180,9 @@ def _generator( else: file_output = "Disabled" - offset, pid, tid, ppid, name = self.get_task_fields(task, decorate_comm) + offset, pid, tid, ppid, name, creation_time = self.get_task_fields( + task, decorate_comm + ) yield 0, ( format_hints.Hex(offset), @@ -185,6 +190,7 @@ def _generator( tid, ppid, name, + creation_time or renderers.NotAvailableValue(), file_output, ) @@ -233,8 +239,23 @@ def run(self): ("TID", int), ("PPID", int), ("COMM", str), + ("CREATION TIME", datetime.datetime), ("File output", str), ] return renderers.TreeGrid( columns, self._generator(filter_func, include_threads, decorate_comm, dump) ) + + def generate_timeline(self): + pids = self.config.get("pid") + filter_func = self.create_pid_filter(pids) + for task in self.list_tasks( + self.context, self.config["kernel"], filter_func, include_threads=True + ): + offset, user_pid, user_tid, _user_ppid, name, creation_time = ( + self.get_task_fields(task) + ) + + description = f"Process {user_pid}/{user_tid} {name} ({offset})" + + yield (description, timeliner.TimeLinerType.CREATED, creation_time) From d25df2357acf157641a6b30a7a9a30d0261bf147 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Fri, 18 Oct 2024 12:59:23 +1100 Subject: [PATCH 04/15] Linux: pslist: Add the boottime plugin --- .../framework/plugins/linux/boottime.py | 98 +++++++++++++++++++ volatility3/framework/plugins/timeliner.py | 13 ++- 2 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 volatility3/framework/plugins/linux/boottime.py diff --git a/volatility3/framework/plugins/linux/boottime.py b/volatility3/framework/plugins/linux/boottime.py new file mode 100644 index 0000000000..eeee418f85 --- /dev/null +++ b/volatility3/framework/plugins/linux/boottime.py @@ -0,0 +1,98 @@ +# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import datetime +from typing import List, Tuple, Iterable + + +from volatility3.framework import interfaces, renderers +from volatility3.framework.configuration import requirements +from volatility3.plugins import timeliner +from volatility3.plugins.linux import pslist + + +class Boottime(interfaces.plugins.PluginInterface, timeliner.TimeLinerInterface): + """Shows the time the system was started""" + + _required_framework_version = (2, 11, 0) + + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=["Intel32", "Intel64"], + ), + requirements.PluginRequirement( + name="pslist", plugin=pslist.PsList, version=(2, 3, 0) + ), + ] + + @classmethod + def get_time_namespaces_bootime( + cls, + context: interfaces.context.ContextInterface, + vmlinux_module_name: str, + ) -> Iterable[Tuple[int, int, int, str, datetime.datetime]]: + """Enumerates tasks' boot times based on their time namespaces. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + vmlinux_module_name: The name of the kernel module on which to operate + pids: Pid list + unique: Filter unique time namespaces + + Yields: + A tuple with the fields to show in the plugin output. + """ + time_namespace_ids = set() + for task in pslist.PsList.list_tasks(context, vmlinux_module_name): + time_namespace_id = task.get_time_namespace_id() + # If it cannot get the time namespace i.e. kernels < 5.6, this still works + # using None to just get the first tasks + if time_namespace_id in time_namespace_ids: + continue + time_namespace_ids.add(time_namespace_id) + boottime = task.get_boottime(root_time_namespace=False) + + fields = ( + time_namespace_id, + boottime, + ) + yield fields + + def _generator(self): + for ( + time_namespace_id, + boottime, + ) in self.get_time_namespaces_bootime( + self.context, + self.config["kernel"], + ): + fields = [ + time_namespace_id or renderers.NotAvailableValue(), + boottime, + ] + yield 0, fields + + def generate_timeline(self): + for ( + time_namespace_id, + boottime, + ) in self.get_time_namespaces_bootime( + self.context, + self.config["kernel"], + ): + description = f"System boot time for time namespace {time_namespace_id}" + + yield description, timeliner.TimeLinerType.BOOTTIME, boottime + + def run(self): + columns = [ + ("TIME NS", int), + ("Boot Time", datetime.datetime), + ] + return renderers.TreeGrid(columns, self._generator()) diff --git a/volatility3/framework/plugins/timeliner.py b/volatility3/framework/plugins/timeliner.py index c754e43eff..70da0c4fbc 100644 --- a/volatility3/framework/plugins/timeliner.py +++ b/volatility3/framework/plugins/timeliner.py @@ -23,6 +23,7 @@ class TimeLinerType(enum.IntEnum): MODIFIED = 2 ACCESSED = 3 CHANGED = 4 + BOOTTIME = 5 class TimeLinerInterface(metaclass=abc.ABCMeta): @@ -171,6 +172,10 @@ def _generator( TimeLinerType.CHANGED, renderers.NotApplicableValue(), ), + times.get( + TimeLinerType.BOOTTIME, + renderers.NotApplicableValue(), + ), ], ) ) @@ -178,11 +183,11 @@ def _generator( # Write each entry because the body file doesn't need to be sorted if fp: times = self.timeline[(plugin_name, item)] - # Body format is: MD5|name|inode|mode_as_string|UID|GID|size|atime|mtime|ctime|crtime + # Body format is: MD5|name|inode|mode_as_string|UID|GID|size|atime|mtime|ctime|crtime|boottime if self._any_time_present(times): fp.write( - "|{} - {}|0|0|0|0|0|{}|{}|{}|{}\n".format( + "|{} - {}|0|0|0|0|0|{}|{}|{}|{}|{}\n".format( plugin_name, self._sanitize_body_format(item), self._text_format( @@ -197,6 +202,9 @@ def _generator( self._text_format( times.get(TimeLinerType.CREATED, "0") ), + self._text_format( + times.get(TimeLinerType.BOOTTIME, "0") + ), ) ) except Exception as e: @@ -320,6 +328,7 @@ def run(self): ("Modified Date", datetime.datetime), ("Accessed Date", datetime.datetime), ("Changed Date", datetime.datetime), + ("Boot Date", datetime.datetime), ], generator=self._generator(plugins_to_run), ) From 1651ecb1d70bb32868be864c473ef4a346141381 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Fri, 18 Oct 2024 13:58:27 +1100 Subject: [PATCH 05/15] linux: boottime api: Fix explicit returns mixed with implicit (fall through) returns --- volatility3/framework/symbols/linux/extensions/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index b3e8361025..2784aeda70 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -424,7 +424,7 @@ def get_time_namespace_id(self) -> int: time_ns = self.get_time_namespace() if not time_ns: # kernels < 5.6 - return + return None # We are good. ns_common (ns) was introduced in kernels 3.19. So by the time the # time namespace was added in kernels 5.6, it already included the ns member. @@ -437,7 +437,7 @@ def _get_time_namespace_offsets( time_ns = self.get_time_namespace() if not time_ns: # kernels < 5.6 - return + return None if not time_ns.has_member("offsets"): # kernels < 5.6 af993f58d69ee9c1f421dfc87c3ed231c113989c From 4895af47bdec7779c7806dca47c657ae686a6e0b Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Tue, 29 Oct 2024 13:46:19 +1100 Subject: [PATCH 06/15] Linux: Boottime timeliner: Rollback timeliner event type changes and use the created time for the boot time plugin --- volatility3/framework/plugins/linux/boottime.py | 2 +- volatility3/framework/plugins/timeliner.py | 13 ++----------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/volatility3/framework/plugins/linux/boottime.py b/volatility3/framework/plugins/linux/boottime.py index eeee418f85..8f63ee7f87 100644 --- a/volatility3/framework/plugins/linux/boottime.py +++ b/volatility3/framework/plugins/linux/boottime.py @@ -88,7 +88,7 @@ def generate_timeline(self): ): description = f"System boot time for time namespace {time_namespace_id}" - yield description, timeliner.TimeLinerType.BOOTTIME, boottime + yield description, timeliner.TimeLinerType.CREATED, boottime def run(self): columns = [ diff --git a/volatility3/framework/plugins/timeliner.py b/volatility3/framework/plugins/timeliner.py index 70da0c4fbc..c754e43eff 100644 --- a/volatility3/framework/plugins/timeliner.py +++ b/volatility3/framework/plugins/timeliner.py @@ -23,7 +23,6 @@ class TimeLinerType(enum.IntEnum): MODIFIED = 2 ACCESSED = 3 CHANGED = 4 - BOOTTIME = 5 class TimeLinerInterface(metaclass=abc.ABCMeta): @@ -172,10 +171,6 @@ def _generator( TimeLinerType.CHANGED, renderers.NotApplicableValue(), ), - times.get( - TimeLinerType.BOOTTIME, - renderers.NotApplicableValue(), - ), ], ) ) @@ -183,11 +178,11 @@ def _generator( # Write each entry because the body file doesn't need to be sorted if fp: times = self.timeline[(plugin_name, item)] - # Body format is: MD5|name|inode|mode_as_string|UID|GID|size|atime|mtime|ctime|crtime|boottime + # Body format is: MD5|name|inode|mode_as_string|UID|GID|size|atime|mtime|ctime|crtime if self._any_time_present(times): fp.write( - "|{} - {}|0|0|0|0|0|{}|{}|{}|{}|{}\n".format( + "|{} - {}|0|0|0|0|0|{}|{}|{}|{}\n".format( plugin_name, self._sanitize_body_format(item), self._text_format( @@ -202,9 +197,6 @@ def _generator( self._text_format( times.get(TimeLinerType.CREATED, "0") ), - self._text_format( - times.get(TimeLinerType.BOOTTIME, "0") - ), ) ) except Exception as e: @@ -328,7 +320,6 @@ def run(self): ("Modified Date", datetime.datetime), ("Accessed Date", datetime.datetime), ("Changed Date", datetime.datetime), - ("Boot Date", datetime.datetime), ], generator=self._generator(plugins_to_run), ) From c4274c942c4e94e909de44bbe3e12b47a15143f2 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Tue, 29 Oct 2024 13:49:58 +1100 Subject: [PATCH 07/15] Linux: Fix exception message in TimespecVol3::__sub__() --- volatility3/framework/symbols/linux/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 95b4762634..8c28ca5628 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -903,7 +903,7 @@ def __sub__(self, timespec) -> "TimespecVol3": """Returns a new TimespecVol3 object that subtracts the values in the timespec argument from the current object's values""" if not isinstance(timespec, (TimespecVol3, extensions.timespec64)): - raise TypeError("Cannot add a TimespecVol3 to this object") + raise TypeError("Cannot substract this object to a TimespecVol3") result = TimespecVol3( tv_sec=self.tv_sec - timespec.tv_sec, From 57ffd5b939df456b7e573d91d6b23bc01f36f353 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Tue, 29 Oct 2024 14:24:16 +1100 Subject: [PATCH 08/15] Linux: Boottime API: Refactor TimespecVol3::negate() to return a new object instead of modifying the original. It also normalizes its values, aligning with the behavior of the other addition and subtraction operators --- volatility3/framework/symbols/linux/__init__.py | 14 +++++++++++--- .../framework/symbols/linux/extensions/__init__.py | 10 ++-------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 8c28ca5628..4123674ed7 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -909,6 +909,7 @@ def __sub__(self, timespec) -> "TimespecVol3": tv_sec=self.tv_sec - timespec.tv_sec, tv_nsec=self.tv_nsec - timespec.tv_nsec, ) + result.normalize() return result @@ -925,6 +926,13 @@ def normalize(self): self.tv_sec -= 1 def negate(self): - """Negates the sign of both tv_sec and tv_nsec""" - self.tv_sec = -self.tv_sec - self.tv_nsec = -self.tv_nsec + """Returns a new TimespecVol3 object with the values of the current object negated""" + + result = TimespecVol3( + tv_sec=-self.tv_sec, + tv_nsec=-self.tv_nsec, + ) + + result.normalize() + + return result diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 2784aeda70..6fd55cb565 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -502,10 +502,7 @@ def _get_boottime_raw(self) -> "linux.TimespecVol3": boottime += timekeeper.total_sleep_time - boottime.negate() - boottime.normalize() - - return boottime + return boottime.negate() elif vmlinux.has_symbol("wall_to_monotonic"): # kernels < 3.4 - Tested on Debian7 3.2.0-4 (3.2.57-3+deb7u2) @@ -523,10 +520,7 @@ def _get_boottime_raw(self) -> "linux.TimespecVol3": # kernels < 2.6.32 total_sleep_time is an unsigned long as seconds boottime.tv_sec += total_sleep_time - boottime.negate() - boottime.normalize() - - return boottime + return boottime.negate() raise exceptions.VolatilityException("Unsupported") From bee2a398eb4d3f3b28644ed2039bb516ccc3e75e Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Tue, 29 Oct 2024 15:13:26 +1100 Subject: [PATCH 09/15] Linux: Minor: Add comment/header on each set of constants --- volatility3/framework/constants/linux/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/volatility3/framework/constants/linux/__init__.py b/volatility3/framework/constants/linux/__init__.py index 056c384ae5..7bfa9268da 100644 --- a/volatility3/framework/constants/linux/__init__.py +++ b/volatility3/framework/constants/linux/__init__.py @@ -304,6 +304,7 @@ class ELF_CLASS(IntEnum): ELFCLASS64 = 2 +# PTrace PT_OPT_FLAG_SHIFT = 3 PTRACE_EVENT_FORK = 1 @@ -341,4 +342,5 @@ def flags(self) -> str: return str(self).replace(self.__class__.__name__ + ".", "") +# Boot time NSEC_PER_SEC = 1e9 From 42d918c05d6a2d08d025d63f51b9c6764076be12 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Wed, 30 Oct 2024 15:21:13 +1100 Subject: [PATCH 10/15] Linux: Boottime API: Refactor Timespec Methods. Move TimespecVol3 methods to an abstract class Timespec64Abstract, which is now inherited by Timespec64 and Timespec64Concrete. --- .../framework/symbols/linux/__init__.py | 106 -------------- .../symbols/linux/extensions/__init__.py | 138 ++++++++++++++++-- 2 files changed, 126 insertions(+), 118 deletions(-) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 84f546a9f5..1f2812c106 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -3,15 +3,11 @@ # import math import contextlib -import datetime -import dataclasses from abc import ABC, abstractmethod from typing import Iterator, List, Tuple, Optional, Union from volatility3 import framework from volatility3.framework import constants, exceptions, interfaces, objects -from volatility3.framework.renderers import conversion -from volatility3.framework.constants.linux import NSEC_PER_SEC from volatility3.framework.objects import utility from volatility3.framework.symbols import intermed from volatility3.framework.symbols.linux import extensions @@ -836,105 +832,3 @@ def get_cached_pages(self) -> interfaces.objects.ObjectInterface: page = self.vmlinux.object("page", offset=page_addr, absolute=True) if page: yield page - - -@dataclasses.dataclass -class TimespecVol3(object): - """Internal helper class to handle all required timespec operations, convertions and - adjustments. - - NOTE: This is intended for exclusive use with get_boottime() and its related functions. - """ - - tv_sec: int = 0 - tv_nsec: int = 0 - - @classmethod - def new_from_timespec(cls, timespec) -> "TimespecVol3": - """Creates a new instance from a TimespecVol3 or timespec64 object""" - if not isinstance(timespec, (TimespecVol3, extensions.timespec64)): - raise TypeError("It requires either a TimespecVol3 or timespec64 type") - - tv_sec = int(timespec.tv_sec) - tv_nsec = int(timespec.tv_nsec) - return cls(tv_sec=tv_sec, tv_nsec=tv_nsec) - - @classmethod - def new_from_nsec(cls, nsec) -> "TimespecVol3": - """Creates a new instance from an integer in nanoseconds""" - - # Based on ns_to_timespec64() - if nsec > 0: - tv_sec = nsec // NSEC_PER_SEC - tv_nsec = nsec % NSEC_PER_SEC - elif nsec < 0: - tv_sec = -((-nsec - 1) // NSEC_PER_SEC) - 1 - rem = (-nsec - 1) % NSEC_PER_SEC - tv_nsec = NSEC_PER_SEC - rem - 1 - else: - tv_sec = tv_nsec = 0 - - return cls(tv_sec=tv_sec, tv_nsec=tv_nsec) - - def to_datetime(self) -> datetime.datetime: - """Converts this TimespecVol3 to a UTC aware datetime""" - return conversion.unixtime_to_datetime( - self.tv_sec + self.tv_nsec / NSEC_PER_SEC - ) - - def to_timedelta(self) -> datetime.timedelta: - """Converts this TimespecVol3 to timedelta""" - return datetime.timedelta(seconds=self.tv_sec + self.tv_nsec / NSEC_PER_SEC) - - def __add__(self, timespec) -> "TimespecVol3": - """Returns a new TimespecVol3 object that sums the current values with those - in the timespec argument""" - if not isinstance(timespec, (TimespecVol3, extensions.timespec64)): - raise TypeError("Cannot add a TimespecVol3 to this object") - - result = TimespecVol3( - tv_sec=self.tv_sec + timespec.tv_sec, - tv_nsec=self.tv_nsec + timespec.tv_nsec, - ) - - result.normalize() - - return result - - def __sub__(self, timespec) -> "TimespecVol3": - """Returns a new TimespecVol3 object that subtracts the values in the timespec - argument from the current object's values""" - if not isinstance(timespec, (TimespecVol3, extensions.timespec64)): - raise TypeError("Cannot substract this object to a TimespecVol3") - - result = TimespecVol3( - tv_sec=self.tv_sec - timespec.tv_sec, - tv_nsec=self.tv_nsec - timespec.tv_nsec, - ) - - result.normalize() - - return result - - def normalize(self): - """Normalize any overflow in tv_sec and tv_nsec after previous addition or subtractions""" - # Based on kernel's set_normalized_timespec64() - while self.tv_nsec >= NSEC_PER_SEC: - self.tv_nsec -= NSEC_PER_SEC - self.tv_sec += 1 - - while self.tv_nsec < 0: - self.tv_nsec += NSEC_PER_SEC - self.tv_sec -= 1 - - def negate(self): - """Returns a new TimespecVol3 object with the values of the current object negated""" - - result = TimespecVol3( - tv_sec=-self.tv_sec, - tv_nsec=-self.tv_nsec, - ) - - result.normalize() - - return result diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index e952ed9686..ea20850b73 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -2,6 +2,7 @@ # which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 # +import abc import collections.abc import logging import functools @@ -18,12 +19,13 @@ from volatility3.framework.constants.linux import TCP_STATES, NETLINK_PROTOCOLS from volatility3.framework.constants.linux import ETH_PROTOCOLS, BLUETOOTH_STATES from volatility3.framework.constants.linux import BLUETOOTH_PROTOCOLS, SOCKET_STATES -from volatility3.framework.constants.linux import CAPABILITIES, PT_FLAGS +from volatility3.framework.constants.linux import CAPABILITIES, PT_FLAGS, NSEC_PER_SEC from volatility3.framework.layers import linear from volatility3.framework.objects import utility from volatility3.framework.symbols import generic, linux, intermed from volatility3.framework.symbols.linux.extensions import elf + vollog = logging.getLogger(__name__) # Keep these in a basic module, to prevent import cycles when symbol providers require them @@ -431,10 +433,10 @@ def _get_task_start_time(self) -> datetime.timedelta: if start_time_obj_type_name != "timespec": # kernels >= 3.17 real_start_time and start_time are u64 # kernels >= 5.5 uses start_boottime which is also a u64 - start_time = linux.TimespecVol3.new_from_nsec(start_time_obj) + start_time = Timespec64Concrete.new_from_nsec(start_time_obj) else: # kernels < 3.17 real_start_time and start_time are timespec - start_time = linux.TimespecVol3.new_from_timespec(start_time_obj) + start_time = Timespec64Concrete.new_from_timespec(start_time_obj) # This is relative to the boot time so it makes sense to be a timedelta. return start_time.to_timedelta() @@ -508,8 +510,8 @@ def _get_time_namespace_boottime_offset( return time_namespace_offsets.boottime - def _get_boottime_raw(self) -> "linux.TimespecVol3": - """Returns the boot time in a TimespecVol3.""" + def _get_boottime_raw(self) -> "Timespec64Concrete": + """Returns the boot time in a Timespec64Concrete object.""" vmlinux = linux.LinuxUtilities.get_module_from_volobj_type(self._context, self) if vmlinux.has_symbol("tk_core"): @@ -522,7 +524,7 @@ def _get_boottime_raw(self) -> "linux.TimespecVol3": else: # 3.17 <= kernels < 4.10 - Tested on Ubuntu 4.4.0-142 boottime_nsec = timekeeper.offs_real.tv64 - timekeeper.offs_boot.tv64 - return linux.TimespecVol3.new_from_nsec(boottime_nsec) + return Timespec64Concrete.new_from_nsec(boottime_nsec) elif vmlinux.has_symbol("timekeeper") and vmlinux.get_type( "timekeeper" @@ -531,7 +533,7 @@ def _get_boottime_raw(self) -> "linux.TimespecVol3": timekeeper = vmlinux.object_from_symbol("timekeeper") # timekeeper.wall_to_monotonic is timespec - boottime = linux.TimespecVol3.new_from_timespec( + boottime = Timespec64Concrete.new_from_timespec( timekeeper.wall_to_monotonic ) @@ -542,7 +544,7 @@ def _get_boottime_raw(self) -> "linux.TimespecVol3": elif vmlinux.has_symbol("wall_to_monotonic"): # kernels < 3.4 - Tested on Debian7 3.2.0-4 (3.2.57-3+deb7u2) wall_to_monotonic = vmlinux.object_from_symbol("wall_to_monotonic") - boottime = linux.TimespecVol3.new_from_timespec(wall_to_monotonic) + boottime = Timespec64Concrete.new_from_timespec(wall_to_monotonic) if vmlinux.has_symbol("total_sleep_time"): # 2.6.23 <= kernels < 3.4 7c3f1a573237b90ef331267260358a0ec4ac9079 total_sleep_time = vmlinux.object_from_symbol("total_sleep_time") @@ -2168,12 +2170,124 @@ def get_capabilities(self) -> int: return cap_value & self.get_kernel_cap_full() -class timespec64(objects.StructType): +class Timespec64Abstract(abc.ABC): + """Abstract class to handle all required timespec64 operations, convertions and + adjustments.""" + + @classmethod + def new_from_timespec(cls, other) -> "Timespec64Concrete": + """Creates a new instance from an Timespec64Abstract subclass object""" + if not isinstance(other, Timespec64Abstract): + raise TypeError("Requires an object subclass of Timespec64Abstract") + + tv_sec = int(other.tv_sec) + tv_nsec = int(other.tv_nsec) + return Timespec64Concrete(tv_sec=tv_sec, tv_nsec=tv_nsec) + + @classmethod + def new_from_nsec(cls, nsec) -> "Timespec64Concrete": + """Creates a new instance from an integer in nanoseconds""" + + # Based on ns_to_timespec64() + if nsec > 0: + tv_sec = nsec // NSEC_PER_SEC + tv_nsec = nsec % NSEC_PER_SEC + elif nsec < 0: + tv_sec = -((-nsec - 1) // NSEC_PER_SEC) - 1 + rem = (-nsec - 1) % NSEC_PER_SEC + tv_nsec = NSEC_PER_SEC - rem - 1 + else: + tv_sec = tv_nsec = 0 + + return Timespec64Concrete(tv_sec=tv_sec, tv_nsec=tv_nsec) + def to_datetime(self) -> datetime.datetime: - """Returns the respective aware datetime""" + """Converts this Timespec64Abstract subclass object to a UTC aware datetime""" + + # pylint: disable=E1101 + return conversion.unixtime_to_datetime( + self.tv_sec + self.tv_nsec / NSEC_PER_SEC + ) + + def to_timedelta(self) -> datetime.timedelta: + """Converts this Timespec64Abstract subclass object to timedelta""" + # pylint: disable=E1101 + return datetime.timedelta(seconds=self.tv_sec + self.tv_nsec / NSEC_PER_SEC) + + def __add__(self, other) -> "Timespec64Concrete": + """Returns a new Timespec64Concrete object that sums the current values with those + in the timespec argument""" + if not isinstance(other, Timespec64Abstract): + raise TypeError("Requires an object subclass of Timespec64Abstract") + + # pylint: disable=E1101 + result = Timespec64Concrete( + tv_sec=self.tv_sec + other.tv_sec, + tv_nsec=self.tv_nsec + other.tv_nsec, + ) + + result.normalize() + + return result + + def __sub__(self, other) -> "Timespec64Concrete": + """Returns a new Timespec64Abstract object that subtracts the values in the timespec + argument from the current object's values""" + if not isinstance(other, Timespec64Abstract): + raise TypeError("Requires an object subclass of Timespec64Abstract") + + # pylint: disable=E1101 + result = Timespec64Concrete( + tv_sec=self.tv_sec - other.tv_sec, + tv_nsec=self.tv_nsec - other.tv_nsec, + ) + + result.normalize() + + return result + + def normalize(self): + """Normalize any overflow in tv_sec and tv_nsec.""" + # Based on kernel's set_normalized_timespec64() + + # pylint: disable=E1101 + while self.tv_nsec >= NSEC_PER_SEC: + self.tv_nsec -= NSEC_PER_SEC + self.tv_sec += 1 + + while self.tv_nsec < 0: + self.tv_nsec += NSEC_PER_SEC + self.tv_sec -= 1 + + def negate(self): + """Returns a new Timespec64Concrete object with the values of the current object negated""" + # pylint: disable=E1101 + result = Timespec64Concrete( + tv_sec=-self.tv_sec, + tv_nsec=-self.tv_nsec, + ) + + result.normalize() + + return result + + +class Timespec64Concrete(Timespec64Abstract): + """Handle all required timespec64 operations, convertions and adjustments. + This is used to dynamically create timespec64-like objects, each its own variables + and the same methods as a timespec64 object extension. + """ + + def __init__(self, tv_sec=0, tv_nsec=0): + self.tv_sec = tv_sec + self.tv_nsec = tv_nsec + - dt = conversion.unixtime_to_datetime(self.tv_sec + self.tv_nsec / 1e9) - return dt +class timespec64(Timespec64Abstract, objects.StructType): + """Handle all required timespec64 operations, convertions and adjustments. + This works as an extension of the timespec64 object while maintaining the same methods + as a Timespec64Concrete object. + """ class inode(objects.StructType): From 7abe92cf2f2ddac1a5e7f15a27afe341e1810901 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Wed, 30 Oct 2024 15:36:46 +1100 Subject: [PATCH 11/15] Linux: Boottime API: Minor. Move negate() up --- .../symbols/linux/extensions/__init__.py | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index ea20850b73..f20d937550 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -2244,6 +2244,18 @@ def __sub__(self, other) -> "Timespec64Concrete": result.normalize() + return self + other.negate() + + def negate(self): + """Returns a new Timespec64Concrete object with the values of the current object negated""" + # pylint: disable=E1101 + result = Timespec64Concrete( + tv_sec=-self.tv_sec, + tv_nsec=-self.tv_nsec, + ) + + result.normalize() + return result def normalize(self): @@ -2259,18 +2271,6 @@ def normalize(self): self.tv_nsec += NSEC_PER_SEC self.tv_sec -= 1 - def negate(self): - """Returns a new Timespec64Concrete object with the values of the current object negated""" - # pylint: disable=E1101 - result = Timespec64Concrete( - tv_sec=-self.tv_sec, - tv_nsec=-self.tv_nsec, - ) - - result.normalize() - - return result - class Timespec64Concrete(Timespec64Abstract): """Handle all required timespec64 operations, convertions and adjustments. From e197fbaf5fe7a7fba8fabce73533725af9110d74 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Wed, 30 Oct 2024 15:39:40 +1100 Subject: [PATCH 12/15] Linux: Boottime API: Refactor __sub__ to operate through __add__() and negate() for improved clarity and reuse --- .../framework/symbols/linux/extensions/__init__.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index f20d937550..c80ffa5150 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -2236,17 +2236,9 @@ def __sub__(self, other) -> "Timespec64Concrete": if not isinstance(other, Timespec64Abstract): raise TypeError("Requires an object subclass of Timespec64Abstract") - # pylint: disable=E1101 - result = Timespec64Concrete( - tv_sec=self.tv_sec - other.tv_sec, - tv_nsec=self.tv_nsec - other.tv_nsec, - ) - - result.normalize() - return self + other.negate() - def negate(self): + def negate(self) -> "Timespec64Concrete": """Returns a new Timespec64Concrete object with the values of the current object negated""" # pylint: disable=E1101 result = Timespec64Concrete( From a05397e8b68c280ba74c58bc968cc6692d25e613 Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Wed, 30 Oct 2024 15:57:00 +1100 Subject: [PATCH 13/15] Linux: Boottime API: Minor. Fix docstring typo --- volatility3/framework/symbols/linux/extensions/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index c80ffa5150..a9127907e0 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -2266,7 +2266,7 @@ def normalize(self): class Timespec64Concrete(Timespec64Abstract): """Handle all required timespec64 operations, convertions and adjustments. - This is used to dynamically create timespec64-like objects, each its own variables + This is used to dynamically create timespec64-like objects, each with its own variables and the same methods as a timespec64 object extension. """ From 2efb4e7d28d60a337aabae448de24da37c3feb7e Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Tue, 5 Nov 2024 19:56:15 +1100 Subject: [PATCH 14/15] Merge branch 'develop' into linux_boottime_support --- .../framework/constants/linux/__init__.py | 8 + .../framework/plugins/linux/hidden_modules.py | 246 ++++++++++++++++++ .../framework/symbols/linux/__init__.py | 6 +- .../symbols/linux/extensions/__init__.py | 225 +++++++++------- 4 files changed, 383 insertions(+), 102 deletions(-) create mode 100644 volatility3/framework/plugins/linux/hidden_modules.py diff --git a/volatility3/framework/constants/linux/__init__.py b/volatility3/framework/constants/linux/__init__.py index 7bfa9268da..6e49e6f37d 100644 --- a/volatility3/framework/constants/linux/__init__.py +++ b/volatility3/framework/constants/linux/__init__.py @@ -344,3 +344,11 @@ def flags(self) -> str: # Boot time NSEC_PER_SEC = 1e9 + + +# Valid sizes for modules. Note that the Linux kernel does not define these values; they +# are based on empirical observations of typical memory allocations for kernel modules. +# We use this to verify that the found module falls within reasonable limits. +MODULE_MAXIMUM_CORE_SIZE = 20000000 +MODULE_MAXIMUM_CORE_TEXT_SIZE = 20000000 +MODULE_MINIMUM_SIZE = 4096 diff --git a/volatility3/framework/plugins/linux/hidden_modules.py b/volatility3/framework/plugins/linux/hidden_modules.py new file mode 100644 index 0000000000..fd4b289430 --- /dev/null +++ b/volatility3/framework/plugins/linux/hidden_modules.py @@ -0,0 +1,246 @@ +# This file is Copyright 2024 Volatility Foundation and licensed under the Volatility Software License 1.0 +# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0 +# +import logging +from typing import List, Set, Tuple, Iterable +from volatility3.framework import renderers, interfaces, exceptions, objects +from volatility3.framework.constants import architectures +from volatility3.framework.renderers import format_hints +from volatility3.framework.configuration import requirements +from volatility3.plugins.linux import lsmod + +vollog = logging.getLogger(__name__) + + +class Hidden_modules(interfaces.plugins.PluginInterface): + """Carves memory to find hidden kernel modules""" + + _required_framework_version = (2, 10, 0) + + _version = (1, 0, 0) + + @classmethod + def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]: + return [ + requirements.ModuleRequirement( + name="kernel", + description="Linux kernel", + architectures=architectures.LINUX_ARCHS, + ), + requirements.PluginRequirement( + name="lsmod", plugin=lsmod.Lsmod, version=(2, 0, 0) + ), + ] + + @staticmethod + def get_modules_memory_boundaries( + context: interfaces.context.ContextInterface, + vmlinux_module_name: str, + ) -> Tuple[int]: + """Determine the boundaries of the module allocation area + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + vmlinux_module_name: The name of the kernel module on which to operate + + Returns: + A tuple containing the minimum and maximum addresses for the module allocation area. + """ + vmlinux = context.modules[vmlinux_module_name] + if vmlinux.has_symbol("mod_tree"): + # Kernel >= 5.19 58d208de3e8d87dbe196caf0b57cc58c7a3836ca + mod_tree = vmlinux.object_from_symbol("mod_tree") + modules_addr_min = mod_tree.addr_min + modules_addr_max = mod_tree.addr_max + elif vmlinux.has_symbol("module_addr_min"): + # 2.6.27 <= kernel < 5.19 3a642e99babe0617febb6f402e1e063479f489db + modules_addr_min = vmlinux.object_from_symbol("module_addr_min") + modules_addr_max = vmlinux.object_from_symbol("module_addr_max") + + if isinstance(modules_addr_min, objects.Void): + raise exceptions.VolatilityException( + "Your ISF symbols lack type information. You may need to update the" + "ISF using the latest version of dwarf2json" + ) + else: + raise exceptions.VolatilityException( + "Cannot find the module memory allocation area. Unsupported kernel" + ) + + return modules_addr_min, modules_addr_max + + @classmethod + def _get_module_address_alignment( + cls, + context: interfaces.context.ContextInterface, + vmlinux_module_name: str, + ) -> int: + """Obtain the module memory address alignment. + + struct module is aligned to the L1 cache line, which is typically 64 bytes for most + common i386/AMD64/ARM64 configurations. In some cases, it can be 128 bytes, but this + will still work. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + vmlinux_module_name: The name of the kernel module on which to operate + + Returns: + The struct module alignment + """ + # FIXME: When dwarf2json/ISF supports type alignments. Read it directly from the type metadata + # Additionally, while 'context' and 'vmlinux_module_name' are currently unused, they will be + # essential for retrieving type metadata in the future. + return 64 + + @staticmethod + def _validate_alignment_patterns( + addresses: Iterable[int], + address_alignment: int, + ) -> bool: + """Check if the memory addresses meet our alignments patterns + + Args: + addresses: Iterable with the address values + address_alignment: Number of bytes for alignment validation + + Returns: + True if all the addresses meet the alignment + """ + return all(addr % address_alignment == 0 for addr in addresses) + + @classmethod + def get_hidden_modules( + cls, + context: interfaces.context.ContextInterface, + vmlinux_module_name: str, + known_module_addresses: Set[int], + modules_memory_boundaries: Tuple, + ) -> Iterable[interfaces.objects.ObjectInterface]: + """Enumerate hidden modules by taking advantage of memory address alignment patterns + + This technique is much faster and uses less memory than the traditional scan method + in Volatility2, but it doesn't work with older kernels. + + From kernels 4.2 struct module allocation are aligned to the L1 cache line size. + In i386/amd64/arm64 this is typically 64 bytes. However, this can be changed in + the Linux kernel configuration via CONFIG_X86_L1_CACHE_SHIFT. The alignment can + also be obtained from the DWARF info i.e. DW_AT_alignment<64>, but dwarf2json + doesn't support this feature yet. + In kernels < 4.2, alignment attributes are absent in the struct module, meaning + alignment cannot be guaranteed. Therefore, for older kernels, it's better to use + the traditional scan technique. + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + vmlinux_module_name: The name of the kernel module on which to operate + known_module_addresses: Set with known module addresses + modules_memory_boundaries: Minimum and maximum address boundaries for module allocation. + Yields: + module objects + """ + vmlinux = context.modules[vmlinux_module_name] + vmlinux_layer = context.layers[vmlinux.layer_name] + + module_addr_min, module_addr_max = modules_memory_boundaries + module_address_alignment = cls._get_module_address_alignment( + context, vmlinux_module_name + ) + if not cls._validate_alignment_patterns( + known_module_addresses, module_address_alignment + ): + vollog.warning( + f"Module addresses aren't aligned to {module_address_alignment} bytes. " + "Switching to 1 byte aligment scan method." + ) + module_address_alignment = 1 + + mkobj_offset = vmlinux.get_type("module").relative_child_offset("mkobj") + mod_offset = vmlinux.get_type("module_kobject").relative_child_offset("mod") + offset_to_mkobj_mod = mkobj_offset + mod_offset + mod_member_template = vmlinux.get_type("module_kobject").child_template("mod") + mod_size = mod_member_template.size + mod_member_data_format = mod_member_template.data_format + + for module_addr in range( + module_addr_min, module_addr_max, module_address_alignment + ): + if module_addr in known_module_addresses: + continue + + try: + # This is just a pre-filter. Module readability and consistency are verified in module.is_valid() + self_referential_bytes = vmlinux_layer.read( + module_addr + offset_to_mkobj_mod, mod_size + ) + self_referential = objects.convert_data_to_value( + self_referential_bytes, int, mod_member_data_format + ) + if self_referential != module_addr: + continue + except ( + exceptions.PagedInvalidAddressException, + exceptions.InvalidAddressException, + ): + continue + + module = vmlinux.object("module", offset=module_addr, absolute=True) + if module and module.is_valid(): + yield module + + @classmethod + def get_lsmod_module_addresses( + cls, + context: interfaces.context.ContextInterface, + vmlinux_module_name: str, + ) -> Set[int]: + """Obtain a set the known module addresses from linux.lsmod plugin + + Args: + context: The context to retrieve required elements (layers, symbol tables) from + vmlinux_module_name: The name of the kernel module on which to operate + + Returns: + A set containing known kernel module addresses + """ + vmlinux = context.modules[vmlinux_module_name] + vmlinux_layer = context.layers[vmlinux.layer_name] + + known_module_addresses = { + vmlinux_layer.canonicalize(module.vol.offset) + for module in lsmod.Lsmod.list_modules(context, vmlinux_module_name) + } + return known_module_addresses + + def _generator(self): + vmlinux_module_name = self.config["kernel"] + known_module_addresses = self.get_lsmod_module_addresses( + self.context, vmlinux_module_name + ) + modules_memory_boundaries = self.get_modules_memory_boundaries( + self.context, vmlinux_module_name + ) + for module in self.get_hidden_modules( + self.context, + vmlinux_module_name, + known_module_addresses, + modules_memory_boundaries, + ): + module_addr = module.vol.offset + module_name = module.get_name() or renderers.NotAvailableValue() + fields = (format_hints.Hex(module_addr), module_name) + yield (0, fields) + + def run(self): + if self.context.symbol_space.verify_table_versions( + "dwarf2json", lambda version, _: (not version) or version < (0, 8, 0) + ): + raise exceptions.SymbolSpaceError( + "Invalid symbol table, please ensure the ISF table produced by dwarf2json was created with version 0.8.0 or later" + ) + + headers = [ + ("Address", format_hints.Hex), + ("Name", str), + ] + return renderers.TreeGrid(headers, self._generator()) diff --git a/volatility3/framework/symbols/linux/__init__.py b/volatility3/framework/symbols/linux/__init__.py index 1f2812c106..3289775b6e 100644 --- a/volatility3/framework/symbols/linux/__init__.py +++ b/volatility3/framework/symbols/linux/__init__.py @@ -615,7 +615,7 @@ def _slot_to_nodep(self, slot) -> int: return nodep - def _iter_node(self, nodep, height) -> int: + def _iter_node(self, nodep, height) -> Iterator[int]: node = self.nodep_to_node(nodep) node_slots = node.slots for off in range(self.CHUNK_SIZE): @@ -632,7 +632,7 @@ def _iter_node(self, nodep, height) -> int: for child_node in self._iter_node(nodep, height - 1): yield child_node - def get_entries(self, root: interfaces.objects.ObjectInterface) -> int: + def get_entries(self, root: interfaces.objects.ObjectInterface) -> Iterator[int]: """Walks the tree data structure Args: @@ -818,7 +818,7 @@ def __init__( self._page_cache = page_cache self._idstorage = IDStorage.choose_id_storage(context, kernel_module_name) - def get_cached_pages(self) -> interfaces.objects.ObjectInterface: + def get_cached_pages(self) -> Iterator[interfaces.objects.ObjectInterface]: """Returns all page cache contents Yields: diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index a9127907e0..0099b17e16 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -14,12 +14,7 @@ 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 -from volatility3.framework.constants.linux import ETH_PROTOCOLS, BLUETOOTH_STATES -from volatility3.framework.constants.linux import BLUETOOTH_PROTOCOLS, SOCKET_STATES -from volatility3.framework.constants.linux import CAPABILITIES, PT_FLAGS, NSEC_PER_SEC +from volatility3.framework.constants import linux as linux_constants from volatility3.framework.layers import linear from volatility3.framework.objects import utility from volatility3.framework.symbols import generic, linux, intermed @@ -33,112 +28,140 @@ class module(generic.GenericIntelProcess): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self._mod_mem_type = None # Initialize _mod_mem_type to None for memoization + def is_valid(self): + """Determine whether it is a valid module object by verifying the self-referential + in module_kobject. This also confirms that the module is actively allocated and + not a remnant of freed memory or a failed module load attempt by verifying the + module memory section sizes. + """ + layer = self._context.layers[self.vol.layer_name] + # Make sure the entire module content is readable + if not layer.is_valid(self.vol.offset, self.vol.size): + return False + + core_size = self.get_core_size() + core_text_size = self.get_core_text_size() + init_size = self.get_init_size() + if not ( + 0 < core_text_size <= linux_constants.MODULE_MAXIMUM_CORE_TEXT_SIZE + and 0 < core_size <= linux_constants.MODULE_MAXIMUM_CORE_SIZE + and core_size + init_size >= linux_constants.MODULE_MINIMUM_SIZE + ): + return False - @property - def mod_mem_type(self): + if not ( + self.mkobj + and self.mkobj.mod + and self.mkobj.mod.is_readable() + and self.mkobj.mod == self.vol.offset + ): + return False + + return True + + @functools.cached_property + def mod_mem_type(self) -> Dict: """Return the mod_mem_type enum choices if available or an empty dict if not""" # mod_mem_type and module_memory were added in kernel 6.4 which replaces # module_layout for storing the information around core_layout etc. # see commit ac3b43283923440900b4f36ca5f9f0b1ca43b70e for more information + symbol_table_name = self.get_symbol_table_name() + mod_mem_type_symname = symbol_table_name + constants.BANG + "mod_mem_type" + symbol_space = self._context.symbol_space + try: + mod_mem_type = symbol_space.get_enumeration(mod_mem_type_symname).choices + except exceptions.SymbolError: + mod_mem_type = {} + vollog.debug( + "Unable to find mod_mem_type enum. This message can be ignored for kernels < 6.4" + ) - if self._mod_mem_type is None: - try: - self._mod_mem_type = self._context.symbol_space.get_enumeration( - self.get_symbol_table_name() + constants.BANG + "mod_mem_type" - ).choices - except exceptions.SymbolError: - vollog.debug( - "Unable to find mod_mem_type enum. This message can be ignored for kernels < 6.4" - ) - # set to empty dict to show that the enum was not found, and so shouldn't be searched for again - self._mod_mem_type = {} - return self._mod_mem_type + return mod_mem_type + + def _get_mem_type(self, mod_mem_type_name): + module_mem_index = self.mod_mem_type.get(mod_mem_type_name) + if module_mem_index is None: + raise AttributeError(f"Unknown module memory type '{mod_mem_type_name}'") + + if not (0 <= module_mem_index < self.mem.count): + raise AttributeError( + f"Invalid module memory type index '{module_mem_index}'" + ) + + return self.mem[module_mem_index] + + def _get_mem_size(self, mod_mem_type_name): + return self._get_mem_type(mod_mem_type_name).size + + def _get_mem_base(self, mod_mem_type_name): + return self._get_mem_type(mod_mem_type_name).base def get_module_base(self): if self.has_member("mem"): # kernels 6.4+ - try: - return self.mem[self.mod_mem_type["MOD_TEXT"]].base - except KeyError: - raise AttributeError( - "module -> get_module_base: Unable to get module base. Cannot read base from MOD_TEXT." - ) + return self._get_mem_base("MOD_TEXT") elif self.has_member("core_layout"): return self.core_layout.base elif self.has_member("module_core"): return self.module_core - raise AttributeError("module -> get_module_base: Unable to get module base") + + raise AttributeError("Unable to get module base") def get_init_size(self): if self.has_member("mem"): # kernels 6.4+ - try: - return ( - self.mem[self.mod_mem_type["MOD_INIT_TEXT"]].size - + self.mem[self.mod_mem_type["MOD_INIT_DATA"]].size - + self.mem[self.mod_mem_type["MOD_INIT_RODATA"]].size - ) - except KeyError: - raise AttributeError( - "module -> get_init_size: Unable to determine .init section size of module. Cannot read size of MOD_INIT_TEXT, MOD_INIT_DATA, and MOD_INIT_RODATA" - ) + return ( + self._get_mem_size("MOD_INIT_TEXT") + + self._get_mem_size("MOD_INIT_DATA") + + self._get_mem_size("MOD_INIT_RODATA") + ) elif self.has_member("init_layout"): return self.init_layout.size elif self.has_member("init_size"): return self.init_size - raise AttributeError( - "module -> get_init_size: Unable to determine .init section size of module" - ) + + raise AttributeError("Unable to determine .init section size of module") def get_core_size(self): if self.has_member("mem"): # kernels 6.4+ - try: - return ( - self.mem[self.mod_mem_type["MOD_TEXT"]].size - + self.mem[self.mod_mem_type["MOD_DATA"]].size - + self.mem[self.mod_mem_type["MOD_RODATA"]].size - + self.mem[self.mod_mem_type["MOD_RO_AFTER_INIT"]].size - ) - except KeyError: - raise AttributeError( - "module -> get_core_size: Unable to determine core size of module. Cannot read size of MOD_TEXT, MOD_DATA, MOD_RODATA, and MOD_RO_AFTER_INIT." - ) + return ( + self._get_mem_size("MOD_TEXT") + + self._get_mem_size("MOD_DATA") + + self._get_mem_size("MOD_RODATA") + + self._get_mem_size("MOD_RO_AFTER_INIT") + ) elif self.has_member("core_layout"): return self.core_layout.size elif self.has_member("core_size"): return self.core_size - raise AttributeError( - "module -> get_core_size: Unable to determine core size of module" - ) + + raise AttributeError("Unable to determine core size of module") + + def get_core_text_size(self): + if self.has_member("mem"): # kernels 6.4+ + return self._get_mem_size("MOD_TEXT") + elif self.has_member("core_layout"): + return self.core_layout.text_size + elif self.has_member("core_text_size"): + return self.core_text_size + + raise AttributeError("Unable to determine core text size of module") def get_module_core(self): if self.has_member("mem"): # kernels 6.4+ - try: - return self.mem[self.mod_mem_type["MOD_TEXT"]].base - except KeyError: - raise AttributeError( - "module -> get_module_core: Unable to get module core. Cannot read base from MOD_TEXT." - ) + return self._get_mem_base("MOD_TEXT") elif self.has_member("core_layout"): return self.core_layout.base elif self.has_member("module_core"): return self.module_core - raise AttributeError("module -> get_module_core: Unable to get module core") + raise AttributeError("Unable to get module core") def get_module_init(self): if self.has_member("mem"): # kernels 6.4+ - try: - return self.mem[self.mod_mem_type["MOD_INIT_TEXT"]].base - except KeyError: - raise AttributeError( - "module -> get_module_core: Unable to get module init. Cannot read base from MOD_INIT_TEXT." - ) + return self._get_mem_base("MOD_INIT_TEXT") elif self.has_member("init_layout"): return self.init_layout.base elif self.has_member("module_init"): return self.module_init - raise AttributeError("module -> get_module_init: Unable to get module init") + raise AttributeError("Unable to get module init") def get_name(self): """Get the name of the module as a string""" @@ -340,7 +363,7 @@ def is_kernel_thread(self) -> bool: Returns: bool: True, if this task is a kernel thread. Otherwise, False. """ - return (self.flags & constants.linux.PF_KTHREAD) != 0 + return (self.flags & linux_constants.PF_KTHREAD) != 0 @property def is_thread_group_leader(self) -> bool: @@ -417,7 +440,11 @@ def get_ptrace_tracee_tids(self) -> List[int]: def get_ptrace_tracee_flags(self) -> Optional[str]: """Returns a string with the ptrace flags""" - return PT_FLAGS(self.ptrace).flags if self.is_being_ptraced else None + return ( + linux_constants.PT_FLAGS(self.ptrace).flags + if self.is_being_ptraced + else None + ) def _get_task_start_time(self) -> datetime.timedelta: """Returns the task's monotonic start_time as a timedelta. @@ -1492,7 +1519,7 @@ def is_equal(self, vfsmount_ptr) -> bool: bool: 'True' if the given argument points to the the same 'vfsmount' as 'self'. """ - if type(vfsmount_ptr) == objects.Pointer: + if isinstance(vfsmount_ptr, objects.Pointer): return self.vol.offset == vfsmount_ptr else: raise exceptions.VolatilityException( @@ -1715,18 +1742,18 @@ def get_inode(self): def get_state(self): socket_state_idx = self.state - if 0 <= socket_state_idx < len(SOCKET_STATES): - return SOCKET_STATES[socket_state_idx] + if 0 <= socket_state_idx < len(linux_constants.SOCKET_STATES): + return linux_constants.SOCKET_STATES[socket_state_idx] class sock(objects.StructType): def get_family(self): family_idx = self.__sk_common.skc_family - if 0 <= family_idx < len(SOCK_FAMILY): - return SOCK_FAMILY[family_idx] + if 0 <= family_idx < len(linux_constants.SOCK_FAMILY): + return linux_constants.SOCK_FAMILY[family_idx] def get_type(self): - return SOCK_TYPES.get(self.sk_type, "") + return linux_constants.SOCK_TYPES.get(self.sk_type, "") def get_inode(self): if not self.sk_socket: @@ -1760,8 +1787,8 @@ def get_state(self): # Unix socket states reuse (a subset) of the inet_sock states contants if self.sk.get_type() == "STREAM": state_idx = self.sk.__sk_common.skc_state - if 0 <= state_idx < len(TCP_STATES): - return TCP_STATES[state_idx] + if 0 <= state_idx < len(linux_constants.TCP_STATES): + return linux_constants.TCP_STATES[state_idx] else: # Return the generic socket state return self.sk.sk_socket.get_state() @@ -1773,15 +1800,15 @@ def get_inode(self): class inet_sock(objects.StructType): def get_family(self): family_idx = self.sk.__sk_common.skc_family - if 0 <= family_idx < len(SOCK_FAMILY): - return SOCK_FAMILY[family_idx] + if 0 <= family_idx < len(linux_constants.SOCK_FAMILY): + return linux_constants.SOCK_FAMILY[family_idx] def get_protocol(self): # If INET6 family and a proto is defined, we use that specific IPv6 protocol. # Otherwise, we use the standard IP protocol. - protocol = IP_PROTOCOLS.get(self.sk.sk_protocol) + protocol = linux_constants.IP_PROTOCOLS.get(self.sk.sk_protocol) if self.get_family() == "AF_INET6": - protocol = IPV6_PROTOCOLS.get(self.sk.sk_protocol, protocol) + protocol = linux_constants.IPV6_PROTOCOLS.get(self.sk.sk_protocol, protocol) return protocol def get_state(self): @@ -1789,8 +1816,8 @@ def get_state(self): if self.sk.get_type() == "STREAM": state_idx = self.sk.__sk_common.skc_state - if 0 <= state_idx < len(TCP_STATES): - return TCP_STATES[state_idx] + if 0 <= state_idx < len(linux_constants.TCP_STATES): + return linux_constants.TCP_STATES[state_idx] else: # Return the generic socket state return self.sk.sk_socket.get_state() @@ -1873,8 +1900,8 @@ def get_dst_addr(self): class netlink_sock(objects.StructType): def get_protocol(self): protocol_idx = self.sk.sk_protocol - if 0 <= protocol_idx < len(NETLINK_PROTOCOLS): - return NETLINK_PROTOCOLS[protocol_idx] + if 0 <= protocol_idx < len(linux_constants.NETLINK_PROTOCOLS): + return linux_constants.NETLINK_PROTOCOLS[protocol_idx] def get_state(self): # Return the generic socket state @@ -1916,8 +1943,8 @@ def get_protocol(self): eth_proto = socket_module.htons(self.num) if eth_proto == 0: return None - elif eth_proto in ETH_PROTOCOLS: - return ETH_PROTOCOLS[eth_proto] + elif eth_proto in linux_constants.ETH_PROTOCOLS: + return linux_constants.ETH_PROTOCOLS[eth_proto] else: return f"0x{eth_proto:x}" @@ -1929,13 +1956,13 @@ def get_state(self): class bt_sock(objects.StructType): def get_protocol(self): type_idx = self.sk.sk_protocol - if 0 <= type_idx < len(BLUETOOTH_PROTOCOLS): - return BLUETOOTH_PROTOCOLS[type_idx] + if 0 <= type_idx < len(linux_constants.BLUETOOTH_PROTOCOLS): + return linux_constants.BLUETOOTH_PROTOCOLS[type_idx] def get_state(self): state_idx = self.sk.__sk_common.skc_state - if 0 <= state_idx < len(BLUETOOTH_STATES): - return BLUETOOTH_STATES[state_idx] + if 0 <= state_idx < len(linux_constants.BLUETOOTH_STATES): + return linux_constants.BLUETOOTH_STATES[state_idx] class xdp_sock(objects.StructType): @@ -2053,7 +2080,7 @@ def get_last_cap_value(cls) -> int: Returns: int: The latest capability ID supported by the framework. """ - return len(CAPABILITIES) - 1 + return len(linux_constants.CAPABILITIES) - 1 def get_kernel_cap_full(self) -> int: """Return the maximum value allowed for this kernel for a capability @@ -2082,7 +2109,7 @@ def capabilities_to_string(cls, capabilities_bitfield: int) -> List[str]: """ capabilities = [] - for bit, name in enumerate(CAPABILITIES): + for bit, name in enumerate(linux_constants.CAPABILITIES): if capabilities_bitfield & (1 << bit) != 0: capabilities.append(name) @@ -2143,10 +2170,10 @@ def has_capability(self, capability: str) -> bool: Returns: bool: "True" if the given capability is enabled. """ - if capability not in CAPABILITIES: + if capability not in linux_constants.CAPABILITIES: raise AttributeError(f"Unknown capability with name '{capability}'") - cap_value = 1 << CAPABILITIES.index(capability) + cap_value = 1 << linux_constants.CAPABILITIES.index(capability) return cap_value & self.get_capabilities() != 0 From c0fa2cfcd67d676dab100150daad2f93948c641b Mon Sep 17 00:00:00 2001 From: Gustavo Moreira Date: Tue, 5 Nov 2024 20:02:55 +1100 Subject: [PATCH 15/15] Linux: Boottime API: User linux_constanst import --- .../symbols/linux/extensions/__init__.py | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/volatility3/framework/symbols/linux/extensions/__init__.py b/volatility3/framework/symbols/linux/extensions/__init__.py index 0099b17e16..d05c34304a 100644 --- a/volatility3/framework/symbols/linux/extensions/__init__.py +++ b/volatility3/framework/symbols/linux/extensions/__init__.py @@ -2217,12 +2217,12 @@ def new_from_nsec(cls, nsec) -> "Timespec64Concrete": # Based on ns_to_timespec64() if nsec > 0: - tv_sec = nsec // NSEC_PER_SEC - tv_nsec = nsec % NSEC_PER_SEC + tv_sec = nsec // linux_constants.NSEC_PER_SEC + tv_nsec = nsec % linux_constants.NSEC_PER_SEC elif nsec < 0: - tv_sec = -((-nsec - 1) // NSEC_PER_SEC) - 1 - rem = (-nsec - 1) % NSEC_PER_SEC - tv_nsec = NSEC_PER_SEC - rem - 1 + tv_sec = -((-nsec - 1) // linux_constants.NSEC_PER_SEC) - 1 + rem = (-nsec - 1) % linux_constants.NSEC_PER_SEC + tv_nsec = linux_constants.NSEC_PER_SEC - rem - 1 else: tv_sec = tv_nsec = 0 @@ -2233,13 +2233,15 @@ def to_datetime(self) -> datetime.datetime: # pylint: disable=E1101 return conversion.unixtime_to_datetime( - self.tv_sec + self.tv_nsec / NSEC_PER_SEC + self.tv_sec + self.tv_nsec / linux_constants.NSEC_PER_SEC ) def to_timedelta(self) -> datetime.timedelta: """Converts this Timespec64Abstract subclass object to timedelta""" # pylint: disable=E1101 - return datetime.timedelta(seconds=self.tv_sec + self.tv_nsec / NSEC_PER_SEC) + return datetime.timedelta( + seconds=self.tv_sec + self.tv_nsec / linux_constants.NSEC_PER_SEC + ) def __add__(self, other) -> "Timespec64Concrete": """Returns a new Timespec64Concrete object that sums the current values with those @@ -2282,12 +2284,12 @@ def normalize(self): # Based on kernel's set_normalized_timespec64() # pylint: disable=E1101 - while self.tv_nsec >= NSEC_PER_SEC: - self.tv_nsec -= NSEC_PER_SEC + while self.tv_nsec >= linux_constants.NSEC_PER_SEC: + self.tv_nsec -= linux_constants.NSEC_PER_SEC self.tv_sec += 1 while self.tv_nsec < 0: - self.tv_nsec += NSEC_PER_SEC + self.tv_nsec += linux_constants.NSEC_PER_SEC self.tv_sec -= 1