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

Linux basic support for scanned tasks #1012

Draft
wants to merge 3 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
61 changes: 43 additions & 18 deletions volatility3/framework/plugins/linux/elfs.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from volatility3.framework.renderers import format_hints
from volatility3.framework.symbols import intermed
from volatility3.framework.symbols.linux.extensions import elf
from volatility3.plugins.linux import pslist
from volatility3.plugins.linux import pslist, psscan

vollog = logging.getLogger(__name__)

Expand All @@ -36,6 +36,9 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="psscan", plugin=psscan.PsScan, version=(1, 1, 0)
),
requirements.ListRequirement(
name="pid",
description="Filter on specific process IDs",
Expand All @@ -48,6 +51,12 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
default=False,
optional=True,
),
requirements.BooleanRequirement(
name="scan",
description="Scan for processes rather than using pslist",
optional=True,
default=False,
),
]

@classmethod
Expand Down Expand Up @@ -133,11 +142,9 @@ def _generator(self, tasks):
proc_layer_name = task.add_process_layer()
if not proc_layer_name:
continue

proc_layer = self.context.layers[proc_layer_name]

name = utility.array_to_string(task.comm)

for vma in task.mm.get_vma_iter():
hdr = proc_layer.read(vma.vm_start, 4, pad=True)
if not (
Expand Down Expand Up @@ -180,18 +187,36 @@ def _generator(self, tasks):
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))

return renderers.TreeGrid(
[
("PID", int),
("Process", str),
("Start", format_hints.Hex),
("End", format_hints.Hex),
("File Path", str),
("File Output", str),
],
self._generator(
pslist.PsList.list_tasks(
self.context, self.config["kernel"], filter_func=filter_func
)
),
)
tree_grid_fields = [
("PID", int),
("Process", str),
("Start", format_hints.Hex),
("End", format_hints.Hex),
("File Path", str),
("File Output", str),
]

if self.config.get("scan", None) == True:
vmlinux_module_name = self.config["kernel"]
vmlinux = self.context.modules[vmlinux_module_name]
return renderers.TreeGrid(
tree_grid_fields,
self._generator(
psscan.PsScan.scan_tasks(
self.context,
vmlinux_module_name,
vmlinux.layer_name,
filter_func=filter_func,
)
),
)

else:
return renderers.TreeGrid(
tree_grid_fields,
self._generator(
pslist.PsList.list_tasks(
self.context, self.config["kernel"], filter_func=filter_func
)
),
)
40 changes: 34 additions & 6 deletions volatility3/framework/plugins/linux/lsof.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from volatility3.framework.interfaces import plugins
from volatility3.framework.objects import utility
from volatility3.framework.symbols import linux
from volatility3.plugins.linux import pslist
from volatility3.plugins.linux import pslist, psscan

vollog = logging.getLogger(__name__)

Expand All @@ -21,7 +21,7 @@ class Lsof(plugins.PluginInterface):

_required_framework_version = (2, 0, 0)

_version = (1, 1, 0)
_version = (1, 2, 0)

@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
Expand All @@ -34,6 +34,9 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="psscan", plugin=psscan.PsScan, version=(1, 1, 0)
),
requirements.VersionRequirement(
name="linuxutils", component=linux.LinuxUtilities, version=(2, 0, 0)
),
Expand All @@ -43,6 +46,12 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
element_type=int,
optional=True,
),
requirements.BooleanRequirement(
name="scan",
description="Scan for processes rather than using pslist",
optional=True,
default=False,
),
]

@classmethod
Expand All @@ -51,9 +60,25 @@ def list_fds(
context: interfaces.context.ContextInterface,
symbol_table: str,
filter_func: Callable[[int], bool] = lambda _: False,
scan: bool = False,
):
linuxutils_symbol_table = None # type: ignore
for task in pslist.PsList.list_tasks(context, symbol_table, filter_func):

# select the function used to find task objects
if scan:
vmlinux = context.modules[symbol_table]
task_finder_function = psscan.PsScan.scan_tasks(
context,
symbol_table,
vmlinux.layer_name,
filter_func=filter_func,
)
else:
task_finder_function = pslist.PsList.list_tasks(
context, symbol_table, filter_func
)

for task in task_finder_function:
if linuxutils_symbol_table is None:
if constants.BANG not in task.vol.type_name:
raise ValueError("Task is not part of a symbol table")
Expand All @@ -69,10 +94,10 @@ def list_fds(
for fd_fields in fd_generator:
yield pid, task_comm, task, fd_fields

def _generator(self, pids, symbol_table):
def _generator(self, pids, symbol_table, scan):
filter_func = pslist.PsList.create_pid_filter(pids)
fds_generator = self.list_fds(
self.context, symbol_table, filter_func=filter_func
self.context, symbol_table, filter_func=filter_func, scan=scan
)

for pid, task_comm, _task, fd_fields in fds_generator:
Expand All @@ -84,6 +109,9 @@ def _generator(self, pids, symbol_table):
def run(self):
pids = self.config.get("pid", None)
symbol_table = self.config["kernel"]
scan = self.config.get("scan", None)

tree_grid_args = [("PID", int), ("Process", str), ("FD", int), ("Path", str)]
return renderers.TreeGrid(tree_grid_args, self._generator(pids, symbol_table))
return renderers.TreeGrid(
tree_grid_args, self._generator(pids, symbol_table, scan)
)
48 changes: 39 additions & 9 deletions volatility3/framework/plugins/linux/psaux.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from volatility3.framework.configuration import requirements
from volatility3.framework.interfaces import plugins
from volatility3.framework.objects import utility
from volatility3.plugins.linux import pslist
from volatility3.plugins.linux import pslist, psscan


class PsAux(plugins.PluginInterface):
Expand All @@ -28,12 +28,21 @@ def get_requirements(cls):
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
),
requirements.PluginRequirement(
name="psscan", plugin=psscan.PsScan, version=(1, 1, 0)
),
requirements.ListRequirement(
name="pid",
description="Filter on specific process IDs",
element_type=int,
optional=True,
),
requirements.BooleanRequirement(
name="scan",
description="Scan for processes rather than using pslist",
optional=True,
default=False,
),
]

