Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

macOS: Plugin enhancements for latest kernel compatibility #1116

Draft
wants to merge 24 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
428e8a2
get_task p_proc_ro attribute check
Abyss-W4tcher Mar 18, 2024
0cff113
set type hinting for kernel module
Abyss-W4tcher Mar 18, 2024
4d37db1
task.p_fd check for newer kernels
Abyss-W4tcher Mar 18, 2024
507afae
fileglob fp_glob check for newer kernels
Abyss-W4tcher Mar 18, 2024
0b27598
handle fg_data as a uintptr_t/base type
Abyss-W4tcher Mar 18, 2024
ea7c785
check task.bsd_info_ro for newer kernels
Abyss-W4tcher Mar 18, 2024
82657a5
fileglob fp_glob check for newer kernels
Abyss-W4tcher Mar 18, 2024
1de638b
handle fg_data as a uintptr_t/base type
Abyss-W4tcher Mar 18, 2024
38516de
temporary vm_map_object workaround
Abyss-W4tcher Mar 18, 2024
69e536c
prefer iterative to recursive to avoid maximum recursion depth limit …
Abyss-W4tcher Mar 18, 2024
307e9a2
remove fixme
Abyss-W4tcher Mar 20, 2024
5778768
remove hardcoded kernel module name, use introspection to get name in…
Abyss-W4tcher Mar 20, 2024
683dba8
add struct module import
Abyss-W4tcher Mar 20, 2024
ab55018
vm_pointer_unpack function
Abyss-W4tcher Mar 20, 2024
23fc9c8
update get_object for recent kernels
Abyss-W4tcher Mar 20, 2024
2abf5f8
add a check for older and recent kernels, on vm_object
Abyss-W4tcher Mar 20, 2024
e30deeb
wrong object to get layer_name from
Abyss-W4tcher Mar 20, 2024
4a7fffb
kernel timer structure update
Abyss-W4tcher Mar 20, 2024
c0e85f4
structures attributes update
Abyss-W4tcher Mar 20, 2024
87f950a
handle unknown ftype
Abyss-W4tcher Mar 21, 2024
64ffe0d
handle unreachable object
Abyss-W4tcher Mar 21, 2024
c93fba4
black formatting
Abyss-W4tcher Mar 23, 2024
ef77c06
Merge branch 'volatilityfoundation:develop' into macosx_plugins_updates
Abyss-W4tcher Apr 17, 2024
2723e78
Merge branch 'volatilityfoundation:develop' into macosx_plugins_updates
Abyss-W4tcher Aug 9, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions volatility3/framework/plugins/mac/kevents.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
56 changes: 36 additions & 20 deletions volatility3/framework/plugins/mac/list_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
19 changes: 15 additions & 4 deletions volatility3/framework/plugins/mac/netstat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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

Expand Down
5 changes: 4 additions & 1 deletion volatility3/framework/plugins/mac/pslist.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
73 changes: 49 additions & 24 deletions volatility3/framework/plugins/mac/timers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,31 @@
#
import logging
from typing import List
from dataclasses import dataclass

from volatility3.framework import exceptions, interfaces
from volatility3.framework import renderers
from volatility3.framework.configuration import requirements
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."""

Expand Down Expand Up @@ -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:
Expand All @@ -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

Expand All @@ -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,
Expand Down
33 changes: 26 additions & 7 deletions volatility3/framework/symbols/mac/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand All @@ -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
Expand Down
Loading
Loading