diff --git a/volatility3/framework/plugins/mac/kevents.py b/volatility3/framework/plugins/mac/kevents.py index 2a8692b772..23c072e843 100644 --- a/volatility3/framework/plugins/mac/kevents.py +++ b/volatility3/framework/plugins/mac/kevents.py @@ -184,18 +184,29 @@ def _generator(self): for task_name, pid, kn in self.list_kernel_events( self.context, self.config["kernel"], filter_func=filter_func ): - filter_index = kn.kn_kevent.filter * -1 + if hasattr(kn.kn_kevent, "filter"): + filter = kn.kn_kevent.filter + elif hasattr(kn.kn_kevent, "kei_filter"): + filter = kn.kn_kevent.kei_filter + filter_index = filter * -1 if filter_index in self.event_types: filter_name = self.event_types[filter_index] else: continue try: - ident = kn.kn_kevent.ident + if hasattr(kn.kn_kevent, "ident"): + ident = kn.kn_kevent.ident + elif hasattr(kn.kn_kevent, "kei_ident"): + ident = kn.kn_kevent.kei_ident except exceptions.InvalidAddressException: continue - context = self._parse_flags(filter_index, kn.kn_sfflags) + if hasattr(kn, "kn_sfflags"): + sfflags = kn.kn_sfflags + elif hasattr(kn.kn_kevent, "kei_sfflags"): + sfflags = kn.kn_kevent.kei_sfflags + context = self._parse_flags(filter_index, sfflags) yield (0, (pid, task_name, ident, filter_name, context)) diff --git a/volatility3/framework/plugins/mac/list_files.py b/volatility3/framework/plugins/mac/list_files.py index c18b0b7a25..f2d4a45530 100644 --- a/volatility3/framework/plugins/mac/list_files.py +++ b/volatility3/framework/plugins/mac/list_files.py @@ -98,56 +98,72 @@ def _add_vnode(cls, context, vnode, loop_vnodes): return added @classmethod - def _walk_vnode(cls, context, vnode, loop_vnodes): + def _walk_vnode( + cls, context, vnode, loop_vnodes, to_explore: list, visited_vnodes: set + ): """ Iterates over the list of vnodes associated with the given one. Also traverses the parent chain for the vnode and adds each one. """ - added = False while vnode: - if vnode in loop_vnodes: - return added + if vnode in loop_vnodes or vnode in visited_vnodes: + return to_explore + + visited_vnodes.add(vnode) if not cls._add_vnode(context, vnode, loop_vnodes): break - added = True - parent = cls._get_parent(context, vnode) - while parent and parent not in loop_vnodes: - if not cls._walk_vnode(context, parent, loop_vnodes): - break - - parent = cls._get_parent(context, parent) + if parent not in to_explore: + to_explore.append(parent) try: vnode = vnode.v_mntvnodes.tqe_next.dereference() except exceptions.InvalidAddressException: break - return added + return to_explore + + @classmethod + def _walk_vnode_iterative(cls, context, vnode, loop_vnodes, visited_vnodes): + to_explore = [vnode] + while to_explore: + vnode = to_explore.pop(-1) + to_explore = cls._walk_vnode( + context, vnode, loop_vnodes, to_explore, visited_vnodes + ) @classmethod - def _walk_vnodelist(cls, context, list_head, loop_vnodes): + def _walk_vnodelist(cls, context, list_head, loop_vnodes, visited_vnodes): for vnode in mac.MacUtilities.walk_tailq(list_head, "v_mntvnodes"): - cls._walk_vnode(context, vnode, loop_vnodes) + cls._walk_vnode_iterative(context, vnode, loop_vnodes, visited_vnodes) @classmethod def _walk_mounts( cls, context: interfaces.context.ContextInterface, kernel_module_name: str ) -> Iterable[interfaces.objects.ObjectInterface]: loop_vnodes = {} + visited_vnodes = set() # iterate each vnode source from each mount list_mounts = mount.Mount.list_mounts(context, kernel_module_name) for mnt in list_mounts: - cls._walk_vnodelist(context, mnt.mnt_vnodelist, loop_vnodes) - cls._walk_vnodelist(context, mnt.mnt_workerqueue, loop_vnodes) - cls._walk_vnodelist(context, mnt.mnt_newvnodes, loop_vnodes) - cls._walk_vnode(context, mnt.mnt_vnodecovered, loop_vnodes) - cls._walk_vnode(context, mnt.mnt_realrootvp, loop_vnodes) - cls._walk_vnode(context, mnt.mnt_devvp, loop_vnodes) + cls._walk_vnodelist(context, mnt.mnt_vnodelist, loop_vnodes, visited_vnodes) + cls._walk_vnodelist( + context, mnt.mnt_workerqueue, loop_vnodes, visited_vnodes + ) + cls._walk_vnodelist(context, mnt.mnt_newvnodes, loop_vnodes, visited_vnodes) + cls._walk_vnode_iterative( + context, mnt.mnt_vnodecovered, loop_vnodes, visited_vnodes + ) + cls._walk_vnode_iterative( + context, mnt.mnt_realrootvp, loop_vnodes, visited_vnodes + ) + cls._walk_vnode_iterative( + context, mnt.mnt_devvp, loop_vnodes, visited_vnodes + ) return loop_vnodes diff --git a/volatility3/framework/plugins/mac/netstat.py b/volatility3/framework/plugins/mac/netstat.py index 76bba25f68..0a74f7279f 100644 --- a/volatility3/framework/plugins/mac/netstat.py +++ b/volatility3/framework/plugins/mac/netstat.py @@ -8,7 +8,7 @@ from volatility3.framework import exceptions, renderers, interfaces from volatility3.framework.configuration import requirements from volatility3.framework.interfaces import plugins -from volatility3.framework.objects import utility +from volatility3.framework.objects import utility, Pointer from volatility3.framework.renderers import format_hints from volatility3.framework.symbols import mac from volatility3.plugins.mac import pslist @@ -74,16 +74,27 @@ def list_sockets( for filp, _, _ in mac.MacUtilities.files_descriptors_for_process( context, context.modules[kernel_module_name].symbol_table_name, task ): + if hasattr(filp, "f_fglob"): + glob = filp.f_fglob + elif hasattr(filp, "fp_glob"): + glob = filp.fp_glob + else: + raise AttributeError("fileglob", "f_fglob || fp_glob") + try: - ftype = filp.f_fglob.get_fg_type() + ftype = glob.get_fg_type() except exceptions.InvalidAddressException: continue if ftype != "SOCKET": continue - try: - socket = filp.f_fglob.fg_data.dereference().cast("socket") + if type(glob.fg_data) == Pointer: + socket = glob.fg_data.dereference().cast("socket") + else: + socket = context.modules[kernel_module_name].object( + "socket", glob.fg_data, absolute=True + ) except exceptions.InvalidAddressException: continue diff --git a/volatility3/framework/plugins/mac/pslist.py b/volatility3/framework/plugins/mac/pslist.py index 9b570f3f9c..9fdc21cc83 100644 --- a/volatility3/framework/plugins/mac/pslist.py +++ b/volatility3/framework/plugins/mac/pslist.py @@ -200,7 +200,10 @@ def list_tasks_tasks( seen[task.vol.offset] = 1 try: - proc = task.bsd_info.dereference().cast("proc") + if hasattr(task, "bsd_info"): + proc = task.bsd_info.dereference().cast("proc") + elif hasattr(task, "bsd_info_ro"): + proc = task.bsd_info_ro.pr_proc.dereference() except exceptions.InvalidAddressException: continue diff --git a/volatility3/framework/plugins/mac/timers.py b/volatility3/framework/plugins/mac/timers.py index 8a267bd557..80d451429a 100644 --- a/volatility3/framework/plugins/mac/timers.py +++ b/volatility3/framework/plugins/mac/timers.py @@ -3,6 +3,7 @@ # import logging from typing import List +from dataclasses import dataclass from volatility3.framework import exceptions, interfaces from volatility3.framework import renderers @@ -10,11 +11,23 @@ from volatility3.framework.interfaces import plugins from volatility3.framework.renderers import format_hints from volatility3.framework.symbols import mac +from volatility3.framework.objects import utility from volatility3.plugins.mac import lsmod vollog = logging.getLogger(__name__) +@dataclass +class TimerStructure: + type_name: str + func: str + qlink: str + entry_time: str + param0: str + param1: str + deadline: str + + class Timers(plugins.PluginInterface): """Check for malicious kernel timers.""" @@ -45,23 +58,34 @@ def _generator(self): self.context, kernel.layer_name, kernel, mods ) - real_ncpus = kernel.object_from_symbol(symbol_name="real_ncpus") - - cpu_data_ptrs_ptr = kernel.get_symbol("cpu_data_ptr").address + if kernel.has_type("call_entry"): + timer_struct = TimerStructure( + type_name="call_entry", + func="func", + qlink="q_link", + entry_time="entry_time", + param0="param0", + param1="param1", + deadline="deadline", + ) + elif kernel.has_type("timer_call"): + timer_struct = TimerStructure( + type_name="timer_call", + func="tc_func", + qlink="tc_qlink", + entry_time="tc_entry_time", + param0="tc_param0", + param1="tc_param1", + deadline="tc_soft_deadline", + ) - # Returns the a pointer to the absolute address - cpu_data_ptrs_addr = kernel.object( - object_type="pointer", - offset=cpu_data_ptrs_ptr, - subtype=kernel.get_type("long unsigned int"), - ) - - cpu_data_ptrs = kernel.object( - object_type="array", - offset=cpu_data_ptrs_addr, - absolute=True, - subtype=kernel.get_type("cpu_data"), - count=real_ncpus, + real_ncpus = kernel.object_from_symbol(symbol_name="real_ncpus") + cpu_data_ptrs_ptr = kernel.object_from_symbol("cpu_data_ptr") + cpu_data_ptrs = utility.array_of_pointers( + cpu_data_ptrs_ptr, + real_ncpus, + cpu_data_ptrs_ptr.vol.subtype, + self.context, ) for cpu_data_ptr in cpu_data_ptrs: @@ -70,14 +94,15 @@ def _generator(self): except exceptions.InvalidAddressException: break - for timer in queue.walk_list(queue, "q_link", "call_entry"): + for timer in queue.walk_list( + queue, timer_struct.qlink, timer_struct.type_name + ): try: - handler = timer.func.dereference().vol.offset + handler = getattr(timer, timer_struct.func).dereference().vol.offset except exceptions.InvalidAddressException: continue - - if timer.has_member("entry_time"): - entry_time = timer.entry_time + if timer.has_member(timer_struct.entry_time): + entry_time = getattr(timer, timer_struct.entry_time) else: entry_time = -1 @@ -89,9 +114,9 @@ def _generator(self): 0, ( format_hints.Hex(handler), - format_hints.Hex(timer.param0), - format_hints.Hex(timer.param1), - timer.deadline, + format_hints.Hex(getattr(timer, timer_struct.param0)), + format_hints.Hex(getattr(timer, timer_struct.param1)), + getattr(timer, timer_struct.deadline), entry_time, module_name, symbol_name, diff --git a/volatility3/framework/symbols/mac/__init__.py b/volatility3/framework/symbols/mac/__init__.py index c695ca77a7..31258f5b78 100644 --- a/volatility3/framework/symbols/mac/__init__.py +++ b/volatility3/framework/symbols/mac/__init__.py @@ -18,7 +18,7 @@ def __init__(self, *args, **kwargs) -> None: self.set_type_class("fileglob", extensions.fileglob) self.set_type_class("vnode", extensions.vnode) self.set_type_class("vm_map_entry", extensions.vm_map_entry) - self.set_type_class("vm_map_object", extensions.vm_map_object) + self.optional_set_type_class("vm_map_object", extensions.vm_map_object) self.set_type_class("socket", extensions.socket) self.set_type_class("inpcb", extensions.inpcb) self.set_type_class("ifnet", extensions.ifnet) @@ -69,7 +69,7 @@ def generate_kernel_handler_info( cls, context: interfaces.context.ContextInterface, layer_name: str, - kernel, # ikelos - how to type this?? + kernel: interfaces.context.ModuleInterface, mods_list: Iterator[Any], ): try: @@ -151,7 +151,10 @@ def files_descriptors_for_process( """ try: - num_fds = task.p_fd.fd_lastfile + if hasattr(task.p_fd, "fd_lastfile"): + num_fds = task.p_fd.fd_lastfile + elif hasattr(task.p_fd, "fd_afterlast"): + num_fds = task.p_fd.fd_afterlast except exceptions.InvalidAddressException: num_fds = 1024 @@ -176,20 +179,36 @@ def files_descriptors_for_process( fds = objects.utility.array_of_pointers( table_addr, count=num_fds, subtype=file_type, context=context ) - + kernel_config_path = context.layers[task.vol.layer_name].config_path.rsplit( + context.config.separator, 1 + )[0] + kernel_module = context.modules[context.config[kernel_config_path]] for fd_num, f in enumerate(fds): if f != 0: + if hasattr(f, "f_fglob"): + glob = f.f_fglob + elif hasattr(f, "fp_glob"): + glob = f.fp_glob + else: + raise AttributeError("fileglob", "f_fglob || fp_glob") try: - ftype = f.f_fglob.get_fg_type() + ftype = glob.get_fg_type() except exceptions.InvalidAddressException: continue if ftype == "VNODE": - vnode = f.f_fglob.fg_data.dereference().cast("vnode") + if type(glob.fg_data) == objects.Pointer: + vnode = glob.fg_data.dereference().cast("vnode") + else: + # On macOS 14+ versions, glob.fg_data is an uintptr_t + vnode = kernel_module.object( + "vnode", glob.fg_data, absolute=True + ) path = vnode.full_path() elif ftype: path = f"<{ftype.lower()}>" - + else: + path = "UNKNOWN" yield f, path, fd_num @classmethod diff --git a/volatility3/framework/symbols/mac/extensions/__init__.py b/volatility3/framework/symbols/mac/extensions/__init__.py index 15fe7aeda9..92c2b8c45f 100644 --- a/volatility3/framework/symbols/mac/extensions/__init__.py +++ b/volatility3/framework/symbols/mac/extensions/__init__.py @@ -3,6 +3,7 @@ # import contextlib import logging +import struct from typing import Generator, Iterable, Optional, Set, Tuple from volatility3.framework import constants, exceptions, interfaces, objects @@ -15,7 +16,11 @@ class proc(generic.GenericIntelProcess): def get_task(self): - return self.task.dereference().cast("task") + if hasattr(self, "p_proc_ro"): + task = self.p_proc_ro.pr_task.dereference() + else: + task = self.task.dereference().cast("task") + return task def add_process_layer( self, config_prefix: str = None, preferred_name: str = None @@ -263,13 +268,57 @@ def get_path(self, context, config_prefix): return ret + def vm_pointer_unpack( + self, + context: interfaces.context.ContextInterface, + kernel_module_name: str, + packed: int, + ) -> int: + """ + https://github.com/apple-open-source/macos/blob/ea4cd5a06831aca49e33df829d2976d6de5316ec/xnu/tools/lldbmacros/kmemory/kmem.py#L34 + """ + kernel_module = context.modules[kernel_module_name] + vm_page_packing = kernel_module.object_from_symbol("vm_page_packing_params") + base_relative = vm_page_packing.vmpp_base_relative + bits = vm_page_packing.vmpp_bits + shift = vm_page_packing.vmpp_shift + base = vm_page_packing.vmpp_base + + if base_relative: + addr = (packed << shift) + base + else: + addr = struct.unpack(">= 64 - bits - shift + + return addr & 0xFFFFFFFFFFFFFFFF + def get_object(self): if self.has_member("vme_object"): return self.vme_object elif self.has_member("object"): return self.object - - raise AttributeError("vm_map_entry -> get_object: Unable to determine object") + # https://github.com/apple-open-source/macos/blob/ea4cd5a06831aca49e33df829d2976d6de5316ec/xnu/tools/lldbmacros/memory.py#L35 + else: + kernel_config_path = self._context.layers[ + self.vol.layer_name + ].config_path.rsplit(self._context.config.separator, 1)[0] + kernel_module = self._context.modules[ + self._context.config[kernel_config_path] + ] + if self.vme_kernel_object: + return kernel_module.get_absolute_symbol_address("kernel_object_store") + else: + packed = self.vme_object_or_delta + # https://github.com/apple-open-source/macos/blob/14.3/xnu/osfmk/vm/vm_map.h#L253 + if isinstance(packed, int): + addr = self.vm_pointer_unpack( + self._context, self._context.config[kernel_config_path], packed + ) + if addr: + return kernel_module.object("vm_object", addr, absolute=True) + else: + return packed + return 0 def get_offset(self): if self.has_member("vme_offset"): @@ -284,7 +333,16 @@ def get_vnode(self, context, config_prefix): return "sub_map" # based on find_vnode_object - vnode_object = self.get_object().get_map_object() + try: + tmp_object = self.get_object() + except exceptions.InvalidAddressException: + return None + + if type(tmp_object) == vm_map_object: + vnode_object = tmp_object.get_map_object() + else: + vnode_object = tmp_object + if vnode_object == 0: return None