def _get_command_line_args(
Expand Down Expand Up @@ -80,6 +89,9 @@ def _get_command_line_args(
s = argv.decode().split("\x00")
args = " ".join(s)
else:
# TODO: when scanning for tasks many are in EXIT_ZOMBIE state and have no mm
# even though they were not kernel threads

# kernel thread
# [ ] mimics ps on a live system
# also helps identify malware masquerading as a kernel thread, which is fairly common
Expand Down Expand Up @@ -112,11 +124,29 @@ def _generator(self, tasks):
def run(self):
filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))

return renderers.TreeGrid(
[("PID", int), ("PPID", int), ("COMM", str), ("ARGS", str)],
self._generator(
pslist.PsList.list_tasks(
self.context, self.config["kernel"], filter_func=filter_func
)
),
)
tree_grid_fields = [("PID", int), ("PPID", int), ("COMM", str), ("ARGS", str)]

if self.config.get("scan", None) == True:
vmlinux_module_name = self.config["kernel"]
vmlinux = self.context.modules[vmlinux_module_name]
return renderers.TreeGrid(
tree_grid_fields,
self._generator(
psscan.PsScan.scan_tasks(
self.context,
vmlinux_module_name,
vmlinux.layer_name,
filter_func=filter_func,
)
),
)

else:
return renderers.TreeGrid(
tree_grid_fields,
self._generator(
pslist.PsList.list_tasks(
self.context, self.config["kernel"], filter_func=filter_func
)
),
)
23 changes: 20 additions & 3 deletions volatility3/framework/plugins/linux/psscan.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# which is available at https://www.volatilityfoundation.org/license/vsl-v1.0
#
import logging
from typing import Iterable, List, Tuple
from typing import Iterable, List, Tuple, Callable
import struct
from enum import Enum

Expand All @@ -11,6 +11,7 @@
from volatility3.framework.objects import utility
from volatility3.framework.layers import scanners
from volatility3.framework.renderers import format_hints
from volatility3.plugins.linux import pslist

vollog = logging.getLogger(__name__)

Expand All @@ -28,7 +29,7 @@ class PsScan(interfaces.plugins.PluginInterface):
"""Scans for processes present in a particular linux image."""

_required_framework_version = (2, 0, 0)
_version = (1, 0, 0)
_version = (1, 1, 0)

@classmethod
def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]:
Expand All @@ -38,6 +39,15 @@ def get_requirements(cls) -> List[interfaces.configuration.RequirementInterface]
description="Linux kernel",
architectures=["Intel32", "Intel64"],
),
requirements.ListRequirement(
name="pid",
description="Filter on specific process IDs",
element_type=int,
optional=True,
),
requirements.PluginRequirement(
name="pslist", plugin=pslist.PsList, version=(2, 0, 0)
),
]

def _get_task_fields(
Expand Down Expand Up @@ -75,8 +85,10 @@ def _generator(self):
vmlinux_module_name = self.config["kernel"]
vmlinux = self.context.modules[vmlinux_module_name]

filter_func = pslist.PsList.create_pid_filter(self.config.get("pid", None))

for task in self.scan_tasks(
self.context, vmlinux_module_name, vmlinux.layer_name
self.context, vmlinux_module_name, vmlinux.layer_name, filter_func
):
row = self._get_task_fields(task)
yield (0, row)
Expand All @@ -87,6 +99,7 @@ def scan_tasks(
context: interfaces.context.ContextInterface,
vmlinux_module_name: str,
kernel_layer_name: str,
filter_func: Callable[[int], bool] = lambda _: False,
) -> Iterable[interfaces.objects.ObjectInterface]:
"""Scans for tasks in the memory layer.

Expand Down Expand Up @@ -168,6 +181,10 @@ def scan_tasks(
f"Skipping task_struct at {hex(ptask.vol.offset)} as pid {ptask.pid} is likely not valid"
)
continue

if filter_func(ptask):
continue

yield ptask

def run(self):
Expand Down
2 changes: 1 addition & 1 deletion volatility3/framework/symbols/generic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def _add_process_layer(
preferred_name = context.layers.free_layer_name(prefix=preferred_name)

# Copy the parent's config and then make suitable changes
parent_layer = context.layers[self.vol.layer_name]
parent_layer = context.layers[self.vol.native_layer_name]
parent_config = parent_layer.build_configuration()
# It's an intel layer, because we hardwire the "memory_layer" config option
# FIXME: this could be for other architectures if we don't hardwire this/these values
Expand Down
2 changes: 1 addition & 1 deletion volatility3/framework/symbols/linux/extensions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def add_process_layer(
Returns the name of the Layer or None.
"""

parent_layer = self._context.layers[self.vol.layer_name]
parent_layer = self._context.layers[self.vol.native_layer_name]
try:
pgd = self.mm.pgd
except exceptions.InvalidAddressException:
Expand Down