Core: Rename the top level namespace

This commit is contained in:
Mike Auty
2021-01-20 21:21:16 +00:00
parent 1477887831
commit 3911098d27
248 changed files with 813 additions and 813 deletions
+111
View File
@@ -0,0 +1,111 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
"""A module containing a collection of plugins that produce data typically
found in mac's /proc file system."""
import datetime
import struct
from volatility3.framework import constants, renderers, symbols
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.layers import scanners
from volatility3.framework.objects import utility
from volatility3.framework.symbols.linux.bash import BashIntermedSymbols
from volatility3.plugins import timeliner
from volatility3.plugins.mac import pslist
class Bash(plugins.PluginInterface, timeliner.TimeLinerInterface):
"""Recovers bash command history from memory."""
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"),
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)),
requirements.ListRequirement(name = 'pid',
description = 'Filter on specific process IDs',
element_type = int,
optional = True)
]
def _generator(self, tasks):
is_32bit = not symbols.symbol_table_is_64bit(self.context, self.config["darwin"])
if is_32bit:
pack_format = "I"
bash_json_file = "bash32"
else:
pack_format = "Q"
bash_json_file = "bash64"
bash_table_name = BashIntermedSymbols.create(self.context, self.config_path, "linux", bash_json_file)
ts_offset = self.context.symbol_space.get_type(bash_table_name + constants.BANG +
"hist_entry").relative_child_offset("timestamp")
for task in tasks:
task_name = utility.array_to_string(task.p_comm)
if task_name not in ["bash", "sh", "dash"]:
continue
proc_layer_name = task.add_process_layer()
if proc_layer_name is None:
continue
proc_layer = self.context.layers[proc_layer_name]
bang_addrs = []
# find '#' values on the heap
for address in proc_layer.scan(self.context,
scanners.BytesScanner(b"#"),
sections = task.get_process_memory_sections(self.context,
self.config['darwin'],
rw_no_file = True)):
bang_addrs.append(struct.pack(pack_format, address))
history_entries = []
for address, _ in proc_layer.scan(self.context,
scanners.MultiStringScanner(bang_addrs),
sections = task.get_process_memory_sections(self.context,
self.config['darwin'],
rw_no_file = True)):
hist = self.context.object(bash_table_name + constants.BANG + "hist_entry",
offset = address - ts_offset,
layer_name = proc_layer_name)
if hist.is_valid():
history_entries.append(hist)
for hist in sorted(history_entries, key = lambda x: x.get_time_as_integer()):
yield (0, (int(task.p_pid), task_name, hist.get_time_object(), hist.get_command()))
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None))
list_tasks = pslist.PsList.get_list_tasks(self.config.get('pslist_method', pslist.PsList.pslist_methods[0]))
return renderers.TreeGrid([("PID", int), ("Process", str), ("CommandTime", datetime.datetime),
("Command", str)],
self._generator(
list_tasks(self.context,
self.config['primary'],
self.config['darwin'],
filter_func = filter_func)))
def generate_timeline(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None))
list_tasks = pslist.PsList.get_list_tasks(self.config.get('pslist_method', pslist.PsList.pslist_methods[0]))
for row in self._generator(
list_tasks(self.context, self.config['primary'], self.config['darwin'], filter_func = filter_func)):
_depth, row_data = row
description = "{} ({}): \"{}\"".format(row_data[0], row_data[1], row_data[3])
yield (description, timeliner.TimeLinerType.CREATED, row_data[2])
@@ -0,0 +1,66 @@
# This file is Copyright 2019 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
from volatility3.framework import exceptions, interfaces
from volatility3.framework import renderers, contexts
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.plugins.mac import lsmod
vollog = logging.getLogger(__name__)
class Check_syscall(plugins.PluginInterface):
"""Check system call table for hooks."""
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"),
requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 0, 0)),
requirements.PluginRequirement(name = 'lsmod', plugin = lsmod.Lsmod, version = (1, 0, 0))
]
def _generator(self):
kernel = contexts.Module(self._context, self.config['darwin'], self.config['primary'], 0)
mods = lsmod.Lsmod.list_modules(self.context, self.config['primary'], self.config['darwin'])
handlers = mac.MacUtilities.generate_kernel_handler_info(self.context, self.config['primary'], kernel, mods)
nsysent = kernel.object_from_symbol(symbol_name = "nsysent")
table = kernel.object_from_symbol(symbol_name = "sysent")
# smear help
num_ents = min(nsysent, table.count)
if num_ents > 1024:
num_ents = 1024
for (i, ent) in enumerate(table):
try:
call_addr = ent.sy_call.dereference().vol.offset
except exceptions.InvalidAddressException:
continue
if not call_addr or call_addr == 0:
continue
module_name, symbol_name = mac.MacUtilities.lookup_module_address(self.context, handlers, call_addr)
yield (0, (format_hints.Hex(table.vol.offset), "SysCall", i, format_hints.Hex(call_addr), module_name,
symbol_name))
def run(self):
return renderers.TreeGrid([("Table Address", format_hints.Hex), ("Table Name", str), ("Index", int),
("Handler Address", format_hints.Hex), ("Handler Module", str),
("Handler Symbol", str)], self._generator())
@@ -0,0 +1,140 @@
# This file is Copyright 2019 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
import volatility3
from volatility3.framework import exceptions, interfaces
from volatility3.framework import renderers, contexts
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import mac
from volatility3.plugins.mac import lsmod
vollog = logging.getLogger(__name__)
class Check_sysctl(plugins.PluginInterface):
"""Check sysctl handlers for hooks."""
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"),
requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 0, 0)),
requirements.PluginRequirement(name = 'lsmod', plugin = lsmod.Lsmod, version = (1, 0, 0))
]
def _parse_global_variable_sysctls(self, kernel, name):
known_sysctls = {
"hostname": "hostname",
"nisdomainname": "domainname",
}
var_str = ""
if name in known_sysctls:
var_name = known_sysctls[name]
try:
var_array = kernel.object_from_symbol(symbol_name = var_name)
except exceptions.SymbolError:
var_array = None
if var_array is not None:
var_str = utility.array_to_string(var_array)
return var_str
def _process_sysctl_list(self, kernel, sysctl_list, recursive = 0):
if type(sysctl_list) == volatility3.framework.objects.Pointer:
sysctl_list = sysctl_list.dereference().cast("sysctl_oid_list")
sysctl = sysctl_list.slh_first
if recursive != 0:
try:
sysctl = sysctl.oid_link.sle_next.dereference()
except exceptions.InvalidAddressException:
return
while sysctl:
try:
name = utility.pointer_to_string(sysctl.oid_name, 128)
except exceptions.InvalidAddressException:
name = ""
if len(name) == 0:
break
ctltype = sysctl.get_ctltype()
try:
arg1_ptr = sysctl.oid_arg1.dereference().vol.offset
except exceptions.InvalidAddressException:
arg1_ptr = 0
arg1 = sysctl.oid_arg1
if arg1 == 0 or arg1_ptr == 0:
val = self._parse_global_variable_sysctls(kernel, name)
elif ctltype == 'CTLTYPE_NODE':
if sysctl.oid_handler == 0:
for info in self._process_sysctl_list(kernel, sysctl.oid_arg1, recursive = 1):
yield info
val = "Node"
elif ctltype in ['CTLTYPE_INT', 'CTLTYPE_QUAD', 'CTLTYPE_OPAQUE']:
try:
val = str(arg1.dereference().cast("int"))
except exceptions.InvalidAddressException:
val = "-1"
elif ctltype == 'CTLTYPE_STRING':
try:
val = utility.pointer_to_string(sysctl.oid_arg1, 64)
except exceptions.InvalidAddressException:
val = ""
else:
val = ctltype
yield (sysctl, name, val)
try:
sysctl = sysctl.oid_link.sle_next
except exceptions.InvalidAddressException:
break
def _generator(self):
kernel = contexts.Module(self._context, self.config['darwin'], self.config['primary'], 0)
mods = lsmod.Lsmod.list_modules(self.context, self.config['primary'], self.config['darwin'])
handlers = mac.MacUtilities.generate_kernel_handler_info(self.context, self.config['primary'], kernel, mods)
sysctl_list = kernel.object_from_symbol(symbol_name = "sysctl__children")
for sysctl, name, val in self._process_sysctl_list(kernel, sysctl_list):
try:
check_addr = sysctl.oid_handler
except exceptions.InvalidAddressException:
continue
module_name, symbol_name = mac.MacUtilities.lookup_module_address(self.context, handlers, check_addr)
yield (0, (name, sysctl.oid_number, sysctl.get_perms(), format_hints.Hex(check_addr), val, module_name,
symbol_name))
def run(self):
return renderers.TreeGrid([("Name", str), ("Number", int), ("Perms", str),
("Handler Address", format_hints.Hex), ("Value", str), ("Handler Module", str),
("Handler Symbol", str)], self._generator())
@@ -0,0 +1,61 @@
# This file is Copyright 2019 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
from volatility3.framework import exceptions, interfaces
from volatility3.framework import renderers, contexts
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.plugins.mac import lsmod
vollog = logging.getLogger(__name__)
class Check_trap_table(plugins.PluginInterface):
"""Check mach trap table for hooks."""
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"),
requirements.PluginRequirement(name = 'lsmod', plugin = lsmod.Lsmod, version = (1, 0, 0)),
requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 0, 0)),
]
def _generator(self):
kernel = contexts.Module(self._context, self.config['darwin'], self.config['primary'], 0)
mods = lsmod.Lsmod.list_modules(self.context, self.config['primary'], self.config['darwin'])
handlers = mac.MacUtilities.generate_kernel_handler_info(self.context, self.config['primary'], kernel, mods)
table = kernel.object_from_symbol(symbol_name = "mach_trap_table")
for i, ent in enumerate(table):
try:
call_addr = ent.mach_trap_function.dereference().vol.offset
except exceptions.InvalidAddressException:
continue
if not call_addr or call_addr == 0:
continue
module_name, symbol_name = mac.MacUtilities.lookup_module_address(self.context, handlers, call_addr)
yield (0, (format_hints.Hex(table.vol.offset), "TrapTable", i, format_hints.Hex(call_addr), module_name,
symbol_name))
def run(self):
return renderers.TreeGrid([("Table Address", format_hints.Hex), ("Table Name", str), ("Index", int),
("Handler Address", format_hints.Hex), ("Handler Module", str),
("Handler Symbol", str)], self._generator())
@@ -0,0 +1,52 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from volatility3.framework import exceptions, renderers, contexts
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.objects import utility
from volatility3.framework.symbols import mac
class Ifconfig(plugins.PluginInterface):
"""Lists loaded kernel modules"""
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"),
requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 0, 0))
]
def _generator(self):
kernel = contexts.Module(self._context, self.config['darwin'], self.config['primary'], 0)
try:
list_head = kernel.object_from_symbol(symbol_name = "ifnet_head")
except exceptions.SymbolError:
list_head = kernel.object_from_symbol(symbol_name = "dlil_ifnet_head")
for ifnet in mac.MacUtilities.walk_tailq(list_head, "if_link"):
name = utility.pointer_to_string(ifnet.if_name, 32)
unit = ifnet.if_unit
prom = ifnet.if_flags & 0x100 == 0x100 # IFF_PROMISC
sock_addr_dl = ifnet.sockaddr_dl()
if sock_addr_dl is None:
mac_addr = renderers.UnreadableValue()
else:
mac_addr = str(sock_addr_dl)
for ifaddr in mac.MacUtilities.walk_tailq(ifnet.if_addrhead, "ifa_link"):
ip = ifaddr.ifa_addr.get_address()
yield (0, ("{0}{1}".format(name, unit), ip, mac_addr, prom))
def run(self):
return renderers.TreeGrid([("Interface", str), ("IP Address", str), ("Mac Address", str),
("Promiscuous", bool)], self._generator())
@@ -0,0 +1,59 @@
# This file is opyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from volatility3.framework import renderers, interfaces, contexts
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import mac
from volatility3.plugins.mac import lsmod, kauth_scopes
class Kauth_listeners(interfaces.plugins.PluginInterface):
""" Lists kauth listeners and their status """
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel"),
requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 1, 0)),
requirements.PluginRequirement(name = 'lsmod', plugin = lsmod.Lsmod, version = (1, 0, 0)),
requirements.PluginRequirement(name = 'kauth_scopes',
plugin = kauth_scopes.Kauth_scopes,
version = (1, 0, 0))
]
def _generator(self):
"""
Enumerates the listeners for each kauth scope
"""
kernel = contexts.Module(self.context, self.config['darwin'], self.config['primary'], 0)
mods = lsmod.Lsmod.list_modules(self.context, self.config['primary'], self.config['darwin'])
handlers = mac.MacUtilities.generate_kernel_handler_info(self.context, self.config['primary'], kernel, mods)
for scope in kauth_scopes.Kauth_scopes.list_kauth_scopes(self.context, self.config['primary'],
self.config['darwin']):
scope_name = utility.pointer_to_string(scope.ks_identifier, 128)
for listener in scope.get_listeners():
callback = listener.kll_callback
if callback == 0:
continue
module_name, symbol_name = mac.MacUtilities.lookup_module_address(self.context, handlers, callback)
yield (0, (scope_name, format_hints.Hex(listener.kll_idata), format_hints.Hex(callback), module_name,
symbol_name))
def run(self):
return renderers.TreeGrid([("Name", str), ("IData", format_hints.Hex), ("Callback Address", format_hints.Hex),
("Module", str), ("Symbol", str)], self._generator())
@@ -0,0 +1,76 @@
# This file is opyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from typing import Iterable, Callable, Tuple
from volatility3.framework import renderers, interfaces, contexts
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import mac
from volatility3.plugins.mac import lsmod
class Kauth_scopes(interfaces.plugins.PluginInterface):
""" Lists kauth scopes and their status """
_version = (1, 0, 0)
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel"),
requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 1, 0)),
requirements.PluginRequirement(name = 'lsmod', plugin = lsmod.Lsmod, version = (1, 0, 0))
]
@classmethod
def list_kauth_scopes(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
darwin_symbols: str,
filter_func: Callable[[int], bool] = lambda _: False) -> \
Iterable[Tuple[interfaces.objects.ObjectInterface,
interfaces.objects.ObjectInterface,
interfaces.objects.ObjectInterface]]:
"""
Enumerates the registered kauth scopes and yields each object
Uses smear-safe enumeration API
"""
kernel = contexts.Module(context, darwin_symbols, layer_name, 0)
scopes = kernel.object_from_symbol("kauth_scopes")
for scope in mac.MacUtilities.walk_tailq(scopes, "ks_link"):
yield scope
def _generator(self):
kernel = contexts.Module(self.context, self.config['darwin'], self.config['primary'], 0)
mods = lsmod.Lsmod.list_modules(self.context, self.config['primary'], self.config['darwin'])
handlers = mac.MacUtilities.generate_kernel_handler_info(self.context, self.config['primary'], kernel, mods)
for scope in self.list_kauth_scopes(self.context, self.config['primary'], self.config['darwin']):
callback = scope.ks_callback
if callback == 0:
continue
module_name, symbol_name = mac.MacUtilities.lookup_module_address(self.context, handlers, callback)
identifier = utility.pointer_to_string(scope.ks_identifier, 128)
yield (0, (identifier, format_hints.Hex(scope.ks_idata), len([l for l in scope.get_listeners()]),
format_hints.Hex(callback), module_name, symbol_name))
def run(self):
return renderers.TreeGrid([("Name", str), ("IData", format_hints.Hex), ("Listeners", int),
("Callback Address", format_hints.Hex), ("Module", str), ("Symbol", str)],
self._generator())
@@ -0,0 +1,174 @@
# This file is opyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from typing import Iterable, Callable, Tuple
from volatility3.framework import renderers, interfaces, exceptions, contexts
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
from volatility3.framework.symbols import mac
from volatility3.plugins.mac import pslist
class Kevents(interfaces.plugins.PluginInterface):
""" Lists event handlers registered by processes """
_required_framework_version = (2, 0, 0)
event_types = {
1: "EVFILT_READ",
2: "EVFILT_WRITE",
3: "EVFILT_AIO",
4: "EVFILT_VNODE",
5: "EVFILT_PROC",
6: "EVFILT_SIGNAL",
7: "EVFILT_TIMER",
8: "EVFILT_MACHPORT",
9: "EVFILT_FS",
10: "EVFILT_USER",
12: "EVFILT_VM"
}
vnode_filters = [("NOTE_DELETE", 1), ("NOTE_WRITE", 2), ("NOTE_EXTEND", 4), ("NOTE_ATTRIB", 8), ("NOTE_LINK", 0x10),
("NOTE_RENAME", 0x20), ("NOTE_REVOKE", 0x40)]
proc_filters = [("NOTE_EXIT", 0x80000000), ("NOTE_EXITSTATUS", 0x04000000), ("NOTE_FORK", 0x40000000),
("NOTE_EXEC", 0x20000000), ("NOTE_SIGNAL", 0x08000000), ("NOTE_REAP", 0x10000000)]
timer_filters = [("NOTE_SECONDS", 1), ("NOTE_USECONDS", 2), ("NOTE_NSECONDS", 4), ("NOTE_ABSOLUTE", 8)]
all_filters = {
4: vnode_filters, # EVFILT_VNODE
5: proc_filters, # EVFILT_PROC
7: timer_filters # EVFILT_TIMER
}
@classmethod
def get_requirements(cls):
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel"),
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)),
requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 2, 0)),
requirements.ListRequirement(name = 'pid',
description = 'Filter on specific process IDs',
element_type = int,
optional = True)
]
def _parse_flags(self, filter_index, filter_flags):
if filter_flags == 0 or filter_index not in self.all_filters:
return ""
context = []
filters = self.all_filters[filter_index]
for flag, index in filters:
if filter_flags & index == index:
context.append(flag)
return ",".join(context)
@classmethod
def _walk_klist_array(cls, kernel, fdp, array_pointer_member, array_size_member):
"""
Convience wrapper for walking an array of lists of kernel events
Handles invalid address references
"""
try:
klist_array_pointer = getattr(fdp, array_pointer_member)
array_size = getattr(fdp, array_size_member)
klist_array = kernel.object(object_type = "array",
offset = klist_array_pointer,
count = array_size + 1,
subtype = kernel.get_type("klist"))
except exceptions.InvalidAddressException:
return
for klist in klist_array:
for kn in mac.MacUtilities.walk_slist(klist, "kn_link"):
yield kn
@classmethod
def _get_task_kevents(cls, kernel, task):
"""
Enumerates event filters per task.
Uses smear-safe APIs throughout as these data structures
see a signifcant amount of smear
"""
fdp = task.p_fd
for kn in cls._walk_klist_array(kernel, fdp, "fd_knlist", "fd_knlistsize"):
yield kn
for kn in cls._walk_klist_array(kernel, fdp, "fd_knhash", "fd_knhashmask"):
yield kn
try:
p_klist = task.p_klist
except exceptions.InvalidAddressException:
return
for kn in mac.MacUtilities.walk_slist(p_klist, "kn_link"):
yield kn
@classmethod
def list_kernel_events(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
darwin_symbols: str,
filter_func: Callable[[int], bool] = lambda _: False) -> \
Iterable[Tuple[interfaces.objects.ObjectInterface,
interfaces.objects.ObjectInterface,
interfaces.objects.ObjectInterface]]:
"""
Returns the kernel event filters registered
Return values:
A tuple of 3 elements:
1) The name of the process that registered the filter
2) The process ID of the process that registered the filter
3) The object of the associated kernel event filter
"""
kernel = contexts.Module(context, darwin_symbols, layer_name, 0)
list_tasks = pslist.PsList.get_list_tasks(pslist.PsList.pslist_methods[0])
for task in list_tasks(context, layer_name, darwin_symbols, filter_func):
task_name = utility.array_to_string(task.p_comm)
pid = task.p_pid
for kn in cls._get_task_kevents(kernel, task):
yield task_name, pid, kn
def _generator(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None))
for task_name, pid, kn in self.list_kernel_events(self.context,
self.config['primary'],
self.config['darwin'],
filter_func = filter_func):
filter_index = kn.kn_kevent.filter * -1
if filter_index in self.event_types:
filter_name = self.event_types[filter_index]
else:
continue
try:
ident = kn.kn_kevent.ident
except exceptions.InvalidAddressException:
continue
context = self._parse_flags(filter_index, kn.kn_sfflags)
yield (0, (pid, task_name, ident, filter_name, context))
def run(self):
return renderers.TreeGrid([("PID", int), ("Process", str), ("Ident", int), ("Filter", str), ("Context", str)],
self._generator())
@@ -0,0 +1,175 @@
# This file is Copyright 2020 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 Iterable, Optional
from volatility3.framework import renderers, interfaces, exceptions
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import mac
from volatility3.plugins.mac import mount
vollog = logging.getLogger(__name__)
class List_Files(plugins.PluginInterface):
"""Lists all open file descriptors for all processes."""
@classmethod
def get_requirements(cls):
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac Kernel"),
requirements.PluginRequirement(name = 'mount', plugin = mount.Mount, version = (1, 0, 0)),
]
@classmethod
def _vnode_name(cls, vnode: interfaces.objects.ObjectInterface) -> Optional[str]:
# roots of mount points have special name handling
if vnode.v_flag & 1 == 1:
v_name = vnode.full_path()
else:
try:
v_name = utility.pointer_to_string(vnode.v_name, 255)
except exceptions.InvalidAddressException:
v_name = None
return v_name
@classmethod
def _get_parent(cls, vnode):
parent = None
# root entries do not have parents
# and parents of normal files can be smeared
try:
parent = vnode.v_parent
except exceptions.InvalidAddressException:
pass
return parent
@classmethod
def _add_vnode(cls, vnode, loop_vnodes):
"""
Adds the given vnode to loop_vnodes.
loop_vnodes is key off the address of a vnode
and holds its name, parent address, and object
"""
key = vnode
added = False
if not key in loop_vnodes:
# We can't do anything with a no-name vnode
v_name = cls._vnode_name(vnode)
if v_name is None:
return added
parent = cls._get_parent(vnode)
if parent:
parent_val = parent
else:
parent_val = None
loop_vnodes[key] = (v_name, parent_val, vnode)
added = True
return added
@classmethod
def _walk_vnode(cls, vnode, loop_vnodes):
"""
Iterates over the list of vnodes associated with the given one.
Also traverses the parent chain for the vnode and adds each one.
"""
while vnode:
if not cls._add_vnode(vnode, loop_vnodes):
break
parent = cls._get_parent(vnode)
while parent:
cls._walk_vnode(parent, loop_vnodes)
parent = cls._get_parent(parent)
try:
vnode = vnode.v_mntvnodes.tqe_next
except exceptions.InvalidAddressException:
break
@classmethod
def _walk_vnodelist(cls, list_head, loop_vnodes):
for vnode in mac.MacUtilities.walk_tailq(list_head, "v_mntvnodes"):
cls._walk_vnode(vnode, loop_vnodes)
@classmethod
def _walk_mounts(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
darwin_symbols: str) -> \
Iterable[interfaces.objects.ObjectInterface]:
loop_vnodes = {}
# iterate each vnode source from each mount
list_mounts = mount.Mount.list_mounts(context, layer_name, darwin_symbols)
for mnt in list_mounts:
cls._walk_vnodelist(mnt.mnt_vnodelist, loop_vnodes)
cls._walk_vnodelist(mnt.mnt_workerqueue, loop_vnodes)
cls._walk_vnodelist(mnt.mnt_newvnodes, loop_vnodes)
cls._walk_vnode(mnt.mnt_vnodecovered, loop_vnodes)
cls._walk_vnode(mnt.mnt_realrootvp, loop_vnodes)
cls._walk_vnode(mnt.mnt_devvp, loop_vnodes)
return loop_vnodes
@classmethod
def _build_path(cls, vnodes, vnode_name, parent_offset):
path = [vnode_name]
while parent_offset in vnodes:
parent_name, parent_offset, _ = vnodes[parent_offset]
if parent_offset is None:
parent_offset = 0
path.insert(0, parent_name)
if len(path) > 1:
path = "/".join(path)
else:
path = vnode_name
if path.startswith("//"):
path = path[1:]
return path
@classmethod
def list_files(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
darwin_symbols: str) -> \
Iterable[interfaces.objects.ObjectInterface]:
vnodes = cls._walk_mounts(context, layer_name, darwin_symbols)
for voff, (vnode_name, parent_offset, vnode) in vnodes.items():
full_path = cls._build_path(vnodes, vnode_name, parent_offset)
yield vnode, full_path
def _generator(self):
for vnode, full_path in self.list_files(self.context, self.config['primary'], self.config['darwin']):
yield (0, (format_hints.Hex(vnode), full_path))
def run(self):
return renderers.TreeGrid([("Address", format_hints.Hex), ("File Path", str)], self._generator())
@@ -0,0 +1,60 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
"""A module containing a collection of plugins that produce data typically
found in Mac's lsmod command."""
from volatility3.framework import renderers, interfaces, contexts
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
class Lsmod(plugins.PluginInterface):
"""Lists loaded kernel modules."""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls):
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel")
]
@classmethod
def list_modules(cls, context: interfaces.context.ContextInterface, layer_name: str, darwin_symbols: str):
"""Lists all the modules in the primary layer.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
darwin_symbols: The name of the table containing the kernel symbols
Returns:
A list of modules from the `layer_name` layer
"""
kernel = contexts.Module(context, darwin_symbols, layer_name, 0)
kmod_ptr = kernel.object_from_symbol(symbol_name = "kmod")
# TODO - use smear-proof list walking API after dev release
kmod = kmod_ptr.dereference().cast("kmod_info")
while kmod != 0:
yield kmod
kmod = kmod.next
def _generator(self):
for module in self.list_modules(self.context, self.config['primary'], self.config['darwin']):
mod_name = utility.array_to_string(module.name)
mod_size = module.size
yield 0, (format_hints.Hex(module.vol.offset), mod_name, mod_size)
def run(self):
return renderers.TreeGrid([("Offset", format_hints.Hex), ("Name", str), ("Size", int)], self._generator())
+54
View File
@@ -0,0 +1,54 @@
# This file is Copyright 2019 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 volatility3.framework import renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.symbols import mac
from volatility3.plugins.mac import pslist
vollog = logging.getLogger(__name__)
class Lsof(plugins.PluginInterface):
"""Lists all open file descriptors for all processes."""
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac Kernel"),
requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 0, 0)),
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)),
requirements.ListRequirement(name = 'pid',
description = 'Filter on specific process IDs',
element_type = int,
optional = True)
]
def _generator(self, tasks):
for task in tasks:
pid = task.p_pid
for _, filepath, fd in mac.MacUtilities.files_descriptors_for_process(self.context, self.config['darwin'],
task):
if filepath and len(filepath) > 0:
yield (0, (pid, fd, filepath))
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None))
list_tasks = pslist.PsList.get_list_tasks(self.config.get('pslist_method', pslist.PsList.pslist_methods[0]))
return renderers.TreeGrid([("PID", int), ("File Descriptor", int), ("File Path", str)],
self._generator(
list_tasks(self.context,
self.config['primary'],
self.config['darwin'],
filter_func = filter_func)))
@@ -0,0 +1,80 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from volatility3.framework import constants
from volatility3.framework import interfaces
from volatility3.framework import renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.plugins.mac import pslist
class Malfind(interfaces.plugins.PluginInterface):
"""Lists process memory ranges that potentially contain injected code."""
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel"),
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)),
requirements.ListRequirement(name = 'pid',
description = 'Filter on specific process IDs',
element_type = int,
optional = True)
]
def _list_injections(self, task):
"""Generate memory regions for a process that may contain injected
code."""
proc_layer_name = task.add_process_layer()
if proc_layer_name is None:
return
proc_layer = self.context.layers[proc_layer_name]
for vma in task.get_map_iter():
if not vma.is_suspicious(self.context, self.config['darwin']):
data = proc_layer.read(vma.links.start, 64, pad = True)
yield vma, data
def _generator(self, tasks):
# determine if we're on a 32 or 64 bit kernel
if self.context.symbol_space.get_type(self.config["darwin"] + constants.BANG + "pointer").size == 4:
is_32bit_arch = True
else:
is_32bit_arch = False
for task in tasks:
process_name = utility.array_to_string(task.p_comm)
for vma, data in self._list_injections(task):
if is_32bit_arch:
architecture = "intel"
else:
architecture = "intel64"
disasm = interfaces.renderers.Disassembly(data, vma.links.start, architecture)
yield (0, (task.p_pid, process_name, format_hints.Hex(vma.links.start), format_hints.Hex(vma.links.end),
vma.get_perms(), format_hints.HexBytes(data), disasm))
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None))
list_tasks = pslist.PsList.get_list_tasks(self.config.get('pslist_method', pslist.PsList.pslist_methods[0]))
return renderers.TreeGrid([("PID", int), ("Process", str), ("Start", format_hints.Hex),
("End", format_hints.Hex), ("Protection", str), ("Hexdump", format_hints.HexBytes),
("Disasm", interfaces.renderers.Disassembly)],
self._generator(
list_tasks(self.context,
self.config['primary'],
self.config['darwin'],
filter_func = filter_func)))
@@ -0,0 +1,60 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
"""A module containing a collection of plugins that produce data typically
found in Mac's mount command."""
from volatility3.framework import renderers, interfaces, contexts
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.objects import utility
from volatility3.framework.symbols import mac
class Mount(plugins.PluginInterface):
"""A module containing a collection of plugins that produce data typically
foundin Mac's mount command"""
_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
@classmethod
def get_requirements(cls):
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 0, 0)),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols")
]
@classmethod
def list_mounts(cls, context: interfaces.context.ContextInterface, layer_name: str, darwin_symbols: str):
"""Lists all the mount structures in the primary layer.
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
darwin_symbols: The name of the table containing the kernel symbols
Returns:
A list of mount structures from the `layer_name` layer
"""
kernel = contexts.Module(context, darwin_symbols, layer_name, 0)
list_head = kernel.object_from_symbol(symbol_name = "mountlist")
for mount in mac.MacUtilities.walk_tailq(list_head, "mnt_list"):
yield mount
def _generator(self):
for mount in self.list_mounts(self.context, self.config['primary'], self.config['darwin']):
vfs = mount.mnt_vfsstat
device_name = utility.array_to_string(vfs.f_mntonname)
mount_point = utility.array_to_string(vfs.f_mntfromname)
mount_type = utility.array_to_string(vfs.f_fstypename)
yield 0, (device_name, mount_point, mount_type)
def run(self):
return renderers.TreeGrid([("Device", str), ("Mount Point", str), ("Type", str)], self._generator())
@@ -0,0 +1,115 @@
# This file is Copyright 2019 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 Iterable, Callable, Tuple
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.renderers import format_hints
from volatility3.framework.symbols import mac
from volatility3.plugins.mac import pslist
vollog = logging.getLogger(__name__)
class Netstat(plugins.PluginInterface):
"""Lists all network connections for all processes."""
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Kernel Address Space',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac Kernel"),
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)),
requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 0, 0)),
requirements.ListRequirement(name = 'pid',
description = 'Filter on specific process IDs',
element_type = int,
optional = True)
]
@classmethod
def list_sockets(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
darwin_symbols: str,
filter_func: Callable[[int], bool] = lambda _: False) -> \
Iterable[Tuple[interfaces.objects.ObjectInterface,
interfaces.objects.ObjectInterface,
interfaces.objects.ObjectInterface]]:
"""
Returns the open socket descriptors of a process
Return values:
A tuple of 3 elements:
1) The name of the process that opened the socket
2) The process ID of the processed that opened the socket
3) The address of the associated socket structure
"""
# This is hardcoded, since a change in the default method would change the expected results
list_tasks = pslist.PsList.get_list_tasks(pslist.PsList.pslist_methods[0])
for task in list_tasks(context, layer_name, darwin_symbols, filter_func):
task_name = utility.array_to_string(task.p_comm)
pid = task.p_pid
for filp, _, _ in mac.MacUtilities.files_descriptors_for_process(context, darwin_symbols, task):
try:
ftype = filp.f_fglob.get_fg_type()
except exceptions.InvalidAddressException:
continue
if ftype != 'SOCKET':
continue
try:
socket = filp.f_fglob.fg_data.dereference().cast("socket")
except exceptions.InvalidAddressException:
continue
yield task_name, pid, socket
def _generator(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None))
for task_name, pid, socket in self.list_sockets(self.context,
self.config['primary'],
self.config['darwin'],
filter_func = filter_func):
family = socket.get_family()
if family == 1:
try:
upcb = socket.so_pcb.dereference().cast("unpcb")
path = utility.array_to_string(upcb.unp_addr.sun_path)
except exceptions.InvalidAddressException:
continue
yield (0, (format_hints.Hex(socket.vol.offset), "UNIX", path, 0, "", 0, "",
"{}/{:d}".format(task_name, pid)))
elif family in [2, 30]:
state = socket.get_state()
proto = socket.get_protocol_as_string()
vals = socket.get_converted_connection_info()
if vals:
(lip, lport, rip, rport) = vals
yield (0, (format_hints.Hex(socket.vol.offset), proto, lip, lport, rip, rport, state,
"{}/{:d}".format(task_name, pid)))
def run(self):
return renderers.TreeGrid([("Offset", format_hints.Hex), ("Proto", str), ("Local IP", str), ("Local Port", int),
("Remote IP", str), ("Remote Port", int), ("State", str), ("Process", str)],
self._generator())
@@ -0,0 +1,54 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from volatility3.framework import renderers, interfaces
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.plugins.mac import pslist
class Maps(interfaces.plugins.PluginInterface):
"""Lists process memory ranges that potentially contain injected code."""
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls):
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel"),
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)),
requirements.ListRequirement(name = 'pid',
description = 'Filter on specific process IDs',
element_type = int,
optional = True)
]
def _generator(self, tasks):
for task in tasks:
process_name = utility.array_to_string(task.p_comm)
process_pid = task.p_pid
for vma in task.get_map_iter():
path = vma.get_path(self.context, self.config['darwin'])
if path == "":
path = vma.get_special_path()
yield (0, (process_pid, process_name, format_hints.Hex(vma.links.start),
format_hints.Hex(vma.links.end), vma.get_perms(), path))
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None))
list_tasks = pslist.PsList.get_list_tasks(self.config.get('pslist_method', pslist.PsList.pslist_methods[0]))
return renderers.TreeGrid([("PID", int), ("Process", str), ("Start", format_hints.Hex),
("End", format_hints.Hex), ("Protection", str), ("Map Name", str)],
self._generator(
list_tasks(self.context,
self.config['primary'],
self.config['darwin'],
filter_func = filter_func)))
+103
View File
@@ -0,0 +1,103 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
"""In-memory artifacts from OSX systems."""
from typing import Iterator, Tuple, Any, Generator, List
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.plugins.mac import pslist
class Psaux(plugins.PluginInterface):
"""Recovers program command line arguments."""
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"),
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0)),
requirements.ListRequirement(name = 'pid',
description = 'Filter on specific process IDs',
element_type = int,
optional = True)
]
def _generator(self, tasks: Iterator[Any]) -> Generator[Tuple[int, Tuple[int, str, int, str]], None, None]:
for task in tasks:
proc_layer_name = task.add_process_layer()
if proc_layer_name is None:
continue
proc_layer = self.context.layers[proc_layer_name]
argsstart = task.user_stack - task.p_argslen
if not proc_layer.is_valid(argsstart) or task.p_argslen == 0 or task.p_argc == 0:
continue
# Add one because the first two are usually duplicates
argc = task.p_argc + 1
# smear protection
if argc > 1024:
continue
task_name = utility.array_to_string(task.p_comm)
args = [] # type: List[bytes]
while argc > 0:
try:
arg = proc_layer.read(argsstart, 256)
except exceptions.InvalidAddressException:
break
idx = arg.find(b'\x00')
if idx != -1:
arg = arg[:idx]
argsstart += len(str(arg)) + 1
# deal with the stupid alignment (leading nulls) and arg duplication
if len(args) == 0:
while argsstart < task.user_stack:
try:
check = proc_layer.read(argsstart, 1)
except exceptions.InvalidAddressException:
break
if check != b"\x00":
break
argsstart = argsstart + 1
args.append(arg)
# also check for initial duplicates since OS X is painful
elif arg != args[0]:
args.append(arg)
argc = argc - 1
args_str = " ".join([s.decode("utf-8", errors = 'replace') for s in args])
yield (0, (task.p_pid, task_name, task.p_argc, args_str))
def run(self) -> renderers.TreeGrid:
filter_func = pslist.PsList.create_pid_filter(self.config.get('pid', None))
list_tasks = pslist.PsList.get_list_tasks(self.config.get('pslist_method', pslist.PsList.pslist_methods[0]))
return renderers.TreeGrid([("PID", int), ("Process", str), ("Argc", int), ("Arguments", str)],
self._generator(
list_tasks(self.context,
self.config['primary'],
self.config['darwin'],
filter_func = filter_func)))
+290
View File
@@ -0,0 +1,290 @@
# This file is Copyright 2019 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 Callable, Iterable, List, Dict
from volatility3.framework import renderers, interfaces, contexts, exceptions
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
from volatility3.framework.symbols import mac
vollog = logging.getLogger(__name__)
class PsList(interfaces.plugins.PluginInterface):
"""Lists the processes present in a particular mac memory image."""
_required_framework_version = (2, 0, 0)
_version = (2, 0, 0)
pslist_methods = ['tasks', 'allproc', 'process_group', 'sessions', 'pid_hash_table']
@classmethod
def get_requirements(cls):
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"),
requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 1, 0)),
requirements.ChoiceRequirement(name = 'pslist_method',
description = 'Method to determine for processes',
choices = cls.pslist_methods,
default = cls.pslist_methods[0],
optional = True),
requirements.ListRequirement(name = 'pid',
description = 'Filter on specific process IDs',
element_type = int,
optional = True)
]
@classmethod
def get_list_tasks(
cls, method: str
) -> Callable[[interfaces.context.ContextInterface, str, str, Callable[[int], bool]],
Iterable[interfaces.objects.ObjectInterface]]:
"""Returns the list_tasks method based on the selector
Args:
method: Must be one fo the available methods in get_task_choices
Returns:
list_tasks method for listing tasks
"""
# Ensure method is one of the suitable choices
if method not in cls.pslist_methods:
method = cls.pslist_methods[0]
if method == 'allproc':
list_tasks = cls.list_tasks_allproc
elif method == 'tasks':
list_tasks = cls.list_tasks_tasks
elif method == 'process_group':
list_tasks = cls.list_tasks_process_group
elif method == 'sessions':
list_tasks = cls.list_tasks_sessions
elif method == 'pid_hash_table':
list_tasks = cls.list_tasks_pid_hash_table
else:
raise ValueError("Impossible method choice chosen")
vollog.debug("Using method {}".format(method))
return list_tasks
@classmethod
def create_pid_filter(cls, pid_list: List[int] = None) -> Callable[[int], bool]:
filter_func = lambda _: False
# FIXME: mypy #4973 or #2608
pid_list = pid_list or []
filter_list = [x for x in pid_list if x is not None]
if filter_list:
def list_filter(x):
return x.p_pid not in filter_list
filter_func = list_filter
return filter_func
def _generator(self):
list_tasks = self.get_list_tasks(self.config.get('pslist_method', self.pslist_methods[0]))
for task in list_tasks(self.context,
self.config['primary'],
self.config['darwin'],
filter_func = self.create_pid_filter(self.config.get('pid', None))):
pid = task.p_pid
ppid = task.p_ppid
name = utility.array_to_string(task.p_comm)
yield (0, (pid, ppid, name))
@classmethod
def list_tasks_allproc(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
darwin_symbols: str,
filter_func: Callable[[int], bool] = lambda _: False) -> \
Iterable[interfaces.objects.ObjectInterface]:
"""Lists all the processes in the primary layer based on the allproc method
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
darwin_symbols: The name of the table containing the kernel symbols
filter_func: A function which takes a process object and returns True if the process should be ignored/filtered
Returns:
The list of process objects from the processes linked list after filtering
"""
kernel = contexts.Module(context, darwin_symbols, layer_name, 0)
kernel_layer = context.layers[layer_name]
proc = kernel.object_from_symbol(symbol_name = "allproc").lh_first
seen = {} # type: Dict[int, int]
while proc is not None and proc.vol.offset != 0:
if proc.vol.offset in seen:
vollog.log(logging.INFO, "Recursive process list detected (a result of non-atomic acquisition).")
break
else:
seen[proc.vol.offset] = 1
if kernel_layer.is_valid(proc.vol.offset, proc.vol.size) and not filter_func(proc):
yield proc
try:
proc = proc.p_list.le_next.dereference()
except exceptions.InvalidAddressException:
break
@classmethod
def list_tasks_tasks(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
darwin_symbols: str,
filter_func: Callable[[int], bool] = lambda _: False) -> \
Iterable[interfaces.objects.ObjectInterface]:
"""Lists all the tasks in the primary layer based on the tasks queue
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
darwin_symbols: The name of the table containing the kernel symbols
filter_func: A function which takes a task object and returns True if the task should be ignored/filtered
Returns:
The list of task objects from the `layer_name` layer's `tasks` list after filtering
"""
kernel = contexts.Module(context, darwin_symbols, layer_name, 0)
kernel_layer = context.layers[layer_name]
queue_entry = kernel.object_from_symbol(symbol_name = "tasks")
seen = {} # type: Dict[int, int]
for task in queue_entry.walk_list(queue_entry, "tasks", "task"):
if task.vol.offset in seen:
vollog.log(logging.INFO, "Recursive process list detected (a result of non-atomic acquisition).")
break
else:
seen[task.vol.offset] = 1
try:
proc = task.bsd_info.dereference().cast("proc")
except exceptions.InvalidAddressException:
continue
if kernel_layer.is_valid(proc.vol.offset, proc.vol.size) and not filter_func(proc):
yield proc
@classmethod
def list_tasks_sessions(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
darwin_symbols: str,
filter_func: Callable[[int], bool] = lambda _: False) -> \
Iterable[interfaces.objects.ObjectInterface]:
"""Lists all the tasks in the primary layer using sessions
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
darwin_symbols: The name of the table containing the kernel symbols
filter_func: A function which takes a task object and returns True if the task should be ignored/filtered
Returns:
The list of task objects from the `layer_name` layer's `tasks` list after filtering
"""
kernel = contexts.Module(context, darwin_symbols, layer_name, 0)
table_size = kernel.object_from_symbol(symbol_name = "sesshash")
sesshashtbl = kernel.object_from_symbol(symbol_name = "sesshashtbl")
proc_array = kernel.object(object_type = "array",
offset = sesshashtbl,
count = table_size + 1,
subtype = kernel.get_type("sesshashhead"))
for proc_list in proc_array:
for proc in mac.MacUtilities.walk_list_head(proc_list, "s_hash"):
if proc.s_leader.is_readable() and not filter_func(proc.s_leader):
yield proc.s_leader
@classmethod
def list_tasks_process_group(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
darwin_symbols: str,
filter_func: Callable[[int], bool] = lambda _: False) -> \
Iterable[interfaces.objects.ObjectInterface]:
"""Lists all the tasks in the primary layer using process groups
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
darwin_symbols: The name of the table containing the kernel symbols
filter_func: A function which takes a task object and returns True if the task should be ignored/filtered
Returns:
The list of task objects from the `layer_name` layer's `tasks` list after filtering
"""
kernel = contexts.Module(context, darwin_symbols, layer_name, 0)
table_size = kernel.object_from_symbol(symbol_name = "pgrphash")
pgrphashtbl = kernel.object_from_symbol(symbol_name = "pgrphashtbl")
proc_array = kernel.object(object_type = "array",
offset = pgrphashtbl,
count = table_size + 1,
subtype = kernel.get_type("pgrphashhead"))
for proc_list in proc_array:
for pgrp in mac.MacUtilities.walk_list_head(proc_list, "pg_hash"):
for proc in mac.MacUtilities.walk_list_head(pgrp.pg_members, "p_pglist"):
if not filter_func(proc):
yield proc
@classmethod
def list_tasks_pid_hash_table(cls,
context: interfaces.context.ContextInterface,
layer_name: str,
darwin_symbols: str,
filter_func: Callable[[int], bool] = lambda _: False) -> \
Iterable[interfaces.objects.ObjectInterface]:
"""Lists all the tasks in the primary layer using the pid hash table
Args:
context: The context to retrieve required elements (layers, symbol tables) from
layer_name: The name of the layer on which to operate
darwin_symbols: The name of the table containing the kernel symbols
filter_func: A function which takes a task object and returns True if the task should be ignored/filtered
Returns:
The list of task objects from the `layer_name` layer's `tasks` list after filtering
"""
kernel = contexts.Module(context, darwin_symbols, layer_name, 0)
table_size = kernel.object_from_symbol(symbol_name = "pidhash")
pidhashtbl = kernel.object_from_symbol(symbol_name = "pidhashtbl")
proc_array = kernel.object(object_type = "array",
offset = pidhashtbl,
count = table_size + 1,
subtype = kernel.get_type("pidhashhead"))
for proc_list in proc_array:
for proc in mac.MacUtilities.walk_list_head(proc_list, "p_hash"):
if not filter_func(proc):
yield proc
def run(self):
return renderers.TreeGrid([("PID", int), ("PPID", int), ("COMM", str)], self._generator())
@@ -0,0 +1,73 @@
# This file is Copyright 2019 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from volatility3.framework import renderers
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.objects import utility
from volatility3.plugins.mac import pslist
class PsTree(plugins.PluginInterface):
"""Plugin for listing processes in a tree based on their parent process
ID."""
_required_framework_version = (2, 0, 0)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._processes = {}
self._levels = {}
self._children = {}
@classmethod
def get_requirements(cls):
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"),
requirements.PluginRequirement(name = 'pslist', plugin = pslist.PsList, version = (2, 0, 0))
]
def _find_level(self, pid):
"""Finds how deep the pid is in the processes list."""
seen = set([])
seen.add(pid)
level = 0
proc = self._processes.get(pid, None)
while proc is not None and proc.vol.offset != 0 and proc.p_ppid != 0 and proc.p_ppid not in seen:
ppid = int(proc.p_ppid)
child_list = self._children.get(ppid, set([]))
child_list.add(proc.p_pid)
self._children[ppid] = child_list
proc = self._processes.get(ppid, None)
level += 1
self._levels[pid] = level
def _generator(self):
"""Generates the tree list of processes"""
list_tasks = pslist.PsList.get_list_tasks(self.config.get('pslist_method', pslist.PsList.pslist_methods[0]))
for proc in list_tasks(self.context, self.config['primary'], self.config['darwin']):
self._processes[proc.p_pid] = proc
# Build the child/level maps
for pid in self._processes:
self._find_level(pid)
def yield_processes(pid):
proc = self._processes[pid]
row = (proc.p_pid, proc.p_ppid, utility.array_to_string(proc.p_comm))
yield (self._levels[pid] - 1, row)
for child_pid in self._children.get(pid, []):
yield from yield_processes(child_pid)
for pid in self._levels:
if self._levels[pid] == 1:
yield from yield_processes(pid)
def run(self):
return renderers.TreeGrid([("PID", int), ("PPID", int), ("COMM", str)], self._generator())
@@ -0,0 +1,73 @@
# This file is Copyright 2020 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
from volatility3.framework import exceptions, interfaces
from volatility3.framework import renderers, contexts
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import mac
from volatility3.plugins.mac import lsmod
vollog = logging.getLogger(__name__)
class Socket_filters(plugins.PluginInterface):
"""Enumerates kernel socket filters."""
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"),
requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 0, 0)),
requirements.PluginRequirement(name = 'lsmod', plugin = lsmod.Lsmod, version = (1, 0, 0))
]
def _generator(self):
kernel = contexts.Module(self._context, self.config['darwin'], self.config['primary'], 0)
mods = lsmod.Lsmod.list_modules(self.context, self.config['primary'], self.config['darwin'])
handlers = mac.MacUtilities.generate_kernel_handler_info(self.context, self.config['primary'], kernel, mods)
members_to_check = [
"sf_unregistered", "sf_attach", "sf_detach", "sf_notify", "sf_getpeername", "sf_getsockname", "sf_data_in",
"sf_data_out", "sf_connect_in", "sf_connect_out", "sf_bind", "sf_setoption", "sf_getoption", "sf_listen",
"sf_ioctl"
]
filter_list = kernel.object_from_symbol(symbol_name = "sock_filter_head")
for filter_container in mac.MacUtilities.walk_tailq(filter_list, "sf_global_next"):
current_filter = filter_container.sf_filter
filter_name = utility.pointer_to_string(current_filter.sf_name, count = 128)
try:
filter_socket = filter_container.sf_entry_head.sfe_socket.vol.offset
except exceptions.InvalidAddressException:
filter_socket = 0
for member in members_to_check:
check_addr = current_filter.member(attr = member)
if check_addr == 0:
continue
module_name, symbol_name = mac.MacUtilities.lookup_module_address(self.context, handlers, check_addr)
yield (0, (format_hints.Hex(current_filter.vol.offset), filter_name, member, \
format_hints.Hex(filter_socket), format_hints.Hex(check_addr), module_name, symbol_name))
def run(self):
return renderers.TreeGrid([("Filter", format_hints.Hex), ("Name", str), ("Member", str),
("Socket", format_hints.Hex), ("Handler", format_hints.Hex), ("Module", str),
("Symbol", str)], self._generator())
@@ -0,0 +1,79 @@
# This file is Copyright 2020 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
from volatility3.framework import exceptions, interfaces
from volatility3.framework import renderers, contexts
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.plugins.mac import lsmod
vollog = logging.getLogger(__name__)
class Timers(plugins.PluginInterface):
"""Check for malicious kernel timers."""
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"),
requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 0, 0)),
requirements.PluginRequirement(name = 'lsmod', plugin = lsmod.Lsmod, version = (1, 0, 0))
]
def _generator(self):
kernel = contexts.Module(self.context, self.config['darwin'], self.config['primary'], 0)
mods = lsmod.Lsmod.list_modules(self.context, self.config['primary'], self.config['darwin'])
handlers = mac.MacUtilities.generate_kernel_handler_info(self.context, self.config['primary'], kernel, mods)
real_ncpus = kernel.object_from_symbol(symbol_name = "real_ncpus")
cpu_data_ptrs_ptr = kernel.get_symbol("cpu_data_ptr").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,
subtype = kernel.get_type('cpu_data'),
count = real_ncpus)
for cpu_data_ptr in cpu_data_ptrs:
try:
queue = cpu_data_ptr.rtclock_timer.queue.head
except exceptions.InvalidAddressException:
break
for timer in queue.walk_list(queue, "q_link", "call_entry"):
try:
handler = timer.func.dereference().vol.offset
except exceptions.InvalidAddressException:
continue
if timer.has_member("entry_time"):
entry_time = timer.entry_time
else:
entry_time = -1
module_name, symbol_name = mac.MacUtilities.lookup_module_address(self.context, handlers, handler)
yield (0, (format_hints.Hex(handler), format_hints.Hex(timer.param0), format_hints.Hex(timer.param1), \
timer.deadline, entry_time, module_name, symbol_name))
def run(self):
return renderers.TreeGrid([("Function", format_hints.Hex), ("Param 0", format_hints.Hex),
("Param 1", format_hints.Hex), ("Deadline", int), ("Entry Time", int),
("Module", str), ("Symbol", str)], self._generator())
@@ -0,0 +1,77 @@
# This file is Copyright 2019 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, Iterator, Any
from volatility3.framework import exceptions, interfaces
from volatility3.framework import renderers, contexts
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.objects import utility
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import mac
from volatility3.plugins.mac import lsmod
vollog = logging.getLogger(__name__)
class Trustedbsd(plugins.PluginInterface):
"""Checks for malicious trustedbsd modules"""
_required_framework_version = (2, 0, 0)
@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel symbols"),
requirements.VersionRequirement(name = 'macutils', component = mac.MacUtilities, version = (1, 0, 0)),
requirements.PluginRequirement(name = 'lsmod', plugin = lsmod.Lsmod, version = (1, 0, 0))
]
def _generator(self, mods: Iterator[Any]):
kernel = contexts.Module(self._context, self.config['darwin'], self.config['primary'], 0)
handlers = mac.MacUtilities.generate_kernel_handler_info(self.context, self.config['primary'], kernel, mods)
policy_list = kernel.object_from_symbol(symbol_name = "mac_policy_list").cast("mac_policy_list")
entries = kernel.object(object_type = "array",
offset = policy_list.entries.dereference().vol.offset,
subtype = kernel.get_type('mac_policy_list_element'),
count = policy_list.staticmax + 1)
for i, ent in enumerate(entries):
# I don't know how this can happen, but the kernel makes this check all over the place
# the policy isn't useful without any ops so a rootkit can't abuse this
try:
mpc = ent.mpc.dereference()
ops = mpc.mpc_ops.dereference()
except exceptions.InvalidAddressException:
continue
try:
ent_name = utility.pointer_to_string(mpc.mpc_name, 255)
except exceptions.InvalidAddressException:
ent_name = "N/A"
for check in ops.vol.members:
call_addr = getattr(ops, check)
if call_addr is None or call_addr == 0:
continue
module_name, symbol_name = mac.MacUtilities.lookup_module_address(self.context, handlers, call_addr)
yield (0, (check, ent_name, format_hints.Hex(call_addr), module_name, symbol_name))
def run(self):
return renderers.TreeGrid([("Member", str), ("Policy Name", str), ("Handler Address", format_hints.Hex),
("Handler Module", str), ("Handler Symbol", str)],
self._generator(
lsmod.Lsmod.list_modules(self.context, self.config['primary'],
self.config['darwin'])))
@@ -0,0 +1,65 @@
# This file is opyright 2020 Volatility Foundation and licensed under the Volatility Software License 1.0
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
from volatility3.framework import renderers, interfaces, exceptions, contexts
from volatility3.framework.configuration import requirements
from volatility3.framework.objects import utility
class VFSevents(interfaces.plugins.PluginInterface):
""" Lists processes that are filtering file system events """
_required_framework_version = (2, 0, 0)
event_types = [
"CREATE_FILE", "DELETE", "STAT_CHANGED", "RENAME", "CONTENT_MODIFIED", "EXCHANGE", "FINDER_INFO_CHANGED",
"CREATE_DIR", "CHOWN", "XATTR_MODIFIED", "XATTR_REMOVED", "DOCID_CREATED", "DOCID_CHANGED"
]
@classmethod
def get_requirements(cls):
return [
requirements.TranslationLayerRequirement(name = 'primary',
description = 'Memory layer for the kernel',
architectures = ["Intel32", "Intel64"]),
requirements.SymbolTableRequirement(name = "darwin", description = "Mac kernel"),
]
def _generator(self):
"""
Lists the registered VFS event watching processes
Also lists which event(s) a process is registered for
"""
kernel = contexts.Module(self.context, self.config['darwin'], self.config['primary'], 0)
watcher_table = kernel.object_from_symbol("watcher_table")
for watcher in watcher_table:
if watcher == 0:
continue
task_name = utility.array_to_string(watcher.proc_name)
task_pid = watcher.pid
events = []
try:
event_array = kernel.object(object_type = "array",
offset = watcher.event_list,
count = 13,
subtype = kernel.get_type("unsigned char"))
except exceptions.InvalidAddressException:
continue
for i, event in enumerate(event_array):
if event == 1:
events.append(self.event_types[i])
if events != []:
yield (0, (task_name, task_pid, ",".join(events)))
def run(self):
return renderers.TreeGrid([("Name", str), ("PID", int), ("Events", str)], self._generator())